Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ wasmtime-cache = { workspace = true }
wasmtime-cli-flags = { workspace = true }
wasmtime-cranelift = { workspace = true }
wasmtime-environ = { workspace = true }
wasmtime-explorer = { workspace = true }
wasmtime-wast = { workspace = true }
wasmtime-wasi = { workspace = true, features = ["exit"] }
wasmtime-wasi-crypto = { workspace = true, optional = true }
Expand All @@ -39,8 +40,8 @@ humantime = "2.0.0"
once_cell = { workspace = true }
listenfd = "1.0.0"
wat = { workspace = true }
serde = "1.0.94"
serde_json = "1.0.26"
serde = { workspace = true }
serde_json = { workspace = true }
wasmparser = { workspace = true }
wasm-coredump-builder = { version = "0.1.11" }

Expand Down Expand Up @@ -70,8 +71,8 @@ component-macro-test = { path = "crates/misc/component-macro-test" }
component-test-util = { workspace = true }
bstr = "0.2.17"
libc = "0.2.60"
serde = "1.0"
serde_json = "1.0"
serde = { workspace = true }
serde_json = { workspace = true }

[target.'cfg(windows)'.dev-dependencies]
windows-sys = { workspace = true, features = ["Win32_System_Memory"] }
Expand Down Expand Up @@ -120,6 +121,7 @@ wasmtime-cli-flags = { path = "crates/cli-flags", version = "=8.0.0" }
wasmtime-cranelift = { path = "crates/cranelift", version = "=8.0.0" }
wasmtime-cranelift-shared = { path = "crates/cranelift-shared", version = "=8.0.0" }
wasmtime-environ = { path = "crates/environ", version = "=8.0.0" }
wasmtime-explorer = { path = "crates/explorer", version = "=8.0.0" }
wasmtime-fiber = { path = "crates/fiber", version = "=8.0.0" }
wasmtime-types = { path = "crates/types", version = "8.0.0" }
wasmtime-jit = { path = "crates/jit", version = "=8.0.0" }
Expand Down Expand Up @@ -196,6 +198,7 @@ heck = "0.4"
similar = "2.1.0"
toml = "0.5.9"
serde = "1.0.94"
serde_json = "1.0.80"
glob = "0.3.0"

[features]
Expand Down
18 changes: 18 additions & 0 deletions crates/explorer/Cargo.toml
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"] }
8 changes: 8 additions & 0 deletions crates/explorer/src/.eslintrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root: true

env:
browser: true
es2022: true

extends:
- "eslint:recommended"
26 changes: 26 additions & 0 deletions crates/explorer/src/index.css
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;
}
238 changes: 238 additions & 0 deletions crates/explorer/src/index.js
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);
}
Loading