/*** 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); }