Skip to content

Commit e9fc5e2

Browse files
WIP
Gemini WIP
1 parent 253c092 commit e9fc5e2

5 files changed

Lines changed: 174 additions & 66 deletions

File tree

src/ir/effects.h

Lines changed: 39 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,10 @@ class EffectAnalyzer {
716716
}
717717

718718
void visitCall(Call* curr) {
719+
if (curr->isReturn) {
720+
parent.branchesOut = true;
721+
}
722+
719723
// call.without.effects has no effects.
720724
if (Intrinsics(parent.module).isCallWithoutEffects(curr)) {
721725
return;
@@ -753,32 +757,35 @@ class EffectAnalyzer {
753757
}
754758
}
755759
void visitCallIndirect(CallIndirect* curr) {
756-
auto* table = getModule()->getTable(curr->table);
757-
if (!Type::isSubType(Type(curr->heapType, Nullability::Nullable), table->type)) {
760+
auto* table = parent.module.getTable(curr->table);
761+
if (!Type::isSubType(Type(curr->heapType, Nullability::Nullable),
762+
table->type)) {
758763
parent.trap = true;
759764
return;
760765
}
766+
if (table->type.isNullable()) {
767+
parent.implicitTrap = true;
768+
}
769+
if (curr->isReturn) {
770+
parent.branchesOut = true;
771+
}
761772

762773
if (auto it = parent.module.typeEffects.find(curr->heapType);
763-
it != parent.module.typeEffects.end()) {
764-
parent.mergeIn(*it->second);
765-
766-
if (table->type.isNullable()) {
767-
parent.implicitTrap = true;
768-
}
774+
it != parent.module.typeEffects.end() && it->second) {
775+
populateEffectsFromGlobalEffects(*it->second, curr);
769776
return;
770777
}
771778

772779
parent.calls = true;
773-
if (curr->isReturn) {
774-
parent.branchesOut = true;
775-
if (parent.features.hasExceptionHandling()) {
780+
// If EH is enabled and we don't have global effects information,
781+
// assume that the call body may throw.
782+
if (parent.features.hasExceptionHandling()) {
783+
if (curr->isReturn) {
776784
parent.hasReturnCallThrow = true;
777785
}
778-
}
779-
if (parent.features.hasExceptionHandling() &&
780-
(parent.tryDepth == 0 && !curr->isReturn)) {
781-
parent.throws_ = true;
786+
if (parent.tryDepth == 0 && !curr->isReturn) {
787+
parent.throws_ = true;
788+
}
782789
}
783790
}
784791
void visitLocalGet(LocalGet* curr) {
@@ -1042,37 +1049,28 @@ class EffectAnalyzer {
10421049
return;
10431050
}
10441051

1045-
const EffectAnalyzer* targetEffects = nullptr;
1046-
if (auto it =
1047-
parent.module.typeEffects.find(curr->target->type.getHeapType());
1048-
it != parent.module.typeEffects.end()) {
1049-
targetEffects = it->second.get();
1050-
parent.mergeIn(*it->second);
1051-
}
1052-
10531052
if (curr->isReturn) {
10541053
parent.branchesOut = true;
1055-
// When EH is enabled, any call can throw.
1056-
if (parent.features.hasExceptionHandling() &&
1057-
(!targetEffects || targetEffects->throws())) {
1058-
parent.hasReturnCallThrow = true;
1059-
}
10601054
}
10611055

1062-
if (targetEffects) {
1056+
if (auto it =
1057+
parent.module.typeEffects.find(curr->target->type.getHeapType());
1058+
it != parent.module.typeEffects.end() && it->second) {
1059+
populateEffectsFromGlobalEffects(*it->second, curr);
10631060
return;
10641061
}
1062+
parent.calls = true;
10651063

1066-
if (curr->isReturn) {
1067-
parent.branchesOut = true;
1068-
if (parent.features.hasExceptionHandling()) {
1064+
// If EH is enabled and we don't have global effects information,
1065+
// assume that the call body may throw.
1066+
if (parent.features.hasExceptionHandling()) {
1067+
if (curr->isReturn) {
10691068
parent.hasReturnCallThrow = true;
10701069
}
1071-
}
1072-
parent.calls = true;
1073-
if (parent.features.hasExceptionHandling() &&
1074-
(parent.tryDepth == 0 && !curr->isReturn)) {
1075-
parent.throws_ = true;
1070+
1071+
if (parent.tryDepth == 0 && !curr->isReturn) {
1072+
parent.throws_ = true;
1073+
}
10761074
}
10771075
}
10781076
void visitRefTest(RefTest* curr) {}
@@ -1357,11 +1355,11 @@ class EffectAnalyzer {
13571355
}
13581356
}
13591357

1360-
private:
1361-
template <typename Call>
1362-
bool populateEffectsFromGlobalEffects(const EffectAnalyzer& effects, const Call* curr) {
1358+
private:
1359+
template<typename CallType>
1360+
void populateEffectsFromGlobalEffects(const EffectAnalyzer& effects,
1361+
const CallType* curr) {
13631362
if (curr->isReturn) {
1364-
parent.branchesOut = true;
13651363
if (effects.throws()) {
13661364
parent.hasReturnCallThrow = true;
13671365
}
@@ -1372,7 +1370,6 @@ class EffectAnalyzer {
13721370
filteredEffects.throws_ = false;
13731371
parent.mergeIn(filteredEffects);
13741372
} else {
1375-
// Just merge in all the effects.
13761373
parent.mergeIn(effects);
13771374
}
13781375
}

src/ir/type-updating.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,27 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) {
324324
for (auto& tag : wasm.tags) {
325325
tag->type = updater.getNew(tag->type);
326326
}
327+
328+
// Update indirect call effects per type.
329+
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>
330+
newTypeEffects;
331+
for (auto& [oldType, effects] : wasm.typeEffects) {
332+
if (!effects) {
333+
continue;
334+
}
335+
336+
auto newType = updater.getNew(oldType);
337+
std::shared_ptr<const EffectAnalyzer>& targetEffects =
338+
newTypeEffects[newType];
339+
if (!targetEffects) {
340+
targetEffects = effects;
341+
} else {
342+
auto merged = std::make_shared<EffectAnalyzer>(*targetEffects);
343+
merged->mergeIn(*effects);
344+
targetEffects = merged;
345+
}
346+
}
347+
wasm.typeEffects = std::move(newTypeEffects);
327348
}
328349

329350
void GlobalTypeRewriter::mapTypeNamesAndIndices(const TypeMap& oldToNewTypes) {

test/lit/passes/global-effects-closed-world-tnh.wast

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,31 @@
1919
;; CHECK-NEXT: (nop)
2020
;; CHECK-NEXT: )
2121
(func $calls-nop-via-nullable-ref (param $ref (ref null $nopType))
22+
;; We would trap if $ref is null, but otherwise this has no effects.
2223
(call_ref $nopType (i32.const 1) (local.get $ref))
2324
)
2425
)
26+
27+
(module
28+
;; CHECK: (type $nopType (func (param i32)))
29+
(type $nopType (func (param i32)))
30+
31+
;; (table 1 1 (ref $nopType))
32+
(table 1 1 funcref)
33+
34+
;; CHECK: (func $nop (type $nopType) (param $0 i32)
35+
;; CHECK-NEXT: (nop)
36+
;; CHECK-NEXT: )
37+
(func $nop (export "nop") (type $nopType)
38+
(nop)
39+
)
40+
41+
;; CHECK: (func $calls-nop-via-ref (type $1)
42+
;; CHECK-NEXT: (nop)
43+
;; CHECK-NEXT: )
44+
(func $calls-nop-via-ref
45+
;; We may trap due to index out of bounds or the function type not matching
46+
;; the table, but otherwise this has no possible effects.
47+
(call_indirect (type $nopType) (i32.const 1) (i32.const 0))
48+
)
49+
)

test/lit/passes/global-effects-closed-world.wast

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
;; CHECK-NEXT: )
2222
(func $calls-nop-via-ref (param $ref (ref $nopType))
2323
;; This can only possibly be a nop in closed-world.
24+
;; The equivalent for call_indirect is tested in
25+
;; test/lit/passes/global-effects-closed-world-tnh.wast.
2426
(call_ref $nopType (i32.const 1) (local.get $ref))
2527
)
2628

@@ -35,28 +37,6 @@
3537
)
3638
)
3739

38-
;; Same as the above but with call_indirect
39-
(module
40-
;; CHECK: (type $nopType (func (param i32)))
41-
(type $nopType (func (param i32)))
42-
43-
(table 1 1 funcref)
44-
45-
;; CHECK: (func $nop (type $nopType) (param $0 i32)
46-
;; CHECK-NEXT: (nop)
47-
;; CHECK-NEXT: )
48-
(func $nop (export "nop") (type $nopType)
49-
(nop)
50-
)
51-
52-
;; CHECK: (func $calls-nop-via-ref (type $1)
53-
;; CHECK-NEXT: (nop)
54-
;; CHECK-NEXT: )
55-
(func $calls-nop-via-ref
56-
(call_indirect (type $nopType) (i32.const 1) (i32.const 0))
57-
)
58-
)
59-
6040
(module
6141
;; CHECK: (type $maybe-has-effects (func (param i32)))
6242
(type $maybe-has-effects (func (param i32)))
@@ -215,7 +195,8 @@
215195
(func $calls-type-with-effects-but-not-addressable (param $ref (ref $only-has-effects-in-not-addressable-function))
216196
;; The type $has-effects-but-not-exported doesn't have an address because
217197
;; it's not exported and it's never the target of a ref.func.
218-
;; So the call_ref has no potential targets and thus no effects.
198+
;; We should be able to determine that $ref can only point to $nop.
199+
;; TODO: Only aggregate effects from functions that are addressed.
219200
(call_ref $only-has-effects-in-not-addressable-function (i32.const 1) (local.get $ref))
220201
)
221202
)
@@ -284,7 +265,7 @@
284265
;; CHECK-NEXT: )
285266
;; CHECK-NEXT: )
286267
(func $indirect-calls (param $ref (ref $t))
287-
;; $indirect-calls might end up calling an imported function,
268+
;; This might end up calling an imported function,
288269
;; so we don't know anything about effects here
289270
(call_ref $t (i32.const 1) (local.get $ref))
290271
)
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
2+
;; RUN: wasm-opt %s --all-features --closed-world --generate-global-effects --vacuum --type-merging --remove-unused-types -S -o - | filecheck %s --check-prefix VACUUM_FIRST
3+
;; RUN: wasm-opt %s --all-features --closed-world --generate-global-effects --type-merging --remove-unused-types --vacuum -S -o - | filecheck %s --check-prefix MERGE_FIRST
4+
5+
;; Test that indirect call effects are preserved when types are rewritten
6+
;; globally. When we rewrite $effectful and $not-effectful into the same type,
7+
;; the resulting type has the same effects as the union of the two. This is
8+
;; pessemistic since indirect calls that targeted $not-effectful now look like
9+
;; they may target $effectful as well which is not true in practice. This is the
10+
;; best we can do without preserving extra information before rewriting.
11+
12+
(module
13+
(rec
14+
;; VACUUM_FIRST: (type $effectful (func (result i32)))
15+
;; MERGE_FIRST: (type $effectful (func (result i32)))
16+
(type $effectful (func (result i32)))
17+
(type $not-effectful (func (result i32)))
18+
)
19+
20+
;; VACUUM_FIRST: (func $unreachable (type $effectful) (result i32)
21+
;; VACUUM_FIRST-NEXT: (unreachable)
22+
;; VACUUM_FIRST-NEXT: )
23+
;; MERGE_FIRST: (func $unreachable (type $effectful) (result i32)
24+
;; MERGE_FIRST-NEXT: (unreachable)
25+
;; MERGE_FIRST-NEXT: )
26+
(func $unreachable (type $effectful)
27+
(unreachable)
28+
)
29+
30+
;; VACUUM_FIRST: (func $const (type $effectful) (result i32)
31+
;; VACUUM_FIRST-NEXT: (i32.const 0)
32+
;; VACUUM_FIRST-NEXT: )
33+
;; MERGE_FIRST: (func $const (type $effectful) (result i32)
34+
;; MERGE_FIRST-NEXT: (i32.const 0)
35+
;; MERGE_FIRST-NEXT: )
36+
(func $const (type $not-effectful)
37+
(i32.const 0)
38+
)
39+
40+
;; VACUUM_FIRST: (func $f (type $1)
41+
;; VACUUM_FIRST-NEXT: (nop)
42+
;; VACUUM_FIRST-NEXT: )
43+
;; MERGE_FIRST: (func $f (type $1)
44+
;; MERGE_FIRST-NEXT: (nop)
45+
;; MERGE_FIRST-NEXT: )
46+
(func $f
47+
;; Reference the functions in a ref.func so that it's possible that they're
48+
;; the target of indirect calls.
49+
(drop (ref.func $unreachable))
50+
(drop (ref.func $const))
51+
)
52+
53+
;; VACUUM_FIRST: (func $test (type $0) (param $effectful-ref (ref $effectful)) (param $not-effectful-ref (ref $effectful))
54+
;; VACUUM_FIRST-NEXT: (drop
55+
;; VACUUM_FIRST-NEXT: (call_ref $effectful
56+
;; VACUUM_FIRST-NEXT: (local.get $effectful-ref)
57+
;; VACUUM_FIRST-NEXT: )
58+
;; VACUUM_FIRST-NEXT: )
59+
;; VACUUM_FIRST-NEXT: )
60+
;; MERGE_FIRST: (func $test (type $0) (param $effectful-ref (ref $effectful)) (param $not-effectful-ref (ref $effectful))
61+
;; MERGE_FIRST-NEXT: (drop
62+
;; MERGE_FIRST-NEXT: (call_ref $effectful
63+
;; MERGE_FIRST-NEXT: (local.get $not-effectful-ref)
64+
;; MERGE_FIRST-NEXT: )
65+
;; MERGE_FIRST-NEXT: )
66+
;; MERGE_FIRST-NEXT: (drop
67+
;; MERGE_FIRST-NEXT: (call_ref $effectful
68+
;; MERGE_FIRST-NEXT: (local.get $effectful-ref)
69+
;; MERGE_FIRST-NEXT: )
70+
;; MERGE_FIRST-NEXT: )
71+
;; MERGE_FIRST-NEXT: )
72+
(func $test (param $effectful-ref (ref $effectful)) (param $not-effectful-ref (ref $not-effectful))
73+
;; If we run global effects followed by vacuum, we can tell that this call
74+
;; can't possibly have any effects and remove it. But if we run global
75+
;; effects, then merge types, we can no longer distinguish this from
76+
;; $effectful, so we have to conservatively not optimize this out.
77+
(drop
78+
(call_ref $not-effectful (local.get $not-effectful-ref))
79+
)
80+
(drop
81+
(call_ref $effectful (local.get $effectful-ref))
82+
)
83+
)
84+
)

0 commit comments

Comments
 (0)