Skip to content

Commit 3b518c1

Browse files
authored
[Custom Descriptors] Fix CFP on an exact ref.get_desc (#7886)
`ref.get_desc` inherits its input ref's exactness: When called on an exact ref, it returns the exact descriptor type. CFP was not aware of that, and thought any subtype can appear as well. That was not only a missed optimization, but also a validation bug: `ref.get_desc` returned an exact type, but if we consider values from subtypes, we may emit a select over two values that is no longer exact. This also helps optimize `struct.get`, as the logic is shared, so now `struct.get` of an exact type (and immutable field) will ignore subtypes and look only at data from `struct.new`.
1 parent 4790636 commit 3b518c1

File tree

4 files changed

+303
-9
lines changed

4 files changed

+303
-9
lines changed

src/passes/ConstantFieldPropagation.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,15 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
114114
return heapType;
115115
}
116116

117-
PossibleConstantValues getInfo(HeapType type, Index index) {
118-
if (auto it = propagatedInfos.find(type); it != propagatedInfos.end()) {
117+
PossibleConstantValues getInfo(HeapType type, Index index, Exactness exact) {
118+
// If the reference is exact and the field immutable, then we are reading
119+
// exactly what was written to struct.news and nothing else.
120+
auto mutable_ = index == StructUtils::DescriptorIndex
121+
? Immutable
122+
: GCTypeUtils::getField(type, index)->mutable_;
123+
auto& infos =
124+
(exact == Inexact || mutable_ == Mutable) ? propagatedInfos : rawNewInfos;
125+
if (auto it = infos.find(type); it != infos.end()) {
119126
// There is information on this type, fetch it.
120127
return it->second[index];
121128
}
@@ -177,7 +184,8 @@ struct FunctionOptimizer : public WalkerPass<PostWalker<FunctionOptimizer>> {
177184
// Find the info for this field, and see if we can optimize. First, see if
178185
// there is any information for this heap type at all. If there isn't, it is
179186
// as if nothing was ever noted for that field.
180-
PossibleConstantValues info = getInfo(heapType, index);
187+
PossibleConstantValues info =
188+
getInfo(heapType, index, ref->type.getExactness());
181189
if (!info.hasNoted()) {
182190
// This field is never written at all. That means that we do not even
183191
// construct any data of this type, and so it is a logic error to reach
@@ -499,12 +507,9 @@ struct ConstantFieldPropagation : public Pass {
499507
// Prepare data we will need later.
500508
SubTypes subTypes(*module);
501509

502-
PCVStructValuesMap rawNewInfos;
503-
if (refTest) {
504-
// The refTest optimizations require the raw new infos (see above), but we
505-
// can skip copying here if we'll never read this.
506-
rawNewInfos = combinedNewInfos;
507-
}
510+
// Copy the unpropagated data before we propagate. We use this in precise
511+
// lookups.
512+
auto rawNewInfos = combinedNewInfos;
508513

509514
// Handle subtyping. |combinedInfo| so far contains data that represents
510515
// each struct.new and struct.set's operation on the struct type used in

test/lit/passes/cfp-reftest-desc.wast

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,83 @@
106106
)
107107
)
108108

109+
(module
110+
(rec
111+
;; CHECK: (rec
112+
;; CHECK-NEXT: (type $super (sub (descriptor $super.desc (struct))))
113+
(type $super (sub (descriptor $super.desc (struct))))
114+
;; CHECK: (type $super.desc (sub (describes $super (struct))))
115+
(type $super.desc (sub (describes $super (struct))))
116+
117+
;; CHECK: (type $func (func (param i32) (result i32)))
118+
(type $func (func (param i32) (result i32)))
119+
120+
;; CHECK: (type $sub (sub $super (descriptor $sub.desc (struct))))
121+
(type $sub (sub $super (descriptor $sub.desc (struct))))
122+
;; CHECK: (type $sub.desc (sub $super.desc (describes $sub (struct))))
123+
(type $sub.desc (sub $super.desc (describes $sub (struct))))
124+
)
125+
126+
;; CHECK: (type $5 (func (result (ref (exact $super.desc)))))
127+
128+
;; CHECK: (global $A (ref (exact $super.desc)) (struct.new_default $super.desc))
129+
(global $A (ref (exact $super.desc)) (struct.new $super.desc))
130+
131+
;; CHECK: (global $B (ref (exact $sub.desc)) (struct.new_default $sub.desc))
132+
(global $B (ref (exact $sub.desc)) (struct.new $sub.desc))
133+
134+
;; CHECK: (func $test (type $5) (result (ref (exact $super.desc)))
135+
;; CHECK-NEXT: (drop
136+
;; CHECK-NEXT: (struct.new_default $super
137+
;; CHECK-NEXT: (global.get $A)
138+
;; CHECK-NEXT: )
139+
;; CHECK-NEXT: )
140+
;; CHECK-NEXT: (drop
141+
;; CHECK-NEXT: (struct.new_default $sub
142+
;; CHECK-NEXT: (global.get $B)
143+
;; CHECK-NEXT: )
144+
;; CHECK-NEXT: )
145+
;; CHECK-NEXT: (block (result (ref (exact $super.desc)))
146+
;; CHECK-NEXT: (drop
147+
;; CHECK-NEXT: (ref.as_non_null
148+
;; CHECK-NEXT: (block (result nullref)
149+
;; CHECK-NEXT: (ref.null none)
150+
;; CHECK-NEXT: )
151+
;; CHECK-NEXT: )
152+
;; CHECK-NEXT: )
153+
;; CHECK-NEXT: (global.get $A)
154+
;; CHECK-NEXT: )
155+
;; CHECK-NEXT: )
156+
(func $test (result (ref (exact $super.desc)))
157+
(drop
158+
(struct.new_default $super
159+
(global.get $A)
160+
)
161+
)
162+
(drop
163+
(struct.new_default $sub
164+
(global.get $B)
165+
)
166+
)
167+
;; We read from an exact $super here, so the type of the ref.get_desc is
168+
;; exact as well. If we ignore that in the optimization, we might think that
169+
;; the two struct.news before us are two possible values, one from $super and
170+
;; one from $sub, and if we emitted a ref.test between those values, we'd get
171+
;; a non-exact value that does not validate.
172+
;;
173+
;; Instead, we should look only at $super itself, and optimize to $A.
174+
(ref.get_desc $super
175+
(block (result (ref null (exact $super)))
176+
(ref.null $super)
177+
)
178+
)
179+
)
180+
181+
;; CHECK: (func $func (type $func) (param $0 i32) (result i32)
182+
;; CHECK-NEXT: (i32.const 42)
183+
;; CHECK-NEXT: )
184+
(func $func (type $func) (param $0 i32) (result i32)
185+
(i32.const 42)
186+
)
187+
)
188+

test/lit/passes/cfp-reftest.wast

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1456,3 +1456,58 @@
14561456
)
14571457
)
14581458
)
1459+
1460+
(module
1461+
;; CHECK: (type $struct (sub (struct (field i32))))
1462+
(type $struct (sub (struct i32)))
1463+
;; CHECK: (type $1 (func))
1464+
1465+
;; CHECK: (type $substruct (sub $struct (struct (field i32) (field f64))))
1466+
(type $substruct (sub $struct (struct i32 f64)))
1467+
1468+
;; CHECK: (type $3 (func (param (ref null (exact $struct))) (result i32)))
1469+
1470+
;; CHECK: (func $create (type $1)
1471+
;; CHECK-NEXT: (drop
1472+
;; CHECK-NEXT: (struct.new $struct
1473+
;; CHECK-NEXT: (i32.const 10)
1474+
;; CHECK-NEXT: )
1475+
;; CHECK-NEXT: )
1476+
;; CHECK-NEXT: (drop
1477+
;; CHECK-NEXT: (struct.new $substruct
1478+
;; CHECK-NEXT: (i32.const 20)
1479+
;; CHECK-NEXT: (f64.const 3.14159)
1480+
;; CHECK-NEXT: )
1481+
;; CHECK-NEXT: )
1482+
;; CHECK-NEXT: )
1483+
(func $create
1484+
;; Used below.
1485+
(drop
1486+
(struct.new $struct
1487+
(i32.const 10)
1488+
)
1489+
)
1490+
(drop
1491+
(struct.new $substruct
1492+
(i32.const 20)
1493+
(f64.const 3.14159)
1494+
)
1495+
)
1496+
)
1497+
;; CHECK: (func $get (type $3) (param $struct (ref null (exact $struct))) (result i32)
1498+
;; CHECK-NEXT: (drop
1499+
;; CHECK-NEXT: (ref.as_non_null
1500+
;; CHECK-NEXT: (local.get $struct)
1501+
;; CHECK-NEXT: )
1502+
;; CHECK-NEXT: )
1503+
;; CHECK-NEXT: (i32.const 10)
1504+
;; CHECK-NEXT: )
1505+
(func $get (param $struct (ref null (exact $struct))) (result i32)
1506+
;; The type here is exact, so we do not even need to do a select: only the
1507+
;; super's value is possible, 10.
1508+
(struct.get $struct 0
1509+
(local.get $struct)
1510+
)
1511+
)
1512+
)
1513+

test/lit/passes/cfp.wast

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2389,6 +2389,116 @@
23892389
)
23902390
)
23912391

