Skip to content

Commit 2156d99

Browse files
WIP
Gemini WIP Try changing call effects
1 parent 253c092 commit 2156d99

5 files changed

Lines changed: 185 additions & 89 deletions

File tree

src/ir/effects.h

Lines changed: 50 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -716,69 +716,64 @@ 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;
722726
}
723727

724-
// Get the target's effects, if they exist. Note that we must handle the
725-
// case of the function not yet existing (we may be executed in the middle
726-
// of a pass, which may have built up calls but not the targets of those
727-
// calls; in such a case, we do not find the targets and therefore assume
728-
// we know nothing about the effects, which is safe).
729-
const EffectAnalyzer* targetEffects = nullptr;
730-
if (auto* target = parent.module.getFunctionOrNull(curr->target)) {
731-
targetEffects = target->effects.get();
732-
}
733-
if (targetEffects) {
734-
populateEffectsFromGlobalEffects(*targetEffects, curr);
728+
if (auto* target = parent.module.getFunctionOrNull(curr->target);
729+
target && target->effects) {
730+
populateEffectsFromGlobalEffects(*target->effects, curr);
735731
return;
736732
}
737733

738-
if (curr->isReturn) {
739-
parent.branchesOut = true;
740-
// When EH is enabled, any call can throw.
741-
if (parent.features.hasExceptionHandling()) {
734+
parent.calls = true;
735+
// If EH is enabled and we don't have global effects information,
736+
// assume that the call body may throw.
737+
if (parent.features.hasExceptionHandling()) {
738+
if (curr->isReturn) {
742739
parent.hasReturnCallThrow = true;
743740
}
744-
}
745741

746-
parent.calls = true;
747-
// When EH is enabled, any call can throw. Skip this for return calls
748-
// because the throw is already more precisely captured by the combination
749-
// of `hasReturnCallThrow` and `branchesOut`.
750-
if (parent.features.hasExceptionHandling() && parent.tryDepth == 0 &&
751-
!curr->isReturn) {
752-
parent.throws_ = true;
742+
if (parent.tryDepth == 0 && !curr->isReturn) {
743+
parent.throws_ = true;
744+
}
753745
}
754746
}
755747
void visitCallIndirect(CallIndirect* curr) {
756-
auto* table = getModule()->getTable(curr->table);
757-
if (!Type::isSubType(Type(curr->heapType, Nullability::Nullable), table->type)) {
748+
auto* table = parent.module.getTable(curr->table);
749+
if (!Type::isSubType(Type(curr->heapType, Nullability::Nullable),
750+
table->type)) {
758751
parent.trap = true;
759752
return;
760753
}
754+
if (table->type.isNullable()) {
755+
parent.implicitTrap = true;
756+
}
757+
if (curr->isReturn) {
758+
parent.branchesOut = true;
759+
}
761760

762761
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-
}
762+
it != parent.module.typeEffects.end() && it->second) {
763+
populateEffectsFromGlobalEffects(*it->second, curr);
769764
return;
770765
}
771766

772767
parent.calls = true;
773-
if (curr->isReturn) {
774-
parent.branchesOut = true;
775-
if (parent.features.hasExceptionHandling()) {
768+
// If EH is enabled and we don't have global effects information,
769+
// assume that the call body may throw.
770+
if (parent.features.hasExceptionHandling()) {
771+
if (curr->isReturn) {
776772
parent.hasReturnCallThrow = true;
777773
}
778-
}
779-
if (parent.features.hasExceptionHandling() &&
780-
(parent.tryDepth == 0 && !curr->isReturn)) {
781-
parent.throws_ = true;
774+
if (parent.tryDepth == 0 && !curr->isReturn) {
775+
parent.throws_ = true;
776+
}
782777
}
783778
}
784779
void visitLocalGet(LocalGet* curr) {
@@ -1042,37 +1037,28 @@ class EffectAnalyzer {
10421037
return;
10431038
}
10441039

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-
10531040
if (curr->isReturn) {
10541041
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-
}
10601042
}
10611043

1062-
if (targetEffects) {
1044+
if (auto it =
1045+
parent.module.typeEffects.find(curr->target->type.getHeapType());
1046+
it != parent.module.typeEffects.end() && it->second) {
1047+
populateEffectsFromGlobalEffects(*it->second, curr);
10631048
return;
10641049
}
1050+
parent.calls = true;
10651051

1066-
if (curr->isReturn) {
1067-
parent.branchesOut = true;
1068-
if (parent.features.hasExceptionHandling()) {
1052+
// If EH is enabled and we don't have global effects information,
1053+
// assume that the call body may throw.
1054+
if (parent.features.hasExceptionHandling()) {
1055+
if (curr->isReturn) {
10691056
parent.hasReturnCallThrow = true;
10701057
}
1071-
}
1072-
parent.calls = true;
1073-
if (parent.features.hasExceptionHandling() &&
1074-
(parent.tryDepth == 0 && !curr->isReturn)) {
1075-
parent.throws_ = true;
1058+
1059+
if (parent.tryDepth == 0 && !curr->isReturn) {
1060+
parent.throws_ = true;
1061+
}
10761062
}
10771063
}
10781064
void visitRefTest(RefTest* curr) {}
@@ -1357,11 +1343,11 @@ class EffectAnalyzer {
13571343
}
13581344
}
13591345

1360-
private:
1361-
template <typename Call>
1362-
bool populateEffectsFromGlobalEffects(const EffectAnalyzer& effects, const Call* curr) {
1346+
private:
1347+
template<typename CallType>
1348+
void populateEffectsFromGlobalEffects(const EffectAnalyzer& effects,
1349+
const CallType* curr) {
13631350
if (curr->isReturn) {
1364-
parent.branchesOut = true;
13651351
if (effects.throws()) {
13661352
parent.hasReturnCallThrow = true;
13671353
}
@@ -1372,7 +1358,6 @@ class EffectAnalyzer {
13721358
filteredEffects.throws_ = false;
13731359
parent.mergeIn(filteredEffects);
13741360
} else {
1375-
// Just merge in all the effects.
13761361
parent.mergeIn(effects);
13771362
}
13781363
}

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)