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.
239 lines
5.8 KiB
JavaScript
239 lines
5.8 KiB
JavaScript
/*** 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);
|
|
}
|