2392+
(module
2393+
(rec
2394+
;; CHECK: (rec
2395+
;; CHECK-NEXT: (type $A (sub (struct (field i32))))
2396+
(type $A (sub (struct (field i32))))
2397+
;; CHECK: (type $B (sub $A (struct (field i32))))
2398+
(type $B (sub $A (struct (field i32))))
2399+
)
2400+
2401+
;; CHECK: (type $2 (func (param i32)))
2402+
2403+
;; CHECK: (func $test (type $2) (param $0 i32)
2404+
;; CHECK-NEXT: (local $A (ref $A))
2405+
;; CHECK-NEXT: (local $B (ref $B))
2406+
;; CHECK-NEXT: (local $A-exact (ref (exact $A)))
2407+
;; CHECK-NEXT: (local $B-exact (ref (exact $B)))
2408+
;; CHECK-NEXT: (local.set $A
2409+
;; CHECK-NEXT: (local.tee $A-exact
2410+
;; CHECK-NEXT: (struct.new $A
2411+
;; CHECK-NEXT: (i32.const 10)
2412+
;; CHECK-NEXT: )
2413+
;; CHECK-NEXT: )
2414+
;; CHECK-NEXT: )
2415+
;; CHECK-NEXT: (local.set $B
2416+
;; CHECK-NEXT: (local.tee $B-exact
2417+
;; CHECK-NEXT: (struct.new $B
2418+
;; CHECK-NEXT: (i32.const 20)
2419+
;; CHECK-NEXT: )
2420+
;; CHECK-NEXT: )
2421+
;; CHECK-NEXT: )
2422+
;; CHECK-NEXT: (drop
2423+
;; CHECK-NEXT: (struct.get $A 0
2424+
;; CHECK-NEXT: (local.get $A)
2425+
;; CHECK-NEXT: )
2426+
;; CHECK-NEXT: )
2427+
;; CHECK-NEXT: (drop
2428+
;; CHECK-NEXT: (block (result i32)
2429+
;; CHECK-NEXT: (drop
2430+
;; CHECK-NEXT: (ref.as_non_null
2431+
;; CHECK-NEXT: (local.get $B)
2432+
;; CHECK-NEXT: )
2433+
;; CHECK-NEXT: )
2434+
;; CHECK-NEXT: (i32.const 20)
2435+
;; CHECK-NEXT: )
2436+
;; CHECK-NEXT: )
2437+
;; CHECK-NEXT: (drop
2438+
;; CHECK-NEXT: (block (result i32)
2439+
;; CHECK-NEXT: (drop
2440+
;; CHECK-NEXT: (ref.as_non_null
2441+
;; CHECK-NEXT: (local.get $A-exact)
2442+
;; CHECK-NEXT: )
2443+
;; CHECK-NEXT: )
2444+
;; CHECK-NEXT: (i32.const 10)
2445+
;; CHECK-NEXT: )
2446+
;; CHECK-NEXT: )
2447+
;; CHECK-NEXT: (drop
2448+
;; CHECK-NEXT: (block (result i32)
2449+
;; CHECK-NEXT: (drop
2450+
;; CHECK-NEXT: (ref.as_non_null
2451+
;; CHECK-NEXT: (local.get $B-exact)
2452+
;; CHECK-NEXT: )
2453+
;; CHECK-NEXT: )
2454+
;; CHECK-NEXT: (i32.const 20)
2455+
;; CHECK-NEXT: )
2456+
;; CHECK-NEXT: )
2457+
;; CHECK-NEXT: )
2458+
(func $test (param $0 i32)
2459+
(local $A (ref $A))
2460+
(local $B (ref $B))
2461+
(local $A-exact (ref (exact $A)))
2462+
(local $B-exact (ref (exact $B)))
2463+
(local.set $A
2464+
(local.tee $A-exact
2465+
(struct.new $A
2466+
(i32.const 10)
2467+
)
2468+
)
2469+
)
2470+
(local.set $B
2471+
(local.tee $B-exact
2472+
(struct.new $B
2473+
(i32.const 20)
2474+
)
2475+
)
2476+
)
2477+
;; We can optimize an inexact $B, but not $A.
2478+
(drop
2479+
(struct.get $A 0
2480+
(local.get $A)
2481+
)
2482+
)
2483+
(drop
2484+
(struct.get $B 0
2485+
(local.get $B)
2486+
)
2487+
)
2488+
;; We can optimize both exact references.
2489+
(drop
2490+
(struct.get $A 0
2491+
(local.get $A-exact)
2492+
)
2493+
)
2494+
(drop
2495+
(struct.get $B 0
2496+
(local.get $B-exact)
2497+
)
2498+
)
2499+
)
2500+
)
2501+
23922502
;; A type with two subtypes. A copy on the parent can affect either child.
23932503
(module
23942504
(rec
@@ -2947,3 +3057,47 @@
29473057
)
29483058
)
29493059
)
3060+
3061+
(module
3062+
;; CHECK: (type $A (struct (field (mut i32))))
3063+
(type $A (struct (field (mut i32))))
3064+
3065+
;; CHECK: (type $1 (func))
3066+
3067+
;; CHECK: (func $test (type $1)
3068+
;; CHECK-NEXT: (local $A.exact (ref (exact $A)))
3069+
;; CHECK-NEXT: (local.set $A.exact
3070+
;; CHECK-NEXT: (struct.new $A
3071+
;; CHECK-NEXT: (i32.const 10)
3072+
;; CHECK-NEXT: )
3073+
;; CHECK-NEXT: )
3074+
;; CHECK-NEXT: (struct.set $A 0
3075+
;; CHECK-NEXT: (local.get $A.exact)
3076+
;; CHECK-NEXT: (i32.const 20)
3077+
;; CHECK-NEXT: )
3078+
;; CHECK-NEXT: (drop
3079+
;; CHECK-NEXT: (struct.get $A 0
3080+
;; CHECK-NEXT: (local.get $A.exact)
3081+
;; CHECK-NEXT: )
3082+
;; CHECK-NEXT: )
3083+
;; CHECK-NEXT: )
3084+
(func $test
3085+
(local $A.exact (ref (exact $A)))
3086+
(local.set $A.exact
3087+
(struct.new $A
3088+
(i32.const 10)
3089+
)
3090+
)
3091+
;; This set prevents us from optimizing. We should not be confused by the
3092+
;; exactness of the ref.
3093+
(struct.set $A 0
3094+
(local.get $A.exact)
3095+
(i32.const 20)
3096+
)
3097+
(drop
3098+
(struct.get $A 0
3099+
(local.get $A.exact)
3100+
)
3101+
)
3102+
)
3103+
)

0 commit comments

Comments
 (0)