Skip to content

Commit 2fe287c

Browse files
authored
Add a pass to remove relaxed SIMD instructions (#8300)
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.
1 parent 7a4758e commit 2fe287c

8 files changed

Lines changed: 323 additions & 0 deletions

File tree

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ set(passes_SOURCES
106106
RemoveImports.cpp
107107
RemoveMemoryInit.cpp
108108
RemoveNonJSOps.cpp
109+
RemoveRelaxedSIMD.cpp
109110
RemoveUnusedBrs.cpp
110111
RemoveUnusedNames.cpp
111112
RemoveUnusedModuleElements.cpp

src/passes/RemoveRelaxedSIMD.cpp

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2026 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
//
18+
// Replaces relaxed SIMD instructions with traps.
19+
//
20+
21+
#include <memory>
22+
23+
#include "ir/localize.h"
24+
#include "ir/utils.h"
25+
#include "pass.h"
26+
#include "wasm-builder.h"
27+
#include "wasm.h"
28+
29+
namespace wasm {
30+
31+
struct RemoveRelaxedSIMD : WalkerPass<PostWalker<RemoveRelaxedSIMD>> {
32+
bool isFunctionParallel() override { return true; }
33+
34+
std::unique_ptr<Pass> create() override {
35+
return std::make_unique<RemoveRelaxedSIMD>();
36+
}
37+
38+
void replace(Expression* curr) {
39+
auto* block =
40+
ChildLocalizer(curr, getFunction(), *getModule(), getPassOptions())
41+
.getChildrenReplacement();
42+
block->list.push_back(Builder(*getModule()).makeUnreachable());
43+
replaceCurrent(block);
44+
}
45+
46+
void visitUnary(Unary* curr) {
47+
switch (curr->op) {
48+
case RelaxedTruncSVecF32x4ToVecI32x4:
49+
case RelaxedTruncUVecF32x4ToVecI32x4:
50+
case RelaxedTruncZeroSVecF64x2ToVecI32x4:
51+
case RelaxedTruncZeroUVecF64x2ToVecI32x4:
52+
replace(curr);
53+
return;
54+
default:
55+
break;
56+
}
57+
}
58+
59+
void visitBinary(Binary* curr) {
60+
switch (curr->op) {
61+
case RelaxedSwizzleVecI8x16:
62+
case RelaxedMinVecF32x4:
63+
case RelaxedMaxVecF32x4:
64+
case RelaxedMinVecF64x2:
65+
case RelaxedMaxVecF64x2:
66+
case RelaxedQ15MulrSVecI16x8:
67+
case DotI8x16I7x16SToVecI16x8:
68+
replace(curr);
69+
return;
70+
default:
71+
break;
72+
}
73+
}
74+
75+
void visitSIMDTernary(SIMDTernary* curr) {
76+
switch (curr->op) {
77+
case RelaxedMaddVecF16x8:
78+
case RelaxedNmaddVecF16x8:
79+
case RelaxedMaddVecF32x4:
80+
case RelaxedNmaddVecF32x4:
81+
case RelaxedMaddVecF64x2:
82+
case RelaxedNmaddVecF64x2:
83+
case LaneselectI8x16:
84+
case LaneselectI16x8:
85+
case LaneselectI32x4:
86+
case LaneselectI64x2:
87+
case DotI8x16I7x16AddSToVecI32x4:
88+
replace(curr);
89+
return;
90+
default:
91+
break;
92+
}
93+
}
94+
95+
void visitFunction(Function* func) {
96+
ReFinalize().walkFunctionInModule(func, getModule());
97+
}
98+
};
99+
100+
Pass* createRemoveRelaxedSIMDPass() { return new RemoveRelaxedSIMD(); }
101+
102+
} // namespace wasm

src/passes/pass.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,9 @@ void PassRegistry::registerPasses() {
408408
registerPass("remove-non-js-ops",
409409
"removes operations incompatible with js",
410410
createRemoveNonJSOpsPass);
411+
registerPass("remove-relaxed-simd",
412+
"replaces relaxed SIMD instructions with unreachable",
413+
createRemoveRelaxedSIMDPass);
411414
registerPass("remove-imports",
412415
"removes imports and replaces them with nops",
413416
createRemoveImportsPass);

src/passes/passes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ Pass* createPrintFunctionMapPass();
133133
Pass* createPropagateGlobalsGloballyPass();
134134
Pass* createRandomizeBranchHintsPass();
135135
Pass* createRemoveNonJSOpsPass();
136+
Pass* createRemoveRelaxedSIMDPass();
136137
Pass* createRemoveImportsPass();
137138
Pass* createRemoveMemoryInitPass();
138139
Pass* createRemoveUnusedBrsPass();

test/lit/help/wasm-metadce.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,9 @@
391391
;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible
392392
;; CHECK-NEXT: with js
393393
;; CHECK-NEXT:
394+
;; CHECK-NEXT: --remove-relaxed-simd replaces relaxed SIMD
395+
;; CHECK-NEXT: instructions with unreachable
396+
;; CHECK-NEXT:
394397
;; CHECK-NEXT: --remove-unused-brs removes breaks from locations
395398
;; CHECK-NEXT: that are not needed
396399
;; CHECK-NEXT:

test/lit/help/wasm-opt.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,9 @@
423423
;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible
424424
;; CHECK-NEXT: with js
425425
;; CHECK-NEXT:
426+
;; CHECK-NEXT: --remove-relaxed-simd replaces relaxed SIMD
427+
;; CHECK-NEXT: instructions with unreachable
428+
;; CHECK-NEXT:
426429
;; CHECK-NEXT: --remove-unused-brs removes breaks from locations
427430
;; CHECK-NEXT: that are not needed
428431
;; CHECK-NEXT:

test/lit/help/wasm2js.test

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,9 @@
355355
;; CHECK-NEXT: --remove-non-js-ops removes operations incompatible
356356
;; CHECK-NEXT: with js
357357
;; CHECK-NEXT:
358+
;; CHECK-NEXT: --remove-relaxed-simd replaces relaxed SIMD
359+
;; CHECK-NEXT: instructions with unreachable
360+
;; CHECK-NEXT:
358361
;; CHECK-NEXT: --remove-unused-brs removes breaks from locations
359362
;; CHECK-NEXT: that are not needed
360363
;; CHECK-NEXT:
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
2+
3+
;; RUN: wasm-opt %s -all --remove-relaxed-simd -S -o - | filecheck %s
4+
5+
(module
6+
;; CHECK: (import "" "" (func $effect (type $1) (result v128)))
7+
(import "" "" (func $effect (result v128)))
8+
;; CHECK: (func $unary (type $0) (param $0 v128)
9+
;; CHECK-NEXT: (drop
10+
;; CHECK-NEXT: (block
11+
;; CHECK-NEXT: (unreachable)
12+
;; CHECK-NEXT: )
13+
;; CHECK-NEXT: )
14+
;; CHECK-NEXT: (drop
15+
;; CHECK-NEXT: (block
16+
;; CHECK-NEXT: (unreachable)
17+
;; CHECK-NEXT: )
18+
;; CHECK-NEXT: )
19+
;; CHECK-NEXT: (drop
20+
;; CHECK-NEXT: (block
21+
;; CHECK-NEXT: (unreachable)
22+
;; CHECK-NEXT: )
23+
;; CHECK-NEXT: )
24+
;; CHECK-NEXT: (drop
25+
;; CHECK-NEXT: (block
26+
;; CHECK-NEXT: (unreachable)
27+
;; CHECK-NEXT: )
28+
;; CHECK-NEXT: )
29+
;; CHECK-NEXT: (drop
30+
;; CHECK-NEXT: (v128.not
31+
;; CHECK-NEXT: (local.get $0)
32+
;; CHECK-NEXT: )
33+
;; CHECK-NEXT: )
34+
;; CHECK-NEXT: )
35+
(func $unary (param v128)
36+
(drop (i32x4.relaxed_trunc_f32x4_s (local.get 0)))
37+
(drop (i32x4.relaxed_trunc_f32x4_u (local.get 0)))
38+
(drop (i32x4.relaxed_trunc_f64x2_s_zero (local.get 0)))
39+
(drop (i32x4.relaxed_trunc_f64x2_u_zero (local.get 0)))
40+
;; Normal SIMD instruction
41+
(drop (v128.not (local.get 0)))
42+
)
43+
44+
;; CHECK: (func $binary (type $2) (param $0 v128) (param $1 v128)
45+
;; CHECK-NEXT: (drop
46+
;; CHECK-NEXT: (block
47+
;; CHECK-NEXT: (unreachable)
48+
;; CHECK-NEXT: )
49+
;; CHECK-NEXT: )
50+
;; CHECK-NEXT: (drop
51+
;; CHECK-NEXT: (block
52+
;; CHECK-NEXT: (unreachable)
53+
;; CHECK-NEXT: )
54+
;; CHECK-NEXT: )
55+
;; CHECK-NEXT: (drop
56+
;; CHECK-NEXT: (block
57+
;; CHECK-NEXT: (unreachable)
58+
;; CHECK-NEXT: )
59+
;; CHECK-NEXT: )
60+
;; CHECK-NEXT: (drop
61+
;; CHECK-NEXT: (block
62+
;; CHECK-NEXT: (unreachable)
63+
;; CHECK-NEXT: )
64+
;; CHECK-NEXT: )
65+
;; CHECK-NEXT: (drop
66+
;; CHECK-NEXT: (block
67+
;; CHECK-NEXT: (unreachable)
68+
;; CHECK-NEXT: )
69+
;; CHECK-NEXT: )
70+
;; CHECK-NEXT: (drop
71+
;; CHECK-NEXT: (block
72+
;; CHECK-NEXT: (unreachable)
73+
;; CHECK-NEXT: )
74+
;; CHECK-NEXT: )
75+
;; CHECK-NEXT: (drop
76+
;; CHECK-NEXT: (block
77+
;; CHECK-NEXT: (unreachable)
78+
;; CHECK-NEXT: )
79+
;; CHECK-NEXT: )
80+
;; CHECK-NEXT: (drop
81+
;; CHECK-NEXT: (v128.xor
82+
;; CHECK-NEXT: (local.get $0)
83+
;; CHECK-NEXT: (local.get $1)
84+
;; CHECK-NEXT: )
85+
;; CHECK-NEXT: )
86+
;; CHECK-NEXT: )
87+
(func $binary (param v128 v128)
88+
(drop (i8x16.relaxed_swizzle (local.get 0) (local.get 1)))
89+
(drop (f32x4.relaxed_min (local.get 0) (local.get 1)))
90+
(drop (f32x4.relaxed_max (local.get 0) (local.get 1)))
91+
(drop (f64x2.relaxed_min (local.get 0) (local.get 1)))
92+
(drop (f64x2.relaxed_max (local.get 0) (local.get 1)))
93+
(drop (i16x8.relaxed_q15mulr_s (local.get 0) (local.get 1)))
94+
(drop (i16x8.dot_i8x16_i7x16_s (local.get 0) (local.get 1)))
95+
;; Normal SIMD instruction
96+
(drop (v128.xor (local.get 0) (local.get 1)))
97+
)
98+
99+
;; CHECK: (func $ternary (type $3) (param $0 v128) (param $1 v128) (param $2 v128)
100+
;; CHECK-NEXT: (drop
101+
;; CHECK-NEXT: (block
102+
;; CHECK-NEXT: (unreachable)
103+
;; CHECK-NEXT: )
104+
;; CHECK-NEXT: )
105+
;; CHECK-NEXT: (drop
106+
;; CHECK-NEXT: (block
107+
;; CHECK-NEXT: (unreachable)
108+
;; CHECK-NEXT: )
109+
;; CHECK-NEXT: )
110+
;; CHECK-NEXT: (drop
111+
;; CHECK-NEXT: (block
112+
;; CHECK-NEXT: (unreachable)
113+
;; CHECK-NEXT: )
114+
;; CHECK-NEXT: )
115+
;; CHECK-NEXT: (drop
116+
;; CHECK-NEXT: (block
117+
;; CHECK-NEXT: (unreachable)
118+
;; CHECK-NEXT: )
119+
;; CHECK-NEXT: )
120+
;; CHECK-NEXT: (drop
121+
;; CHECK-NEXT: (block
122+
;; CHECK-NEXT: (unreachable)
123+
;; CHECK-NEXT: )
124+
;; CHECK-NEXT: )
125+
;; CHECK-NEXT: (drop
126+
;; CHECK-NEXT: (block
127+
;; CHECK-NEXT: (unreachable)
128+
;; CHECK-NEXT: )
129+
;; CHECK-NEXT: )
130+
;; CHECK-NEXT: (drop
131+
;; CHECK-NEXT: (block
132+
;; CHECK-NEXT: (unreachable)
133+
;; CHECK-NEXT: )
134+
;; CHECK-NEXT: )
135+
;; CHECK-NEXT: (drop
136+
;; CHECK-NEXT: (v128.bitselect
137+
;; CHECK-NEXT: (local.get $0)
138+
;; CHECK-NEXT: (local.get $1)
139+
;; CHECK-NEXT: (local.get $2)
140+
;; CHECK-NEXT: )
141+
;; CHECK-NEXT: )
142+
;; CHECK-NEXT: )
143+
(func $ternary (param v128 v128 v128)
144+
(drop (i32x4.dot_i8x16_i7x16_add_s (local.get 0) (local.get 1) (local.get 2)))
145+
(drop (f16x8.relaxed_madd (local.get 0) (local.get 1) (local.get 2)))
146+
(drop (f16x8.relaxed_nmadd (local.get 0) (local.get 1) (local.get 2)))
147+
(drop (f32x4.relaxed_madd (local.get 0) (local.get 1) (local.get 2)))
148+
(drop (f32x4.relaxed_nmadd (local.get 0) (local.get 1) (local.get 2)))
149+
(drop (f64x2.relaxed_madd (local.get 0) (local.get 1) (local.get 2)))
150+
(drop (f64x2.relaxed_nmadd (local.get 0) (local.get 1) (local.get 2)))
151+
;; Normal SIMD instruction
152+
(drop (v128.bitselect (local.get 0) (local.get 1) (local.get 2)))
153+
)
154+
155+
;; CHECK: (func $refinalize (type $0) (param $0 v128)
156+
;; CHECK-NEXT: (drop
157+
;; CHECK-NEXT: (block $l
158+
;; CHECK-NEXT: (block
159+
;; CHECK-NEXT: (unreachable)
160+
;; CHECK-NEXT: )
161+
;; CHECK-NEXT: )
162+
;; CHECK-NEXT: )
163+
;; CHECK-NEXT: )
164+
(func $refinalize (param v128)
165+
(drop
166+
;; This block should become unreachable.
167+
(block $l (result v128)
168+
(i32x4.relaxed_trunc_f32x4_s (local.get 0))
169+
)
170+
)
171+
)
172+
173+
;; CHECK: (func $effects (type $0) (param $0 v128)
174+
;; CHECK-NEXT: (local $1 v128)
175+
;; CHECK-NEXT: (local $2 v128)
176+
;; CHECK-NEXT: (drop
177+
;; CHECK-NEXT: (block
178+
;; CHECK-NEXT: (local.set $1
179+
;; CHECK-NEXT: (call $effect)
180+
;; CHECK-NEXT: )
181+
;; CHECK-NEXT: (local.set $2
182+
;; CHECK-NEXT: (block (result v128)
183+
;; CHECK-NEXT: (drop
184+
;; CHECK-NEXT: (call $effect)
185+
;; CHECK-NEXT: )
186+
;; CHECK-NEXT: (local.get $0)
187+
;; CHECK-NEXT: )
188+
;; CHECK-NEXT: )
189+
;; CHECK-NEXT: (unreachable)
190+
;; CHECK-NEXT: )
191+
;; CHECK-NEXT: )
192+
;; CHECK-NEXT: )
193+
(func $effects (param v128)
194+
(drop
195+
(f16x8.relaxed_madd
196+
(call $effect)
197+
(local.get 0)
198+
(block (result v128)
199+
(drop
200+
(call $effect)
201+
)
202+
(local.get 0)
203+
)
204+
)
205+
)
206+
)
207+
)

0 commit comments

Comments
 (0)