Skip to content

Commit 738f662

Browse files
committed
Add fixup function for nested pops in catch
This adds `EHUtils::handleBlockNestedPops`, which can be called at the end of passes that has a possibility to put `pop`s inside `block`s. This method assumes there exists a `pop` in a first-descendant line, even though it can be nested within a block. This allows a `pop` to be nested within a `block` or a `try`, but not a `loop`, since that means the `pop` can run multile times. In case of `if`, `pop` can exist only in its condition; if a `pop` is in its true or false body, that's not in the first-descendant line. To test this, this adds `passes/test_passes.cpp`, which is intended to contain multiple test passes that test a single (or more) utility functions separately. Without this kind of pass, it is hard to test various cases in which nested `pop`s can be generated in existing passes. This PR also adds `PassRegistry::registerTestPass`, which registers a pass that's intended only for internal testing and does not show up in `wasm-opt --help`.
1 parent be672c0 commit 738f662

File tree

8 files changed

+504
-41
lines changed

8 files changed

+504
-41
lines changed

src/ir/eh-utils.cpp

Lines changed: 119 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,53 +16,137 @@
1616

1717
#include "ir/eh-utils.h"
1818
#include "ir/branch-utils.h"
19+
#include "ir/find_all.h"
1920

2021
namespace wasm {
2122

2223
namespace EHUtils {
2324

24-
bool isPopValid(Expression* catchBody) {
25-
Expression* firstChild = nullptr;
26-
auto* block = catchBody->dynCast<Block>();
27-
if (!block) {
28-
firstChild = catchBody;
29-
} else {
30-
// When there are multiple expressions within a catch body, an implicit
31-
// block is created within it for convenience purposes, and if there are no
32-
// branches that targets the block, it will be omitted when written back.
33-
// But if there is a branch targetting this block, this block cannot be
34-
// removed, and 'pop''s location will be like
35-
// (catch $e
36-
// (block $l0
37-
// (pop i32) ;; within a block!
38-
// (br $l0)
39-
// ...
40-
// )
41-
// )
42-
// which is invalid.
43-
if (BranchUtils::BranchSeeker::has(block, block->name)) {
44-
return false;
45-
}
46-
// There should be a pop somewhere
47-
if (block->list.empty()) {
48-
return false;
49-
}
50-
firstChild = *block->list.begin();
51-
}
25+
// This returns three values, some of them as output parameters:
26+
// - Return value: 'pop' expression (Expression*), when there is one in
27+
// first-descendant line. If there's no such pop, it returns null.
28+
// - isPopNested: Whether the discovered 'pop' is nested within a block
29+
// - popPtr: 'pop' expression's pointer (Expression**), when there is one found
30+
//
31+
// When 'catchBody' itself is a 'pop', 'pop''s pointer is null, because there is
32+
// no way to get the given expression's address. But that's fine because pop's
33+
// pointer is only necessary (in handleBlockNestedPops) to fix it up when it is
34+
// nested, and if 'catchBody' itself is a pop, we don't need to fix it up.
35+
static Expression*
36+
getFirstPop(Expression* catchBody, bool& isPopNested, Expression**& popPtr) {
37+
Expression* firstChild = catchBody;
38+
isPopNested = false;
39+
popPtr = nullptr;
40+
// When there are multiple expressions within a catch body, an implicit
41+
// block is created within it for convenience purposes.
42+
auto* implicitBlock = catchBody->dynCast<Block>();
5243

5344
// Go down the line for the first child until we reach a leaf. A pop should be
54-
// in that first-decendent line.
45+
// in that first-decendant line.
46+
Expression** firstChildPtr = nullptr;
5547
while (true) {
5648
if (firstChild->is<Pop>()) {
57-
return true;
49+
popPtr = firstChildPtr;
50+
return firstChild;
5851
}
59-
// We use ValueChildIterator in order not to go into block/loop/try/if
60-
// bodies, because a pop cannot be in those control flow expressions.
61-
ValueChildIterator it(firstChild);
52+
53+
if (Properties::isControlFlowStructure(firstChild)) {
54+
// If's condition is a value child who comes before an 'if' instruction
55+
// in binary, it is fine if a 'pop' is in there. We don't allow a 'pop' to
56+
// be in an 'if''s then or else body because they are not
57+
// first descendants.
58+
if (auto* if_ = firstChild->dynCast<If>()) {
59+
firstChild = if_->condition;
60+
continue;
61+
}
62+
// We don't allow the pop to be included in a loop, because it cannot be
63+
// run more than once
64+
if (firstChild->is<Loop>()) {
65+
return nullptr;
66+
}
67+
if (firstChild->is<Block>()) {
68+
// If there are no branches that targets the implicit block, it will be
69+
// removed when written back. But there are branches that target the
70+
// implicit block,
71+
// (catch $e
72+
// (block $l0
73+
// (pop i32) ;; within a block!
74+
// (br $l0)
75+
// ...
76+
// )
77+
// This cannot be removed, so this is considered a nested pop (which we
78+
// should fix).
79+
if (firstChild == implicitBlock) {
80+
if (BranchUtils::BranchSeeker::has(implicitBlock,
81+
implicitBlock->name)) {
82+
isPopNested = true;
83+
}
84+
} else {
85+
isPopNested = true;
86+
}
87+
}
88+
if (firstChild->is<Try>()) {
89+
isPopNested = true;
90+
}
91+
}
92+
ChildIterator it(firstChild);
6293
if (it.begin() == it.end()) {
63-
return false;
94+
return nullptr;
95+
}
96+
firstChildPtr = &*it.begin();
97+
firstChild = *firstChildPtr;
98+
}
99+
}
100+
101+
bool isPopValid(Expression* catchBody) {
102+
bool isPopNested = false;
103+
Expression** popPtr = nullptr;
104+
auto* pop = getFirstPop(catchBody, isPopNested, popPtr);
105+
return pop != nullptr && !isPopNested;
106+
}
107+
108+
void handleBlockNestedPops(Function* func, Module& wasm) {
109+
Builder builder(wasm);
110+
FindAll<Try> trys(func->body);
111+
for (auto* try_ : trys.list) {
112+
for (Index i = 0; i < try_->catchTags.size(); i++) {
113+
Name tagName = try_->catchTags[i];
114+
auto* tag = wasm.getTagOrNull(tagName);
115+
if (tag->sig.params == Type::none) {
116+
continue;
117+
}
118+
119+
auto* catchBody = try_->catchBodies[i];
120+
bool isPopNested = false;
121+
Expression** popPtr = nullptr;
122+
Expression* pop = getFirstPop(catchBody, isPopNested, popPtr);
123+
assert(pop && "Pop has not been found in this catch");
124+
125+
// Change code like
126+
// (catch $e
127+
// ...
128+
// (block
129+
// (pop i32)
130+
// )
131+
// )
132+
// into
133+
// (catch $e
134+
// (local.set $new
135+
// (pop i32)
136+
// )
137+
// ...
138+
// (block
139+
// (local.get $new)
140+
// )
141+
// )
142+
if (isPopNested) {
143+
assert(popPtr);
144+
Index newLocal = builder.addVar(func, pop->type);
145+
try_->catchBodies[i] =
146+
builder.makeSequence(builder.makeLocalSet(newLocal, pop), catchBody);
147+
*popPtr = builder.makeLocalGet(newLocal, pop->type);
148+
}
64149
}
65-
firstChild = *it.begin();
66150
}
67151
}
68152

src/ir/eh-utils.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
namespace wasm {
2121

2222
class Expression;
23+
class Function;
24+
class Module;
2325

2426
namespace EHUtils {
2527

@@ -31,6 +33,12 @@ namespace EHUtils {
3133
// catch body, which is invalid. That will be taken care of in validation.
3234
bool isPopValid(Expression* catchBody);
3335

36+
// Fixes up 'pop's nested in blocks, which are currently not supported without
37+
// block param types, by creating a new local, putting a (local.set $new (pop
38+
// type)) right after 'catch', and putting a '(local.get $new)' where the 'pop'
39+
// used to be.
40+
void handleBlockNestedPops(Function* func, Module& wasm);
41+
3442
} // namespace EHUtils
3543

3644
} // namespace wasm

src/pass.h

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ struct PassRegistry {
3939
typedef std::function<Pass*()> Creator;
4040

4141
void registerPass(const char* name, const char* description, Creator create);
42+
// Register a pass that's used for internal testing. These pass do not show up
43+
// in --help.
44+
void
45+
registerTestPass(const char* name, const char* description, Creator create);
4246
std::unique_ptr<Pass> createPass(std::string name);
43-
std::vector<std::string> getRegisteredNames();
47+
std::vector<std::string> getRegisteredNames(bool includeHidden = false);
4448
std::string getPassDescription(std::string name);
4549

4650
private:
@@ -49,9 +53,10 @@ struct PassRegistry {
4953
struct PassInfo {
5054
std::string description;
5155
Creator create;
56+
bool hidden;
5257
PassInfo() = default;
53-
PassInfo(std::string description, Creator create)
54-
: description(description), create(create) {}
58+
PassInfo(std::string description, Creator create, bool hidden = false)
59+
: description(description), create(create), hidden(false) {}
5560
};
5661
std::map<std::string, PassInfo> passInfos;
5762
};

src/passes/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ add_custom_command(
99
FILE(GLOB passes_HEADERS *.h)
1010
set(passes_SOURCES
1111
pass.cpp
12+
test_passes.cpp
1213
AlignmentLowering.cpp
1314
Asyncify.cpp
1415
AvoidReinterprets.cpp

src/passes/pass.cpp

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ void PassRegistry::registerPass(const char* name,
4747
passInfos[name] = PassInfo(description, create);
4848
}
4949

50+
void PassRegistry::registerTestPass(const char* name,
51+
const char* description,
52+
Creator create) {
53+
assert(passInfos.find(name) == passInfos.end());
54+
passInfos[name] = PassInfo(description, create, true);
55+
}
56+
5057
std::unique_ptr<Pass> PassRegistry::createPass(std::string name) {
5158
if (passInfos.find(name) == passInfos.end()) {
5259
Fatal() << "Could not find pass: " << name << "\n";
@@ -57,10 +64,11 @@ std::unique_ptr<Pass> PassRegistry::createPass(std::string name) {
5764
return ret;
5865
}
5966

60-
std::vector<std::string> PassRegistry::getRegisteredNames() {
67+
std::vector<std::string> PassRegistry::getRegisteredNames(bool includeHidden) {
6168
std::vector<std::string> ret;
6269
for (auto pair : passInfos) {
63-
ret.push_back(pair.first);
70+
if (includeHidden || !pair.second.hidden)
71+
ret.push_back(pair.first);
6472
}
6573
return ret;
6674
}
@@ -407,6 +415,11 @@ void PassRegistry::registerPasses() {
407415
registerPass("vacuum", "removes obviously unneeded code", createVacuumPass);
408416
// registerPass(
409417
// "lower-i64", "lowers i64 into pairs of i32s", createLowerInt64Pass);
418+
419+
// Register passes used for internal testing. These don't show up in --help.
420+
registerTestPass("catch-pop-fixup",
421+
"fixup nested pops within catches",
422+
createCatchPopFixupPass);
410423
}
411424

412425
void PassRunner::addIfNoDWARFIssues(std::string passName) {

src/passes/passes.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ namespace wasm {
2121

2222
class Pass;
2323

24-
// All passes:
24+
// Normal passes:
2525
Pass* createAlignmentLoweringPass();
2626
Pass* createAsyncifyPass();
2727
Pass* createAvoidReinterpretsPass();
@@ -135,6 +135,9 @@ Pass* createTypeRefiningPass();
135135
Pass* createUnteePass();
136136
Pass* createVacuumPass();
137137

138+
// Test passes:
139+
Pass* createCatchPopFixupPass();
140+
138141
} // namespace wasm
139142

140143
#endif

src/passes/test_passes.cpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
//
18+
// This file contains passes used for internal testing. This file can be used to
19+
// test utility functions separately.
20+
//
21+
22+
#include "ir/eh-utils.h"
23+
#include "pass.h"
24+
#include "wasm-builder.h"
25+
#include "wasm.h"
26+
27+
namespace wasm {
28+
29+
namespace {
30+
31+
// Pass to test EHUtil::handleBlockNestedPops function
32+
struct CatchPopFixup : public WalkerPass<PostWalker<CatchPopFixup>> {
33+
bool isFunctionParallel() override { return true; }
34+
Pass* create() override { return new CatchPopFixup; }
35+
36+
void doWalkFunction(Function* func) {
37+
EHUtils::handleBlockNestedPops(func, *getModule());
38+
}
39+
void run(PassRunner* runner, Module* module) override {}
40+
};
41+
42+
} // anonymous namespace
43+
44+
Pass* createCatchPopFixupPass() { return new CatchPopFixup(); }
45+
46+
} // namespace wasm

0 commit comments

Comments
 (0)