@@ -2441,7 +2441,7 @@ BinaryConsts::ASTNodes WasmBinaryBuilder::readExpression(Expression*& curr) {
2441
2441
visitRefFunc ((curr = allocator.alloc <RefFunc>())->cast <RefFunc>());
2442
2442
break ;
2443
2443
case BinaryConsts::Try:
2444
- visitTry (( curr = allocator. alloc <Try>())-> cast <Try>() );
2444
+ visitTryOrTryInBlock ( curr);
2445
2445
break ;
2446
2446
case BinaryConsts::Throw:
2447
2447
visitThrow ((curr = allocator.alloc <Throw>())->cast <Throw>());
@@ -2692,21 +2692,11 @@ void WasmBinaryBuilder::visitBlock(Block* curr) {
2692
2692
}
2693
2693
2694
2694
// Gets a block of expressions. If it's just one, return that singleton.
2695
- // numPops is the number of pop instructions we add before starting to parse the
2696
- // block. Can be used when we need to assume certain number of values are on top
2697
- // of the stack in the beginning.
2698
- Expression* WasmBinaryBuilder::getBlockOrSingleton (Type type,
2699
- unsigned numPops) {
2695
+ Expression* WasmBinaryBuilder::getBlockOrSingleton (Type type) {
2700
2696
Name label = getNextLabel ();
2701
2697
breakStack.push_back ({label, type});
2702
2698
auto start = expressionStack.size ();
2703
2699
2704
- Builder builder (wasm);
2705
- for (unsigned i = 0 ; i < numPops; i++) {
2706
- auto * pop = builder.makePop (Type::exnref);
2707
- pushExpression (pop);
2708
- }
2709
-
2710
2700
processExpressions ();
2711
2701
size_t end = expressionStack.size ();
2712
2702
if (end < start) {
@@ -2757,12 +2747,12 @@ void WasmBinaryBuilder::visitLoop(Loop* curr) {
2757
2747
auto start = expressionStack.size ();
2758
2748
processExpressions ();
2759
2749
size_t end = expressionStack.size ();
2750
+ if (start > end) {
2751
+ throwError (" block cannot pop from outside" );
2752
+ }
2760
2753
if (end - start == 1 ) {
2761
2754
curr->body = popExpression ();
2762
2755
} else {
2763
- if (start > end) {
2764
- throwError (" block cannot pop from outside" );
2765
- }
2766
2756
auto * block = allocator.alloc <Block>();
2767
2757
pushBlockElements (block, curr->type , start);
2768
2758
block->finalize (curr->type );
@@ -4834,8 +4824,9 @@ void WasmBinaryBuilder::visitRefFunc(RefFunc* curr) {
4834
4824
curr->finalize ();
4835
4825
}
4836
4826
4837
- void WasmBinaryBuilder::visitTry (Try* curr ) {
4827
+ void WasmBinaryBuilder::visitTryOrTryInBlock (Expression*& out ) {
4838
4828
BYN_TRACE (" zz node: Try\n " );
4829
+ auto * curr = allocator.alloc <Try>();
4839
4830
startControlFlow (curr);
4840
4831
// For simplicity of implementation, like if scopes, we create a hidden block
4841
4832
// within each try-body and catch-body, and let branches target those inner
@@ -4845,11 +4836,84 @@ void WasmBinaryBuilder::visitTry(Try* curr) {
4845
4836
if (lastSeparator != BinaryConsts::Catch) {
4846
4837
throwError (" No catch instruction within a try scope" );
4847
4838
}
4848
- curr->catchBody = getBlockOrSingleton (curr->type , 1 );
4839
+
4840
+ // For simplicity, we create an inner block within the catch body too, but the
4841
+ // one within the 'catch' *must* be omitted when we write out the binary back
4842
+ // later, because the 'catch' instruction pushes a value onto the stack and
4843
+ // the inner block does not support block input parameters without multivalue
4844
+ // support.
4845
+ // try
4846
+ // ...
4847
+ // catch ;; Pushes a value onto the stack
4848
+ // block ;; Inner block. Should be deleted when writing binary!
4849
+ // use the pushed value
4850
+ // end
4851
+ // end
4852
+ //
4853
+ // But when input binary code is like
4854
+ // try
4855
+ // ...
4856
+ // catch
4857
+ // br 0
4858
+ // end
4859
+ //
4860
+ // 'br 0' accidentally happens to target the inner block, creating code like
4861
+ // this in Binaryen IR, making the inner block not deletable, resulting in a
4862
+ // validation error:
4863
+ // (try
4864
+ // ...
4865
+ // (catch
4866
+ // (block $label0 ;; Cannot be deleted, because there's a branch to this
4867
+ // ...
4868
+ // (br $label0)
4869
+ // )
4870
+ // )
4871
+ // )
4872
+ //
4873
+ // When this happens, we fix this by creating a block that wraps the whole
4874
+ // try-catch, and making the branches target that block instead, like this:
4875
+ // (block $label ;; New enclosing block, new target for the branch
4876
+ // (try
4877
+ // ...
4878
+ // (catch
4879
+ // (block ;; Now this can be deleted when writing binary
4880
+ // ...
4881
+ // (br $label0)
4882
+ // )
4883
+ // )
4884
+ // )
4885
+ // )
4886
+ Name catchLabel = getNextLabel ();
4887
+ breakStack.push_back ({catchLabel, curr->type });
4888
+ auto start = expressionStack.size ();
4889
+
4890
+ Builder builder (wasm);
4891
+ pushExpression (builder.makePop (Type::exnref));
4892
+
4893
+ processExpressions ();
4894
+ size_t end = expressionStack.size ();
4895
+ if (start > end) {
4896
+ throwError (" block cannot pop from outside" );
4897
+ }
4898
+ if (end - start == 1 ) {
4899
+ curr->catchBody = popExpression ();
4900
+ } else {
4901
+ auto * block = allocator.alloc <Block>();
4902
+ pushBlockElements (block, curr->type , start);
4903
+ block->finalize (curr->type );
4904
+ curr->catchBody = block;
4905
+ }
4849
4906
curr->finalize (curr->type );
4850
- if (lastSeparator != BinaryConsts::End) {
4851
- throwError (" try should end with end" );
4907
+
4908
+ if (breakTargetNames.find (catchLabel) == breakTargetNames.end ()) {
4909
+ out = curr;
4910
+ } else {
4911
+ // Create a new block that encloses the whole try-catch
4912
+ auto * block = builder.makeBlock (catchLabel, curr);
4913
+ out = block;
4852
4914
}
4915
+ breakStack.pop_back ();
4916
+ breakTargetNames.erase (catchLabel);
4853
4917
}
4854
4918
4855
4919
void WasmBinaryBuilder::visitThrow (Throw* curr) {
0 commit comments