-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce the
wasmtime-explorer
crate
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.
- Loading branch information
Showing
11 changed files
with
546 additions
and
8 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
[package] | ||
name = "wasmtime-explorer" | ||
authors.workspace = true | ||
description = "Compiler explorer for Wasmtime and Cranelift" | ||
documentation = "https://docs.rs/wasmtime-explorer/" | ||
edition.workspace = true | ||
license = "Apache-2.0 WITH LLVM-exception" | ||
repository = "https://github.com/bytecodealliance/wasmtime" | ||
version.workspace = true | ||
|
||
[dependencies] | ||
anyhow = { workspace = true } | ||
capstone = { workspace = true } | ||
serde = { workspace = true } | ||
serde_json = { workspace = true } | ||
target-lexicon = { workspace = true } | ||
wasmprinter = { workspace = true } | ||
wasmtime = { workspace = true, features = ["cranelift"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
root: true | ||
|
||
env: | ||
browser: true | ||
es2022: true | ||
|
||
extends: | ||
- "eslint:recommended" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
* { | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
.hbox { | ||
display: flex; | ||
flex-direction: row; | ||
} | ||
|
||
html, body { | ||
width: 100%; | ||
height: 100%; | ||
} | ||
|
||
#wat { | ||
width: 50%; | ||
height: 100%; | ||
overflow: scroll; | ||
} | ||
|
||
#asm { | ||
width: 50%; | ||
height: 100%; | ||
overflow: scroll; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} |
Oops, something went wrong.