Skip to content
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

rethrow's immediate arguments #74

Closed
aheejin opened this issue Dec 3, 2018 · 9 comments
Closed

rethrow's immediate arguments #74

aheejin opened this issue Dec 3, 2018 · 9 comments

Comments

@aheejin
Copy link
Member

aheejin commented Dec 3, 2018

In the current spec, rethrow does not take any immediate argument, and only pops except_ref from the top of stack and rethrows it.

I'm planning to add two immediate arguments to rethrow. These two are not new; they have been discussed previously.

Depths to break out of

Branches can break out of arbitrary number of blocks and loops thanks to their immediate depth argument, but rethrow currently does not have it. So I am planning to add a depth immediate argument to rethrow, which was first proposed in #29 (comment).

One thing we should consider is, depth calculation should be slightly different from that of branches. I'd like to make the depth calculation for rethrow the same as that of branches, but for branches, the depth is increased at block start instructions (block, loop, and try) and is decreased at block end instruction (end), whereas for rethrows, the depth should be decreased not at block end instruction (end) but at catch instruction. For example,

try
  try
    br 0       // branches to (2)
    rethrow 0  // rethrows to (1)
  catch        <-- (1)
    br 0       // branches to (2)
    rethrow 0  // rethrows to (3)
  end
  ...          <-- (2)
catch          <-- (3)
end

Here two br instructions are within the same try~end scope, so they branch to the same place. But two rethrow instructions rethrow to different places, because the level is decreased not at end but at catch.

To resolve this, the current LLVM toolchain implemenation maintains separate EH stack for rethrow depth calculation, in the way that EH stack depth is only incremented with try and decremented with catch and does not count other blocks or loops. As in the example below, rethrow does not count all blocks when computing depth.

try                                                                              
  block                                                                          
    block                                                                        
      block                                                                      
        block                                                                    
          try                                                                    
            rethrow 0  // rethrows to (1)                                        
            rethrow 1  // rethrows to (2)                                        
          catch        <-- (1)                                                   
          end                                                                    
        end                                                                      
      end                                                                        
    end                                                                          
  end                                                                            
catch                                                                            
end                    <-- (2)

@mstarzinger and I discussed how to compute rethrow's depth argument over email chain a little, and I'm also not sure if we settled on a conclusion.

Which exception to rethrow

The first version of EH proposal had an immediate argument to specify which exceptions on the stack to rethrow. So

try
  ...
catch 1
  ...
  block
    ...
    try
      ...
    catch 2
      ...
      try
        ...
      catch 3
        ...
        rethrow N
      end
    end
  end
  ...
end

In this example, N is used to disambiguate which caught exception is being rethrown. It could rethrow any of the three caught exceptions. Hence, rethrow 0 corresponds to the exception caught by catch 3, rethrow 1 corresponds to the exception caught by catch 2, and rethrow 3 corresponds to the exception caught by catch 1.

I don't see any use cases of this in C++, but I recall @rossberg suggested to keep this for other languages. Is that right?

@aheejin aheejin changed the title rethrow's immediate arguments rethrow's immediate arguments Dec 3, 2018
@aheejin
Copy link
Member Author

aheejin commented Dec 3, 2018

In #73 (comment), @rossberg said

I don't understand. I thought that with the current design, rethrow doesn't have any immediate anymore, and that you can simply use a regular branch?

So suppose we have this code. The rethrow instruction here wants to rethrow to (1).

try
  other instructions*
  try
    rethrow 1    // rethrows to (1)
  catch
  end
catch            <-- (1)
end

Do you mean, we can convert the above to this?

try
  other instructions*
  block
    try
      br 1
    catch
    end
  end
  rethrow
catch         <-- (1)
end

This is already more code, and in this case those other instructions have to somehow skip the newly added rethrow to maintain the semantics. How can we manage that? Maybe we can add additional ifs or branches and state keeping variables, but that's even more code.

@rossberg
Copy link
Member

rossberg commented Dec 3, 2018

Well, all this becomes unnecessary with first-class exception references. Which exception you throw is a stack operand. And targeting try-catch blocks is no longer needed since you can use regular branches with exception ref argument to jump to a handler.

@aheejin
Copy link
Member Author

aheejin commented Dec 3, 2018

Oh I think I see what you mean. So you mean we can convert

try
  other instructions*
  try
    rethrow 1    // rethrows to (1)
  catch
  end
