Skip to content

Commit 5669b9a

Browse files
ziadziad
authored andcommitted
* Add intrinsic functions for wasm extensions
* add tests for decompiler
1 parent 1b36ea6 commit 5669b9a

28 files changed

Lines changed: 3268 additions & 98 deletions

.prettierignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
lib/
2+
build/
3+
node_modules/
4+
playground/playground.bundle.js
5+
playground/playground.bundle.js.map
6+
generator/generated/

.prettierrc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"singleQuote": true,
3+
"trailingComma": "all",
4+
"printWidth": 120,
5+
"tabWidth": 2,
6+
"semi": true,
7+
"bracketSpacing": true,
8+
"arrowParens": "always"
9+
}

playground/explorer.ts

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3935,8 +3935,9 @@ export default class Explorer {
39353935

39363936
if (this.moduleInfo.imports.length > 0) {
39373937
const importTable = this.createInfoTable();
3938-
for (let importIndex = 0; importIndex < this.moduleInfo.imports.length; importIndex++) {
3939-
const importEntry = this.moduleInfo.imports[importIndex];
3938+
const sortedImports = this.moduleInfo.imports.map((entry, index) => ({ entry, index }));
3939+
sortedImports.sort((a, b) => a.entry.fieldName.localeCompare(b.entry.fieldName));
3940+
for (const { entry: importEntry, index: importIndex } of sortedImports) {
39403941
const kindName = EXPORT_KIND_NAMES[importEntry.kind] || 'unknown';
39413942
let signature = '';
39423943
if (importEntry.kind === 0 && importEntry.typeIndex !== undefined && importEntry.typeIndex < flatTypes.length) {
@@ -3951,7 +3952,8 @@ export default class Explorer {
39513952

39523953
const nameLink = document.createElement('a');
39533954
nameLink.className = 'detail-info-link';
3954-
nameLink.style.flex = '0 0 140px';
3955+
nameLink.style.flex = '0 0 auto';
3956+
nameLink.style.maxWidth = '50%';
39553957
nameLink.textContent = importEntry.fieldName;
39563958
nameLink.title = `${importEntry.moduleName}.${importEntry.fieldName}`;
39573959
nameLink.href = '#';
@@ -3994,8 +3996,9 @@ export default class Explorer {
39943996

39953997
if (this.moduleInfo.exports.length > 0) {
39963998
const exportTable = this.createInfoTable();
3997-
for (let exportIndex = 0; exportIndex < this.moduleInfo.exports.length; exportIndex++) {
3998-
const exportEntry = this.moduleInfo.exports[exportIndex];
3999+
const sortedExports = this.moduleInfo.exports.map((entry, index) => ({ entry, index }));
4000+
sortedExports.sort((a, b) => a.entry.name.localeCompare(b.entry.name));
4001+
for (const { entry: exportEntry, index: exportIndex } of sortedExports) {
39994002
const kindName = EXPORT_KIND_NAMES[exportEntry.kind] || 'unknown';
40004003
let signature = '';
40014004
const targetSection = this.getExportTargetSection(exportEntry.kind);
@@ -4023,7 +4026,8 @@ export default class Explorer {
40234026

40244027
const nameLink = document.createElement('a');
40254028
nameLink.className = 'detail-info-link';
4026-
nameLink.style.flex = '0 0 140px';
4029+
nameLink.style.flex = '0 0 auto';
4030+
nameLink.style.maxWidth = '50%';
40274031
nameLink.textContent = exportEntry.name;
40284032
nameLink.title = exportEntry.name;
40294033
nameLink.href = '#';
@@ -4143,8 +4147,8 @@ export default class Explorer {
41434147
: 1;
41444148

41454149
// Tier thresholds
4146-
const highThreshold = maxScore * 0.6;
4147-
const mediumThreshold = maxScore * 0.2;
4150+
const highThreshold = 150;
4151+
const mediumThreshold = 75;
41484152
const highCount = entries.filter(entry => (entry.branchCount * 3 + entry.maxNestingDepth * 5 + entry.instructionCount) >= highThreshold).length;
41494153
const medCount = entries.filter(entry => {
41504154
const score = entry.branchCount * 3 + entry.maxNestingDepth * 5 + entry.instructionCount;

playground/playground.bundle.js

Lines changed: 252 additions & 48 deletions
Large diffs are not rendered by default.

playground/playground.bundle.js.map

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Disassembler.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ const valueTypeNames: Record<number, string> = {
1818
0x71: 'nullref',
1919
0x73: 'nullfuncref',
2020
0x72: 'nullexternref',
21+
// Signed keys (readVarInt7 returns signed values for these)
22+
[-1]: 'i32', [-2]: 'i64', [-3]: 'f32', [-4]: 'f64', [-5]: 'v128',
23+
[-16]: 'funcref', [-17]: 'externref', [-18]: 'anyref', [-19]: 'eqref',
24+
[-20]: 'i31ref', [-21]: 'structref', [-22]: 'arrayref',
25+
[-15]: 'nullref', [-13]: 'nullfuncref', [-14]: 'nullexternref',
2126
};
2227

2328
const blockTypeNames: Record<number, string> = {
@@ -290,6 +295,8 @@ export default class Disassembler {
290295
const names: Record<number, string> = {
291296
0x7f: 'i32', 0x7e: 'i64', 0x7d: 'f32', 0x7c: 'f64', 0x7b: 'v128',
292297
0x70: 'funcref', 0x6f: 'externref',
298+
[-1]: 'i32', [-2]: 'i64', [-3]: 'f32', [-4]: 'f64', [-5]: 'v128',
299+
[-16]: 'funcref', [-17]: 'externref',
293300
};
294301
return names[valueType] || `type_${valueType}`;
295302
}
@@ -369,7 +376,7 @@ export default class Disassembler {
369376
}
370377
case 1: { // table
371378
const tt = imp.tableType!;
372-
const elemType = valueTypeNames[tt.elementType & 0xff] || 'anyfunc';
379+
const elemType = valueTypeNames[tt.elementType] || 'anyfunc';
373380
const max = tt.maximum !== null ? ` ${tt.maximum}` : '';
374381
this.line(`(import ${mod} ${field} (table ${tt.initial}${max} ${elemType}))`);
375382
break;
@@ -546,7 +553,7 @@ export default class Disassembler {
546553
private writeTables(): void {
547554
for (let i = 0; i < this.module.tables.length; i++) {
548555
const t = this.module.tables[i];
549-
const elemType = valueTypeNames[t.elementType & 0xff] || 'anyfunc';
556+
const elemType = valueTypeNames[t.elementType] || 'anyfunc';
550557
const max = t.maximum !== null ? ` ${t.maximum}` : '';
551558
this.line(`(table (;${this.importedTableCount + i};) ${t.initial}${max} ${elemType})`);
552559
}

src/DwarfParser.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ function parseDebugInfo(
811811
reader.readUint32(); // type_offset (4 bytes)
812812
}
813813
// Skip non-compile units entirely (type units, skeleton units, etc.)
814-
if (unitType !== 0x01 && unitType !== 0x11) {
814+
if (unitType !== 0x01) {
815815
reader.offset = unitEnd;
816816
continue;
817817
}
@@ -858,13 +858,29 @@ function parseDebugInfo(
858858
break;
859859
}
860860

861+
const attrStartOffset = reader.offset;
861862
const attrs = new Map<number, string | number | boolean | null>();
862863
for (const attrDef of abbrev.attributes) {
863864
const value = readAttrValue(reader, attrDef.form, addressSize, debugStr, debugLineStr, attrDef.implicitConst, unitStart, debugStrOffsets, strOffsetsBase);
864865
attrs.set(attrDef.attribute, value);
865866
}
866867

867868
if (abbrev.tag === DW_TAG_compile_unit) {
869+
// Extract str_offsets_base first — if non-zero, re-parse CU attributes with correct base
870+
const strOffsetsBaseVal = attrs.get(DW_AT_str_offsets_base);
871+
if (typeof strOffsetsBaseVal === 'number' && strOffsetsBaseVal !== strOffsetsBase) {
872+
strOffsetsBase = strOffsetsBaseVal;
873+
// Re-parse CU DIE attributes with the correct strOffsetsBase
874+
const savedOffset = reader.offset;
875+
reader.offset = attrStartOffset;
876+
attrs.clear();
877+
for (const attrDef of abbrev.attributes) {
878+
const value = readAttrValue(reader, attrDef.form, addressSize, debugStr, debugLineStr, attrDef.implicitConst, unitStart, debugStrOffsets, strOffsetsBase);
879+
attrs.set(attrDef.attribute, value);
880+
}
881+
reader.offset = savedOffset;
882+
}
883+
868884
const nameVal = attrs.get(DW_AT_name);
869885
if (typeof nameVal === 'string') { unitName = nameVal; }
870886
const producerVal = attrs.get(DW_AT_producer);
@@ -873,8 +889,6 @@ function parseDebugInfo(
873889
if (typeof langVal === 'number') { unitLanguage = langVal; }
874890
const compDirVal = attrs.get(DW_AT_comp_dir);
875891
if (typeof compDirVal === 'string') { unitCompDir = compDirVal; }
876-
const strOffsetsBaseVal = attrs.get(DW_AT_str_offsets_base);
877-
if (typeof strOffsetsBaseVal === 'number') { strOffsetsBase = strOffsetsBaseVal; }
878892
}
879893

880894
if (abbrev.tag === DW_TAG_subprogram) {

src/decompiler/ControlFlowGraph.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,10 @@ export function buildControlFlowGraph(instructions: DecodedInstruction[]): Contr
135135
}
136136

137137
if (mnemonic === 'delegate') {
138-
// delegate ends the current try scope. The depth immediate identifies which
139-
// enclosing try block handles the exception — this is runtime behavior, not CFG structure.
138+
// delegate terminates the current try block (pops one scope).
139+
// The depth immediate identifies which enclosing try re-catches the exception —
140+
// this affects runtime exception routing but not scope nesting.
141+
// The delegate instruction itself acts like 'end' for the current try.
140142
if (scopeStack.length > 0) {
141143
const scope = scopeStack.pop()!;
142144
if (!scope.endTarget) {

src/decompiler/LoweredEmitter.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,12 @@ export function emitLowered(
273273
} else {
274274
const addr = formatExpression(stmt.address, 0);
275275
const offsetStr = stmt.offset > 0 ? (addr === '0' ? String(stmt.offset) : `${addr} + ${stmt.offset}`) : addr;
276-
emit(`memory[${offsetStr}] = ${formatExpression(stmt.value, 0)};`);
276+
if (stmt.storeType.includes('v128') || stmt.storeType.includes('atomic')) {
277+
const intrinsicName = stmt.storeType.replace(/\./g, '_');
278+
emit(`${intrinsicName}(${offsetStr}, ${formatExpression(stmt.value, 0)});`);
279+
} else {
280+
emit(`memory[${offsetStr}] = ${formatExpression(stmt.value, 0)};`);
281+
}
277282
}
278283
break;
279284
}
@@ -313,6 +318,15 @@ export function emitLowered(
313318
}
314319
}
315320

321+
function isIntrinsicOp(op: string): boolean {
322+
return op.includes('x4_') || op.includes('x8_') || op.includes('x16_') || op.includes('x2_') ||
323+
op.startsWith('v128_') || op.includes('atomic_') ||
324+
op.startsWith('struct_') || op.startsWith('array_') ||
325+
op.startsWith('table_') || op.startsWith('memory_') ||
326+
op.startsWith('ref_') || op.startsWith('i31_') ||
327+
op.startsWith('extern_') || op.startsWith('any_');
328+
}
329+
316330
function formatExpression(expr: Expression, parentPrec: number): string {
317331
switch (expr.kind) {
318332
case 'var':
@@ -334,6 +348,9 @@ export function emitLowered(
334348
const left = formatExpression(expr.left, prec);
335349
return maybeWrap(`${left} * ${multiplier}`, prec, parentPrec);
336350
}
351+
if (isIntrinsicOp(expr.op)) {
352+
return `${expr.op}(${formatExpression(expr.left, 0)}, ${formatExpression(expr.right, 0)})`;
353+
}
337354
const unsigned = UNSIGNED_OPS[expr.op];
338355
if (unsigned) {
339356
const left = `(unsigned)${formatExpression(expr.left, PRECEDENCE['unary'])}`;
@@ -378,6 +395,11 @@ export function emitLowered(
378395
}
379396
const addr = formatExpression(expr.address, 0);
380397
const offsetStr = expr.offset > 0 ? (addr === '0' ? String(expr.offset) : `${addr} + ${expr.offset}`) : addr;
398+
// SIMD/atomic loads emit as intrinsic calls
399+
if (expr.loadType.includes('v128') || expr.loadType.includes('atomic')) {
400+
const intrinsicName = expr.loadType.replace(/\./g, '_');
401+
return `${intrinsicName}(${offsetStr})`;
402+
}
381403
const loadCast = LOAD_TYPE_CAST[expr.loadType];
382404
if (loadCast) {
383405
return `(${loadCast})memory[${offsetStr}]`;

src/decompiler/MemoryPatterns.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ function transformStatement(statement: Statement, structBases: Set<string>): Sta
157157
case 'store': {
158158
const address = xExpr(statement.address);
159159
const value = xExpr(statement.value);
160+
// Skip struct/array pattern detection for v128 and atomic stores
161+
if (statement.storeType.includes('v128') || statement.storeType.includes('atomic')) {
162+
return { kind: 'store', address, offset: statement.offset, storeType: statement.storeType, value };
163+
}
160164
// Struct field store: base + const where base is a struct pointer
161165
const structStore = tryStructAccess(address, statement.offset, structBases);
162166
if (structStore) {
@@ -205,6 +209,10 @@ function transformExpr(expression: Expression, structBases: Set<string>): Expres
205209
switch (expression.kind) {
206210
case 'load': {
207211
const address = xExpr(expression.address);
212+
// Skip struct/array pattern detection for v128 and atomic loads
213+
if (expression.loadType.includes('v128') || expression.loadType.includes('atomic')) {
214+
return { kind: 'load', address, offset: expression.offset, loadType: expression.loadType };
215+
}
208216
// Struct field access: base + const where base is a struct pointer
209217
const structAccess = tryStructAccess(address, expression.offset, structBases);
210218
if (structAccess) {

0 commit comments

Comments
 (0)