From 34c74a0b63c53cd290c5082b689ecd348dd5a1ad Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 9 Jun 2026 12:07:03 +0300 Subject: [PATCH 1/4] update binaryen to 130.0.0-nightly. Add wide int ops api (wip) --- package-lock.json | 14 ++++---- package.json | 2 +- src/glue/binaryen.d.ts | 3 ++ src/glue/binaryen.js | 3 ++ src/module.ts | 54 +++++++++++++++++++++++++----- tests/compiler/ternary.release.wat | 5 +-- 6 files changed, 62 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index d9b6348f52..b3b4565299 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0", "license": "Apache-2.0", "dependencies": { - "binaryen": "129.0.0-nightly.20260428", + "binaryen": "130.0.0-nightly.20260609", "long": "^5.2.4" }, "bin": { @@ -978,9 +978,9 @@ } }, "node_modules/binaryen": { - "version": "129.0.0-nightly.20260428", - "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-129.0.0-nightly.20260428.tgz", - "integrity": "sha512-RpjRVPZw8NOhkGzx3tTaKYdv4C+zO1VVI0VB9GSCNLM81flHOAZUwRJTLDM3Hqco3OmwB8hTLpkkpMSJjUfHXw==", + "version": "130.0.0-nightly.20260609", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-130.0.0-nightly.20260609.tgz", + "integrity": "sha512-Zyl9Tw638x08LDew22YtxdYiUGxn+quzpR3ySIS4Nccv6yAiO5j1Yko9IEPNpj/aBZf71xtD/6hYXsgZ2ye3ew==", "license": "Apache-2.0", "bin": { "wasm-as": "bin/wasm-as", @@ -995,9 +995,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 47b23f9761..a13ad55699 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ }, "engineStrict": true, "dependencies": { - "binaryen": "129.0.0-nightly.20260428", + "binaryen": "130.0.0-nightly.20260609", "long": "^5.2.4" }, "devDependencies": { diff --git a/src/glue/binaryen.d.ts b/src/glue/binaryen.d.ts index 347428aa8b..5b693f8fa0 100644 --- a/src/glue/binaryen.d.ts +++ b/src/glue/binaryen.d.ts @@ -267,6 +267,9 @@ export declare function _BinaryenUnarySetOp(expr: ExpressionRef, op: Op): void; export declare function _BinaryenUnaryGetValue(expr: ExpressionRef): ExpressionRef; export declare function _BinaryenUnarySetValue(expr: ExpressionRef, valueExpr: ExpressionRef): void; +export declare function _BinaryenWideIntAddSub(module: ModuleRef, op: Op, leftLowExpr: ExpressionRef, leftHighExpr: ExpressionRef, rightLowExpr: ExpressionRef, rightHighExpr: ExpressionRef): ExpressionRef; +export declare function _BinaryenWideIntMul(module: ModuleRef, op: Op, leftExpr: ExpressionRef, rightExpr: ExpressionRef): ExpressionRef; + export declare function _BinaryenBinary(module: ModuleRef, op: Op, leftExpr: ExpressionRef, rightExpr: ExpressionRef): ExpressionRef; export declare function _BinaryenBinaryGetOp(expr: ExpressionRef): Op; export declare function _BinaryenBinarySetOp(expr: ExpressionRef, op: Op): void; diff --git a/src/glue/binaryen.js b/src/glue/binaryen.js index 4caf71afbb..c47cc5ca77 100644 --- a/src/glue/binaryen.js +++ b/src/glue/binaryen.js @@ -223,6 +223,9 @@ export const { _BinaryenUnaryGetValue, _BinaryenUnarySetValue, + _BinaryenWideIntAddSub, + _BinaryenWideIntMul, + _BinaryenBinary, _BinaryenBinaryGetOp, _BinaryenBinarySetOp, diff --git a/src/module.ts b/src/module.ts index f02ffb2f44..f37cc80460 100644 --- a/src/module.ts +++ b/src/module.ts @@ -331,7 +331,9 @@ export const enum ExpressionId { ResumeThrow = 102 /* _BinaryenResumeThrowId */, StackSwitch = 103 /* _BinaryenStackSwitchId */, StructWait = 104 /* _BinaryenStructWaitId */, - StructNotify = 105 /* _BinaryenStructNotifyId */ + StructNotify = 105 /* _BinaryenStructNotifyId */, + WideIntAddSub = 106 /* _BinaryenWideIntAddSubId */, + WideIntMul = 107 /* _BinaryenWideIntMulId */ } /** Binaryen external kind constants. */ @@ -1057,7 +1059,7 @@ export const enum BinaryOp { /** i16x8.relaxed_q15mulr_s */ RelaxedQ15MulrI16x8 = 215 /* _BinaryenRelaxedQ15MulrSVecI16x8 */, /** i16x8.relaxed_dot_i8x16_i7x16_s */ - RelaxedDotI8x16I7x16ToI16x8 = 216 /* _BinaryenDotI8x16I7x16SToVecI16x8 */, + RelaxedDotI8x16I7x16ToI16x8 = 216 /* _BinaryenRelaxedDotI8x16I7x16SToVecI16x8 */, _last = RelaxedDotI8x16I7x16ToI16x8, @@ -1258,15 +1260,15 @@ export const enum SIMDTernaryOp { /** f64x2.relaxed_nmadd */ RelaxedNmaddF64x2 = 4 /* _BinaryenRelaxedNmaddVecF64x2 */, /** i8x16.relaxed_laneselect */ - RelaxedLaneselectI8x16 = 5 /* _BinaryenLaneselectI8x16 */, + RelaxedLaneselectI8x16 = 5 /* _BinaryenRelaxedLaneselectI8x16 */, /** i16x8.relaxed_laneselect */ - RelaxedLaneselectI16x8 = 6 /* _BinaryenLaneselectI16x8 */, + RelaxedLaneselectI16x8 = 6 /* _BinaryenRelaxedLaneselectI16x8 */, /** i32x4.relaxed_laneselect */ - RelaxedLaneselectI32x4 = 7 /* _BinaryenLaneselectI32x4 */, + RelaxedLaneselectI32x4 = 7 /* _BinaryenRelaxedLaneselectI32x4 */, /** i64x2.relaxed_laneselect */ - RelaxedLaneselectI64x2 = 8 /* _BinaryenLaneselectI64x2 */, + RelaxedLaneselectI64x2 = 8 /* _BinaryenRelaxedLaneselectI64x2 */, /** i32x4.relaxed_dot_i8x16_i7x16_add_s */ - RelaxedDotI8x16I7x16AddToI32x4 = 9 /* _BinaryenDotI8x16I7x16AddSToVecI32x4 */, + RelaxedDotI8x16I7x16AddToI32x4 = 9 /* _BinaryenRelaxedDotI8x16I7x16AddSToVecI32x4 */, // FIXME: f16x8 madd/nmadd (= 10, 11) are in wasm.h but not yet exported via C API } @@ -1275,9 +1277,25 @@ export const enum RefAsOp { /** ref.as_non_null */ NonNull = 0 /* _BinaryenRefAsNonNull */, /** any.convert_extern */ - ExternInternalize = 1 /* _BinaryenRefAsAnyConvertExtern */, + AnyConvertExtern = 1 /* _BinaryenRefAsAnyConvertExtern */, /** extern.convert_any */ - ExternExternalize = 2 /* _BinaryenRefAsExternConvertAny */ + ExternConvertAny = 2 /* _BinaryenRefAsExternConvertAny */ +} + +/** Binaryen wide integer add/sub operation constants. */ +export const enum WideIntAddSubOp { + /** i64.add128 */ + Add = 0 /* _BinaryenAddInt128 */, + /** i64.sub128 */ + Sub = 1 /* _BinaryenSubInt128 */ +} + +/** Binaryen wide integer multiply operation constants. */ +export const enum WideIntMulOp { + /** i64.mul_wide_s */ + SignedI64 = 0 /* _BinaryenMulWideSInt64 */, + /** i64.mul_wide_u */ + UnsignedI64 = 1 /* _BinaryenMulWideUInt64 */ } /** Binaryen BrOn operation constants. */ @@ -1511,6 +1529,24 @@ export class Module { return binaryen._BinaryenBinary(this.ref, op, left, right); } + wide_int_add_sub( + op: WideIntAddSubOp, + leftLow: ExpressionRef, + leftHigh: ExpressionRef, + rightLow: ExpressionRef, + rightHigh: ExpressionRef + ): ExpressionRef { + return binaryen._BinaryenWideIntAddSub(this.ref, op, leftLow, leftHigh, rightLow, rightHigh); + } + + wide_int_mul( + op: WideIntMulOp, + left: ExpressionRef, + right: ExpressionRef + ): ExpressionRef { + return binaryen._BinaryenWideIntMul(this.ref, op, left, right); + } + memory_size(name: string = CommonNames.DefaultMemory, is64: bool = false): ExpressionRef { let cStr = this.allocStringCached(name); return binaryen._BinaryenMemorySize(this.ref, cStr, is64); diff --git a/tests/compiler/ternary.release.wat b/tests/compiler/ternary.release.wat index fa841a2a0c..7d2cc0437a 100644 --- a/tests/compiler/ternary.release.wat +++ b/tests/compiler/ternary.release.wat @@ -48,6 +48,7 @@ select ) (func $export:ternary/testVoidInclTypeMismatch (param $0 i32) (param $1 i32) + (local $2 i32) global.get $~lib/memory/__stack_pointer i32.const 4 i32.sub @@ -68,11 +69,11 @@ i32.store local.get $1 i32.load + local.tee $2 call_indirect (type $0) local.get $0 if - local.get $1 - i32.load + local.get $2 call_indirect (type $0) end local.get $0 From a506ecbf4f8f9b13ab21694a24f52cd45cb9da9c Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 9 Jun 2026 13:07:36 +0300 Subject: [PATCH 2/4] fix by declaring function table before indirect call analysis --- src/builtins.ts | 1 + src/compiler.ts | 25 ++++++++++++++++--- src/module.ts | 8 ++++++ tests/compiler/assert-nonnull.release.wat | 9 +------ .../compiler/features/relaxed-simd.debug.wat | 20 +++++++-------- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/builtins.ts b/src/builtins.ts index f876049603..fe7b7b5222 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -3812,6 +3812,7 @@ function builtin_call_indirect(ctx: BuiltinFunctionContext): ExpressionRef { paramTypeRefs[i] = compiler.currentType.toRef(); } compiler.currentType = returnType; + compiler.ensureFunctionTable(); return module.call_indirect(null /* TODO */, indexArg, operandExprs, createType(paramTypeRefs), returnType.toRef()); } builtinFunctions.set(BuiltinNames.call_indirect, builtin_call_indirect); diff --git a/src/compiler.ts b/src/compiler.ts index 5a5f9e8b77..dc15af284a 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -458,6 +458,8 @@ export class Compiler extends DiagnosticEmitter { staticGcObjectOffsets: Map> = new Map(); /** Function table being compiled. First elem is blank. */ functionTable: Function[] = []; + /** Whether the default function table has been declared in the module yet. */ + functionTableDeclared: bool = false; /** Arguments length helper global. */ builtinArgumentsLength: GlobalRef = 0; /** Requires runtime features. */ @@ -877,11 +879,12 @@ export class Compiler extends DiagnosticEmitter { } } - private initDefaultTable(): void { + /** Ensures the default function table is declared so indirect calls can be built and their effects analyzed. */ + ensureFunctionTable(): void { + if (this.functionTableDeclared) return; + this.functionTableDeclared = true; let options = this.options; let module = this.module; - - // import and/or export table if requested (default table is named '0' by Binaryen) if (options.importTable) { module.addTableImport( CommonNames.DefaultTable, @@ -895,6 +898,19 @@ export class Compiler extends DiagnosticEmitter { null ); } + } else { + module.ensureFunctionTable(CommonNames.DefaultTable); + } + } + + private initDefaultTable(): void { + let options = this.options; + let module = this.module; + + // declare the default table now if imported (default table is named '0' by Binaryen). + // non-imported tables are declared lazily on first indirect call, see ensureFunctionTable + if (options.importTable) { + this.ensureFunctionTable(); } if (options.exportTable) { module.addTableExport(CommonNames.DefaultTable, ExportNames.Table); @@ -2152,7 +2168,8 @@ export class Compiler extends DiagnosticEmitter { let memorySegment = instance.memorySegment; if (!memorySegment) { - // Add to the function table + // Add to the function table, declaring it on first use so effect analysis works + this.ensureFunctionTable(); let functionTable = this.functionTable; let tableBase = this.options.tableBase; if (!tableBase) tableBase = 1; // leave first elem blank diff --git a/src/module.ts b/src/module.ts index f37cc80460..c6c68ea710 100644 --- a/src/module.ts +++ b/src/module.ts @@ -2486,6 +2486,14 @@ export class Module { binaryen._free(cArr); } + /** Ensures that an empty function table of the given name exists, to be sized and filled later. */ + ensureFunctionTable(name: string): void { + let cStr = this.allocStringCached(name); + if (!binaryen._BinaryenGetTable(this.ref, cStr)) { + binaryen._BinaryenAddTable(this.ref, cStr, 0, Module.UNLIMITED_TABLE, TypeRef.Funcref); + } + } + /* setFunctionTable( initial: Index, maximum: Index, diff --git a/tests/compiler/assert-nonnull.release.wat b/tests/compiler/assert-nonnull.release.wat index 53e5430763..aabd543b52 100644 --- a/tests/compiler/assert-nonnull.release.wat +++ b/tests/compiler/assert-nonnull.release.wat @@ -1,7 +1,6 @@ (module (type $0 (func (param i32) (result i32))) - (type $1 (func (result i32))) - (type $2 (func (param i32 i32 i32 i32))) + (type $1 (func (param i32 i32 i32 i32))) (import "env" "abort" (func $~lib/builtins/abort (param i32 i32 i32 i32))) (global $~lib/memory/__stack_pointer (mut i32) (i32.const 34236)) (memory $0 1) @@ -15,7 +14,6 @@ (data $3.1 (i32.const 1304) "\02\00\00\00\1a\00\00\00~\00l\00i\00b\00/\00a\00r\00r\00a\00y\00.\00t\00s") (data $4 (i32.const 1340) "|") (data $4.1 (i32.const 1352) "\02\00\00\00^\00\00\00E\00l\00e\00m\00e\00n\00t\00 \00t\00y\00p\00e\00 \00m\00u\00s\00t\00 \00b\00e\00 \00n\00u\00l\00l\00a\00b\00l\00e\00 \00i\00f\00 \00a\00r\00r\00a\00y\00 \00i\00s\00 \00h\00o\00l\00e\00y") - (table $0 1 1 funcref) (export "memory" (memory $0)) (export "testVar" (func $export:assert-nonnull/testVar)) (export "testObj" (func $export:assert-nonnull/testObj)) @@ -165,7 +163,6 @@ br_if $folding-inner1 local.get $0 i32.load - call_indirect (type $1) unreachable end i32.const 34256 @@ -283,7 +280,6 @@ br_if $folding-inner1 local.get $0 i32.load - call_indirect (type $1) unreachable end i32.const 34256 @@ -344,7 +340,6 @@ end local.get $0 i32.load - call_indirect (type $1) unreachable end i32.const 34256 @@ -456,7 +451,6 @@ i32.store offset=4 local.get $0 i32.load - call_indirect (type $1) unreachable end i32.const 34256 @@ -505,7 +499,6 @@ end local.get $0 i32.load - call_indirect (type $1) unreachable end i32.const 34256 diff --git a/tests/compiler/features/relaxed-simd.debug.wat b/tests/compiler/features/relaxed-simd.debug.wat index af8063a69d..0ede0f4814 100644 --- a/tests/compiler/features/relaxed-simd.debug.wat +++ b/tests/compiler/features/relaxed-simd.debug.wat @@ -83,42 +83,42 @@ global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i8x16.laneselect + i8x16.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i8x16.laneselect + i8x16.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i16x8.laneselect + i16x8.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i16x8.laneselect + i16x8.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i32x4.laneselect + i32x4.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i32x4.laneselect + i32x4.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i64x2.laneselect + i64x2.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i64x2.laneselect + i64x2.relaxed_laneselect global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v @@ -162,11 +162,11 @@ global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i16x8.dot_i8x16_i7x16_s + i16x8.relaxed_dot_i8x16_i7x16_s global.set $features/relaxed-simd/r global.get $features/relaxed-simd/v global.get $features/relaxed-simd/v - i16x8.dot_i8x16_i7x16_s + i16x8.relaxed_dot_i8x16_i7x16_s global.set $features/relaxed-simd/r ) (func $~start From 1cdee6725e7cd3a993daeac2ef1b050e6ac1fcb3 Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 9 Jun 2026 13:42:36 +0300 Subject: [PATCH 3/4] simplify init table --- src/builtins.ts | 1 - src/compiler.ts | 32 +++++++++++++------------------- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/src/builtins.ts b/src/builtins.ts index fe7b7b5222..f876049603 100644 --- a/src/builtins.ts +++ b/src/builtins.ts @@ -3812,7 +3812,6 @@ function builtin_call_indirect(ctx: BuiltinFunctionContext): ExpressionRef { paramTypeRefs[i] = compiler.currentType.toRef(); } compiler.currentType = returnType; - compiler.ensureFunctionTable(); return module.call_indirect(null /* TODO */, indexArg, operandExprs, createType(paramTypeRefs), returnType.toRef()); } builtinFunctions.set(BuiltinNames.call_indirect, builtin_call_indirect); diff --git a/src/compiler.ts b/src/compiler.ts index dc15af284a..cb69b4f2c0 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -458,8 +458,6 @@ export class Compiler extends DiagnosticEmitter { staticGcObjectOffsets: Map> = new Map(); /** Function table being compiled. First elem is blank. */ functionTable: Function[] = []; - /** Whether the default function table has been declared in the module yet. */ - functionTableDeclared: bool = false; /** Arguments length helper global. */ builtinArgumentsLength: GlobalRef = 0; /** Requires runtime features. */ @@ -879,12 +877,21 @@ export class Compiler extends DiagnosticEmitter { } } - /** Ensures the default function table is declared so indirect calls can be built and their effects analyzed. */ - ensureFunctionTable(): void { - if (this.functionTableDeclared) return; - this.functionTableDeclared = true; + /** Declares the default function table early so an indirect call's effects can be analyzed before it is populated. */ + private ensureFunctionTable(): void { + // imported tables are declared by initDefaultTable. only the local default table + // needs to exist early, since effect analysis of an indirect call looks it up + if (!this.options.importTable) { + this.module.ensureFunctionTable(CommonNames.DefaultTable); + } + } + + private initDefaultTable(): void { let options = this.options; let module = this.module; + + // import and/or export table if requested (default table is named '0' by Binaryen). + // non-imported tables are declared lazily on first indirect call, see ensureFunctionTable if (options.importTable) { module.addTableImport( CommonNames.DefaultTable, @@ -898,19 +905,6 @@ export class Compiler extends DiagnosticEmitter { null ); } - } else { - module.ensureFunctionTable(CommonNames.DefaultTable); - } - } - - private initDefaultTable(): void { - let options = this.options; - let module = this.module; - - // declare the default table now if imported (default table is named '0' by Binaryen). - // non-imported tables are declared lazily on first indirect call, see ensureFunctionTable - if (options.importTable) { - this.ensureFunctionTable(); } if (options.exportTable) { module.addTableExport(CommonNames.DefaultTable, ExportNames.Table); From 0575a7b39874aa54aae8ccd82432bf1d6644dc2e Mon Sep 17 00:00:00 2001 From: MaxGraey Date: Tue, 9 Jun 2026 18:39:19 +0300 Subject: [PATCH 4/4] refactor --- src/compiler.ts | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/compiler.ts b/src/compiler.ts index cb69b4f2c0..f99bd5a049 100644 --- a/src/compiler.ts +++ b/src/compiler.ts @@ -877,15 +877,6 @@ export class Compiler extends DiagnosticEmitter { } } - /** Declares the default function table early so an indirect call's effects can be analyzed before it is populated. */ - private ensureFunctionTable(): void { - // imported tables are declared by initDefaultTable. only the local default table - // needs to exist early, since effect analysis of an indirect call looks it up - if (!this.options.importTable) { - this.module.ensureFunctionTable(CommonNames.DefaultTable); - } - } - private initDefaultTable(): void { let options = this.options; let module = this.module; @@ -2163,7 +2154,10 @@ export class Compiler extends DiagnosticEmitter { if (!memorySegment) { // Add to the function table, declaring it on first use so effect analysis works - this.ensureFunctionTable(); + if (!this.options.importTable) { + this.module.ensureFunctionTable(CommonNames.DefaultTable); + } + let functionTable = this.functionTable; let tableBase = this.options.tableBase; if (!tableBase) tableBase = 1; // leave first elem blank @@ -7139,13 +7133,11 @@ export class Compiler extends DiagnosticEmitter { // provided, so we must set `argumentsLength` in any case. Inject setting it // into the index argument, which becomes executed last after any operands. let argumentsLength = this.ensureArgumentsLength(); - - let functionArgWithVararg = module.block(null, [ module.global_set(argumentsLength, module.i32(numArguments)), functionArg ], sizeTypeRef); - + if (operands) this.operandsTostack(signature, operands); let expr = module.call_indirect( null, // TODO: handle multiple tables