Skip to content

Commit 4a84a4b

Browse files
authored
[Parser] Preserve try labels (#6505)
In the standard text format, try scopes can be targeted by both normal branches and delegates, but in Binaryen IR we only allow them to be targeted by delegates, so we have to translate branches to try scopes into branches to wrapper blocks instead. These wrapper blocks must have different names than the try expressions they wrap, so we actually need to track two label names for try expressions: one for delegates and another for normal branches. We previously tried to avoid this complexity by tracking only the branch label and computing the delegate label from the branch label as necessary, but that produced unnecessary wrapper blocks and ugly label names that did not appear in the source. To produce better IR and minimize the diff when switching to the new text parser, bite the bullet and track the delegate and branch label names separately. This eliminates unnecessary wrapper blocks and keeps try names the same as in the wat source where possible.
1 parent d8dc55c commit 4a84a4b

3 files changed

Lines changed: 148 additions & 184 deletions

File tree

src/wasm-ir-builder.h

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,10 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
298298

299299
// The branch label name for this scope. Always fresh, never shadowed.
300300
Name label;
301+
// For Try/Catch/CatchAll scopes, we need to separately track a label used
302+
// for branches, since the normal label is only used for delegates.
303+
Name branchLabel;
304+
301305
bool labelUsed = false;
302306

303307
std::vector<Expression*> exprStack;
@@ -308,6 +312,8 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
308312
ScopeCtx() : scope(NoScope{}) {}
309313
ScopeCtx(Scope scope) : scope(scope) {}
310314
ScopeCtx(Scope scope, Name label) : scope(scope), label(label) {}
315+
ScopeCtx(Scope scope, Name label, Name branchLabel)
316+
: scope(scope), label(label), branchLabel(branchLabel) {}
311317

312318
static ScopeCtx makeFunc(Function* func) {
313319
return ScopeCtx(FuncScope{func});
@@ -325,11 +331,13 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
325331
static ScopeCtx makeTry(Try* tryy, Name originalLabel = {}) {
326332
return ScopeCtx(TryScope{tryy, originalLabel});
327333
}
328-
static ScopeCtx makeCatch(Try* tryy, Name originalLabel, Name label) {
329-
return ScopeCtx(CatchScope{tryy, originalLabel}, label);
334+
static ScopeCtx
335+
makeCatch(Try* tryy, Name originalLabel, Name label, Name branchLabel) {
336+
return ScopeCtx(CatchScope{tryy, originalLabel}, label, branchLabel);
330337
}
331-
static ScopeCtx makeCatchAll(Try* tryy, Name originalLabel, Name label) {
332-
return ScopeCtx(CatchAllScope{tryy, originalLabel}, label);
338+
static ScopeCtx
339+
makeCatchAll(Try* tryy, Name originalLabel, Name label, Name branchLabel) {
340+
return ScopeCtx(CatchAllScope{tryy, originalLabel}, label, branchLabel);
333341
}
334342
static ScopeCtx makeTryTable(TryTable* trytable, Name originalLabel = {}) {
335343
return ScopeCtx(TryTableScope{trytable, originalLabel});
@@ -507,8 +515,11 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> {
507515
// `block`, but otherwise we will have to allocate a new block.
508516
Result<Expression*> finishScope(Block* block = nullptr);
509517

510-
[[nodiscard]] Result<Name> getLabelName(Index label);
511-
[[nodiscard]] Result<Name> getDelegateLabelName(Index label);
518+
[[nodiscard]] Result<Name> getLabelName(Index label,
519+
bool forDelegate = false);
520+
[[nodiscard]] Result<Name> getDelegateLabelName(Index label) {
521+
return getLabelName(label, true);
522+
}
512523
[[nodiscard]] Result<Index> addScratchLocal(Type);
513524

514525
struct HoistedVal {

src/wasm/wasm-ir-builder.cpp

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,10 @@ void IRBuilder::dump() {
235235
std::cerr << " (label: " << scope.label << ")";
236236
}
237237

238+
if (scope.branchLabel) {
239+
std::cerr << " (branch label: " << scope.branchLabel << ")";
240+
}
241+
238242
if (scope.unreachable) {
239243
std::cerr << " (unreachable)";
240244
}
@@ -703,9 +707,6 @@ Result<> IRBuilder::visitLoopStart(Loop* loop) {
703707

704708
Result<> IRBuilder::visitTryStart(Try* tryy, Name label) {
705709
applyDebugLoc(tryy);
706-
// The delegate label will be regenerated if we need it. See
707-
// `getDelegateLabelName` for details.
708-
tryy->name = Name();
709710
pushScope(ScopeCtx::makeTry(tryy, label));
710711
return Ok{};
711712
}
@@ -829,6 +830,7 @@ Result<> IRBuilder::visitCatch(Name tag) {
829830
}
830831
auto originalLabel = scope.getOriginalLabel();
831832
auto label = scope.label;
833+
auto branchLabel = scope.branchLabel;
832834
auto expr = finishScope();
833835
CHECK_ERR(expr);
834836
if (wasTry) {
@@ -837,7 +839,7 @@ Result<> IRBuilder::visitCatch(Name tag) {
837839
tryy->catchBodies.push_back(*expr);
838840
}
839841
tryy->catchTags.push_back(tag);
840-
pushScope(ScopeCtx::makeCatch(tryy, originalLabel, label));
842+
pushScope(ScopeCtx::makeCatch(tryy, originalLabel, label, branchLabel));
841843
// Push a pop for the exception payload.
842844
auto params = wasm.getTag(tag)->sig.params;
843845
if (params != Type::none) {
@@ -859,44 +861,18 @@ Result<> IRBuilder::visitCatchAll() {
859861
}
860862
auto originalLabel = scope.getOriginalLabel();
861863
auto label = scope.label;
864+
auto branchLabel = scope.branchLabel;
862865
auto expr = finishScope();
863866
CHECK_ERR(expr);
864867
if (wasTry) {
865868
tryy->body = *expr;
866869
} else {
867870
tryy->catchBodies.push_back(*expr);
868871
}
869-
pushScope(ScopeCtx::makeCatchAll(tryy, originalLabel, label));
872+
pushScope(ScopeCtx::makeCatchAll(tryy, originalLabel, label, branchLabel));
870873
return Ok{};
871874
}
872875

873-
Result<Name> IRBuilder::getDelegateLabelName(Index label) {
874-
if (label >= scopeStack.size()) {
875-
return Err{"invalid label: " + std::to_string(label)};
876-
}
877-
auto& scope = scopeStack[scopeStack.size() - label - 1];
878-
auto* delegateTry = scope.getTry();
879-
if (!delegateTry) {
880-
delegateTry = scope.getCatch();
881-
}
882-
if (!delegateTry) {
883-
delegateTry = scope.getCatchAll();
884-
}
885-
if (!delegateTry) {
886-
return Err{"expected try scope at label " + std::to_string(label)};
887-
}
888-
// Only delegate and rethrow can reference the try name in Binaryen IR, so
889-
// trys might need two labels: one for delegate/rethrow and one for all
890-
// other control flow. These labels must be different to satisfy the
891-
// Binaryen validator. To keep this complexity contained within the
892-
// handling of trys and delegates, pretend there is just the single normal
893-
// label and add a prefix to it to generate the delegate label.
894-
auto delegateName =
895-
Name(std::string("__delegate__") + getLabelName(label)->toString());
896-
delegateTry->name = delegateName;
897-
return delegateName;
898-
}
899-
900876
Result<> IRBuilder::visitDelegate(Index label) {
901877
auto& scope = getScope();
902878
auto* tryy = scope.getTry();
@@ -946,8 +922,10 @@ Result<> IRBuilder::visitEnd() {
946922
// type of the scope expression.
947923
auto originalScopeType = scope.getResultType();
948924
auto maybeWrapForLabel = [&](Expression* curr) -> Expression* {
949-
if (scope.label) {
950-
return builder.makeBlock(scope.label,
925+
bool isTry = scope.getTry() || scope.getCatch() || scope.getCatchAll();
926+
auto& label = isTry ? scope.branchLabel : scope.label;
927+
if (label) {
928+
return builder.makeBlock(label,
951929
{curr},
952930
scope.labelUsed ? originalScopeType
953931
: scope.getResultType());
@@ -981,11 +959,13 @@ Result<> IRBuilder::visitEnd() {
981959
push(maybeWrapForLabel(iff));
982960
} else if (auto* tryy = scope.getTry()) {
983961
tryy->body = *expr;
962+
tryy->name = scope.label;
984963
tryy->finalize(tryy->type);
985964
push(maybeWrapForLabel(tryy));
986965
} else if (Try * tryy;
987966
(tryy = scope.getCatch()) || (tryy = scope.getCatchAll())) {
988967
tryy->catchBodies.push_back(*expr);
968+
tryy->name = scope.label;
989969
tryy->finalize(tryy->type);
990970
push(maybeWrapForLabel(tryy));
991971
} else if (auto* trytable = scope.getTryTable()) {
@@ -1029,10 +1009,17 @@ Result<Index> IRBuilder::getLabelIndex(Name label, bool inDelegate) {
10291009
return index;
10301010
}
10311011

1032-
Result<Name> IRBuilder::getLabelName(Index label) {
1012+
Result<Name> IRBuilder::getLabelName(Index label, bool forDelegate) {
10331013
auto scope = getScope(label);
10341014
CHECK_ERR(scope);
1035-
auto& scopeLabel = (*scope)->label;
1015+
1016+
// For normal branches to try blocks, we need to use the secondary label.
1017+
bool useTryBranchLabel =
1018+
!forDelegate &&
1019+
((*scope)->getTry() || (*scope)->getCatch() || (*scope)->getCatchAll());
1020+
auto& scopeLabel =
1021+
useTryBranchLabel ? (*scope)->branchLabel : (*scope)->label;
1022+
10361023
if (!scopeLabel) {
10371024
// The scope does not already have a name, so we need to create one.
10381025
if ((*scope)->getBlock()) {
@@ -1041,7 +1028,9 @@ Result<Name> IRBuilder::getLabelName(Index label) {
10411028
scopeLabel = makeFresh("label");
10421029
}
10431030
}
1044-
(*scope)->labelUsed = true;
1031+
if (!forDelegate) {
1032+
(*scope)->labelUsed = true;
1033+
}
10451034
return scopeLabel;
10461035
}
10471036

0 commit comments

Comments
 (0)