From 8e1f558a50d8fff2c5c36dd18a308d7199d8c458 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 11 Feb 2026 14:12:39 -0800 Subject: [PATCH 1/5] Add a pass to remove relaxed SIMD instructions and replace them with unreachables. This might be useful in situations where a module needs to be processed by tools that do not support relaxed SIMD, but where the relaxed SIMD usage also does not affect the output of the tool. --- src/passes/CMakeLists.txt | 1 + src/passes/RemoveRelaxedSIMD.cpp | 101 +++++++++++++ src/passes/pass.cpp | 3 + src/passes/passes.h | 1 + test/lit/passes/remove-relaxed-simd.wast | 171 +++++++++++++++++++++++ 5 files changed, 277 insertions(+) create mode 100644 src/passes/RemoveRelaxedSIMD.cpp create mode 100644 test/lit/passes/remove-relaxed-simd.wast 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..4928b4c14ae --- /dev/null +++ b/src/passes/RemoveRelaxedSIMD.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2015 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 "ir/localize.h" +#include "ir/utils.h" +#include "pass.h" +#include "wasm-builder.h" +#include "wasm.h" +#include + +namespace wasm { + +struct RemoveRelaxedSIMD : WalkerPass> { + bool isFunctionParallel() { return true; } + + 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()); + } + + std::unique_ptr create() { + return std::make_unique(); + } +}; + +Pass* createRemoveRelaxedSIMDPass() { return new RemoveRelaxedSIMD(); } + +} // namespace wasm \ No newline at end of file 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/passes/remove-relaxed-simd.wast b/test/lit/passes/remove-relaxed-simd.wast new file mode 100644 index 00000000000..160a889e33d --- /dev/null +++ b/test/lit/passes/remove-relaxed-simd.wast @@ -0,0 +1,171 @@ +;; 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: (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 $1) (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 $2) (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)) + ) + ) + ) +) From 53f34a4c571863a7e6704598b8df7ba3216bd11c Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 11 Feb 2026 14:15:45 -0800 Subject: [PATCH 2/5] Update copyright year --- src/passes/RemoveRelaxedSIMD.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/passes/RemoveRelaxedSIMD.cpp b/src/passes/RemoveRelaxedSIMD.cpp index 4928b4c14ae..3eaeb990be9 100644 --- a/src/passes/RemoveRelaxedSIMD.cpp +++ b/src/passes/RemoveRelaxedSIMD.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015 WebAssembly Community Group participants + * 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. @@ -98,4 +98,4 @@ struct RemoveRelaxedSIMD : WalkerPass> { Pass* createRemoveRelaxedSIMDPass() { return new RemoveRelaxedSIMD(); } -} // namespace wasm \ No newline at end of file +} // namespace wasm From c27cff19a8df3894ab1520c0b8c4a679fc17069a Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 11 Feb 2026 14:16:11 -0800 Subject: [PATCH 3/5] Fix header order --- src/passes/RemoveRelaxedSIMD.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/RemoveRelaxedSIMD.cpp b/src/passes/RemoveRelaxedSIMD.cpp index 3eaeb990be9..a7594e75342 100644 --- a/src/passes/RemoveRelaxedSIMD.cpp +++ b/src/passes/RemoveRelaxedSIMD.cpp @@ -18,12 +18,13 @@ // 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" -#include namespace wasm { From 8870263295a6c2654f11cee4355e84e889ef35b9 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 11 Feb 2026 15:44:24 -0800 Subject: [PATCH 4/5] address feedback --- src/passes/RemoveRelaxedSIMD.cpp | 10 +++--- test/lit/passes/remove-relaxed-simd.wast | 42 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/src/passes/RemoveRelaxedSIMD.cpp b/src/passes/RemoveRelaxedSIMD.cpp index 4928b4c14ae..c6adece7442 100644 --- a/src/passes/RemoveRelaxedSIMD.cpp +++ b/src/passes/RemoveRelaxedSIMD.cpp @@ -28,7 +28,11 @@ namespace wasm { struct RemoveRelaxedSIMD : WalkerPass> { - bool isFunctionParallel() { return true; } + bool isFunctionParallel() override { return true; } + + std::unique_ptr create() override { + return std::make_unique(); + } void replace(Expression* curr) { auto* block = @@ -90,10 +94,6 @@ struct RemoveRelaxedSIMD : WalkerPass> { void visitFunction(Function* func) { ReFinalize().walkFunctionInModule(func, getModule()); } - - std::unique_ptr create() { - return std::make_unique(); - } }; Pass* createRemoveRelaxedSIMDPass() { return new RemoveRelaxedSIMD(); } diff --git a/test/lit/passes/remove-relaxed-simd.wast b/test/lit/passes/remove-relaxed-simd.wast index 160a889e33d..2269aedc4d2 100644 --- a/test/lit/passes/remove-relaxed-simd.wast +++ b/test/lit/passes/remove-relaxed-simd.wast @@ -3,7 +3,8 @@ ;; 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 @@ -40,7 +41,7 @@ (drop (v128.not (local.get 0))) ) - ;; CHECK: (func $binary (type $1) (param $0 v128) (param $1 v128) + ;; CHECK: (func $binary (type $2) (param $0 v128) (param $1 v128) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (unreachable) @@ -95,7 +96,7 @@ (drop (v128.xor (local.get 0) (local.get 1))) ) - ;; CHECK: (func $ternary (type $2) (param $0 v128) (param $1 v128) (param $2 v128) + ;; CHECK: (func $ternary (type $3) (param $0 v128) (param $1 v128) (param $2 v128) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (block ;; CHECK-NEXT: (unreachable) @@ -168,4 +169,39 @@ ) ) ) + + ;; 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) + ) + ) + ) + ) ) From 7b16ed174c84157c5f7c2caf0ae83fc0fd58c731 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 11 Feb 2026 16:12:34 -0800 Subject: [PATCH 5/5] update help tests --- test/lit/help/wasm-metadce.test | 3 +++ test/lit/help/wasm-opt.test | 3 +++ test/lit/help/wasm2js.test | 3 +++ 3 files changed, 9 insertions(+) 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: