From b271162c423d21c48786fec81e3572f7f67adbbe Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 14 Apr 2026 22:10:26 +0000 Subject: [PATCH] Support for wide arithmetic --- scripts/gen-s-parser.py | 1 + src/gen-s-parser.inc | 21 +++- src/interpreter/interpreter.cpp | 16 +++ src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 8 ++ src/ir/cost.h | 7 ++ src/ir/effects.h | 1 + src/ir/possible-contents.cpp | 1 + src/ir/subtype-exprs.h | 1 + src/parser/contexts.h | 10 ++ src/parser/parsers.h | 13 +++ src/passes/I64ToI32Lowering.cpp | 4 + src/passes/Precompute.cpp | 4 + src/passes/Print.cpp | 11 +++ src/passes/TypeGeneralizing.cpp | 1 + src/wasm-binary.h | 4 + src/wasm-builder.h | 8 ++ src/wasm-delegations-fields.def | 4 + src/wasm-delegations.def | 1 + src/wasm-interpreter.h | 22 +++++ src/wasm-ir-builder.h | 1 + src/wasm.h | 15 +++ src/wasm/wasm-binary.cpp | 2 + src/wasm/wasm-ir-builder.cpp | 9 ++ src/wasm/wasm-stack.cpp | 11 +++ src/wasm/wasm-validator.cpp | 41 ++++++-- src/wasm/wasm.cpp | 15 +++ src/wasm2js.h | 4 + test/spec/wide-arithmetic.wast | 166 ++++++++++++++++++++++++++++++++ 29 files changed, 389 insertions(+), 14 deletions(-) create mode 100644 test/spec/wide-arithmetic.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index d0f08d0546b..0e10b29a9d1 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -146,6 +146,7 @@ ("i64.shr_u", "makeBinary(BinaryOp::ShrUInt64)"), ("i64.rotl", "makeBinary(BinaryOp::RotLInt64)"), ("i64.rotr", "makeBinary(BinaryOp::RotRInt64)"), + ("i64.add128", "makeWideIntBinary(WideIntBinaryOp::AddInt128)"), ("f32.abs", "makeUnary(UnaryOp::AbsFloat32)"), ("f32.neg", "makeUnary(UnaryOp::NegFloat32)"), ("f32.ceil", "makeUnary(UnaryOp::CeilFloat32)"), diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index eca86c6ed77..f6e07026dd9 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -3405,12 +3405,23 @@ switch (buf[0]) { switch (buf[4]) { case 'a': { switch (buf[5]) { - case 'd': - if (op == "i64.add"sv) { - CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::AddInt64)); - return Ok{}; + case 'd': { + switch (buf[7]) { + case '\0': + if (op == "i64.add"sv) { + CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::AddInt64)); + return Ok{}; + } + goto parse_error; + case '1': + if (op == "i64.add128"sv) { + CHECK_ERR(makeWideIntBinary(ctx, pos, annotations, WideIntBinaryOp::AddInt128)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } case 'n': if (op == "i64.and"sv) { CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::AndInt64)); diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index d15abcfd4a0..c4f7bcea43e 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -217,6 +217,22 @@ struct ExpressionInterpreter : OverriddenVisitor { } } Flow visitSelect(Select* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitWideIntBinary(WideIntBinary* curr) { + if (curr->op == AddInt128) { + uint64_t highRHS = pop().geti64(); + uint64_t lowRHS = pop().geti64(); + uint64_t highLHS = pop().geti64(); + uint64_t lowLHS = pop().geti64(); + + uint64_t lowRes = lowLHS + lowRHS; + uint64_t highRes = highLHS + highRHS + (lowRes < lowLHS); + + push(Literal(lowRes)); + push(Literal(highRes)); + return {}; + } + WASM_UNREACHABLE("TODO"); + } Flow visitDrop(Drop* curr) { WASM_UNREACHABLE("TODO"); } Flow visitReturn(Return* curr) { WASM_UNREACHABLE("TODO"); } Flow visitMemorySize(MemorySize* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 24afae568c5..17d0ad44aff 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -109,6 +109,7 @@ void ReFinalize::visitMemoryFill(MemoryFill* curr) { curr->finalize(); } void ReFinalize::visitConst(Const* curr) { curr->finalize(); } void ReFinalize::visitUnary(Unary* curr) { curr->finalize(); } void ReFinalize::visitBinary(Binary* curr) { curr->finalize(); } +void ReFinalize::visitWideIntBinary(WideIntBinary* curr) { curr->finalize(); } void ReFinalize::visitSelect(Select* curr) { curr->finalize(); } void ReFinalize::visitDrop(Drop* curr) { curr->finalize(); } void ReFinalize::visitReturn(Return* curr) { curr->finalize(); } diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 385e0fa8290..6ce267e396f 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -703,6 +703,14 @@ template struct ChildTyper : OverriddenVisitor { } } + void visitWideIntBinary(WideIntBinary* curr) { + size_t num = 4; + curr->operands.resize(num); + for (size_t i = 0; i < num; ++i) { + note(&curr->operands[i], Type::i64); + } + } + void visitSelect(Select* curr, std::optional type = std::nullopt) { if (type) { note(&curr->ifTrue, *type); diff --git a/src/ir/cost.h b/src/ir/cost.h index 0042d27bcb2..e190e1a4db3 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -573,6 +573,13 @@ struct CostAnalyzer : public OverriddenVisitor { } return ret + visit(curr->left) + visit(curr->right); } + CostType visitWideIntBinary(WideIntBinary* curr) { + CostType ret = 1; + for (auto* child : curr->operands) { + ret += visit(child); + } + return ret; + } CostType visitSelect(Select* curr) { return 1 + visit(curr->condition) + visit(curr->ifTrue) + visit(curr->ifFalse); diff --git a/src/ir/effects.h b/src/ir/effects.h index af866b9e536..ea24cd4695a 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -945,6 +945,7 @@ class EffectAnalyzer { } } } + void visitWideIntBinary(WideIntBinary* curr) {} void visitSelect(Select* curr) {} void visitDrop(Drop* curr) {} void visitReturn(Return* curr) { parent.branchesOut = true; } diff --git a/src/ir/possible-contents.cpp b/src/ir/possible-contents.cpp index 4dc6da9c9d9..993d431a2ea 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -634,6 +634,7 @@ struct InfoCollector addRoot(curr); } void visitBinary(Binary* curr) { addRoot(curr); } + void visitWideIntBinary(WideIntBinary* curr) { addRoot(curr); } void visitSelect(Select* curr) { receiveChildValue(curr->ifTrue, curr); receiveChildValue(curr->ifFalse, curr); diff --git a/src/ir/subtype-exprs.h b/src/ir/subtype-exprs.h index 8a4677ec9da..1123d4a6635 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -213,6 +213,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitConst(Const* curr) {} void visitUnary(Unary* curr) {} void visitBinary(Binary* curr) {} + void visitWideIntBinary(WideIntBinary* curr) {} void visitSelect(Select* curr) { self()->noteSubtype(curr->ifTrue, curr); self()->noteSubtype(curr->ifFalse, curr); diff --git a/src/parser/contexts.h b/src/parser/contexts.h index 88b7d8a941c..05a9674ff67 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -476,6 +476,10 @@ struct NullInstrParserCtx { Result<> makeBinary(Index, const std::vector&, BinaryOp) { return Ok{}; } + Result<> + makeWideIntBinary(Index, const std::vector&, WideIntBinaryOp) { + return Ok{}; + } Result<> makeUnary(Index, const std::vector&, UnaryOp) { return Ok{}; } @@ -2159,6 +2163,12 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeBinary(op)); } + Result<> makeWideIntBinary(Index pos, + const std::vector& annotations, + WideIntBinaryOp op) { + return withLoc(pos, irBuilder.makeWideIntBinary(op)); + } + Result<> makeUnary(Index pos, const std::vector& annotations, UnaryOp op) { return withLoc(pos, irBuilder.makeUnary(op)); diff --git a/src/parser/parsers.h b/src/parser/parsers.h index 4a5fb71b771..c4f9aa68a19 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -91,6 +91,11 @@ Result<> makeNop(Ctx&, Index, const std::vector&); template Result<> makeBinary(Ctx&, Index, const std::vector&, BinaryOp op); template +Result<> makeWideIntBinary(Ctx&, + Index, + const std::vector&, + WideIntBinaryOp op); +template Result<> makeUnary(Ctx&, Index, const std::vector&, UnaryOp op); template Result<> makeSelect(Ctx&, Index, const std::vector&); @@ -1592,6 +1597,14 @@ Result<> makeBinary(Ctx& ctx, return ctx.makeBinary(pos, annotations, op); } +template +Result<> makeWideIntBinary(Ctx& ctx, + Index pos, + const std::vector& annotations, + WideIntBinaryOp op) { + return ctx.makeWideIntBinary(pos, annotations, op); +} + template Result<> makeUnary(Ctx& ctx, Index pos, diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 42feafa3861..cff8f37c2f1 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -1553,6 +1553,10 @@ struct I64ToI32Lowering : public WalkerPass> { } } + void visitWideIntBinary(WideIntBinary* curr) { + WASM_UNREACHABLE("TODO: wide arithmetic lowering"); + } + void visitSelect(Select* curr) { if (handleUnreachable(curr)) { return; diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index fbaf09232b9..7009b379805 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -282,6 +282,10 @@ class PrecomputingExpressionRunner return Flow(NONCONSTANT_FLOW); } + Flow visitWideIntBinary(WideIntBinary* curr) { + return Super::visitWideIntBinary(curr); + } + Flow visitStringEncode(StringEncode* curr) { // string.encode_wtf16_array is effectively an Array write operation, so // just like ArraySet and ArrayCopy above we must mark it as disallowed diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 68981953211..ab197a13a6d 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2025,6 +2025,17 @@ struct PrintExpressionContents } restoreNormalColor(o); } + void visitWideIntBinary(WideIntBinary* curr) { + prepareColor(o); + switch (curr->op) { + case AddInt128: + o << "i64.add128"; + break; + default: + WASM_UNREACHABLE("invalid wide int binary op"); + } + restoreNormalColor(o); + } void visitSelect(Select* curr) { prepareColor(o) << "select"; restoreNormalColor(o); diff --git a/src/passes/TypeGeneralizing.cpp b/src/passes/TypeGeneralizing.cpp index 03a0a0f11a7..3646146e62c 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -433,6 +433,7 @@ struct TransferFn : OverriddenVisitor { void visitConst(Const* curr) {} void visitUnary(Unary* curr) {} void visitBinary(Binary* curr) {} + void visitWideIntBinary(WideIntBinary* curr) {} void visitSelect(Select* curr) { if (curr->type.isRef()) { diff --git a/src/wasm-binary.h b/src/wasm-binary.h index abacbda0d13..62e7ceeaba3 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1136,6 +1136,10 @@ enum ASTNodes { MemoryCopy = 0x0a, MemoryFill = 0x0b, + // wide arithmetic opcodes + + I64Add128 = 0x13, + // reference types opcodes TableGrow = 0x0f, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 30465e9e128..b02fbd514d9 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -660,6 +660,14 @@ class Builder { ret->finalize(); return ret; } + WideIntBinary* makeWideIntBinary(WideIntBinaryOp op, + const std::vector& operands) { + auto* ret = wasm.allocator.alloc(); + ret->op = op; + ret->operands.set(operands); + ret->finalize(); + return ret; + } Select* makeSelect(Expression* condition, Expression* ifTrue, Expression* ifFalse) { auto* ret = wasm.allocator.alloc