diff --git a/src/passes/CMakeLists.txt b/src/passes/CMakeLists.txt index a763006ab66..f604c7ba921 100644 --- a/src/passes/CMakeLists.txt +++ b/src/passes/CMakeLists.txt @@ -106,6 +106,7 @@ set(passes_SOURCES RemoveImports.cpp RemoveMemoryInit.cpp RemoveNonJSOps.cpp + RemoveRelaxedSIMD.cpp RemoveUnusedBrs.cpp RemoveUnusedNames.cpp RemoveUnusedModuleElements.cpp diff --git a/src/passes/RemoveRelaxedSIMD.cpp b/src/passes/RemoveRelaxedSIMD.cpp new file mode 100644 index 00000000000..5dfd82ecd70 --- /dev/null +++ b/src/passes/RemoveRelaxedSIMD.cpp @@ -0,0 +1,102 @@ +/* + * Copyright 2026 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. + */ + +// +// Replaces relaxed SIMD instructions with traps. +// + +#include + +#include "ir/localize.h" +#include "ir/utils.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" + +namespace wasm { + +struct RemoveRelaxedSIMD : WalkerPass> { + bool isFunctionParallel() override { return true; } + + std::unique_ptr create() override { + return std::make_unique(); + } + + void replace(Expression* curr) { + auto* block = + ChildLocalizer(curr, getFunction(), *getModule(), getPassOptions()) + .getChildrenReplacement(); + block->list.push_back(Builder(*getModule()).makeUnreachable()); + replaceCurrent(block); + } + + void visitUnary(Unary* curr) { + switch (curr->op) { + case RelaxedTruncSVecF32x4ToVecI32x4: + case RelaxedTruncUVecF32x4ToVecI32x4: + case RelaxedTruncZeroSVecF64x2ToVecI32x4: + case RelaxedTruncZeroUVecF64x2ToVecI32x4: + replace(curr); + return; + default: + break; + } + } + + void visitBinary(Binary* curr) { + switch (curr->op) { + case RelaxedSwizzleVecI8x16: + case RelaxedMinVecF32x4: + case RelaxedMaxVecF32x4: + case RelaxedMinVecF64x2: + case RelaxedMaxVecF64x2: + case RelaxedQ15MulrSVecI16x8: + case DotI8x16I7x16SToVecI16x8: + replace(curr); + return; + default: + break; + } + } + + void visitSIMDTernary(SIMDTernary* curr) { + switch (curr->op) { + case RelaxedMaddVecF16x8: + case RelaxedNmaddVecF16x8: + case RelaxedMaddVecF32x4: + case RelaxedNmaddVecF32x4: + case RelaxedMaddVecF64x2: + case RelaxedNmaddVecF64x2: + case LaneselectI8x16: + case LaneselectI16x8: + case LaneselectI32x4: + case LaneselectI64x2: + case DotI8x16I7x16AddSToVecI32x4: + replace(curr); + return; + default: + break; + } + } + + void visitFunction(Function* func) { + ReFinalize().walkFunctionInModule(func, getModule()); + } +}; + +Pass* createRemoveRelaxedSIMDPass() { return new RemoveRelaxedSIMD(); } + +} // namespace wasm diff --git a/src/passes/pass.cpp b/src/passes/pass.cpp index 0f83013b8ab..dd49609c378 100644 --- a/src/passes/pass.cpp +++ b/src/passes/pass.cpp @@ -408,6 +408,9 @@ void PassRegistry::registerPasses() { registerPass("remove-non-js-ops", "removes operations incompatible with js", createRemoveNonJSOpsPass); + registerPass("remove-relaxed-simd", + "replaces relaxed SIMD instructions with unreachable", + createRemoveRelaxedSIMDPass); registerPass("remove-imports", "removes imports and replaces them with nops", createRemoveImportsPass); diff --git a/src/passes/passes.h b/src/passes/passes.h index 6481e062c6d..208b4f803e0 100644 --- a/src/passes/passes.h +++ b/src/passes/passes.h @@ -133,6 +133,7 @@ Pass* createPrintFunctionMapPass(); Pass* createPropagateGlobalsGloballyPass(); Pass* createRandomizeBranchHintsPass(); Pass* createRemoveNonJSOpsPass(); +Pass* createRemoveRelaxedSIMDPass(); Pass* createRemoveImportsPass(); Pass* createRemoveMemoryInitPass(); Pass* createRemoveUnusedBrsPass(); diff --git a/test/lit/help/wasm-metadce.test b/test/lit/help/wasm-metadce.test index 98785fda862..b566afd8471 100644 --- a/test/lit/help/wasm-metadce.test +++ b/test/lit/help/wasm-metadce.test @@ -391,6 +391,9 @@ ;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible ;; CHECK-NEXT: with js ;; CHECK-NEXT: +;; CHECK-NEXT: --remove-relaxed-simd replaces relaxed SIMD +;; CHECK-NEXT: instructions with unreachable +;; CHECK-NEXT: ;; CHECK-NEXT: --remove-unused-brs removes breaks from locations ;; CHECK-NEXT: that are not needed ;; CHECK-NEXT: diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test index 4c0f4f67047..eff3724891c 100644 --- a/test/lit/help/wasm-opt.test +++ b/test/lit/help/wasm-opt.test @@ -423,6 +423,9 @@ ;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible ;; CHECK-NEXT: with js ;; CHECK-NEXT: +;; CHECK-NEXT: --remove-relaxed-simd replaces relaxed SIMD +;; CHECK-NEXT: instructions with unreachable +;; CHECK-NEXT: ;; CHECK-NEXT: --remove-unused-brs removes breaks from locations ;; CHECK-NEXT: that are not needed ;; CHECK-NEXT: diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test index 3c201f79ac4..cd55ac22235 100644 --- a/test/lit/help/wasm2js.test +++ b/test/lit/help/wasm2js.test @@ -355,6 +355,9 @@ ;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible ;; CHECK-NEXT: with js ;; CHECK-NEXT: +;; CHECK-NEXT: --remove-relaxed-simd replaces relaxed SIMD +;; CHECK-NEXT: instructions with unreachable +;; CHECK-NEXT: ;; CHECK-NEXT: --remove-unused-brs removes breaks from locations ;; CHECK-NEXT: that are not needed ;; CHECK-NEXT: diff --git a/test/lit/passes/remove-relaxed-simd.wast b/test/lit/passes/remove-relaxed-simd.wast new file mode 100644 index 00000000000..2269aedc4d2 --- /dev/null +++ b/test/lit/passes/remove-relaxed-simd.wast @@ -0,0 +1,207 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s -all --remove-relaxed-simd -S -o - | filecheck %s + +(module + ;; CHECK: (import "" "" (func $effect (type $1) (result v128))) + (import "" "" (func $effect (result v128))) + ;; CHECK: (func $unary (type $0) (param $0 v128) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (v128.not + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $unary (param v128) + (drop (i32x4.relaxed_trunc_f32x4_s (local.get 0))) + (drop (i32x4.relaxed_trunc_f32x4_u (local.get 0))) + (drop (i32x4.relaxed_trunc_f64x2_s_zero (local.get 0))) + (drop (i32x4.relaxed_trunc_f64x2_u_zero (local.get 0))) + ;; Normal SIMD instruction + (drop (v128.not (local.get 0))) + ) + + ;; CHECK: (func $binary (type $2) (param $0 v128) (param $1 v128) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (v128.xor + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $binary (param v128 v128) + (drop (i8x16.relaxed_swizzle (local.get 0) (local.get 1))) + (drop (f32x4.relaxed_min (local.get 0) (local.get 1))) + (drop (f32x4.relaxed_max (local.get 0) (local.get 1))) + (drop (f64x2.relaxed_min (local.get 0) (local.get 1))) + (drop (f64x2.relaxed_max (local.get 0) (local.get 1))) + (drop (i16x8.relaxed_q15mulr_s (local.get 0) (local.get 1))) + (drop (i16x8.dot_i8x16_i7x16_s (local.get 0) (local.get 1))) + ;; Normal SIMD instruction + (drop (v128.xor (local.get 0) (local.get 1))) + ) + + ;; CHECK: (func $ternary (type $3) (param $0 v128) (param $1 v128) (param $2 v128) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (v128.bitselect + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: (local.get $2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $ternary (param v128 v128 v128) + (drop (i32x4.dot_i8x16_i7x16_add_s (local.get 0) (local.get 1) (local.get 2))) + (drop (f16x8.relaxed_madd (local.get 0) (local.get 1) (local.get 2))) + (drop (f16x8.relaxed_nmadd (local.get 0) (local.get 1) (local.get 2))) + (drop (f32x4.relaxed_madd (local.get 0) (local.get 1) (local.get 2))) + (drop (f32x4.relaxed_nmadd (local.get 0) (local.get 1) (local.get 2))) + (drop (f64x2.relaxed_madd (local.get 0) (local.get 1) (local.get 2))) + (drop (f64x2.relaxed_nmadd (local.get 0) (local.get 1) (local.get 2))) + ;; Normal SIMD instruction + (drop (v128.bitselect (local.get 0) (local.get 1) (local.get 2))) + ) + + ;; CHECK: (func $refinalize (type $0) (param $0 v128) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $l + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $refinalize (param v128) + (drop + ;; This block should become unreachable. + (block $l (result v128) + (i32x4.relaxed_trunc_f32x4_s (local.get 0)) + ) + ) + ) + + ;; CHECK: (func $effects (type $0) (param $0 v128) + ;; CHECK-NEXT: (local $1 v128) + ;; CHECK-NEXT: (local $2 v128) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (block (result v128) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (call $effect) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $effects (param v128) + (drop + (f16x8.relaxed_madd + (call $effect) + (local.get 0) + (block (result v128) + (drop + (call $effect) + ) + (local.get 0) + ) + ) + ) + ) +)