From 9c5b78b143c320cd7aa4f04b00933a076caf975b Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Tue, 7 Apr 2026 20:59:11 +0000 Subject: [PATCH] [FP16] Implement f32x4.promote_low_f16x8. Specified at https://github.com/WebAssembly/half-precision/blob/main/proposals/half-precision/Overview.md Note: The instruction name `promote_low_f16x8` is different than the overview. I intend to update the spec overview to fix the name to match the other promote instruction. --- scripts/gen-s-parser.py | 1 + src/binaryen-c.cpp | 3 +++ src/binaryen-c.h | 1 + src/gen-s-parser.inc | 29 ++++++++++++++++++++--------- src/ir/child-typer.h | 1 + src/ir/cost.h | 1 + src/literal.h | 1 + src/passes/Print.cpp | 3 +++ src/tools/fuzzing/fuzzing.cpp | 3 ++- src/wasm-binary.h | 1 + src/wasm-interpreter.h | 2 ++ src/wasm.h | 1 + src/wasm/literal.cpp | 8 ++++++++ src/wasm/wasm-binary.cpp | 2 ++ src/wasm/wasm-stack.cpp | 4 ++++ src/wasm/wasm-validator.cpp | 1 + src/wasm/wasm.cpp | 1 + test/lit/basic/f16.wast | 22 ++++++++++++++++++++++ test/spec/f16.wast | 21 +++++++++++++++++++++ 19 files changed, 96 insertions(+), 10 deletions(-) diff --git a/scripts/gen-s-parser.py b/scripts/gen-s-parser.py index 4b73fee9f89..d0f08d0546b 100755 --- a/scripts/gen-s-parser.py +++ b/scripts/gen-s-parser.py @@ -549,6 +549,7 @@ ("i16x8.trunc_sat_f16x8_u", "makeUnary(UnaryOp::TruncSatUVecF16x8ToVecI16x8)"), ("f16x8.convert_i16x8_s", "makeUnary(UnaryOp::ConvertSVecI16x8ToVecF16x8)"), ("f16x8.convert_i16x8_u", "makeUnary(UnaryOp::ConvertUVecI16x8ToVecF16x8)"), + ("f32x4.promote_low_f16x8", "makeUnary(UnaryOp::PromoteLowVecF16x8ToVecF32x4)"), ("f16x8.madd", "makeSIMDTernary(SIMDTernaryOp::MaddVecF16x8)"), ("f16x8.nmadd", "makeSIMDTernary(SIMDTernaryOp::NmaddVecF16x8)"), diff --git a/src/binaryen-c.cpp b/src/binaryen-c.cpp index d7dcb8224f0..8a87bdb917a 100644 --- a/src/binaryen-c.cpp +++ b/src/binaryen-c.cpp @@ -1021,6 +1021,9 @@ BinaryenOp BinaryenDemoteZeroVecF64x2ToVecF32x4(void) { BinaryenOp BinaryenPromoteLowVecF32x4ToVecF64x2(void) { return PromoteLowVecF32x4ToVecF64x2; } +BinaryenOp BinaryenPromoteLowVecF16x8ToVecF32x4(void) { + return PromoteLowVecF16x8ToVecF32x4; +} BinaryenOp BinaryenRelaxedTruncSVecF32x4ToVecI32x4(void) { return RelaxedTruncSVecF32x4ToVecI32x4; } diff --git a/src/binaryen-c.h b/src/binaryen-c.h index 63a8020e095..a0c66ff9b7e 100644 --- a/src/binaryen-c.h +++ b/src/binaryen-c.h @@ -684,6 +684,7 @@ BINARYEN_API BinaryenOp BinaryenTruncSatZeroSVecF64x2ToVecI32x4(void); BINARYEN_API BinaryenOp BinaryenTruncSatZeroUVecF64x2ToVecI32x4(void); BINARYEN_API BinaryenOp BinaryenDemoteZeroVecF64x2ToVecF32x4(void); BINARYEN_API BinaryenOp BinaryenPromoteLowVecF32x4ToVecF64x2(void); +BINARYEN_API BinaryenOp BinaryenPromoteLowVecF16x8ToVecF32x4(void); BINARYEN_API BinaryenOp BinaryenRelaxedTruncSVecF32x4ToVecI32x4(void); BINARYEN_API BinaryenOp BinaryenRelaxedTruncUVecF32x4ToVecI32x4(void); BINARYEN_API BinaryenOp BinaryenRelaxedTruncZeroSVecF64x2ToVecI32x4(void); diff --git a/src/gen-s-parser.inc b/src/gen-s-parser.inc index 132806efcf1..eca86c6ed77 100644 --- a/src/gen-s-parser.inc +++ b/src/gen-s-parser.inc @@ -1127,16 +1127,27 @@ switch (buf[0]) { } } case 'p': { - switch (buf[8]) { - case 'a': - if (op == "f32x4.pmax"sv) { - CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::PMaxVecF32x4)); - return Ok{}; + switch (buf[7]) { + case 'm': { + switch (buf[8]) { + case 'a': + if (op == "f32x4.pmax"sv) { + CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::PMaxVecF32x4)); + return Ok{}; + } + goto parse_error; + case 'i': + if (op == "f32x4.pmin"sv) { + CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::PMinVecF32x4)); + return Ok{}; + } + goto parse_error; + default: goto parse_error; } - goto parse_error; - case 'i': - if (op == "f32x4.pmin"sv) { - CHECK_ERR(makeBinary(ctx, pos, annotations, BinaryOp::PMinVecF32x4)); + } + case 'r': + if (op == "f32x4.promote_low_f16x8"sv) { + CHECK_ERR(makeUnary(ctx, pos, annotations, UnaryOp::PromoteLowVecF16x8ToVecF32x4)); return Ok{}; } goto parse_error; diff --git a/src/ir/child-typer.h b/src/ir/child-typer.h index 132a0168855..385e0fa8290 100644 --- a/src/ir/child-typer.h +++ b/src/ir/child-typer.h @@ -447,6 +447,7 @@ template struct ChildTyper : OverriddenVisitor { case TruncSatUVecF16x8ToVecI16x8: case ConvertSVecI16x8ToVecF16x8: case ConvertUVecI16x8ToVecF16x8: + case PromoteLowVecF16x8ToVecF32x4: case AnyTrueVec128: case AllTrueVecI8x16: case AllTrueVecI16x8: diff --git a/src/ir/cost.h b/src/ir/cost.h index 1cf67abe9e6..0042d27bcb2 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -284,6 +284,7 @@ struct CostAnalyzer : public OverriddenVisitor { case TruncSatUVecF16x8ToVecI16x8: case ConvertSVecI16x8ToVecF16x8: case ConvertUVecI16x8ToVecF16x8: + case PromoteLowVecF16x8ToVecF32x4: ret = 1; break; case InvalidUnary: diff --git a/src/literal.h b/src/literal.h index 80dda773410..4fcb2ee8a2e 100644 --- a/src/literal.h +++ b/src/literal.h @@ -723,6 +723,7 @@ class Literal { Literal truncSatZeroUToI32x4() const; Literal demoteZeroToF32x4() const; Literal promoteLowToF64x2() const; + Literal promoteLowF16x8ToF32x4() const; Literal truncSatToSI16x8() const; Literal truncSatToUI16x8() const; Literal convertSToF16x8() const; diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index 1e910868ef8..d043735f315 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -1401,6 +1401,9 @@ struct PrintExpressionContents case ConvertUVecI16x8ToVecF16x8: o << "f16x8.convert_i16x8_u"; break; + case PromoteLowVecF16x8ToVecF32x4: + o << "f32x4.promote_low_f16x8"; + break; case InvalidUnary: WASM_UNREACHABLE("unvalid unary operator"); } diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index ba786b927a3..222d68698ae 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -4526,7 +4526,8 @@ Expression* TranslateToFuzzReader::makeUnary(Type type) { TruncSatSVecF16x8ToVecI16x8, TruncSatUVecF16x8ToVecI16x8, ConvertSVecI16x8ToVecF16x8, - ConvertUVecI16x8ToVecF16x8)), + ConvertUVecI16x8ToVecF16x8, + PromoteLowVecF16x8ToVecF32x4)), make(Type::v128)}); } WASM_UNREACHABLE("invalid value"); diff --git a/src/wasm-binary.h b/src/wasm-binary.h index c6fa761a2bd..386f495a905 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -1126,6 +1126,7 @@ enum ASTNodes { I16x8TruncSatF16x8U = 0x146, F16x8ConvertI16x8S = 0x147, F16x8ConvertI16x8U = 0x148, + F32x4PromoteLowF16x8 = 0x14b, // bulk memory opcodes diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index af9567ae762..e47559b597d 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -1164,6 +1164,8 @@ class ExpressionRunner : public OverriddenVisitor { return value.convertSToF16x8(); case ConvertUVecI16x8ToVecF16x8: return value.convertUToF16x8(); + case PromoteLowVecF16x8ToVecF32x4: + return value.promoteLowF16x8ToF32x4(); case InvalidUnary: WASM_UNREACHABLE("invalid unary op"); } diff --git a/src/wasm.h b/src/wasm.h index 941d759ce6b..5935cd47c66 100644 --- a/src/wasm.h +++ b/src/wasm.h @@ -251,6 +251,7 @@ enum UnaryOp { TruncSatUVecF16x8ToVecI16x8, ConvertSVecI16x8ToVecF16x8, ConvertUVecI16x8ToVecF16x8, + PromoteLowVecF16x8ToVecF32x4, InvalidUnary }; diff --git a/src/wasm/literal.cpp b/src/wasm/literal.cpp index 5c2a114af75..b3156fab0b3 100644 --- a/src/wasm/literal.cpp +++ b/src/wasm/literal.cpp @@ -2915,6 +2915,14 @@ Literal Literal::demoteZeroToF32x4() const { Literal Literal::promoteLowToF64x2() const { return extendF32(*this); } +Literal Literal::promoteLowF16x8ToF32x4() const { + auto lanes = getLanesF16x8(); + LaneArray<4> result; + for (size_t i = 0; i < 4; ++i) { + result[i] = lanes[i]; + } + return Literal(result); +} Literal Literal::swizzleI8x16(const Literal& other) const { auto lanes = getLanesUI8x16(); diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index 8f3d7600457..da49533f55d 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -4474,6 +4474,8 @@ Result<> WasmBinaryReader::readInst() { return builder.makeUnary(ConvertSVecI16x8ToVecF16x8); case BinaryConsts::F16x8ConvertI16x8U: return builder.makeUnary(ConvertUVecI16x8ToVecF16x8); + case BinaryConsts::F32x4PromoteLowF16x8: + return builder.makeUnary(PromoteLowVecF16x8ToVecF32x4); case BinaryConsts::I8x16ExtractLaneS: return builder.makeSIMDExtract(ExtractLaneSVecI8x16, getLaneIndex(16)); diff --git a/src/wasm/wasm-stack.cpp b/src/wasm/wasm-stack.cpp index ea932dc175c..cb308271bc7 100644 --- a/src/wasm/wasm-stack.cpp +++ b/src/wasm/wasm-stack.cpp @@ -1459,6 +1459,10 @@ void BinaryInstWriter::visitUnary(Unary* curr) { o << static_cast(BinaryConsts::SIMDPrefix) << U32LEB(BinaryConsts::F16x8ConvertI16x8U); break; + case PromoteLowVecF16x8ToVecF32x4: + o << static_cast(BinaryConsts::SIMDPrefix) + << U32LEB(BinaryConsts::F32x4PromoteLowF16x8); + break; case InvalidUnary: WASM_UNREACHABLE("invalid unary op"); } diff --git a/src/wasm/wasm-validator.cpp b/src/wasm/wasm-validator.cpp index a6a7b292958..f8c394072dd 100644 --- a/src/wasm/wasm-validator.cpp +++ b/src/wasm/wasm-validator.cpp @@ -2380,6 +2380,7 @@ void FunctionValidator::visitUnary(Unary* curr) { case TruncSatZeroUVecF64x2ToVecI32x4: case DemoteZeroVecF64x2ToVecF32x4: case PromoteLowVecF32x4ToVecF64x2: + case PromoteLowVecF16x8ToVecF32x4: case RelaxedTruncSVecF32x4ToVecI32x4: case RelaxedTruncUVecF32x4ToVecI32x4: case RelaxedTruncZeroSVecF64x2ToVecI32x4: diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index 536f33ae59f..a77a25ce874 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -714,6 +714,7 @@ void Unary::finalize() { case TruncSatUVecF16x8ToVecI16x8: case ConvertSVecI16x8ToVecF16x8: case ConvertUVecI16x8ToVecF16x8: + case PromoteLowVecF16x8ToVecF32x4: type = Type::v128; break; case AnyTrueVec128: diff --git a/test/lit/basic/f16.wast b/test/lit/basic/f16.wast index c7240b25aab..d5e204d87f7 100644 --- a/test/lit/basic/f16.wast +++ b/test/lit/basic/f16.wast @@ -597,6 +597,22 @@ (local.get $0) ) ) + + ;; CHECK-TEXT: (func $f32x4.promote_low_f16x8 (type $1) (param $0 v128) (result v128) + ;; CHECK-TEXT-NEXT: (f32x4.promote_low_f16x8 + ;; CHECK-TEXT-NEXT: (local.get $0) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-TEXT-NEXT: ) + ;; CHECK-BIN: (func $f32x4.promote_low_f16x8 (type $1) (param $0 v128) (result v128) + ;; CHECK-BIN-NEXT: (f32x4.promote_low_f16x8 + ;; CHECK-BIN-NEXT: (local.get $0) + ;; CHECK-BIN-NEXT: ) + ;; CHECK-BIN-NEXT: ) + (func $f32x4.promote_low_f16x8 (param $0 v128) (result v128) + (f32x4.promote_low_f16x8 + (local.get $0) + ) + ) ) ;; CHECK-BIN-NODEBUG: (type $0 (func (param v128 v128) (result v128))) @@ -827,3 +843,9 @@ ;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) ;; CHECK-BIN-NODEBUG-NEXT: ) ;; CHECK-BIN-NODEBUG-NEXT: ) + +;; CHECK-BIN-NODEBUG: (func $32 (type $1) (param $0 v128) (result v128) +;; CHECK-BIN-NODEBUG-NEXT: (f32x4.promote_low_f16x8 +;; CHECK-BIN-NODEBUG-NEXT: (local.get $0) +;; CHECK-BIN-NODEBUG-NEXT: ) +;; CHECK-BIN-NODEBUG-NEXT: ) diff --git a/test/spec/f16.wast b/test/spec/f16.wast index 4664e92f5cf..a36d5032d4f 100644 --- a/test/spec/f16.wast +++ b/test/spec/f16.wast @@ -38,6 +38,7 @@ (func (export "i16x8.trunc_sat_f16x8_u") (param $0 v128) (result v128) (i16x8.trunc_sat_f16x8_u (local.get $0))) (func (export "f16x8.convert_i16x8_s") (param $0 v128) (result v128) (f16x8.convert_i16x8_s (local.get $0))) (func (export "f16x8.convert_i16x8_u") (param $0 v128) (result v128) (f16x8.convert_i16x8_u (local.get $0))) + (func (export "f32x4.promote_low_f16x8") (param $0 v128) (result v128) (f32x4.promote_low_f16x8 (local.get $0))) ;; Multiple operation tests: (func (export "splat_replace") (result v128) (f16x8.replace_lane 0 (f16x8.splat (f32.const 1)) (f32.const 99)) ) @@ -247,3 +248,23 @@ (v128.const i16x8 0 1 -1 -32 0 0 0 0)) ;; 1 inf 65504 (v128.const i16x8 0 0x3c00 0x7c00 0x7bff 0 0 0 0)) + +(assert_return (invoke "f32x4.promote_low_f16x8" + ;; 1.0 -1.0 2.0 -2.0 0 0 0 0 + (v128.const i16x8 0x3c00 0xbc00 0x4000 0xc000 0 0 0 0)) + ;; 1.0 -1.0 2.0 -2.0 + (v128.const i32x4 0x3f800000 0xbf800000 0x40000000 0xc0000000)) + +;; Edge cases: Infinities, NaNs, Zeros +(assert_return (invoke "f32x4.promote_low_f16x8" + ;; inf -inf nan -0.0 0 0 0 0 + (v128.const i16x8 0x7c00 0xfc00 0x7e00 0x8000 0 0 0 0)) + ;; inf -inf nan -0.0 + (v128.const i32x4 0x7f800000 0xff800000 0x7fc00000 0x80000000)) + +;; Edge cases: Denormal +(assert_return (invoke "f32x4.promote_low_f16x8" + ;; denormal + (v128.const i16x8 0x0001 0 0 0 0 0 0 0)) + ;; 2^-24 + (v128.const i32x4 0x33800000 0 0 0))