Skip to content

Commit 91f54df

Browse files
authored
[Wasm GC] Fix RemoveUnusedModuleEffects struct field reads with call.without.effects (#5477)
If we see (struct.new $A (call $call.without.effects ;; intrinsic (ref.func $getter) ) ) then we can ignore side effects in that call, as the intrinsic tells us to. However, that function is still called (it will be called normally, after intrinsics are lowered away), and we should not remove it. That is, such an intrinsic call can be removed, but it cannot be left as it is and also ignored as if it does not exist.
1 parent d1b7597 commit 91f54df

File tree

2 files changed

+104
-5
lines changed

2 files changed

+104
-5
lines changed

src/passes/RemoveUnusedModuleElements.cpp

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
#include <memory>
3939

4040
#include "ir/element-utils.h"
41+
#include "ir/find_all.h"
4142
#include "ir/intrinsics.h"
4243
#include "ir/module-utils.h"
4344
#include "ir/subtypes.h"
@@ -492,11 +493,36 @@ struct Analyzer {
492493
for (Index i = 0; i < new_->operands.size(); i++) {
493494
auto* operand = new_->operands[i];
494495
auto structField = StructField{type, i};
495-
if (readStructFields.count(structField) ||
496-
EffectAnalyzer(options, *module, operand).hasSideEffects()) {
497-
// This data can be read, so just walk it. Or, this has side effects,
498-
// which is tricky to reason about - the side effects must happen even
499-
// if we never read the struct field - so give up and consider it used.
496+
497+
// If this struct field has already been read, then we should use the
498+
// contents there now.
499+
auto useOperandNow = readStructFields.count(structField);
500+
501+
// Side effects are tricky to reason about - the side effects must happen
502+
// even if we never read the struct field - so give up and consider it
503+
// used.
504+
if (!useOperandNow) {
505+
useOperandNow =
506+
EffectAnalyzer(options, *module, operand).hasSideEffects();
507+
}
508+
509+
// We must handle the call.without.effects intrinsic here in a special
510+
// manner. That intrinsic is reported as having no side effects in
511+
// EffectAnalyzer, but even though for optimization purposes we can ignore
512+
// effects, the called code *is* actually reached, and it might have side
513+
// effects. In other words, the point of the intrinsic is to temporarily
514+
// ignore those effects during one phase of optimization. Or, put another
515+
// way, the intrinsic lets us ignore the effects of computing some value,
516+
// but we do still need to compute that value if it is received and used
517+
// (if it is not received and used, other passes will remove it).
518+
if (!useOperandNow) {
519+
// To detect this, look for any call. A non-intrinsic call would have
520+
// already been detected when we looked for side effects, so this will
521+
// only notice intrinsic calls.
522+
useOperandNow = !FindAll<Call>(operand).list.empty();
523+
}
524+
525+
if (useOperandNow) {
500526
use(operand);
501527
} else {
502528
// This data does not need to be read now, but might be read later. Note

test/lit/passes/remove-unused-module-elements-refs.wast

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,3 +1841,76 @@
18411841
(func $f (type $void)
18421842
)
18431843
)
1844+
1845+
;; The call.without.effects intrinsic reports itself as having no side effects.
1846+
;; We do still need to consider the target as being called, however, even if it
1847+
;; is in a struct field.
1848+
(module
1849+
;; CHECK: (type $funcref_=>_i32 (func (param funcref) (result i32)))
1850+
1851+
;; CHECK: (type $none_=>_none (func))
1852+
1853+
;; CHECK: (type $A (struct (field i32)))
1854+
;; OPEN_WORLD: (type $funcref_=>_i32 (func (param funcref) (result i32)))
1855+
1856+
;; OPEN_WORLD: (type $none_=>_none (func))
1857+
1858+
;; OPEN_WORLD: (type $A (struct (field i32)))
1859+
(type $A (struct (field i32)))
1860+
1861+
;; CHECK: (type $none_=>_i32 (func (result i32)))
1862+
1863+
;; CHECK: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32)))
1864+
;; OPEN_WORLD: (type $none_=>_i32 (func (result i32)))
1865+
1866+
;; OPEN_WORLD: (import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32)))
1867+
(import "binaryen-intrinsics" "call.without.effects" (func $call.without.effects (param funcref) (result i32)))
1868+
1869+
;; CHECK: (elem declare func $getter)
1870+
1871+
;; CHECK: (export "main" (func $main))
1872+
1873+
;; CHECK: (func $main (type $none_=>_none)
1874+
;; CHECK-NEXT: (drop
1875+
;; CHECK-NEXT: (struct.new $A
1876+
;; CHECK-NEXT: (call $call.without.effects
1877+
;; CHECK-NEXT: (ref.func $getter)
1878+
;; CHECK-NEXT: )
1879+
;; CHECK-NEXT: )
1880+
;; CHECK-NEXT: )
1881+
;; CHECK-NEXT: )
1882+
;; OPEN_WORLD: (elem declare func $getter)
1883+
1884+
;; OPEN_WORLD: (export "main" (func $main))
1885+
1886+
;; OPEN_WORLD: (func $main (type $none_=>_none)
1887+
;; OPEN_WORLD-NEXT: (drop
1888+
;; OPEN_WORLD-NEXT: (struct.new $A
1889+
;; OPEN_WORLD-NEXT: (call $call.without.effects
1890+
;; OPEN_WORLD-NEXT: (ref.func $getter)
1891+
;; OPEN_WORLD-NEXT: )
1892+
;; OPEN_WORLD-NEXT: )
1893+
;; OPEN_WORLD-NEXT: )
1894+
;; OPEN_WORLD-NEXT: )
1895+
(func $main (export "main")
1896+
(drop
1897+
(struct.new $A
1898+
(call $call.without.effects
1899+
(ref.func $getter)
1900+
)
1901+
)
1902+
)
1903+
)
1904+
1905+
;; CHECK: (func $getter (type $none_=>_i32) (result i32)
1906+
;; CHECK-NEXT: (i32.const 42)
1907+
;; CHECK-NEXT: )
1908+
;; OPEN_WORLD: (func $getter (type $none_=>_i32) (result i32)
1909+
;; OPEN_WORLD-NEXT: (i32.const 42)
1910+
;; OPEN_WORLD-NEXT: )
1911+
(func $getter (result i32)
1912+
;; This function body should not be turned into an unreachable. It is
1913+
;; reached from $main, even though the call is marked as not having effects.
1914+
(i32.const 42)
1915+
)
1916+
)

0 commit comments

Comments
 (0)