Description
The following is the new instruction delegate
's semantics, taken from my LLVM CL's comment:
Linearizing the control flow by placing TRY / END_TRY markers can create
mismatches in unwind destinations for throwing instructions, such as calls.
We use the 'delegate' instruction to fix the unwind mismatches. 'delegate'
instruction delegates an exception to an outer 'catch'. It can target not
only 'catch' but all block-like structures including another 'delegate',
but with slightly different semantics than branches. When it targets a
'catch', it will delegate the exception to that catch. It is being
discussed how to define the semantics when 'delegate''s target is a non-try
block: it will either be a validation failure or it will target the next
outer try-catch. But anyway our LLVM backend currently does not generate
such code. The example below illustrates where the 'delegate' instruction
in the middle will delegate the exception to, depending on the value of N.
try
try
block
try
try
call @foo
delegate N ;; Where will this delegate to?
catch ;; N == 0
end
end ;; N == 1 (invalid; will not be generated)
delegate ;; N == 2
catch ;; N == 3
end
;; N == 4 (to caller)
So delegate
's immediate field can target all control flow structures including block
s and loop
s, like br
s. But if the target is block
or loop
, it will be a validation failure. When it targets a try
, it is really targetting not try
but its catch
.
Then here comes the catch: Currently if
and try
do not take label names; they use wrapping blocks or inner blocks to achieve the same semantics, mostly because there are too many places to handle if we add a new control flow structure that can have a label. But if we want to give delegate
a label, it should target a try
(more precisely its catch
), and if it targets a block
, it will be a validation failure. But I'm not sure if we want to give try
a label and add its handling in a bunch of places, which is exactly what we wanted to avoid when we implemented if
and try
this way.
We are already doing some ugly things to implement try-catch.
- We create an inner block within
catch
, but we assume that block is removed when we print the text/binary. The reason it should be removed is catch body should start with apop
instruction (or a value on the stack in binary), andblock
should not interfere in between. This is easily done when there's no branch that targets the inner block. - When there is
br 0
within acatch
, we wrap the whole try-catch with ablock
and make thebr
target it, not the inner block withincatch
, for the reason I described in 1. (Fix inner block problem with 'catch' #3129)
We can probably do a similar thing for delegate
's target... Like, wrap the target try-catch with a block
and make delegate
target that instead, and we delete that block
when we write the binary/text...? But unlike the wrapping block we created for br
, delegate
targetting a block
is a validation failure and it must be removed when we write that out. Also in this case we should maintain the relationship between the wrapping block
and try
for the whole optimization pipeline, i.e., no other instruction should interfere between them and the block
should not be removed.
Not sure what is the cleaniest way. Any ideas?
@kripken @tlively