From 9f98b32cfce4a84e2b9b116f6455541fa32c4b51 Mon Sep 17 00:00:00 2001 From: stevenfontanella Date: Tue, 14 Apr 2026 22:10:26 +0000 Subject: [PATCH 1/4] Support for wide arithmetic --- scripts/gen-s-parser.py | 2 + src/gen-s-parser.inc | 42 +++-- src/interpreter/interpreter.cpp | 1 + src/ir/ReFinalize.cpp | 1 + src/ir/child-typer.h | 7 + src/ir/cost.h | 4 + src/ir/effects.h | 1 + src/ir/possible-contents.cpp | 1 + src/ir/subtype-exprs.h | 1 + src/parser/contexts.h | 11 ++ src/parser/parsers.h | 13 ++ src/passes/I64ToI32Lowering.cpp | 4 + src/passes/Print.cpp | 14 ++ src/passes/TypeGeneralizing.cpp | 1 + src/support/stdckdint.h | 20 ++- src/wasm-binary.h | 5 + src/wasm-builder.h | 14 ++ src/wasm-delegations-fields.def | 7 + src/wasm-delegations.def | 1 + src/wasm-interpreter.h | 34 ++++ src/wasm-ir-builder.h | 1 + src/wasm.h | 20 +++ src/wasm/wasm-binary.cpp | 4 + src/wasm/wasm-ir-builder.cpp | 9 + src/wasm/wasm-stack.cpp | 14 ++ src/wasm/wasm-validator.cpp | 28 ++- src/wasm/wasm.cpp | 11 ++ src/wasm2js.h | 4 + test/spec/wide-arithmetic.wast | 310 ++++++++++++++++++++++++++++++++ 29 files changed, 564 insertions(+), 21 deletions(-) create mode 100644 test/spec/wide-arithmetic.wast diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index b2a2573c4e4..d8ebce98e31 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -148,6 +148,8 @@ ("i64.shr_u", "makeBinary(BinaryOp::ShrUInt64)"), ("i64.rotl", "makeBinary(BinaryOp::RotLInt64)"), ("i64.rotr", "makeBinary(BinaryOp::RotRInt64)"), + ("i64.add128", "makeWideIntAddSub(WideIntAddSubOp::AddInt128)"), + ("i64.sub128", "makeWideIntAddSub(WideIntAddSubOp::SubInt128)"), ("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 345afa302aa..d3d3b99a702 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -3427,12 +3427,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(makeWideIntAddSub(ctx, pos, annotations, WideIntAddSubOp::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)); @@ -4113,12 +4124,23 @@ switch (buf[0]) { default: goto parse_error; } } - case 'u': - if (op == "i64.sub"sv) { - CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::SubInt64)); - return Ok{}; + case 'u': { + switch (buf[7]) { + case '\0': + if (op == "i64.sub"sv) { + CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::SubInt64)); + return Ok{}; + } + goto parse_error; + case '1': + if (op == "i64.sub128"sv) { + CHECK_ERR(makeWideIntAddSub(ctx, pos, annotations, WideIntAddSubOp::SubInt128)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; + } default: goto parse_error; } } diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index d15abcfd4a0..e3abe63525d 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -216,6 +216,7 @@ struct ExpressionInterpreter : OverriddenVisitor { WASM_UNREACHABLE("TODO"); } } + Flow visitWideIntAddSub(WideIntAddSub* curr) { WASM_UNREACHABLE("TODO"); } Flow visitSelect(Select* curr) { WASM_UNREACHABLE("TODO"); } Flow visitDrop(Drop* curr) { WASM_UNREACHABLE("TODO"); } Flow visitReturn(Return* curr) { WASM_UNREACHABLE("TODO"); } diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp index 24afae568c5..6c722cbb03c 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::visitWideIntAddSub(WideIntAddSub* 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 e223eb2ad59..2aa1a06d549 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -705,6 +705,13 @@ template struct ChildTyper : OverriddenVisitor { } } + void visitWideIntAddSub(WideIntAddSub* curr) { + note(&curr->leftLow, Type::i64); + note(&curr->leftHigh, Type::i64); + note(&curr->rightLow, Type::i64); + note(&curr->rightHigh, 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 7c02dafce7b..7d044a49947 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -575,6 +575,10 @@ struct CostAnalyzer : public OverriddenVisitor { } return ret + visit(curr->left) + visit(curr->right); } + CostType visitWideIntAddSub(WideIntAddSub* curr) { + return 1 + visit(curr->leftLow) + visit(curr->leftHigh) + + visit(curr->rightLow) + visit(curr->rightHigh); + } 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..ec96053a13d 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -945,6 +945,7 @@ class EffectAnalyzer { } } } + void visitWideIntAddSub(WideIntAddSub* 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..08cf1cf4f8d 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 visitWideIntAddSub(WideIntAddSub* 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..b0a910f3fa7 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 visitWideIntAddSub(WideIntAddSub* 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..e62d3d5869a 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -476,6 +476,11 @@ struct NullInstrParserCtx { Result<> makeBinary(Index, const std::vector&, BinaryOp) { return Ok{}; } + Result<> + makeWideIntAddSub(Index, const std::vector&, WideIntAddSubOp) { + return Ok{}; + } + Result<> makeUnary(Index, const std::vector&, UnaryOp) { return Ok{}; } @@ -2159,6 +2164,12 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeBinary(op)); } + Result<> makeWideIntAddSub(Index pos, + const std::vector& annotations, + WideIntAddSubOp op) { + return withLoc(pos, irBuilder.makeWideIntAddSub(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..c0d2804e29f 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<> makeWideIntAddSub(Ctx&, + Index, + const std::vector&, + WideIntAddSubOp 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<> makeWideIntAddSub(Ctx& ctx, + Index pos, + const std::vector& annotations, + WideIntAddSubOp op) { + return ctx.makeWideIntAddSub(pos, annotations, op); +} + template Result<> makeUnary(Ctx& ctx, Index pos, diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 42feafa3861..0724b448674 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -1553,6 +1553,10 @@ struct I64ToI32Lowering : public WalkerPass> { } } + void visitWideIntAddSub(WideIntAddSub* curr) { + WASM_UNREACHABLE("TODO: wide arithmetic lowering"); + } + void visitSelect(Select* curr) { if (handleUnreachable(curr)) { return; diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 8239edc71be..c926c914720 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2031,6 +2031,20 @@ struct PrintExpressionContents } restoreNormalColor(o); } + void visitWideIntAddSub(WideIntAddSub* curr) { + prepareColor(o); + switch (curr->op) { + case AddInt128: { + o << "i64.add128"; + break; + } + case SubInt128: { + o << "i64.sub128"; + break; + } + } + 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..1cd9a1b7885 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 visitWideIntAddSub(WideIntAddSub* curr) {} void visitSelect(Select* curr) { if (curr->type.isRef()) { diff --git a/src/support/stdckdint.h b/src/support/stdckdint.h index 42e87f9a26d..c5132058332 100644 --- a/src/support/stdckdint.h +++ b/src/support/stdckdint.h @@ -29,12 +29,20 @@ template bool ckd_add(T* output, T a, T b) { // Atm this polyfill only supports unsigned types. static_assert(std::is_unsigned_v); - T result = a + b; - if (result < a) { - return true; - } - *output = result; - return false; + *output = a + b; + return *output < a; +#endif +} + +template bool ckd_sub(T* output, T a, T b) { +#if __has_builtin(__builtin_sub_overflow) + return __builtin_sub_overflow(a, b, output); +#else + // Atm this polyfill only supports unsigned types. + static_assert(std::is_unsigned_v); + + *output = a - b; + return *output > a; #endif } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 82363964d0b..85458e97a3b 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1138,6 +1138,11 @@ enum ASTNodes { MemoryCopy = 0x0a, MemoryFill = 0x0b, + // wide arithmetic opcodes + + I64Add128 = 0x13, + I64Sub128 = 0x14, + // reference types opcodes TableGrow = 0x0f, diff --git a/src/wasm-builder.h b/src/wasm-builder.h index 30465e9e128..cd9d0c34ddd 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -660,6 +660,20 @@ class Builder { ret->finalize(); return ret; } + WideIntAddSub* makeWideIntAddSub(WideIntAddSubOp op, + Expression* leftLow, + Expression* leftHigh, + Expression* rightLow, + Expression* rightHigh) { + auto* ret = wasm.allocator.alloc(); + ret->op = op; + ret->leftLow = leftLow; + ret->leftHigh = leftHigh; + ret->rightLow = rightLow; + ret->rightHigh = rightHigh; + ret->finalize(); + return ret; + } Select* makeSelect(Expression* condition, Expression* ifTrue, Expression* ifFalse) { auto* ret = wasm.allocator.alloc