-
Notifications
You must be signed in to change notification settings - Fork 827
Support control flow inputs in IRBuilder #7149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -1467,10 +1467,12 @@ class WasmBinaryReader { | |||||
|
|
||||||
| bool getBasicType(int32_t code, Type& out); | ||||||
| bool getBasicHeapType(int64_t code, HeapType& out); | ||||||
| // Get the signature of control flow structure. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| Signature getBlockType(); | ||||||
| // Read a value and get a type for it. | ||||||
| Type getType(); | ||||||
| // Get a type given the initial S32LEB has already been read, and is provided. | ||||||
| Type getType(int initial); | ||||||
| Type getType(int code); | ||||||
| HeapType getHeapType(); | ||||||
| HeapType getIndexedHeapType(); | ||||||
|
|
||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -80,15 +80,18 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| // the corresponding `makeXYZ` function below instead of `visitXYZStart`, but | ||
| // either way must call `visitEnd` and friends at the appropriate times. | ||
| Result<> visitFunctionStart(Function* func); | ||
| Result<> visitBlockStart(Block* block); | ||
| Result<> visitIfStart(If* iff, Name label = {}); | ||
| Result<> visitBlockStart(Block* block, Type inputType = Type::none); | ||
| Result<> visitIfStart(If* iff, Name label = {}, Type inputType = Type::none); | ||
| Result<> visitElse(); | ||
| Result<> visitLoopStart(Loop* iff); | ||
| Result<> visitTryStart(Try* tryy, Name label = {}); | ||
| Result<> visitLoopStart(Loop* iff, Type inputType = Type::none); | ||
| Result<> | ||
| visitTryStart(Try* tryy, Name label = {}, Type inputType = Type::none); | ||
| Result<> visitCatch(Name tag); | ||
| Result<> visitCatchAll(); | ||
| Result<> visitDelegate(Index label); | ||
| Result<> visitTryTableStart(TryTable* trytable, Name label = {}); | ||
| Result<> visitTryTableStart(TryTable* trytable, | ||
| Name label = {}, | ||
| Type inputType = Type::none); | ||
| Result<> visitEnd(); | ||
|
|
||
| // Used to visit break nodes when traversing a single block without its | ||
|
|
@@ -113,9 +116,9 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| // nodes. This is generally safer than calling `visit` because the function | ||
| // signatures ensure that there are no missing fields. | ||
| Result<> makeNop(); | ||
| Result<> makeBlock(Name label, Type type); | ||
| Result<> makeIf(Name label, Type type); | ||
| Result<> makeLoop(Name label, Type type); | ||
| Result<> makeBlock(Name label, Signature sig); | ||
| Result<> makeIf(Name label, Signature sig); | ||
| Result<> makeLoop(Name label, Signature sig); | ||
| Result<> makeBreak(Index label, bool isConditional); | ||
| Result<> makeSwitch(const std::vector<Index>& labels, Index defaultLabel); | ||
| // Unlike Builder::makeCall, this assumes the function already exists. | ||
|
|
@@ -180,9 +183,9 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| Result<> makeTableFill(Name table); | ||
| Result<> makeTableCopy(Name destTable, Name srcTable); | ||
| Result<> makeTableInit(Name elem, Name table); | ||
| Result<> makeTry(Name label, Type type); | ||
| Result<> makeTry(Name label, Signature sig); | ||
| Result<> makeTryTable(Name label, | ||
| Type type, | ||
| Signature sig, | ||
| const std::vector<Name>& tags, | ||
| const std::vector<Index>& labels, | ||
| const std::vector<bool>& isRefs); | ||
|
|
@@ -323,13 +326,21 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
|
|
||
| // The branch label name for this scope. Always fresh, never shadowed. | ||
| Name label; | ||
|
|
||
| // For Try/Catch/CatchAll scopes, we need to separately track a label used | ||
| // for branches, since the normal label is only used for delegates. | ||
| Name branchLabel; | ||
|
|
||
| bool labelUsed = false; | ||
|
|
||
| // If the control flow scope has an input type, we need to lower it using a | ||
| // scratch local because we cannot represent control flow input in the IR. | ||
| Type inputType; | ||
| Index inputLocal = -1; | ||
|
|
||
| // The stack of instructions being built in this scope. | ||
| std::vector<Expression*> exprStack; | ||
|
|
||
| // Whether we have seen an unreachable instruction and are in | ||
| // stack-polymorphic unreachable mode. | ||
| bool unreachable = false; | ||
|
|
@@ -338,29 +349,39 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| size_t startPos = 0; | ||
|
|
||
| ScopeCtx() : scope(NoScope{}) {} | ||
| ScopeCtx(Scope scope) : scope(scope) {} | ||
| ScopeCtx(Scope scope, Name label, bool labelUsed) | ||
| : scope(scope), label(label), labelUsed(labelUsed) {} | ||
| ScopeCtx(Scope scope, Type inputType) | ||
| : scope(scope), inputType(inputType) {} | ||
| ScopeCtx( | ||
| Scope scope, Name label, bool labelUsed, Type inputType, Index inputLocal) | ||
| : scope(scope), label(label), labelUsed(labelUsed), inputType(inputType), | ||
| inputLocal(inputLocal) {} | ||
| ScopeCtx(Scope scope, Name label, bool labelUsed, Name branchLabel) | ||
| : scope(scope), label(label), branchLabel(branchLabel), | ||
| labelUsed(labelUsed) {} | ||
|
|
||
| static ScopeCtx makeFunc(Function* func) { | ||
| return ScopeCtx(FuncScope{func}); | ||
| return ScopeCtx(FuncScope{func}, Type::none); | ||
| } | ||
| static ScopeCtx makeBlock(Block* block) { | ||
| return ScopeCtx(BlockScope{block}); | ||
| static ScopeCtx makeBlock(Block* block, Type inputType) { | ||
| return ScopeCtx(BlockScope{block}, inputType); | ||
| } | ||
| static ScopeCtx makeIf(If* iff, Name originalLabel = {}) { | ||
| return ScopeCtx(IfScope{iff, originalLabel}); | ||
| static ScopeCtx makeIf(If* iff, Name originalLabel, Type inputType) { | ||
| return ScopeCtx(IfScope{iff, originalLabel}, inputType); | ||
| } | ||
| static ScopeCtx | ||
| makeElse(If* iff, Name originalLabel, Name label, bool labelUsed) { | ||
| return ScopeCtx(ElseScope{iff, originalLabel}, label, labelUsed); | ||
| static ScopeCtx makeElse(If* iff, | ||
| Name originalLabel, | ||
| Name label, | ||
| bool labelUsed, | ||
| Type inputType, | ||
| Index inputLocal) { | ||
| return ScopeCtx( | ||
| ElseScope{iff, originalLabel}, label, labelUsed, inputType, inputLocal); | ||
| } | ||
| static ScopeCtx makeLoop(Loop* loop) { return ScopeCtx(LoopScope{loop}); } | ||
| static ScopeCtx makeTry(Try* tryy, Name originalLabel = {}) { | ||
| return ScopeCtx(TryScope{tryy, originalLabel}); | ||
| static ScopeCtx makeLoop(Loop* loop, Type inputType) { | ||
| return ScopeCtx(LoopScope{loop}, inputType); | ||
| } | ||
| static ScopeCtx makeTry(Try* tryy, Name originalLabel, Type inputType) { | ||
| return ScopeCtx(TryScope{tryy, originalLabel}, inputType); | ||
| } | ||
| static ScopeCtx makeCatch(Try* tryy, | ||
| Name originalLabel, | ||
|
|
@@ -378,8 +399,9 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| return ScopeCtx( | ||
| CatchAllScope{tryy, originalLabel}, label, labelUsed, branchLabel); | ||
| } | ||
| static ScopeCtx makeTryTable(TryTable* trytable, Name originalLabel = {}) { | ||
| return ScopeCtx(TryTableScope{trytable, originalLabel}); | ||
| static ScopeCtx | ||
| makeTryTable(TryTable* trytable, Name originalLabel, Type inputType) { | ||
| return ScopeCtx(TryTableScope{trytable, originalLabel}, inputType); | ||
| } | ||
|
|
||
| bool isNone() { return std::get_if<NoScope>(&scope); } | ||
|
|
@@ -518,6 +540,7 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| } | ||
| WASM_UNREACHABLE("unexpected scope kind"); | ||
| } | ||
| bool isDelimiter() { return getElse() || getCatch() || getCatchAll(); } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is Delegate not a delimiter? (if not, what does that term mean here?)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By delimiter here, I mean a separator between different sections of a control flow structure with multiple bodies. Delegate does not introduce a new control flow body, so it is more like an extra fancy |
||
| }; | ||
|
|
||
| // The stack of block contexts currently being parsed. | ||
|
|
@@ -541,7 +564,7 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| Index blockHint = 0; | ||
| Index labelHint = 0; | ||
|
|
||
| void pushScope(ScopeCtx scope) { | ||
| Result<> pushScope(ScopeCtx&& scope) { | ||
| if (auto label = scope.getOriginalLabel()) { | ||
| // Assign a fresh label to the scope, if necessary. | ||
| if (!scope.label) { | ||
|
|
@@ -554,7 +577,21 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| scope.startPos = lastBinaryPos; | ||
| lastBinaryPos = *binaryPos; | ||
| } | ||
| scopeStack.push_back(scope); | ||
| bool hasInput = scope.inputType != Type::none; | ||
| Index inputLocal = scope.inputLocal; | ||
| if (hasInput && !scope.isDelimiter()) { | ||
| if (inputLocal == Index(-1)) { | ||
| auto scratch = addScratchLocal(scope.inputType); | ||
| CHECK_ERR(scratch); | ||
| inputLocal = scope.inputLocal = *scratch; | ||
| } | ||
| CHECK_ERR(makeLocalSet(inputLocal)); | ||
| } | ||
| scopeStack.emplace_back(std::move(scope)); | ||
| if (hasInput) { | ||
| CHECK_ERR(makeLocalGet(inputLocal)); | ||
| } | ||
| return Ok{}; | ||
| } | ||
|
|
||
| ScopeCtx& getScope() { | ||
|
|
@@ -610,6 +647,8 @@ class IRBuilder : public UnifiedExpressionVisitor<IRBuilder, Result<>> { | |
| Result<Type> getLabelType(Index label); | ||
| Result<Type> getLabelType(Name labelName); | ||
|
|
||
| void fixLoopWithInput(Loop* loop, Type inputType, Index scratch); | ||
|
|
||
| void dump(); | ||
| }; | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect it to make basically no difference after optimizations, so I'm not sure we need to be this conservative in the changelog note. If producers can get simpler unoptimized modules by using control flow inputs and it basically makes no change to their optimized output, then it's a still a net win.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, yeah, so my wording wasn't good. But still, I can imagine someone does a bunch of work to use inputs to control flow, and expect Binaryen to preserve that benefit, which seems surprising when it doesn't. Maybe a note that we do not emit such shapes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expanded the note to say that we lower the input parameters away, which I think is the important information here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sgtm though I don't see that pushed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, my bad. Will fix.