catch            <-- (1)
  handler code
end
following code

to this, by factoring out the handler code to outside?

block
  try
    skip_handler = 1
    other instructions*
    try
      skip_handler = 0
      br 1
    catch
    end
  catch            <-- (1)
    skip_handler = 0
    br 0
  end
  br_if skip_handler 0
  handler code
end
following code

Well, it looks possible, but it will complicates code generation, because every catch handler code that's targeted by more than one scope has to be all factored out. And this is more code too, because we need more branches, and the factored out handler code should be skipped in some case not in other cases...

@rossberg
Copy link
Member

rossberg commented Dec 3, 2018

I think it's even simpler:

block $b
  try $t
    other instructions*
    try
      br $t
    catch
    end
    br $b
  catch
  end
  handler code
end
following code

@aheejin
Copy link
Member Author

aheejin commented Dec 3, 2018

I think my code examples were a bit inaccurate. What I tried to say is, without rethrow's depth argument, code size is much bigger (especially in case of nested try-catches) and code generation becomes unnecessarily complicated.

Nested example w/ rethrow depth:

try $t                                                                           
  try                                                                            
    try                                                                          
      try                                                                        
      catch                                                                      
        rethrow $t   // rethrows to (1)                                          
      end                                                                        
    catch                                                                        
    end                                                                          
  catch                                                                          
  end                                                                            
catch               <-- (1)                                                      
  handler code                                                                   
end                                                                              
following code   

Without rethrow's depth argument, we have to factor out 'handler code' part to outside. Then there has to be a lot more branches to skip the 'handler code' part.

block $b                                                                         
  try $t                                                                         
    try                                                                          
      try                                                                        
        try                                                                      
        catch                                                                    
          br $t                                                                  
        end                                                                      
        br $b         // to skip 'handler code'                                  
      catch                                                                      
        br $b         // to skip 'handler code'                                  
      end                                                                        
      br $b           // to skip 'handler code'                                  
    catch                                                                        
      br $b           // to skip 'handler code'                                  
    end                                                                          
    br $b             // to skip 'handler code'                                  
  catch               <-- (1)                                                    
    br $t                                                                        
  end                                                                            
  handler code                                                                   
end                                                                              
following code 

So now we have one more block, one more end, and 5 more brs, and one less rethrow. Apparently rethrow's depth argument helps code size and code generation. What's the rational against it?

And do you still think we should restore the second argument that specifies which exception to rethrow?

@rossberg
Copy link
Member

rossberg commented Dec 3, 2018

AFAICS, your two versions are very different: the original does not do the equivalent of all the br $bs. For the equivalent of the original you still only need one extra br:

block $b
  try $t                                                                           
    try                                                                            
      try                                                                          
        try                                                                        
        catch                                                                      
          br $t                                      
        end                                                                        
      catch                                                                        
      end                                                                          
    catch                                                                          
    end                                                                            
    br $b
  catch   
  end
  handler code                                                                   
end                                                                              
following code

In general, you need one block and one extra branch for a handler you want to target with a "rethrow". Assuming that try-catch blocks are dominated by the size of the inner code that is a negligible code size change, I'd argue.

@aheejin
Copy link
Member Author

aheejin commented Dec 3, 2018

Oh, right... I didn't need those many brs. I was confused. Sorry. But anyway having depth-argument rethrow is convenient and better for code size. Having to extract handler part every time that's targeted by more than one scopes seems also unnecessarily complicated. Is there any reason against having it? (I actually thought we kinda agreed on having it and implemented the toolchain based on it, and opened this thread just to discuss how to compute depth argument...)

And what do you think about the second argument, by which we specify which exception to rethrow?

@rossberg
Copy link
Member

rossberg commented Dec 3, 2018

All the previous discussions were in the context of the other proposal variant. For some (including me) it actually was one of the key advantages of this one that it made these rethrow oddities completely unnecessary. ;)

The code size win seems way too small to still justify introducing yet another special concept. That leaves code generation convenience. But we can essentially implement try with extended rethrow as just a desugaring on top of try as is, so I would be surprised if it was very difficult to handle in a codegen, compared to everything else. Do you see a problem with it?

@aheejin
Copy link
Member Author

aheejin commented Feb 25, 2021

I think this was also resolved by #137.

@aheejin aheejin closed this as completed Feb 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants