Skip to content

Commit 7b92d58

Browse files
Fix type updating for indirect call effects
1 parent 193792f commit 7b92d58

8 files changed

Lines changed: 272 additions & 33 deletions

File tree

src/ir/linear-execution.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {
187187

188188
auto* effects = find_or_null(self->getModule()->indirectCallEffects,
189189
callRef->target->type.getHeapType());
190-
if (!effects) {
190+
if (!effects || !*effects) {
191191
return false;
192192
}
193193
return !(*effects)->throws_;
@@ -203,7 +203,7 @@ struct LinearExecutionWalker : public PostWalker<SubType, VisitorType> {
203203
if (self->getModule()) {
204204
if (auto* effects = find_or_null(
205205
self->getModule()->indirectCallEffects, callIndirect->heapType);
206-
effects) {
206+
effects && *effects) {
207207
refutesThrowEffect = !(*effects)->throws_;
208208
}
209209
}

src/ir/module-utils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,12 @@ void renameFunction(Module& wasm, Name oldName, Name newName);
7676

7777
// Convenient iteration over imported/non-imported module elements
7878

79+
inline void iterTypes(const Module& wasm, auto&& visitor) {
80+
for (const auto& [type, _] : wasm.typeIndices) {
81+
visitor(type);
82+
}
83+
}
84+
7985
inline void iterImportedMemories(const Module& wasm, auto&& visitor) {
8086
for (auto& import : wasm.memories) {
8187
if (import->imported()) {

src/ir/type-updating.cpp

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,66 @@
2626

2727
namespace wasm {
2828

29+
namespace {
30+
31+
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>
32+
updateIndirectCallEffects(const Module& wasm,
33+
const GlobalTypeRewriter::TypeMap& oldToNewTypes) {
34+
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>
35+
newTypeEffects;
36+
37+
ModuleUtils::iterTypes(wasm, [&](HeapType oldType) {
38+
HeapType newType;
39+
{
40+
auto it = oldToNewTypes.find(oldType);
41+
if (it == oldToNewTypes.end()) {
42+
// This type wasn't renamed. Copy over the existing entry if present.
43+
if (const auto* oldEffects =
44+
find_or_null(wasm.indirectCallEffects, oldType)) {
45+
newTypeEffects[oldType] = *oldEffects;
46+
}
47+
return;
48+
}
49+
newType = it->second;
50+
}
51+
52+
const std::shared_ptr<const EffectAnalyzer>* oldEffects =
53+
find_or_null(wasm.indirectCallEffects, oldType);
54+
55+
if (!oldEffects) {
56+
// We never knew the effects of this (either GlobalEffects never ran or a
57+
// new type was created without copying over effects). Nothing to update.
58+
return;
59+
}
60+
61+
auto [it, inserted] = newTypeEffects.emplace(newType, nullptr);
62+
auto& newEffects = it->second;
63+
64+
if (!inserted && !newEffects) {
65+
// Effects of the new type were already unknown. Nothing to do.
66+
return;
67+
}
68+
69+
if (!*oldEffects) {
70+
// oldType is explicitly unknown. Set the new effects to unknown.
71+
newEffects = nullptr;
72+
return;
73+
}
74+
75+
if (inserted) {
76+
newEffects = *oldEffects;
77+
} else {
78+
auto merged = std::make_shared<EffectAnalyzer>(*newEffects);
79+
merged->mergeIn(**oldEffects);
80+
newEffects = std::move(merged);
81+
}
82+
});
83+
84+
return newTypeEffects;
85+
}
86+
87+
} // anonymous namespace
88+
2989
GlobalTypeRewriter::GlobalTypeRewriter(Module& wasm, WorldMode worldMode)
3090
: wasm(wasm), publicGroups(wasm.features) {
3191
// Find the heap types that are not publicly observable. Even in a closed
@@ -329,31 +389,9 @@ void GlobalTypeRewriter::mapTypes(const TypeMap& oldToNewTypes) {
329389
// Update indirect call effects per type.
330390
// When A is rewritten to B, B inherits the effects of A and A loses its
331391
// effects.
332-
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>
333-
newTypeEffects;
334-
335-
for (const auto& [oldType, newType] : oldToNewTypes) {
336-
std::shared_ptr<const EffectAnalyzer>* oldEffects =
337-
find_or_null(wasm.indirectCallEffects, oldType);
338-
std::shared_ptr<const EffectAnalyzer>* targetEffects =
339-
find_or_null(wasm.indirectCallEffects, newType);
340-
341-
if (!targetEffects) {
342-
// Nothing to update, we already know nothing and assume all effects.
343-
continue;
344-
}
345-
346-
if (!oldEffects) {
347-
targetEffects->reset();
348-
continue;
349-
}
350-
351-
auto merged = std::make_shared<EffectAnalyzer>(**targetEffects);
352-
merged->mergeIn(**oldEffects);
353-
*targetEffects = std::move(merged);
392+
if (!wasm.indirectCallEffects.empty()) {
393+
wasm.indirectCallEffects = updateIndirectCallEffects(wasm, oldToNewTypes);
354394
}
355-
356-
wasm.indirectCallEffects = std::move(newTypeEffects);
357395
}
358396

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

src/passes/GlobalEffects.cpp

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -321,9 +321,9 @@ void mergeMaybeEffects(std::shared_ptr<EffectAnalyzer>& dest,
321321
dest->mergeIn(*src);
322322
}
323323

324-
// Propagate effects from callees to callers transitively
325-
// e.g. if A -> B -> C (A calls B which calls C)
326-
// Then B inherits effects from C and A inherits effects from both B and C.
324+
// Propagate effects from callees to callers transitively and populate direct
325+
// and indirect call effects. e.g. if A -> B -> C (A calls B which calls C) Then
326+
// B inherits effects from C and A inherits effects from both B and C.
327327
//
328328
// Generate SCC for the call graph, then traverse it in reverse topological
329329
// order processing each callee before its callers. When traversing:
@@ -335,7 +335,7 @@ void propagateEffects(
335335
const PassOptions& passOptions,
336336
std::map<Function*, FuncInfo>& funcInfos,
337337
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>&
338-
typeEffects,
338+
indirectCallEffects,
339339
const CallGraph& callGraph) {
340340
// We only care about Functions that are roots, not types.
341341
// A type would be a root if a function exists with that type, but no-one
@@ -435,9 +435,9 @@ void propagateEffects(
435435
// Assign each function's effects to its CC effects.
436436
for (auto node : cc) {
437437
std::visit(overloaded{[&](HeapType type) {
438-
if (ccEffects != UnknownEffects) {
439-
typeEffects[type] = ccEffects;
440-
}
438+
// Assign the key even if ccEffects is nullptr.
439+
// See the comment in Module::indirectCallEffects.
440+
indirectCallEffects[type] = ccEffects;
441441
},
442442
[&](Function* f) { f->effects = ccEffects; }},
443443
node);
@@ -455,6 +455,7 @@ struct GenerateGlobalEffects : public Pass {
455455
auto callGraph = buildCallGraph(
456456
*module, funcInfos, referencedFuncs, getPassOptions().worldMode);
457457

458+
module->indirectCallEffects.clear();
458459
propagateEffects(*module,
459460
getPassOptions(),
460461
funcInfos,
@@ -468,6 +469,7 @@ struct DiscardGlobalEffects : public Pass {
468469
for (auto& func : module->functions) {
469470
func->effects.reset();
470471
}
472+
module->indirectCallEffects.clear();
471473
}
472474
};
473475

src/passes/pass.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,6 +1079,11 @@ void PassRunner::handleAfterEffects(Pass* pass, Function* func) {
10791079
// Binaryen IR is modified, so we may have work here.
10801080

10811081
if (!func) {
1082+
if (pass->addsEffects()) {
1083+
// Indirect call effects are now under-approximating. Clear them to avoid
1084+
// incorrect optimizations.
1085+
wasm->indirectCallEffects.clear();
1086+
}
10821087
// If no function is provided, then this is not a function-parallel pass,
10831088
// and it may have operated on any of the functions in theory, so run on
10841089
// them all.

src/wasm.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,6 +2746,13 @@ class Module {
27462746
// This data is only meaningful for indirect calls. If no indirect call
27472747
// exists to a function, the data can be out of date (no effort is made to
27482748
// clean up the data if e.g. all indirect calls to a function are removed).
2749+
//
2750+
// Null values are possible in the case that a types's effects are unknown.
2751+
// Note that this is different from a missing key in the map, which is
2752+
// important when rewriting types. Suppose we rewrite A -> B where B is a
2753+
// brand new type. Then B should inherit A's effects. OTOH if B is an existing
2754+
// type with *explicitly* unknown effects (present with nullptr), then B
2755+
// remains explicitly unknown.
27492756
// TODO: Account for exactness here.
27502757
std::unordered_map<HeapType, std::shared_ptr<const EffectAnalyzer>>
27512758
indirectCallEffects;

test/gtest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ set(unittest_SOURCES
3434
suffix_tree.cpp
3535
topological-sort.cpp
3636
type-builder.cpp
37+
type-updating.cpp
3738
wat-lexer.cpp
3839
validator.cpp
3940
)

test/gtest/type-updating.cpp

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/*
2+
* Copyright 2021 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "gtest/gtest.h"
18+
19+
#include "ir/effects.h"
20+
#include "ir/type-updating.h"
21+
#include "parser/wat-parser.h"
22+
#include "support/result.h"
23+
#include "wasm.h"
24+
25+
using namespace wasm;
26+
using namespace testing;
27+
28+
namespace {
29+
30+
class IndirectCallEffectsTest : public Test {
31+
protected:
32+
Module wasm;
33+
PassOptions options;
34+
35+
void SetUp() override {
36+
auto moduleText = R"wasm(
37+
(module
38+
(rec
39+
(type $A (func))
40+
(type $B (func))
41+
(type $C (func))
42+
(type $D (func))
43+
)
44+
)
45+
)wasm";
46+
47+
if (auto err = wasm::WATParser::parseModule(wasm, moduleText).getErr()) {
48+
Fatal() << err->msg;
49+
}
50+
}
51+
52+
std::unordered_map<std::string, std::shared_ptr<const EffectAnalyzer>>
53+
updateEffects(
54+
const std::unordered_map<std::string,
55+
std::shared_ptr<const EffectAnalyzer>>& oldEffects,
56+
const std::unordered_map<std::string, std::string>& typeMap) {
57+
58+
std::unordered_map<Name, HeapType> types;
59+
for (const auto& [type, name] : wasm.typeNames) {
60+
types[name.name] = type;
61+
}
62+
63+
for (const auto& [name, effects] : oldEffects) {
64+
wasm.indirectCallEffects[types.at(name)] = effects;
65+
}
66+
67+
GlobalTypeRewriter::TypeMap map;
68+
for (const auto& [oldName, newName] : typeMap) {
69+
map[types.at(oldName)] = types.at(newName);
70+
}
71+
72+
GlobalTypeRewriter rewriter(wasm, WorldMode::Open);
73+
rewriter.mapTypes(map);
74+
75+
std::unordered_map<std::string, std::shared_ptr<const EffectAnalyzer>>
76+
result;
77+
for (const auto& [type, effects] : wasm.indirectCallEffects) {
78+
auto name = wasm.typeNames.at(type).name.toString();
79+
result[name] = effects;
80+
}
81+
return result;
82+
}
83+
};
84+
85+
} // anonymous namespace
86+
87+
TEST_F(IndirectCallEffectsTest, SrcHasNoEffects) {
88+
auto effectsB = std::make_shared<EffectAnalyzer>(options, wasm);
89+
effectsB->writesMemory = true;
90+
91+
auto merged =
92+
updateEffects(/*oldEffects=*/{{"B", effectsB}}, /*typeMap=*/{{"A", "B"}});
93+
94+
EXPECT_FALSE(merged.contains("A"));
95+
ASSERT_TRUE(merged.contains("B"));
96+
97+
// Pointer comparison
98+
EXPECT_EQ(merged.at("B"), effectsB);
99+
}
100+
101+
TEST_F(IndirectCallEffectsTest, DestHasNoEffects) {
102+
auto effectsA = std::make_shared<EffectAnalyzer>(options, wasm);
103+
effectsA->calls = true;
104+
105+
auto merged =
106+
updateEffects(/*oldEffects=*/{{"A", effectsA}}, /*typeMap=*/{{"A", "B"}});
107+
108+
EXPECT_FALSE(merged.contains("A"));
109+
ASSERT_TRUE(merged.contains("B"));
110+
111+
// Pointer comparison
112+
EXPECT_EQ(merged.at("B"), effectsA);
113+
}
114+
115+
TEST_F(IndirectCallEffectsTest, BothHaveNoEffects) {
116+
auto merged = updateEffects(/*oldEffects=*/{}, /*typeMap=*/{{"A", "B"}});
117+
118+
EXPECT_FALSE(merged.contains("A"));
119+
EXPECT_FALSE(merged.contains("B"));
120+
}
121+
122+
TEST_F(IndirectCallEffectsTest, BothHaveEffects) {
123+
auto effectsA = std::make_shared<EffectAnalyzer>(options, wasm);
124+
effectsA->calls = true;
125+
auto effectsB = std::make_shared<EffectAnalyzer>(options, wasm);
126+
effectsB->writesMemory = true;
127+
128+
auto merged = updateEffects(/*oldEffects=*/{{"A", effectsA}, {"B", effectsB}},
129+
/*typeMap=*/{{"A", "B"}});
130+
131+
EXPECT_FALSE(merged.contains("A"));
132+
ASSERT_TRUE(merged.contains("B"));
133+
EXPECT_TRUE(merged.at("B")->calls);
134+
EXPECT_TRUE(merged.at("B")->writesMemory);
135+
}
136+
137+
TEST_F(IndirectCallEffectsTest, SrcHasNullptrEffects) {
138+
auto effectsB = std::make_shared<EffectAnalyzer>(options, wasm);
139+
effectsB->writesMemory = true;
140+
141+
auto merged = updateEffects(/*oldEffects=*/{{"A", nullptr}, {"B", effectsB}},
142+
/*typeMap=*/{{"A", "B"}});
143+
144+
EXPECT_FALSE(merged.contains("A"));
145+
ASSERT_TRUE(merged.contains("B"));
146+
EXPECT_EQ(merged.at("B"), nullptr);
147+
}
148+
149+
TEST_F(IndirectCallEffectsTest, DestHasNullptrEffects) {
150+
auto effectsA = std::make_shared<EffectAnalyzer>(options, wasm);
151+
effectsA->calls = true;
152+
153+
auto merged = updateEffects(/*oldEffects=*/{{"A", effectsA}, {"B", nullptr}},
154+
/*typeMap=*/{{"A", "B"}});
155+
156+
EXPECT_FALSE(merged.contains("A"));
157+
ASSERT_TRUE(merged.contains("B"));
158+
EXPECT_EQ(merged.at("B"), nullptr);
159+
}
160+
161+
TEST_F(IndirectCallEffectsTest, MergeThreeTypes) {
162+
auto effectsA = std::make_shared<EffectAnalyzer>(options, wasm);
163+
effectsA->calls = true;
164+
auto effectsB = std::make_shared<EffectAnalyzer>(options, wasm);
165+
effectsB->writesMemory = true;
166+
auto effectsC = std::make_shared<EffectAnalyzer>(options, wasm);
167+
effectsC->throws_ = true;
168+
169+
auto merged = updateEffects(
170+
/*oldEffects=*/{{"A", effectsA}, {"B", effectsB}, {"C", effectsC}},
171+
/*typeMap=*/{{"A", "D"}, {"B", "D"}, {"C", "D"}});
172+
173+
EXPECT_FALSE(merged.contains("A"));
174+
EXPECT_FALSE(merged.contains("B"));
175+
EXPECT_FALSE(merged.contains("C"));
176+
ASSERT_TRUE(merged.contains("D"));
177+
EXPECT_TRUE(merged.at("D")->calls);
178+
EXPECT_TRUE(merged.at("D")->writesMemory);
179+
EXPECT_TRUE(merged.at("D")->throws_);
180+
}

0 commit comments

Comments
 (0)