Skip to content

Commit f5c6fb3

Browse files
ziadziad
authored andcommitted
refactor explorer code
1 parent d4656e4 commit f5c6fb3

17 files changed

Lines changed: 8759 additions & 8320 deletions

playground/explorer.ts

Lines changed: 282 additions & 4538 deletions
Large diffs are not rendered by default.
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
import InstructionDecoder from '../../src/InstructionDecoder';
2+
import type { DetailContext } from './detail-renderers';
3+
import {
4+
appendHeading,
5+
appendSubheading,
6+
createInfoTable,
7+
formatFileSize,
8+
} from './ui-helpers';
9+
10+
export function renderFunctionComplexity(context: DetailContext): void {
11+
const detail = context.detailContainer;
12+
const moduleInfo = context.moduleInfo;
13+
const importedFuncCount = context.getImportedCount(0);
14+
15+
appendHeading(detail, 'Function Complexity');
16+
17+
interface ComplexityEntry {
18+
localIndex: number;
19+
globalIndex: number;
20+
name: string | null;
21+
instructionCount: number;
22+
branchCount: number;
23+
maxNestingDepth: number;
24+
bodySize: number;
25+
}
26+
27+
const entries: ComplexityEntry[] = [];
28+
29+
for (let funcIndex = 0; funcIndex < moduleInfo.functions.length; funcIndex++) {
30+
const globalIndex = importedFuncCount + funcIndex;
31+
const func = moduleInfo.functions[funcIndex];
32+
const instructions = func.instructions || InstructionDecoder.decodeFunctionBody(func.body);
33+
const funcName = context.getFunctionName(globalIndex);
34+
35+
let instructionCount = 0;
36+
let branchCount = 0;
37+
let currentDepth = 0;
38+
let maxDepth = 0;
39+
40+
for (const instruction of instructions) {
41+
const mnemonic = instruction.opCode.mnemonic;
42+
if (mnemonic === 'end') {
43+
currentDepth = Math.max(0, currentDepth - 1);
44+
continue;
45+
}
46+
instructionCount++;
47+
if (mnemonic === 'block' || mnemonic === 'loop' || mnemonic === 'if' || mnemonic === 'try') {
48+
currentDepth++;
49+
maxDepth = Math.max(maxDepth, currentDepth);
50+
}
51+
if (mnemonic === 'br' || mnemonic === 'br_if' || mnemonic === 'br_table' || mnemonic === 'if') {
52+
branchCount++;
53+
}
54+
}
55+
56+
entries.push({
57+
localIndex: funcIndex,
58+
globalIndex,
59+
name: funcName,
60+
instructionCount,
61+
branchCount,
62+
maxNestingDepth: maxDepth,
63+
bodySize: func.body.length,
64+
});
65+
}
66+
67+
entries.sort((entryA, entryB) => {
68+
const scoreA = entryA.branchCount * 3 + entryA.maxNestingDepth * 5 + entryA.instructionCount;
69+
const scoreB = entryB.branchCount * 3 + entryB.maxNestingDepth * 5 + entryB.instructionCount;
70+
return scoreB - scoreA;
71+
});
72+
73+
const maxScore = entries.length > 0
74+
? entries[0].branchCount * 3 + entries[0].maxNestingDepth * 5 + entries[0].instructionCount
75+
: 1;
76+
77+
const highThreshold = 150;
78+
const mediumThreshold = 75;
79+
const highCount = entries.filter(entry => (entry.branchCount * 3 + entry.maxNestingDepth * 5 + entry.instructionCount) >= highThreshold).length;
80+
const medCount = entries.filter(entry => {
81+
const score = entry.branchCount * 3 + entry.maxNestingDepth * 5 + entry.instructionCount;
82+
return score >= mediumThreshold && score < highThreshold;
83+
}).length;
84+
const lowCount = entries.length - highCount - medCount;
85+
86+
const summaryGrid = document.createElement('div');
87+
summaryGrid.className = 'module-summary-grid';
88+
for (const [tierLabel, tierCount, tierColor] of [
89+
['High', highCount, '#f38ba8'] as const,
90+
['Medium', medCount, '#fab387'] as const,
91+
['Low', lowCount, '#a6e3a1'] as const,
92+
]) {
93+
const card = document.createElement('div');
94+
card.className = 'module-summary-card';
95+
card.style.borderColor = tierColor;
96+
const countEl = document.createElement('span');
97+
countEl.className = 'module-summary-count';
98+
countEl.style.color = tierColor;
99+
countEl.textContent = String(tierCount);
100+
card.appendChild(countEl);
101+
const labelEl = document.createElement('span');
102+
labelEl.className = 'module-summary-label';
103+
labelEl.textContent = tierLabel;
104+
card.appendChild(labelEl);
105+
summaryGrid.appendChild(card);
106+
}
107+
detail.appendChild(summaryGrid);
108+
109+
appendSubheading(detail, 'By Complexity Score');
110+
111+
const headerRow = document.createElement('div');
112+
headerRow.className = 'detail-info-row complexity-header';
113+
for (const label of ['Function', 'Score', 'Instructions', 'Branches', 'Depth', 'Bytes']) {
114+
const cell = document.createElement('span');
115+
cell.className = 'complexity-header-cell';
116+
cell.textContent = label;
117+
headerRow.appendChild(cell);
118+
}
119+
detail.appendChild(headerRow);
120+
121+
for (const entry of entries) {
122+
const row = document.createElement('div');
123+
row.className = 'detail-info-row';
124+
125+
const displayName = entry.name || `func_${entry.globalIndex}`;
126+
const link = document.createElement('a');
127+
link.className = 'detail-info-link';
128+
link.textContent = displayName;
129+
link.style.flex = '2';
130+
link.href = '#';
131+
link.addEventListener('click', (event) => {
132+
event.preventDefault();
133+
context.navigateToItem('function', entry.localIndex);
134+
});
135+
row.appendChild(link);
136+
137+
const score = entry.branchCount * 3 + entry.maxNestingDepth * 5 + entry.instructionCount;
138+
let tierColor = '#a6e3a1';
139+
if (score >= highThreshold) {
140+
tierColor = '#f38ba8';
141+
} else if (score >= mediumThreshold) {
142+
tierColor = '#fab387';
143+
}
144+
145+
const scoreCell = document.createElement('span');
146+
scoreCell.className = 'complexity-cell';
147+
148+
const barContainer = document.createElement('span');
149+
barContainer.className = 'size-bar-container';
150+
barContainer.style.display = 'inline-block';
151+
barContainer.style.width = '60px';
152+
barContainer.style.verticalAlign = 'middle';
153+
barContainer.style.marginRight = '8px';
154+
const bar = document.createElement('span');
155+
bar.className = 'size-bar';
156+
bar.style.width = `${Math.max(2, (score / maxScore) * 100)}%`;
157+
bar.style.background = tierColor;
158+
bar.style.display = 'block';
159+
barContainer.appendChild(bar);
160+
scoreCell.appendChild(barContainer);
161+
scoreCell.appendChild(document.createTextNode(String(score)));
162+
row.appendChild(scoreCell);
163+
164+
for (const value of [entry.instructionCount, entry.branchCount, entry.maxNestingDepth, entry.bodySize]) {
165+
const cell = document.createElement('span');
166+
cell.className = 'complexity-cell';
167+
cell.textContent = String(value);
168+
row.appendChild(cell);
169+
}
170+
171+
detail.appendChild(row);
172+
}
173+
}
174+
175+
export function renderDeadCode(context: DetailContext): void {
176+
const detail = context.detailContainer;
177+
const moduleInfo = context.moduleInfo;
178+
const importedFuncCount = context.getImportedCount(0);
179+
const callGraphData = context.getCallGraph();
180+
181+
const exportedFuncIndices = new Set<number>();
182+
for (const exportEntry of moduleInfo.exports) {
183+
if (exportEntry.kind === 0) {
184+
exportedFuncIndices.add(exportEntry.index);
185+
}
186+
}
187+
188+
if (moduleInfo.start !== null) {
189+
exportedFuncIndices.add(moduleInfo.start);
190+
}
191+
192+
for (const elementEntry of moduleInfo.elements) {
193+
for (const funcIdx of elementEntry.functionIndices) {
194+
exportedFuncIndices.add(funcIdx);
195+
}
196+
}
197+
198+
const reachable = new Set<number>();
199+
const worklist = Array.from(exportedFuncIndices);
200+
while (worklist.length > 0) {
201+
const current = worklist.pop()!;
202+
if (reachable.has(current)) {
203+
continue;
204+
}
205+
reachable.add(current);
206+
const callees = callGraphData.callees.get(current);
207+
if (callees) {
208+
for (const callee of callees) {
209+
if (!reachable.has(callee)) {
210+
worklist.push(callee);
211+
}
212+
}
213+
}
214+
}
215+
216+
const deadFunctions: { localIndex: number; globalIndex: number; name: string | null; bodySize: number }[] = [];
217+
for (let funcIndex = 0; funcIndex < moduleInfo.functions.length; funcIndex++) {
218+
const globalIndex = importedFuncCount + funcIndex;
219+
if (!reachable.has(globalIndex)) {
220+
deadFunctions.push({
221+
localIndex: funcIndex,
222+
globalIndex,
223+
name: context.getFunctionName(globalIndex),
224+
bodySize: moduleInfo.functions[funcIndex].body.length,
225+
});
226+
}
227+
}
228+
229+
appendHeading(detail, 'Dead Code Analysis');
230+
231+
const wastedBytes = deadFunctions.reduce((sum, func) => sum + func.bodySize, 0);
232+
const totalCodeSize = moduleInfo.functions.reduce((sum, func) => sum + func.body.length, 0);
233+
const wastedPercent = totalCodeSize > 0 ? ((wastedBytes / totalCodeSize) * 100).toFixed(1) : '0';
234+
235+
const summaryGrid = document.createElement('div');
236+
summaryGrid.className = 'module-summary-grid';
237+
238+
for (const [label, count, color] of [
239+
['Reachable', reachable.size - importedFuncCount, '#a6e3a1'] as const,
240+
['Unreachable', deadFunctions.length, deadFunctions.length > 0 ? '#f38ba8' : '#a6e3a1'] as const,
241+
['Wasted', formatFileSize(wastedBytes), '#fab387'] as const,
242+
]) {
243+
const card = document.createElement('div');
244+
card.className = 'module-summary-card';
245+
const countEl = document.createElement('span');
246+
countEl.className = 'module-summary-count';
247+
countEl.style.color = color as string;
248+
countEl.textContent = String(count);
249+
card.appendChild(countEl);
250+
const labelEl = document.createElement('span');
251+
labelEl.className = 'module-summary-label';
252+
labelEl.textContent = label;
253+
card.appendChild(labelEl);
254+
summaryGrid.appendChild(card);
255+
}
256+
detail.appendChild(summaryGrid);
257+
258+
if (wastedBytes > 0 && totalCodeSize > 0) {
259+
const wastedInfo = document.createElement('div');
260+
wastedInfo.className = 'detail-description';
261+
wastedInfo.textContent = `${wastedPercent}% of code bytes are unreachable.`;
262+
detail.appendChild(wastedInfo);
263+
}
264+
265+
if (deadFunctions.length === 0) {
266+
const noDeadCode = document.createElement('div');
267+
noDeadCode.className = 'detail-description';
268+
noDeadCode.textContent = 'No unreachable functions detected.';
269+
detail.appendChild(noDeadCode);
270+
return;
271+
}
272+
273+
deadFunctions.sort((funcA, funcB) => funcB.bodySize - funcA.bodySize);
274+
const maxDeadSize = deadFunctions[0].bodySize;
275+
276+
appendSubheading(detail, `Unreachable Functions (${deadFunctions.length})`);
277+
const funcTable = createInfoTable();
278+
for (const deadFunc of deadFunctions) {
279+
const displayName = deadFunc.name || `func_${deadFunc.globalIndex}`;
280+
const row = document.createElement('div');
281+
row.className = 'detail-info-row';
282+
283+
const link = document.createElement('a');
284+
link.className = 'detail-info-link';
285+
link.style.flex = '0 0 180px';
286+
link.textContent = displayName;
287+
link.href = '#';
288+
link.addEventListener('click', (event) => {
289+
event.preventDefault();
290+
context.navigateToItem('function', deadFunc.localIndex);
291+
});
292+
row.appendChild(link);
293+
294+
const barContainer = document.createElement('div');
295+
barContainer.className = 'size-bar-container';
296+
const bar = document.createElement('div');
297+
bar.className = 'size-bar';
298+
bar.style.width = `${Math.max(2, (deadFunc.bodySize / maxDeadSize) * 100)}%`;
299+
bar.style.background = '#f38ba8';
300+
barContainer.appendChild(bar);
301+
row.appendChild(barContainer);
302+
303+
const valueElement = document.createElement('span');
304+
valueElement.className = 'size-value';
305+
valueElement.textContent = formatFileSize(deadFunc.bodySize);
306+
row.appendChild(valueElement);
307+
308+
funcTable.appendChild(row);
309+
}
310+
detail.appendChild(funcTable);
311+
}

0 commit comments

Comments
 (0)