diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index d8ebce98e31..282854757ea 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -150,6 +150,8 @@ ("i64.rotr", "makeBinary(BinaryOp::RotRInt64)"), ("i64.add128", "makeWideIntAddSub(WideIntAddSubOp::AddInt128)"), ("i64.sub128", "makeWideIntAddSub(WideIntAddSubOp::SubInt128)"), + ("i64.mul_wide_s", "makeWideIntMul(WideIntMulOp::MulWideSInt64)"), + ("i64.mul_wide_u", "makeWideIntMul(WideIntMulOp::MulWideUInt64)"), ("f32.abs", "makeUnary(UnaryOp::AbsFloat32)"), ("f32.neg", "makeUnary(UnaryOp::NegFloat32)"), ("f32.ceil", "makeUnary(UnaryOp::CeilFloat32)"), diff --git a/scripts/test/shared.py b/scripts/test/shared.py index ecb8b6218bc..8f872676a96 100644 --- a/scripts/test/shared.py +++ b/scripts/test/shared.py @@ -404,7 +404,6 @@ def get_tests(test_dir, extensions=[], recursive=False): 'threads/atomic.wast', ] SPEC_TESTSUITE_PROPOSALS_TO_SKIP = [ - 'wide-arithmetic', ] # Paths are relative to the test/spec/testsuite directory diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index d3d3b99a702..ac2ab0a1330 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -3991,12 +3991,34 @@ switch (buf[0]) { default: goto parse_error; } } - case 'm': - if (op == "i64.mul"sv) { - CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::MulInt64)); - return Ok{}; + case 'm': { + switch (buf[7]) { + case '\0': + if (op == "i64.mul"sv) { + CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::MulInt64)); + return Ok{}; + } + goto parse_error; + case '_': { + switch (buf[13]) { + case 's': + if (op == "i64.mul_wide_s"sv) { + CHECK_ERR(makeWideIntMul(ctx, pos, annotations, WideIntMulOp::MulWideSInt64)); + return Ok{}; + } + goto parse_error; + case 'u': + if (op == "i64.mul_wide_u"sv) { + CHECK_ERR(makeWideIntMul(ctx, pos, annotations, WideIntMulOp::MulWideUInt64)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; + } + } + default: goto parse_error; } - goto parse_error; + } case 'n': if (op == "i64.ne"sv) { CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::NeInt64)); diff --git a/src/interpreter/interpreter.cpp b/src/interpreter/interpreter.cpp index e3abe63525d..96a1f41a424 100644 --- a/src/interpreter/interpreter.cpp +++ b/src/interpreter/interpreter.cpp @@ -217,6 +217,7 @@ struct ExpressionInterpreter : OverriddenVisitor { } } Flow visitWideIntAddSub(WideIntAddSub* curr) { WASM_UNREACHABLE("TODO"); } + Flow visitWideIntMul(WideIntMul* 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 6c722cbb03c..07820ed6eee 100644 --- a/src/ir/ReFinalize.cpp +++ b/src/ir/ReFinalize.cpp @@ -110,6 +110,7 @@ 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::visitWideIntMul(WideIntMul* 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 2aa1a06d549..b87cd3f9c16 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -712,6 +712,11 @@ template struct ChildTyper : OverriddenVisitor { note(&curr->rightHigh, Type::i64); } + void visitWideIntMul(WideIntMul* curr) { + note(&curr->left, Type::i64); + note(&curr->right, 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 7d044a49947..99bf7c05333 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -579,6 +579,9 @@ struct CostAnalyzer : public OverriddenVisitor { return 1 + visit(curr->leftLow) + visit(curr->leftHigh) + visit(curr->rightLow) + visit(curr->rightHigh); } + CostType visitWideIntMul(WideIntMul* curr) { + return 4 + visit(curr->left) + visit(curr->right); + } 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 ec96053a13d..44cc8031f45 100644 --- a/src/ir/effects.h +++ b/src/ir/effects.h @@ -946,6 +946,7 @@ class EffectAnalyzer { } } void visitWideIntAddSub(WideIntAddSub* curr) {} + void visitWideIntMul(WideIntMul* 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 08cf1cf4f8d..3e1213cd05b 100644 --- a/src/ir/possible-contents.cpp +++ b/src/ir/possible-contents.cpp @@ -635,6 +635,7 @@ struct InfoCollector } void visitBinary(Binary* curr) { addRoot(curr); } void visitWideIntAddSub(WideIntAddSub* curr) { addRoot(curr); } + void visitWideIntMul(WideIntMul* 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 b0a910f3fa7..940144cba04 100644 --- a/src/ir/subtype-exprs.h +++ b/src/ir/subtype-exprs.h @@ -214,6 +214,7 @@ struct SubtypingDiscoverer : public OverriddenVisitor { void visitUnary(Unary* curr) {} void visitBinary(Binary* curr) {} void visitWideIntAddSub(WideIntAddSub* curr) {} + void visitWideIntMul(WideIntMul* 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 e62d3d5869a..eac35a543c5 100644 --- a/src/parser/contexts.h +++ b/src/parser/contexts.h @@ -481,6 +481,10 @@ struct NullInstrParserCtx { return Ok{}; } + Result<> makeWideIntMul(Index, const std::vector&, WideIntMulOp) { + return Ok{}; + } + Result<> makeUnary(Index, const std::vector&, UnaryOp) { return Ok{}; } @@ -2170,6 +2174,12 @@ struct ParseDefsCtx : TypeParserCtx, AnnotationParserCtx { return withLoc(pos, irBuilder.makeWideIntAddSub(op)); } + Result<> makeWideIntMul(Index pos, + const std::vector& annotations, + WideIntMulOp op) { + return withLoc(pos, irBuilder.makeWideIntMul(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 c0d2804e29f..ffbcdfae233 100644 --- a/src/parser/parsers.h +++ b/src/parser/parsers.h @@ -96,6 +96,9 @@ Result<> makeWideIntAddSub(Ctx&, const std::vector&, WideIntAddSubOp op); template +Result<> +makeWideIntMul(Ctx&, Index, const std::vector&, WideIntMulOp op); +template Result<> makeUnary(Ctx&, Index, const std::vector&, UnaryOp op); template Result<> makeSelect(Ctx&, Index, const std::vector&); @@ -1605,6 +1608,14 @@ Result<> makeWideIntAddSub(Ctx& ctx, return ctx.makeWideIntAddSub(pos, annotations, op); } +template +Result<> makeWideIntMul(Ctx& ctx, + Index pos, + const std::vector& annotations, + WideIntMulOp op) { + return ctx.makeWideIntMul(pos, annotations, op); +} + template Result<> makeUnary(Ctx& ctx, Index pos, diff --git a/src/passes/I64ToI32Lowering.cpp b/src/passes/I64ToI32Lowering.cpp index 0724b448674..ddf072caa2b 100644 --- a/src/passes/I64ToI32Lowering.cpp +++ b/src/passes/I64ToI32Lowering.cpp @@ -1557,6 +1557,10 @@ struct I64ToI32Lowering : public WalkerPass> { WASM_UNREACHABLE("TODO: wide arithmetic lowering"); } + void visitWideIntMul(WideIntMul* 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 c926c914720..4ffdb031dd4 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2045,6 +2045,20 @@ struct PrintExpressionContents } restoreNormalColor(o); } + void visitWideIntMul(WideIntMul* curr) { + prepareColor(o); + switch (curr->op) { + case MulWideSInt64: { + o << "i64.mul_wide_s"; + break; + } + case MulWideUInt64: { + o << "i64.mul_wide_u"; + 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 1cd9a1b7885..749372ff107 100644 --- a/src/passes/TypeGeneralizing.cpp +++ b/src/passes/TypeGeneralizing.cpp @@ -434,6 +434,7 @@ struct TransferFn : OverriddenVisitor { void visitUnary(Unary* curr) {} void visitBinary(Binary* curr) {} void visitWideIntAddSub(WideIntAddSub* curr) {} + void visitWideIntMul(WideIntMul* curr) {} void visitSelect(Select* curr) { if (curr->type.isRef()) { diff --git a/src/support/CMakeLists.txt b/src/support/CMakeLists.txt index d264979e7a8..3fc559ff36b 100644 --- a/src/support/CMakeLists.txt +++ b/src/support/CMakeLists.txt @@ -7,6 +7,7 @@ set(support_SOURCES debug.cpp dfa_minimization.cpp file.cpp + int128.cpp intervals.cpp istring.cpp json.cpp diff --git a/src/support/int128.cpp b/src/support/int128.cpp new file mode 100644 index 00000000000..b1e676b672d --- /dev/null +++ b/src/support/int128.cpp @@ -0,0 +1,89 @@ +// Copyright 2024 WebAssembly Community Group participants +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "support/int128.h" + +namespace wasm { + +#ifdef __SIZEOF_INT128__ + +Int128 mul_wide_s(uint64_t lhs, uint64_t rhs) { + __int128 result = static_cast<__int128>(static_cast(lhs)) * + static_cast<__int128>(static_cast(rhs)); + return {static_cast(result >> 64), static_cast(result)}; +} + +Int128 mul_wide_u(uint64_t lhs, uint64_t rhs) { + unsigned __int128 result = + static_cast(lhs) * static_cast(rhs); + return {static_cast(result >> 64), static_cast(result)}; +} + +#else + +Int128 mul_wide_s(uint64_t lhs, uint64_t rhs) { + return detail::mul_wide_s_fallback(lhs, rhs); +} + +Int128 mul_wide_u(uint64_t lhs, uint64_t rhs) { + return detail::mul_wide_u_fallback(lhs, rhs); +} + +#endif + +namespace detail { + +Int128 mul_wide_s_fallback(uint64_t lhs, uint64_t rhs) { + auto [high, low] = mul_wide_u_fallback(lhs, rhs); + + if (static_cast(lhs) < 0) { + high -= rhs; + } + if (static_cast(rhs) < 0) { + high -= lhs; + } + + return {high, low}; +} + +Int128 mul_wide_u_fallback(uint64_t lhs, uint64_t rhs) { + uint32_t lhsLow = lhs & 0xffffffff; + uint32_t lhsHigh = lhs >> 32; + uint32_t rhsLow = rhs & 0xffffffff; + uint32_t rhsHigh = rhs >> 32; + + uint64_t mulLowLow = static_cast(lhsLow) * rhsLow; + uint64_t mulLowHigh = static_cast(lhsLow) * rhsHigh; + uint64_t mulHighLow = static_cast(lhsHigh) * rhsLow; + uint64_t mulHighHigh = static_cast(lhsHigh) * rhsHigh; + + uint64_t cross = mulLowHigh + (mulLowLow >> 32); + uint64_t carry = cross >> 32; + cross = (cross & 0xffffffff) + mulHighLow; + + // The lower 64 bits of the 128-bit result are formed by: + // (low * low) + ((low * high) << 32) + ((high * low) << 32) + // + // The upper 64 bits of the 128-bit result are formed by: + // (high * high) + the upper 32-bits of (low * high) + the upper 32-bits of + // (high * low) + carries from the lower 64 bits + uint64_t lowResult = (cross << 32) | (mulLowLow & 0xffffffff); + uint64_t highResult = mulHighHigh + carry + (cross >> 32); + + return {highResult, lowResult}; +} + +} // namespace detail + +} // namespace wasm diff --git a/src/support/int128.h b/src/support/int128.h new file mode 100644 index 00000000000..a63c2e85d54 --- /dev/null +++ b/src/support/int128.h @@ -0,0 +1,45 @@ +// Copyright 2024 WebAssembly Community Group participants +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef wasm_support_int128_h +#define wasm_support_int128_h + +#include + +namespace wasm { + +struct Int128 { + uint64_t high; + uint64_t low; + + bool operator==(const Int128& other) const { + return high == other.high && low == other.low; + } +}; + +// Computes the 128-bit product of two signed 64-bit integers. +Int128 mul_wide_s(uint64_t lhs, uint64_t rhs); + +// Computes the 128-bit product of two unsigned 64-bit integers. +Int128 mul_wide_u(uint64_t lhs, uint64_t rhs); + +namespace detail { +// Fallback implementations exposed for testing. +Int128 mul_wide_s_fallback(uint64_t lhs, uint64_t rhs); +Int128 mul_wide_u_fallback(uint64_t lhs, uint64_t rhs); +} // namespace detail + +} // namespace wasm + +#endif // wasm_support_i128_h diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 85458e97a3b..92bb588d53c 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1142,6 +1142,8 @@ enum ASTNodes { I64Add128 = 0x13, I64Sub128 = 0x14, + I64MulWideS = 0x15, + I64MulWideU = 0x16, // reference types opcodes diff --git a/src/wasm-builder.h b/src/wasm-builder.h index cd9d0c34ddd..1c8894c4094 100644 --- a/src/wasm-builder.h +++ b/src/wasm-builder.h @@ -674,6 +674,16 @@ class Builder { ret->finalize(); return ret; } + + WideIntMul* + makeWideIntMul(WideIntMulOp op, Expression* left, Expression* right) { + auto* ret = wasm.allocator.alloc(); + ret->op = op; + ret->left = left; + ret->right = right; + ret->finalize(); + return ret; + } Select* makeSelect(Expression* condition, Expression* ifTrue, Expression* ifFalse) { auto* ret = wasm.allocator.alloc