Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/explorer/src/index.js
1692 views
1
class State {
2
constructor(wat, clif, asm) {
3
this.wat = wat;
4
this.clif = clif;
5
this.asm = asm;
6
}
7
}
8
9
const state = (window.STATE = new State(window.WAT, window.CLIF, window.ASM));
10
11
/*** LRU Cache *****************************************************************/
12
13
class LruCache {
14
constructor(maxSize, getFunc) {
15
// Maps preserve the insertion order, so we can use it to implement a naïve LRU
16
// cache.
17
this.cache = new Map();
18
this.maxSize = maxSize;
19
this.getFunc = getFunc;
20
}
21
22
get(key) {
23
let v = this.cache.get(key);
24
if (v !== undefined) {
25
// Remove the found element from the cache so it can be inserted it again
26
// at the end before returning.
27
this.cache.delete(key);
28
} else {
29
v = this.getFunc(key);
30
if (this.cache.size > this.cache.maxSize) {
31
// Evict the oldest item from the cache.
32
this.cache.delete(this.cache.keys().next().value);
33
}
34
}
35
this.cache.set(key, v);
36
return v;
37
}
38
}
39
40
/*** Colors for Offsets **********************************************************/
41
42
const rgbToLuma = rgb => {
43
// Use the NTSC color space (https://en.wikipedia.org/wiki/YIQ) to determine
44
// the luminance (Y) of this color. (This is an approximation using powers of two,
45
// to avoid multiplications and divisions. It's not accurate, but it's good enough
46
// for our purposes.)
47
let [r, g, b] = rgbToTriple(rgb);
48
return (((r << 8) + (g << 9) + (b << 7)) >> 10) + (g & 31);
49
};
50
51
// Convert a color as a 24-bit number into a list with 3 elements: R, G, and B,
52
// each ranging [0, 255].
53
const rgbToTriple = rgb => [(rgb >> 16) & 0xff, (rgb >> 8) & 0xff, rgb & 0xff];
54
55
// Use CRC24 as a way to calculate a color for a given Wasm offset. This
56
// particular algorithm has been chosen because it produces bright, vibrant
57
// colors, that don't repeat often, and is easily implementable.
58
const calculateRgbForOffset = offset => {
59
const crc24 = (crc, byte) => {
60
// CRC computation adapted from Wikipedia[1] (shift-register based division versions.)
61
// [1] https://en.m.wikipedia.org/wiki/Computation_of_cyclic_redundancy_checks
62
crc ^= byte << 16;
63
for (let bit = 0; bit < 8; bit++) {
64
crc = ((crc << 1) ^ (crc & 0x800000 ? 0xfa5711 : 0)) & 0xffffff;
65
}
66
return crc;
67
};
68
69
// Feed the offset into the CRC24 algorithm, one byte at a time.
70
let color = offset;
71
while (offset) {
72
color = crc24(color, offset & 0xff);
73
offset >>= 8;
74
}
75
76
// Avoid colors that are too close to white. Flip some bits around
77
// so that the color components are more pronounced.
78
return rgbToLuma(color) > 200 ? color ^ 0xa5a5a5 : color;
79
};
80
81
// Memoize all colors for a given Wasm offset. Cache isn't used here since,
82
// when rendering the Wat side, we use the fact that if a color has not been
83
// assigned during the rendering of the Native Asm side, that block of Wasm
84
// instructions isn't colored.
85
let offsetToRgb = new Map();
86
const rgbForOffset = offset => {
87
let rgb = offsetToRgb.get(offset);
88
if (rgb === undefined) {
89
rgb = calculateRgbForOffset(offset);
90
offsetToRgb.set(offset, rgb);
91
}
92
return rgb;
93
};
94
95
// Convert a color in a 24-bit number to a string suitable for CSS styling.
96
const rgbToCss = rgb => `rgba(${rgbToTriple(rgb).join(",")})`;
97
98
// Darkens a color in a 24-bit number slightly by subtracting at most 0x20
99
// from each color component; e.g. RGB(175, 161, 10) becomes RGB(143, 129, 0).
100
// This loses some color information, but it's good enough for our use case here.
101
const rgbDarken = rgb => {
102
let [r, g, b] = rgbToTriple(rgb);
103
return (
104
((r - Math.min(r, 0x20)) << 16) |
105
((g - Math.min(g, 0x20)) << 8) |
106
(b - Math.min(b, 0x20))
107
);
108
};
109
110
// Adjust the color styles of a DOM element for a given Wasm offset.
111
const adjustColorForOffset = (element, offset) => {
112
let backgroundColor = rgbForOffset(offset);
113
element.style.backgroundColor = rgbToCss(backgroundColor);
114
element.classList.add(
115
rgbToLuma(backgroundColor) > 128 ? "dark-text" : "light-text",
116
);
117
};
118
119
/*** Event Handlers ************************************************************/
120
121
// Connects callbacks to mouse hovering events so elements are properly highlighted when
122
// hovered, and the bridging element is drawn between the instruction lists.
123
const linkedElementCache = new LruCache(256, offset =>
124
document.querySelectorAll(`[data-wasm-offset="${offset}"]`),
125
);
126
127
const eachElementWithSameWasmOff = (event, closure) => {
128
let offset = event.target.dataset.wasmOffset;
129
if (offset !== null) {
130
// Run the loop inside an animation frame. Since we're modifying the DOM,
131
// do so when the browser has some breathing room.
132
window.requestAnimationFrame(() => {
133
linkedElementCache.get(offset).forEach(closure);
134
});
135
}
136
};
137
138
const linkElements = element => {
139
element.addEventListener(
140
"click",
141
event => {
142
eachElementWithSameWasmOff(event, elem => {
143
if (elem === event.target) return; // Only scroll into view the other elements.
144
elem.scrollIntoView({
145
behavior: "smooth",
146
block: "center",
147
inline: "nearest",
148
});
149
});
150
},
151
{ passive: true },
152
);
153
154
element.addEventListener("mouseenter", event => {
155
let offset = event.target.dataset.wasmOffset;
156
if (offset === null) return;
157
158
// Gather all elements related to the desired offset.
159
let elems = linkedElementCache.get(offset);
160
161
// Perform the DOM modification inside an animation frame to give the browser a bit of
162
// a breathing room.
163
window.requestAnimationFrame(() => {
164
// Draw a 2px dark outline in each block of instructions so it stands out a bit better
165
// when hovered.
166
let outline = `2px solid ${rgbToCss(rgbDarken(rgbForOffset(offset)))}`;
167
for (const elem of elems) {
168
elem.setAttribute("title", `Wasm offset @ ${offset}`);
169
elem.classList.add("hovered");
170
elem.style.outline = outline;
171
}
172
});
173
});
174
175
element.addEventListener("mouseleave", event => {
176
eachElementWithSameWasmOff(event, elem => {
177
elem.removeAttribute("title");
178
elem.classList.remove("hovered");
179
elem.style.outline = "";
180
});
181
});
182
};
183
184
/*** Rendering *****************************************************************/
185
186
const repeat = (s, n) => {
187
return s.repeat(n >= 0 ? n : 0);
188
};
189
190
const renderAddress = addr => {
191
let hex = addr.toString(16);
192
return repeat("0", 8 - hex.length) + hex;
193
};
194
195
const renderBytes = bytes => {
196
let s = "";
197
for (let i = 0; i < bytes.length; i++) {
198
if (i != 0) {
199
s += " ";
200
}
201
const hexByte = bytes[i].toString(16);
202
s += hexByte.length == 2 ? hexByte : "0" + hexByte;
203
}
204
return s + repeat(" ", 30 - s.length);
205
};
206
207
const renderInst = (mnemonic, operands) => {
208
if (operands.length == 0) {
209
return mnemonic;
210
} else {
211
return mnemonic + " " + operands;
212
}
213
};
214
215
const createDivForCode = () => {
216
let div = document.createElement("div");
217
div.classList.add("highlight");
218
return div;
219
};
220
221
// Render the CLIF (if any).
222
const clifElem = document.getElementById("clif");
223
if (clifElem) {
224
for (const func of state.clif.functions) {
225
const funcElem = document.createElement("div");
226
227
const funcHeader = document.createElement("h3");
228
let func_name =
229
func.name === null ? `function[${func.func_index}]` : func.name;
230
let demangled_name =
231
func.demangled_name !== null ? func.demangled_name : func_name;
232
funcHeader.textContent = `Intermediate Representation of function <${demangled_name}>:`;
233
funcHeader.title = `Function ${func.func_index}: ${func_name}`;
234
funcElem.appendChild(funcHeader);
235
236
for (const inst of func.instructions) {
237
const instElem = createDivForCode();
238
instElem.textContent = `${inst.clif}\n`;
239
if (inst.wasm_offset != null) {
240
instElem.dataset.wasmOffset = inst.wasm_offset;
241
adjustColorForOffset(instElem, inst.wasm_offset);
242
linkElements(instElem);
243
}
244
funcElem.appendChild(instElem);
245
}
246
247
clifElem.appendChild(funcElem);
248
}
249
}
250
251
// Render the ASM.
252
const asmElem = document.getElementById("asm");
253
for (const func of state.asm.functions) {
254
const funcElem = document.createElement("div");
255
256
const funcHeader = document.createElement("h3");
257
let functionName =
258
func.name === null ? `function[${func.func_index}]` : func.name;
259
let demangledName =
260
func.demangled_name !== null ? func.demangled_name : functionName;
261
funcHeader.textContent = `Disassembly of function <${demangledName}>:`;
262
funcHeader.title = `Function ${func.func_index}: ${functionName}`;
263
funcElem.appendChild(funcHeader);
264
265
let currentBlock = createDivForCode();
266
let disasmBuffer = [];
267
let lastOffset = null;
268
269
const addCurrentBlock = offset => {
270
currentBlock.dataset.wasmOffset = offset;
271
272
if (offset !== null) {
273
adjustColorForOffset(currentBlock, offset);
274
linkElements(currentBlock);
275
}
276
277
currentBlock.innerText = disasmBuffer.join("\n");
278
funcElem.appendChild(currentBlock);
279
disasmBuffer = [];
280
};
281
282
for (const inst of func.instructions) {
283
if (lastOffset !== inst.wasm_offset) {
284
addCurrentBlock(lastOffset);
285
currentBlock = createDivForCode();
286
lastOffset = inst.wasm_offset;
287
}
288
disasmBuffer.push(
289
`${renderAddress(inst.address)} ${renderBytes(inst.bytes)} ${renderInst(inst.mnemonic, inst.operands)}`,
290
);
291
}
292
addCurrentBlock(lastOffset);
293
294
asmElem.appendChild(funcElem);
295
}
296
297
// Render the WAT.
298
const watElem = document.getElementById("wat");
299
for (const chunk of state.wat.chunks) {
300
if (chunk.wasm_offset === null) continue;
301
const block = createDivForCode();
302
block.dataset.wasmOffset = chunk.wasm_offset;
303
block.innerText = chunk.wat;
304
305
if (offsetToRgb.get(chunk.wasm_offset) !== undefined) {
306
adjustColorForOffset(block, chunk.wasm_offset);
307
linkElements(block);
308
}
309
310
watElem.appendChild(block);
311
}
312
313