Introduce the wasmtime-explorer crate (#5975)
This implements Godbolt Compiler Explorer-like functionality for Wasmtime and Cranelift. Given a Wasm module, it compiles the module to native code and then writes a standalone HTML file that gives a split pane view between the WAT and ASM disassemblies.
This commit is contained in:
238
crates/explorer/src/index.js
Normal file
238
crates/explorer/src/index.js
Normal file
@@ -0,0 +1,238 @@
|
||||
/*** State *********************************************************************/
|
||||
|
||||
class State {
|
||||
constructor(wat, asm) {
|
||||
this.wat = wat;
|
||||
this.asm = asm;
|
||||
}
|
||||
}
|
||||
|
||||
const state = window.STATE = new State(window.WAT, window.ASM);
|
||||
|
||||
/*** Hues for Offsets **********************************************************/
|
||||
|
||||
const hues = [
|
||||
80,
|
||||
160,
|
||||
240,
|
||||
320,
|
||||
40,
|
||||
120,
|
||||
200,
|
||||
280,
|
||||
20,
|
||||
100,
|
||||
180,
|
||||
260,
|
||||
340,
|
||||
60,
|
||||
140,
|
||||
220,
|
||||
300,
|
||||
];
|
||||
|
||||
const nextHue = (function () {
|
||||
let i = 0;
|
||||
return () => {
|
||||
return hues[++i % hues.length];
|
||||
};
|
||||
}());
|
||||
|
||||
// NB: don't just assign hues based on something simple like `hues[offset %
|
||||
// hues.length]` since that can suffer from bias due to certain alignments
|
||||
// happening more or less frequently.
|
||||
const offsetToHue = new Map();
|
||||
|
||||
// Get the hue for the given offset, or assign it a new one if it doesn't have
|
||||
// one already.
|
||||
const hueForOffset = offset => {
|
||||
if (offsetToHue.has(offset)) {
|
||||
return offsetToHue.get(offset);
|
||||
} else {
|
||||
let hue = nextHue();
|
||||
offsetToHue.set(offset, hue);
|
||||
return hue;
|
||||
}
|
||||
};
|
||||
|
||||
// Get the hue for the given offset, only if the offset has already been
|
||||
// assigned a hue.
|
||||
const existingHueForOffset = offset => {
|
||||
return offsetToHue.get(offset);
|
||||
};
|
||||
|
||||
// Get WAT chunk elements by Wasm offset.
|
||||
const watByOffset = new Map();
|
||||
|
||||
// Get asm instruction elements by Wasm offset.
|
||||
const asmByOffset = new Map();
|
||||
|
||||
// Get all (WAT chunk or asm instruction) elements by offset.
|
||||
const anyByOffset = new Map();
|
||||
|
||||
const addWatElem = (offset, elem) => {
|
||||
if (!watByOffset.has(offset)) {
|
||||
watByOffset.set(offset, []);
|
||||
}
|
||||
watByOffset.get(offset).push(elem);
|
||||
|
||||
if (!anyByOffset.has(offset)) {
|
||||
anyByOffset.set(offset, []);
|
||||
}
|
||||
anyByOffset.get(offset).push(elem);
|
||||
};
|
||||
|
||||
const addAsmElem = (offset, elem) => {
|
||||
if (!asmByOffset.has(offset)) {
|
||||
asmByOffset.set(offset, []);
|
||||
}
|
||||
asmByOffset.get(offset).push(elem);
|
||||
|
||||
if (!anyByOffset.has(offset)) {
|
||||
anyByOffset.set(offset, []);
|
||||
}
|
||||
anyByOffset.get(offset).push(elem);
|
||||
};
|
||||
|
||||
/*** Event Handlers ************************************************************/
|
||||
|
||||
const watElem = document.getElementById("wat");
|
||||
watElem.addEventListener("click", event => {
|
||||
if (event.target.dataset.wasmOffset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = parseInt(event.target.dataset.wasmOffset);
|
||||
if (!asmByOffset.get(offset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstAsmElem = asmByOffset.get(offset)[0];
|
||||
firstAsmElem.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
inline: "nearest",
|
||||
});
|
||||
}, { passive: true });
|
||||
|
||||
const asmElem = document.getElementById("asm");
|
||||
asmElem.addEventListener("click", event => {
|
||||
if (event.target.dataset.wasmOffset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = parseInt(event.target.dataset.wasmOffset);
|
||||
if (!watByOffset.get(offset)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstWatElem = watByOffset.get(offset)[0];
|
||||
firstWatElem.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "center",
|
||||
inline: "nearest",
|
||||
});
|
||||
}, { passive: true });
|
||||
|
||||
const onMouseEnter = event => {
|
||||
if (event.target.dataset.wasmOffset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = parseInt(event.target.dataset.wasmOffset);
|
||||
const hue = hueForOffset(offset);
|
||||
for (const elem of anyByOffset.get(offset)) {
|
||||
elem.style.backgroundColor = `hsl(${hue} 75% 80%)`;
|
||||
}
|
||||
};
|
||||
|
||||
const onMouseLeave = event => {
|
||||
if (event.target.dataset.wasmOffset == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const offset = parseInt(event.target.dataset.wasmOffset);
|
||||
const hue = hueForOffset(offset);
|
||||
for (const elem of anyByOffset.get(offset)) {
|
||||
elem.style.backgroundColor = `hsl(${hue} 50% 95%)`;
|
||||
}
|
||||
};
|
||||
|
||||
/*** Rendering *****************************************************************/
|
||||
|
||||
const repeat = (s, n) => {
|
||||
return s.repeat(n >= 0 ? n : 0);
|
||||
};
|
||||
|
||||
const renderAddress = addr => {
|
||||
let hex = addr.toString(16);
|
||||
return repeat("0", 8 - hex.length) + hex;
|
||||
};
|
||||
|
||||
const renderBytes = bytes => {
|
||||
let s = "";
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (i != 0) {
|
||||
s += " ";
|
||||
}
|
||||
const hexByte = bytes[i].toString(16);
|
||||
s += hexByte.length == 2 ? hexByte : "0" + hexByte;
|
||||
}
|
||||
return s + repeat(" ", 30 - s.length);
|
||||
};
|
||||
|
||||
const renderInst = (mnemonic, operands) => {
|
||||
if (operands.length == 0) {
|
||||
return mnemonic;
|
||||
} else {
|
||||
return mnemonic + " " + operands;
|
||||
}
|
||||
};
|
||||
|
||||
// Render the ASM.
|
||||
|
||||
let nthFunc = 0;
|
||||
for (const func of state.asm.functions) {
|
||||
const funcElem = document.createElement("div");
|
||||
|
||||
const funcHeader = document.createElement("h3");
|
||||
funcHeader.textContent = `Defined Function ${nthFunc}`;
|
||||
funcElem.appendChild(funcHeader);
|
||||
|
||||
const bodyElem = document.createElement("pre");
|
||||
for (const inst of func.instructions) {
|
||||
const instElem = document.createElement("span");
|
||||
instElem.textContent = `${renderAddress(inst.address)} ${renderBytes(inst.bytes)} ${renderInst(inst.mnemonic, inst.operands)}\n`;
|
||||
if (inst.wasm_offset != null) {
|
||||
instElem.setAttribute("data-wasm-offset", inst.wasm_offset);
|
||||
const hue = hueForOffset(inst.wasm_offset);
|
||||
instElem.style.backgroundColor = `hsl(${hue} 50% 90%)`;
|
||||
instElem.addEventListener("mouseenter", onMouseEnter);
|
||||
instElem.addEventListener("mouseleave", onMouseLeave);
|
||||
addAsmElem(inst.wasm_offset, instElem);
|
||||
}
|
||||
bodyElem.appendChild(instElem);
|
||||
}
|
||||
funcElem.appendChild(bodyElem);
|
||||
|
||||
asmElem.appendChild(funcElem);
|
||||
nthFunc++;
|
||||
}
|
||||
|
||||
// Render the WAT.
|
||||
|
||||
for (const chunk of state.wat.chunks) {
|
||||
const chunkElem = document.createElement("span");
|
||||
if (chunk.wasm_offset != null) {
|
||||
chunkElem.dataset.wasmOffset = chunk.wasm_offset;
|
||||
const hue = existingHueForOffset(chunk.wasm_offset);
|
||||
if (hue) {
|
||||
chunkElem.style.backgroundColor = `hsl(${hue} 50% 95%)`;
|
||||
chunkElem.addEventListener("mouseenter", onMouseEnter);
|
||||
chunkElem.addEventListener("mouseleave", onMouseLeave);
|
||||
addWatElem(chunk.wasm_offset, chunkElem);
|
||||
}
|
||||
}
|
||||
chunkElem.textContent = chunk.wat;
|
||||
watElem.appendChild(chunkElem);
|
||||
}
|
||||
Reference in New Issue
Block a user