Skip to content

Commit b4bdcc3

Browse files
authored
[Wasm GC] Fix GUFA on trampled params in TrapsNeverHappens mode (#7368)
GUFA in TNH mode will infer that this will trap: (func $foo (param $x) (ref.cast $T (local.get $x)) ) (func $bar (call $foo (X)) ) If X's type will cause a trap when cast to T, then we infer that the call will trap, and optimize. However, we were missing a check for the param being trampled, (func $foo (param $x) (local.set $x (Y)) ;; !!!!!!!!!! (ref.cast $T (local.get $x)) ) Then if Y can be cast, we should not actually trap. Fix this by just tracking which params are written to. We only look in the first basic block anyhow, and traverse it in order, so that is enough. Fixes #7366
1 parent a1758cf commit b4bdcc3

File tree

2 files changed

+93
-2
lines changed

2 files changed

+93
-2
lines changed

src/ir/possible-contents.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1545,6 +1545,22 @@ void TNHOracle::scan(Function* func,
15451545
self->inEntryBlock = false;
15461546
}
15471547

1548+
// We note params that are written to, as local changes prevent us from
1549+
// inferences:
1550+
//
1551+
// (func $foo (param $x)
1552+
// (local.set $x ..)
1553+
// (ref.cast (local.get $x)) ;; this is no longer casting the actual
1554+
// ;; parameter
1555+
//
1556+
std::unordered_set<Index> writtenParams;
1557+
1558+
void visitLocalSet(LocalSet* curr) {
1559+
if (getFunction()->isParam(curr->index)) {
1560+
writtenParams.insert(curr->index);
1561+
}
1562+
}
1563+
15481564
void visitCall(Call* curr) { info.calls.push_back(curr); }
15491565

15501566
void visitCallRef(CallRef* curr) {
@@ -1570,13 +1586,15 @@ void TNHOracle::scan(Function* func,
15701586

15711587
auto* fallthrough = Properties::getFallthrough(expr, options, wasm);
15721588
if (auto* get = fallthrough->dynCast<LocalGet>()) {
1573-
// To optimize, this needs to be a param, and of a useful type.
1589+
// To optimize, this needs to be an unmodified param, and of a useful
1590+
// type.
15741591
//
15751592
// Note that if we see more than one cast we keep the first one. This is
15761593
// not important in optimized code, as the most refined cast would be
15771594
// the only one to exist there, so it's ok to keep things simple here.
15781595
if (getFunction()->isParam(get->index) && type != get->type &&
1579-
info.castParams.count(get->index) == 0) {
1596+
info.castParams.count(get->index) == 0 &&
1597+
!writtenParams.count(get->index)) {
15801598
info.castParams[get->index] = type;
15811599
}
15821600
}

test/lit/passes/gufa-tnh.wast

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2063,3 +2063,76 @@
20632063
)
20642064
)
20652065
)
2066+
2067+
;; Test writes to locals that interfere with inferences about casts.
2068+
(module
2069+
(rec
2070+
;; CHECK: (rec
2071+
;; CHECK-NEXT: (type $top (sub (struct)))
2072+
(type $top (sub (struct)))
2073+
;; CHECK: (type $bot (sub $top (struct)))
2074+
(type $bot (sub $top (struct)))
2075+
)
2076+
2077+
;; CHECK: (type $2 (func (param (ref $top))))
2078+
2079+
;; CHECK: (type $3 (func (param (ref extern))))
2080+
2081+
;; CHECK: (export "$invokeMain" (func $invokeMain))
2082+
2083+
;; CHECK: (func $main-set (type $2) (param $0 (ref $top))
2084+
;; CHECK-NEXT: (local.set $0
2085+
;; CHECK-NEXT: (struct.new_default $bot)
2086+
;; CHECK-NEXT: )
2087+
;; CHECK-NEXT: (drop
2088+
;; CHECK-NEXT: (ref.cast (ref $bot)
2089+
;; CHECK-NEXT: (local.get $0)
2090+
;; CHECK-NEXT: )
2091+
;; CHECK-NEXT: )
2092+
;; CHECK-NEXT: )
2093+
(func $main-set (param (ref $top))
2094+
;; We receive a top as input, but write a bot to it, trampling the ignored
2095+
;; parameter. Thanks to the trampling, the cast below will succeed, and so we
2096+
;; should not make anything unreachable in the caller - nothing traps here.
2097+
(local.set 0
2098+
(struct.new $bot)
2099+
)
2100+
(drop
2101+
(ref.cast (ref $bot)
2102+
(local.get 0)
2103+
)
2104+
)
2105+
)
2106+
2107+
;; CHECK: (func $main-noset (type $2) (param $0 (ref $top))
2108+
;; CHECK-NEXT: (drop
2109+
;; CHECK-NEXT: (unreachable)
2110+
;; CHECK-NEXT: )
2111+
;; CHECK-NEXT: )
2112+
(func $main-noset (param (ref $top))
2113+
;; As above, but without the local.set. Here we will trap, so the caller can
2114+
;; optimize to unreachable.
2115+
(drop
2116+
(ref.cast (ref $bot)
2117+
(local.get 0)
2118+
)
2119+
)
2120+
)
2121+
2122+
;; CHECK: (func $invokeMain (type $3) (param $0 (ref extern))
2123+
;; CHECK-NEXT: (call $main-set
2124+
;; CHECK-NEXT: (struct.new_default $top)
2125+
;; CHECK-NEXT: )
2126+
;; CHECK-NEXT: (call $main-noset
2127+
;; CHECK-NEXT: (unreachable)
2128+
;; CHECK-NEXT: )
2129+
;; CHECK-NEXT: )
2130+
(func $invokeMain (export "$invokeMain") (param (ref extern))
2131+
(call $main-set
2132+
(struct.new $top)
2133+
)
2134+
(call $main-noset
2135+
(struct.new $top)
2136+
)
2137+
)
2138+
)

0 commit comments

Comments
 (0)