class State {
constructor(wat, clif, asm) {
this.wat = wat;
this.clif = clif;
this.asm = asm;
}
}
const state = (window.STATE = new State(window.WAT, window.CLIF, window.ASM));
class LruCache {
constructor(maxSize, getFunc) {
this.cache = new Map();
this.maxSize = maxSize;
this.getFunc = getFunc;
}
get(key) {
let v = this.cache.get(key);
if (v !== undefined) {
this.cache.delete(key);
} else {
v = this.getFunc(key);
if (this.cache.size > this.cache.maxSize) {
this.cache.delete(this.cache.keys().next().value);
}
}
this.cache.set(key, v);
return v;
}
}
const rgbToLuma = rgb => {
let [r, g, b] = rgbToTriple(rgb);
return (((r << 8) + (g << 9) + (b << 7)) >> 10) + (g & 31);
};
const rgbToTriple = rgb => [(rgb >> 16) & 0xff, (rgb >> 8) & 0xff, rgb & 0xff];
const calculateRgbForOffset = offset => {
const crc24 = (crc, byte) => {
crc ^= byte << 16;
for (let bit = 0; bit < 8; bit++) {
crc = ((crc << 1) ^ (crc & 0x800000 ? 0xfa5711 : 0)) & 0xffffff;
}
return crc;
};
let color = offset;
while (offset) {
color = crc24(color, offset & 0xff);
offset >>= 8;
}
return rgbToLuma(color) > 200 ? color ^ 0xa5a5a5 : color;
};
let offsetToRgb = new Map();
const rgbForOffset = offset => {
let rgb = offsetToRgb.get(offset);
if (rgb === undefined) {
rgb = calculateRgbForOffset(offset);
offsetToRgb.set(offset, rgb);
}
return rgb;
};
const rgbToCss = rgb => `rgba(${rgbToTriple(rgb).join(",")})`;
const rgbDarken = rgb => {
let [r, g, b] = rgbToTriple(rgb);
return (
((r - Math.min(r, 0x20)) << 16) |
((g - Math.min(g, 0x20)) << 8) |
(b - Math.min(b, 0x20))
);
};
const adjustColorForOffset = (element, offset) => {
let backgroundColor = rgbForOffset(offset);
element.style.backgroundColor = rgbToCss(backgroundColor);
element.classList.add(
rgbToLuma(backgroundColor) > 128 ? "dark-text" : "light-text",
);
};
const linkedElementCache = new LruCache(256, offset =>
document.querySelectorAll(`[data-wasm-offset="${offset}"]`),
);
const eachElementWithSameWasmOff = (event, closure) => {
let offset = event.target.dataset.wasmOffset;
if (offset !== null) {
window.requestAnimationFrame(() => {
linkedElementCache.get(offset).forEach(closure);
});
}
};
const linkElements = element => {
element.addEventListener(
"click",
event => {
eachElementWithSameWasmOff(event, elem => {
if (elem === event.target) return;
elem.scrollIntoView({
behavior: "smooth",
block: "center",
inline: "nearest",
});
});
},
{ passive: true },
);
element.addEventListener("mouseenter", event => {
let offset = event.target.dataset.wasmOffset;
if (offset === null) return;
let elems = linkedElementCache.get(offset);
window.requestAnimationFrame(() => {
let outline = `2px solid ${rgbToCss(rgbDarken(rgbForOffset(offset)))}`;
for (const elem of elems) {
elem.setAttribute("title", `Wasm offset @ ${offset}`);
elem.classList.add("hovered");
elem.style.outline = outline;
}
});
});
element.addEventListener("mouseleave", event => {
eachElementWithSameWasmOff(event, elem => {
elem.removeAttribute("title");
elem.classList.remove("hovered");
elem.style.outline = "";
});
});
};
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;
}
};
const createDivForCode = () => {
let div = document.createElement("div");
div.classList.add("highlight");
return div;
};
const clifElem = document.getElementById("clif");
if (clifElem) {
for (const func of state.clif.functions) {
const funcElem = document.createElement("div");
const funcHeader = document.createElement("h3");
let func_name =
func.name === null ? `function[${func.func_index}]` : func.name;
let demangled_name =
func.demangled_name !== null ? func.demangled_name : func_name;
funcHeader.textContent = `Intermediate Representation of function <${demangled_name}>:`;
funcHeader.title = `Function ${func.func_index}: ${func_name}`;
funcElem.appendChild(funcHeader);
for (const inst of func.instructions) {
const instElem = createDivForCode();
instElem.textContent = `${inst.clif}\n`;
if (inst.wasm_offset != null) {
instElem.dataset.wasmOffset = inst.wasm_offset;
adjustColorForOffset(instElem, inst.wasm_offset);
linkElements(instElem);
}
funcElem.appendChild(instElem);
}
clifElem.appendChild(funcElem);
}
}
const asmElem = document.getElementById("asm");
for (const func of state.asm.functions) {
const funcElem = document.createElement("div");
const funcHeader = document.createElement("h3");
let functionName =
func.name === null ? `function[${func.func_index}]` : func.name;
let demangledName =
func.demangled_name !== null ? func.demangled_name : functionName;
funcHeader.textContent = `Disassembly of function <${demangledName}>:`;
funcHeader.title = `Function ${func.func_index}: ${functionName}`;
funcElem.appendChild(funcHeader);
let currentBlock = createDivForCode();
let disasmBuffer = [];
let lastOffset = null;
const addCurrentBlock = offset => {
currentBlock.dataset.wasmOffset = offset;
if (offset !== null) {
adjustColorForOffset(currentBlock, offset);
linkElements(currentBlock);
}
currentBlock.innerText = disasmBuffer.join("\n");
funcElem.appendChild(currentBlock);
disasmBuffer = [];
};
for (const inst of func.instructions) {
if (lastOffset !== inst.wasm_offset) {
addCurrentBlock(lastOffset);
currentBlock = createDivForCode();
lastOffset = inst.wasm_offset;
}
disasmBuffer.push(
`${renderAddress(inst.address)} ${renderBytes(inst.bytes)} ${renderInst(inst.mnemonic, inst.operands)}`,
);
}
addCurrentBlock(lastOffset);
asmElem.appendChild(funcElem);
}
const watElem = document.getElementById("wat");
for (const chunk of state.wat.chunks) {
if (chunk.wasm_offset === null) continue;
const block = createDivForCode();
block.dataset.wasmOffset = chunk.wasm_offset;
block.innerText = chunk.wat;
if (offsetToRgb.get(chunk.wasm_offset) !== undefined) {
adjustColorForOffset(block, chunk.wasm_offset);
linkElements(block);
}
watElem.appendChild(block);
}