Skip to content

Commit 1db1e2c

Browse files
authored
[NFC] Clarify why RemoveUnusedModuleElements does not need descriptor-specific tracking (#7880)
1 parent 33b9aab commit 1db1e2c

2 files changed

Lines changed: 90 additions & 2 deletions

File tree

src/passes/RemoveUnusedModuleElements.cpp

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -561,8 +561,26 @@ struct Analyzer {
561561

562562
auto* new_ = curr->cast<StructNew>();
563563

564-
// Use the descriptor right now, normally. (We only have special
565-
// optimization for struct.new operands, below.)
564+
// Use the descriptor right now, normally. We only have special
565+
// optimization for struct.new operands, below, because this is not needed
566+
// for descriptors: a descriptor must be a struct, and our "lazy reading"
567+
// optimization operates on it (if it could be a function, then we'd need to
568+
// do more here). In other words, descriptor reads always have a struct "in
569+
// the middle", that we can optimize, like here:
570+
//
571+
// (struct.new $A
572+
// (ref.func $c)
573+
// (struct.new $A.desc
574+
// (ref.func $d)
575+
// )
576+
// )
577+
//
578+
// The struct has a ref.func on it, and the descriptor as well. Say we never
579+
// read field 0 from $A, then we can avoid marking $c as reached; this is
580+
// the usual struct optimization we do, below. Now, say we never read the
581+
// descriptor, then we also never read field 0 from $A.desc, that is, the
582+
// usual struct optimization on the descriptor class is enough for us to
583+
// avoid marking $d as reached.
566584
if (new_->desc) {
567585
use(new_->desc);
568586
}

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,73 @@
127127
(elem $no-trap-get anyref (item (struct.new $struct (global.get $desc))))
128128
)
129129

130+
(module
131+
;; CHECK: (type $void (func))
132+
(type $void (func))
133+
134+
(rec
135+
;; CHECK: (rec
136+
;; CHECK-NEXT: (type $vtable (sub (descriptor $vtable.desc (struct (field (ref $void))))))
137+
(type $vtable (sub (descriptor $vtable.desc (struct (field (ref $void))))))
138+
;; CHECK: (type $vtable.desc (sub (describes $vtable (struct (field (ref $void))))))
139+
(type $vtable.desc (sub (describes $vtable (struct (field (ref $void))))))
140+
)
141+
142+
;; CHECK: (global $vtable (ref $vtable) (struct.new $vtable
143+
;; CHECK-NEXT: (ref.func $a)
144+
;; CHECK-NEXT: (struct.new $vtable.desc
145+
;; CHECK-NEXT: (ref.func $b)
146+
;; CHECK-NEXT: )
147+
;; CHECK-NEXT: ))
148+
(global $vtable (ref $vtable) (struct.new $vtable
149+
(ref.func $a)
150+
(struct.new $vtable.desc
151+
(ref.func $b)
152+
)
153+
))
154+
155+
;; CHECK: (export "export" (func $export))
156+
157+
;; CHECK: (func $export (type $void)
158+
;; CHECK-NEXT: (call_ref $void
159+
;; CHECK-NEXT: (struct.get $vtable 0
160+
;; CHECK-NEXT: (global.get $vtable)
161+
;; CHECK-NEXT: )
162+
;; CHECK-NEXT: )
163+
;; CHECK-NEXT: )
164+
(func $export (export "export")
165+
;; Read $a and call it. $b, in the descriptor, should not be callable.
166+
(call_ref $void
167+
(struct.get $vtable 0
168+
(global.get $vtable)
169+
)
170+
)
171+
)
172+
173+
;; CHECK: (func $a (type $void)
174+
;; CHECK-NEXT: (drop
175+
;; CHECK-NEXT: (i32.const 42)
176+
;; CHECK-NEXT: )
177+
;; CHECK-NEXT: )
178+
(func $a (type $void)
179+
;; This is reached from above.
180+
(drop (i32.const 42))
181+
)
182+
183+
;; CHECK: (func $b (type $void)
184+
;; CHECK-NEXT: (unreachable)
185+
;; CHECK-NEXT: )
186+
(func $b (type $void)
187+
;; This is not reached: We never read the descriptor, so we never read field 0
188+
;; in it, leaving this as dead (in closed world). That it itself seems to read
189+
;; the descriptor should not confuse us.
190+
(call_ref $void
191+
(struct.get $vtable.desc 0
192+
(ref.get_desc $vtable
193+
(global.get $vtable)
194+
)
195+
)
196+
)
197+
)
198+
)
199+

0 commit comments

Comments
 (0)