LLVM Exception Handling

Hi guys,

I have begun a modification to the invoke/unwind instructions. The following .ll file demonstrates the change.

define i32 @v(i32 %o) {

%r = icmp eq i32 %o, 0

br i1 %r, label %raise, label %ok

ok:
%m = mul i32 %o, 2
ret i32 %m

raise:
%ex = inttoptr i32 255 to i8 *

; unwind now takes an i8* “exception” pointer
unwind i8* %ex
}

define i32 @g(i32 %o) {
entry:
; invoke produces a different value depending on whether if
; branches to the success case or the failure case.
%s = invoke i32 @v(i32 %o) to label %ok
unwind %x to label %catch
ok:
ret i32 %s

catch:
%v = ptrtoint i8 * %x to i32
%r = icmp eq i32 %v, 255
br i1 %r, label %bad, label %worse

bad:
ret i32 -1

worse:
ret i32 -2
}

With my current change, the unwind instruction is able to pass a value to the unwind branch of the invoke instruction. I was able to coax LLVM into generating correct code for this using the LowerInvoke pass generating expensive but correct code via setjmp/longjmp.

The unwind instruction now takes a single i8* parameter. This value is propagated to the nearest invoke instruction that generated the call to the function containing the unwind instruction.

The invoke instruction now generated one of two different values depending on how the call exits. If the call exits via a return instruction, the invoke instruction generates a return value (denoted by %s in the sample code). If the call exits via an unwind instruction, the invoke generates an exception value (denoted by %x in the sample code). The return value is only valid if the invoke branches to the return branch. The exception value is only valid if the invoke instruction branches to the unwind branch.

For sources that are not attempting to integrate into a third parting exception handling mechanism (gcc, or SEH), this would be enough to implement exception handling. When integrating into external exception handling mechanisms, the “exception” value generated from the invoke instruction would replace the call the ‘eh.exception’ intrinsic, and would have the benefit of making it much easier for analysis passes to associate this value with the invoke that generated it. For the unwind, if all thats needed is an exception pointer than an unwind instruction could be used, and lowered to the appropriate runtime library.

To make this work, the fundamental concept that an instruction always produces a single value needs to change. This concept was already somewhat violated by the invoke instruction since if it branched to the unwind block, the return value was not actually generated. But in its existing form, it looks like it only generates one value. As far as SSA is concerned, I don’t see any problem with an operation generating multiple values under different circumstances since there is still only one source for any value. As long as the block being branched to dominates any usage of the respective value I think its correct and optimizations should be able to perform correctly.

Unfortunately, the fact that a value and the instruction that generates it are one and the same makes it very difficult to generate a representation where a single instruction can generate more that one value. My current solution (which feels wrong) is to have the invoke instruction own an additional “exception” value that represents the value that is generated when continuing via the unwind branch. This value is quite different from other values and therefore inherits directly from llvm::Value. When lowering the invoke instruction the LowerInvoke pass replaces usage of this “exception” value with the return value of the setjmp call after is has been determined that the setjmp returned from a longjmp. When lowering the unwind instruction the LowerInvoke pass puts the argument to the unwind instruction as the value parameter to the longjmp call.

While the lowering of this representation seemed natural, parsing it has proven difficult. This “exception” value must be in the functions symbol table, but in the current structure of the parser, the name of the instructions value is not and cannot be set until after it has been added to the containing basic block. The problem is that at that point, the parser doesn’t know that the instruction produces another value, and even if it did, it has lost the needed information to properly register the name with the symbol table. To get past this point, I put a nasty hack in place. I gave LLParser permission to see the internals of instruction so I could temporarily assign the invoke instructions parent pointer ahead of time so that the call to setName on the “exception” value could succeed. Once this value is in the symbol table, there is currently no way to get it out. The code that removes an Instruction’s entry from the symbol table is unaware of the additional value that needs to be removed. This causes a seemingly benign assertion at shutdown about the symbol table not being empty.

Bitcode I/O is also another problem, in my current build, it is broken. There is currently no way to bind to the “exception” value of the invoke instruction. I have yet to look into this in any way as it was not needed to get my sample code through to the code generator.

In closing, I am looking for some feedback as the whether this approach makes sense. I would also like to know if anyone has any suggestions on how to deal with some of the issues. I have included a patch with the changes I have made so far. It is still very rough but I though it might be usefull.

-Nathan

exception.patch (24.1 KB)

If I understood correctly, you're trying to pass the clean-up flag
through %x directly on the invoke call. But later avoid the
eh.exception call, assuming that was in %x.

The problem is that you're mixing two concepts: The exception
structure contains information about the object that was thrown and
not a "good" number. That's the role of the clean-up flag (in case the
catch blocks can't deal with the exception) or the landing pads (that
should reflect the return values the user asked for in their
programs).

It's the users role to tell what's good and what's not (return values
included). the only thing you (compiler) can do is to explode
prematurely in case you can't properly catch the error (ie. throw
inside throw, throw inside delete, etc).

If that's the case, your implementation will not work for dwarf
exceptions, and I wouldn't recommend having an *invoke* syntax for
each type of exception handling mechanism.

Other question: why are you passing untyped %x? I haven't seen any
untyped variable in LLVM, so far, and I think it's good to be
redundant in this case. That alone would have caught the mistake. If
you need an i32 (for your bad/worse comparison), throwing i8* would
have hinted that you crossed the concepts.

On a side note...

Exception handling was designed by the devil himself. Part of the flow
control is designed by the user (try/catch blocks, throw
specifications), part of it is designed by the compiler, in exception
tables (specific unwinding instructions and types), and part by the
library writers (unwinding and personality routines). All that,
decided in three different time frames, by three different kinds of
developers, have to communicate perfectly in run time.

It'd be very difficult for the compiler to optimize automatically
without breaking run-time assumptions. All of that is controlled by
different ABIs, that make sure all three universes are talking the
same language. You can't change one without changing all the others...

To be honest, I'm still surprised that it actually works at all! :wink:

I may me wrong, but I think Nathan used ints for demonstration
purposes only. unwind always takes i8* argument that ideally should be
a pointer to exception structure, variable %x in invoke is also typed
i8*, it's not "untyped". Probably more llvm-ish syntax would be

unwind i8* %x to label %catch

to show the type explicitly.
However throwing a pointer to a structure raises questions about
structure's ownership, so I think Nathan cheated a bit and threw an
int to make the code snippet simpler. Landing pad code then checked
the int, whereas real code would expect exception object.
"Real" code would look more like:

define i32 @v(i32 %o) {

  %r = icmp eq i32 %o, 0
  
  br i1 %r, label %raise, label %ok

ok:
  %m = mul i32 %o, 2
  ret i32 %m

raise:
  %ex = call i8* @allocate_exception()
  call void @init_div_by_0_eh(i8* %ex)

  ; unwind now takes an i8* "exception" pointer
  unwind i8* %ex
}

define i32 @g(i32 %o) {
entry:
  ; invoke produces a different value depending on whether if
  ; branches to the success case or the failure case.
  %s = invoke i32 @v(i32 %o) to label %ok
                   unwind %x to label %catch
ok:
  ret i32 %s

catch:
  %type = call i32 @exception_type(i8* %x)
  %r = icmp eq i32 %type, 255 ; 255 is DivisionByZeroException type
  br i1 %r, label %bad, label %worse
  
bad:
  ret i32 -1

worse:
  ret i32 -2
}

Nathan -- is this approach simpler than using intrinsics @eh.throw
(assuming it's added) and @eh.exception? The latter seems more
flexible in supporting various levels of ABI (I think ideally LLVM
exception handling should follow general platform ABI and also allow
front-ends for specific languages generate code in accordance with
language specific ABI).
I going with invoke instruction that returns exception pointer (which
feels right to me) maybe this is a good candidate for using union type
-- invoke can produce a single result which is either normal return
value or an exception pointer, since only one of the two values can be
actually produced. This sounds logical but may be taking us too far
from ABIs.

Eugene

catch:
%v = ptrtoint i8 * %x to i32
%r = icmp eq i32 %v, 255
br i1 %r, label %bad, label %worse
bad:
ret i32 -1
worse:
ret i32 -2
}

If I understood correctly, you’re trying to pass the clean-up flag
through %x directly on the invoke call. But later avoid the
eh.exception call, assuming that was in %x.

The problem is that you’re mixing two concepts: The exception
structure contains information about the object that was thrown and
not a “good” number. That’s the role of the clean-up flag (in case the
catch blocks can’t deal with the exception) or the landing pads (that
should reflect the return values the user asked for in their
programs).

It’s the users role to tell what’s good and what’s not (return values
included). the only thing you (compiler) can do is to explode
prematurely in case you can’t properly catch the error (ie. throw
inside throw, throw inside delete, etc).

The argument to the unwind instruction is always an i8* pointer, which in the case of dwarf exception handling would be the allocated exception object. I did cheat for the example, but this also demonstrates that an arbitrary value could be passed if using an LLVM specific exception handling implementation like the setjmp/longjmp version provided by the LowerInvoke pass.

If that’s the case, your implementation will not work for dwarf
exceptions, and I wouldn’t recommend having an invoke syntax for
each type of exception handling mechanism.

Other question: why are you passing untyped %x? I haven’t seen any
untyped variable in LLVM, so far, and I think it’s good to be
redundant in this case. That alone would have caught the mistake. If
you need an i32 (for your bad/worse comparison), throwing i8* would
have hinted that you crossed the concepts.

The syntax for the invoke instruction is a little misleading. %x is a value that is being generated by the instruction, not passed to is. It is no different in that regard as to say ‘%x = call @eh.exception …’. Since you don’t specify the type in that type of assignment, I chose not to here either.

On a side note…

Exception handling was designed by the devil himself. Part of the flow
control is designed by the user (try/catch blocks, throw
specifications), part of it is designed by the compiler, in exception
tables (specific unwinding instructions and types), and part by the
library writers (unwinding and personality routines). All that,
decided in three different time frames, by three different kinds of
developers, have to communicate perfectly in run time.

It’d be very difficult for the compiler to optimize automatically
without breaking run-time assumptions. All of that is controlled by
different ABIs, that make sure all three universes are talking the
same language. You can’t change one without changing all the others…

I agree; this change is not attempting to change how exception handling works, just provide a small change in how it is represented in the IR to make it more direct. Especially for users not using gcc/dwarf exception handling (I hope to attempt an SEH implementation)

To be honest, I’m still surprised that it actually works at all! :wink:


cheers,
–renato

http://systemcall.org/

Reclaim your digital rights, eliminate DRM, learn more at
http://www.defectivebydesign.org/what_is_drm

Thanks for the feedback

-Nathan

The syntax for the invoke instruction is a little misleading. %x is a value
that is being generated by the instruction, not passed to is. It is no
different in that regard as to say '%x = call @eh.exception ...'. Since you
don't specify the type in that type of assignment, I chose not to here
either.

I see, you're saving a call to @llvm.eh.exception in the IR. But is
that really necessary?

I mean, if you're using sj/lj you can always optimize them away if not
using, but on dwarf exceptions that is really a bonus. Several invokes
can unwind to a single landing pad, so it makes more sense to put the
call on the landing pad, because it is there that the exception
structure is going to be used.

Nevertheless, the docs say having the eh.exception in the landing pad
is actually a limitation of the IR, but I honestly don't know what
that limitation is. That could be what you're trying to fix, but I can
only see that it'll be better for sj/lj, I can't see how that's better
for dwarf exceptions.

I agree; this change is not attempting to change how exception handling
works, just provide a small change in how it is represented in the IR to
make it more direct. Especially for users not using gcc/dwarf exception
handling (I hope to attempt an SEH implementation)

I agree that it's simpler for sj/lj, but you have to be careful not to
break the dwarf contract between the three parties: eh-lib, eh-tables
and try/catch blocks. It is really tricky to make sure that contract
is working, the easiest being having a huge test codebase to run
before/after your changes and the surest having Knuth to prove it's
right. :wink:

Whatever your changes are, just remember that the syntax has to make
sense for dwarf EH users as well. Removing clutter from sj/lj and
introducing it to dwarf is no gain.

I may me wrong, but I think Nathan used ints for demonstration
purposes only. unwind always takes i8* argument that ideally should be
a pointer to exception structure, variable %x in invoke is also typed
i8*, it’s not “untyped”. Probably more llvm-ish syntax would be

unwind i8* %x to label %catch

to show the type explicitly.

Yes, I didn’t specify a type because %x is an assignment (like ‘%x = call @eh.exception …’). Admittedly this specify syntax is misleading. I think a syntax like:

invoke i32 @v(i32 %o)
success %s to label %ok
unwind %x to label %catch

would be more consistent and have the benefit better indicating what value is generated when branching to what label.

[snip]

Nathan – is this approach simpler than using intrinsics @eh.throw
(assuming it’s added) and @eh.exception? The latter seems more
flexible in supporting various levels of ABI (I think ideally LLVM
exception handling should follow general platform ABI and also allow
front-ends for specific languages generate code in accordance with
language specific ABI).

I don’t know the specifics of all the platforms that LLVM may run on so this is a hard question to answer. I think looking at this as a representation and not an implementation may help. If it is assumed that all exception handling mechanisms fundamentally pass pointers to exception information than this representation would work, and it would be lowered to the appropriate platform specific representation by a platform specific pass. Another approach (that should be selectable by the compiler) would be for the code generator to lower this representation into what ever it would take to pass the unwind pointer argument to the catch block referenced by the invoke instruction to allow the compiler to be platform agnostic at the cost of some flexibility with regards to the types of options available with platform specific implementations. I do think that a direct representation is more desirable than one implemented via intrinsics. I think that that makes it easier for passes to deal with.

I going with invoke instruction that returns exception pointer (which
feels right to me) maybe this is a good candidate for using union type
– invoke can produce a single result which is either normal return
value or an exception pointer, since only one of the two values can be
actually produced. This sounds logical but may be taking us too far
from ABIs.

This could work too if unions are going to be supported. Perhaps this could be another reason why LLVM needs unions. I don’t now any details on the semantics of the existing union support, but for this purpose I think I can see a set of semantics. It would revolve around have a way tie what field of the union is valid to one or more basic blocks.

[snip]

Thanks for the feedback,

-Nathan

%s = invoke i32 @v(i32 %o) to label %ok
unwind %x to label %catch
ok:
ret i32 %s

catch:
%type = call i32 @exception_type(i8* %x)
%r = icmp eq i32 %type, 255 ; 255 is DivisionByZeroException type
br i1 %r, label %bad, label %worse

bad:
ret i32 -1

worse:
ret i32 -2
}

That could be enough for SJ/LJ (I have no idea), but it certainly is
not for Dwarf exceptions. When an invoke throws an exception, the flow
doesn't go directly to the catch area. First you go to a cleanup area,
where you have to deallocate all temporaries, in which you can also
throw again, or even terminate.

Then you go the analysis of the exception structure, to see if that's
valid in accordance with the function specification and the
personality routine (when the EH table goes in context). It is only
after that that you go through the catch blocks by: first checking the
type is the same as the catch block is catching for and entering the
block if it is, or jumping to the next type check/catch block.

At the end, if it was not caught, you have to jump to another cleanup
area before throwing again, and continue with the unwinding cycle on
the caller function. ALL that is generated by the compiler and happen
oblivious to the user's code. The flow only return to the user's code
if you enter in a catch area.

Which means that, %type is NOT a number, it is a TypeInfo, and it's
not inside the catch area, but rather before it. I assume, in your
example, that the *icmp* is user code. If %x is what came from the
catch (Type& x) {}, you'll have to do one of two things to get %type:

1. Give the user an intrinsic, which breaks the EH contract
2. Give the type directly, which also breaks the contract (they also
need the value)

If the *icmp* is not user code, this is even worse, because an
exception handling code automatically generated by the compiler will
be returning a value from a function, completely breaking the EH
contract.

In other words, this approach will not work with Dwarf exceptions.

I going with invoke instruction that returns exception pointer (which
feels right to me) maybe this is a good candidate for using union type

I'm an advocate of re-introducing unions, but in this case I think
it'll rather obfuscate things even more...

Don't take me wrong, I'm not against the change, really. If the docs
say there's something wrong, I thing we should fix it, but I fear that
the issue is being seen from the sj/lj point of view only (or
mainly)...

Is there any language front-end (other than C++) using exceptions? We
might consider their views as well...

cheers,
--renato

Agreed! Exception handling is an important part of many languages and
should be represented directly.

Internally, most of the C++ unwinding functions are actually library
calls (prolly why they used intrinsics), but using direct
representation would ease not only validation passes, but also
optimization passes. In the end, they'd be lowered to the same lib
calls anyway...

The syntax for the invoke instruction is a little misleading. %x is a value
that is being generated by the instruction, not passed to is. It is no
different in that regard as to say ‘%x = call @eh.exception …’. Since you
don’t specify the type in that type of assignment, I chose not to here
either.

I see, you’re saving a call to @llvm.eh.exception in the IR. But is
that really necessary?

I mean, if you’re using sj/lj you can always optimize them away if not
using, but on dwarf exceptions that is really a bonus. Several invokes
can unwind to a single landing pad, so it makes more sense to put the
call on the landing pad, because it is there that the exception
structure is going to be used.

Nevertheless, the docs say having the eh.exception in the landing pad
is actually a limitation of the IR, but I honestly don’t know what
that limitation is. That could be what you’re trying to fix, but I can
only see that it’ll be better for sj/lj, I can’t see how that’s better
for dwarf exceptions.

I believe the perceived problem with using eh.exception is that is disassociates the source of the value with the invoke instruction that generated it. As far as reusing the landing pad, that is still possible, it would just require a phi node in the landing pad to bring all the different exception values together into one that could be processed by the exception handling code.

I agree; this change is not attempting to change how exception handling
works, just provide a small change in how it is represented in the IR to
make it more direct. Especially for users not using gcc/dwarf exception
handling (I hope to attempt an SEH implementation)

I agree that it’s simpler for sj/lj, but you have to be careful not to
break the dwarf contract between the three parties: eh-lib, eh-tables
and try/catch blocks. It is really tricky to make sure that contract
is working, the easiest being having a huge test codebase to run
before/after your changes and the surest having Knuth to prove it’s
right. :wink:

Whatever your changes are, just remember that the syntax has to make
sense for dwarf EH users as well. Removing clutter from sj/lj and
introducing it to dwarf is no gain.

Once again, I fully agree, and I think that this approach changes nothing semantically about how dwarf exception handling works.

cheers,
–renato

http://systemcall.org/

Reclaim your digital rights, eliminate DRM, learn more at
http://www.defectivebydesign.org/what_is_drm

I will continue my response in your next message.

-Nathan

%s = invoke i32 @v(i32 %o) to label %ok
unwind %x to label %catch
ok:
ret i32 %s

catch:
%type = call i32 @exception_type(i8* %x)
%r = icmp eq i32 %type, 255 ; 255 is DivisionByZeroException type
br i1 %r, label %bad, label %worse

bad:
ret i32 -1

worse:
ret i32 -2
}

That could be enough for SJ/LJ (I have no idea), but it certainly is
not for Dwarf exceptions. When an invoke throws an exception, the flow
doesn’t go directly to the catch area. First you go to a cleanup area,
where you have to deallocate all temporaries, in which you can also
throw again, or even terminate.

Then you go the analysis of the exception structure, to see if that’s
valid in accordance with the function specification and the
personality routine (when the EH table goes in context). It is only
after that that you go through the catch blocks by: first checking the
type is the same as the catch block is catching for and entering the
block if it is, or jumping to the next type check/catch block.

At the end, if it was not caught, you have to jump to another cleanup
area before throwing again, and continue with the unwinding cycle on
the caller function. ALL that is generated by the compiler and happen
oblivious to the user’s code. The flow only return to the user’s code
if you enter in a catch area.

Which means that, %type is NOT a number, it is a TypeInfo, and it’s
not inside the catch area, but rather before it. I assume, in your
example, that the icmp is user code. If %x is what came from the
catch (Type& x) {}, you’ll have to do one of two things to get %type:

  1. Give the user an intrinsic, which breaks the EH contract
  2. Give the type directly, which also breaks the contract (they also
    need the value)

If the icmp is not user code, this is even worse, because an
exception handling code automatically generated by the compiler will
be returning a value from a function, completely breaking the EH
contract.

In other words, this approach will not work with Dwarf exceptions.

For dwarf exception handling tied to gcc’s unwinding library, the minimal change to make it work with this new approach would be to replace the call to ‘eh.exception’ in the one landing pad with a phi node pulling in the ‘exception’ value of all the invoke instructions. The use of unwind to initiate the throwing of the exception could be bypassed just like it is currently.

I going with invoke instruction that returns exception pointer (which
feels right to me) maybe this is a good candidate for using union type

I’m an advocate of re-introducing unions, but in this case I think
it’ll rather obfuscate things even more…

Don’t take me wrong, I’m not against the change, really. If the docs
say there’s something wrong, I thing we should fix it, but I fear that
the issue is being seen from the sj/lj point of view only (or
mainly)…

Is there any language front-end (other than C++) using exceptions? We
might consider their views as well…

My motivation for this change is to support a compiler I am developing as a personal project. My primary development platform is windows where I currently link against either visual studio’s, or mingw’s runtime. I envision an optional platform agnostic interpretation of the unwind/invoke that would do whatever is necessary to make the value I pass into the unwind instruction come out the invoke instruction with out me needing to concern my self with the implementation. I accept that in this case, for exceptions outside my language, my program will just terminate. I think this is a reasonable limitation, especially if my program was just going to terminate anyway once it got to the root of the call stack and didn’t find a handler.

cheers,
–renato

thanks,

-Nathan

Let me see if I got it right...

Invokes have a specific landing pad, so you can define precisely all
predecessors of a landing pad. But the call to the @eh.exception can
be made anywhere. You shouldn't, but you can. Doing it via
predecessors or named values + PHI nodes yields exactly the same
semantics, but the second links the value to the exception and forces
the implementation to use the PHI nodes at the beginning of the
landing pad (since anywhere else would be an error).

In that case, you're forcing the user to implement the exception
handling correctly without using any new EH specific constructs. Makes
sense...

But, that is as far as it goes. The examples on getting the type and
relying on that won't work at all on dwarf debugging. As long as the
only difference is the named value + PHI nodes, I guess it would work.
I would recommend you to test also on Linux and Mac, especially with
GCC, to make sure it didn't hurt the current implementation.

As a secondary issue, I would prefer to have it typed, though. :wink: I
know you're creating the value, but it makes it more difficult when
building the PHI node, if you get it wrong with another value of
different type.

Sorry, I meant "Dwarf exception handling"... :wink:

I believe the perceived problem with using eh.exception is that
is disassociates the source of the value with the invoke instruction that
generated it. As far as reusing the landing pad, that is still possible, it
would just require a phi node in the landing pad to bring all the different
exception values together into one that could be processed by the exception
handling code.

Let me see if I got it right…

Invokes have a specific landing pad, so you can define precisely all
predecessors of a landing pad. But the call to the @eh.exception can
be made anywhere. You shouldn’t, but you can. Doing it via
predecessors or named values + PHI nodes yields exactly the same
semantics, but the second links the value to the exception and forces
the implementation to use the PHI nodes at the beginning of the
landing pad (since anywhere else would be an error).

In that case, you’re forcing the user to implement the exception
handling correctly without using any new EH specific constructs. Makes
sense…

But, that is as far as it goes. The examples on getting the type and
relying on that won’t work at all on dwarf debugging. As long as the
only difference is the named value + PHI nodes, I guess it would work.
I would recommend you to test also on Linux and Mac, especially with
GCC, to make sure it didn’t hurt the current implementation.

Exactly. At this point, the patch I attached to my original post doesn’t full deal with dwarf or sjlj exception handling completely. It only deals with the version of exception handling that is generated when LowerInvoke generates expensive but correct exception handling via the ‘-enable-correct-eh-support’ option. I don’t see any reason updating dwarf & sjlj to work with the new representation would be problematic. I only included the patch in-case anyone was interested in the implementation so far.

To get this to a point where it is commit ready would require more work and some coordination with at clang and a lot of testing. I believe their are some other projects using LLVM exception handling (LDC that I know of). Though I think the changes needed would be minimal.

As a secondary issue, I would prefer to have it typed, though. :wink: I
know you’re creating the value, but it makes it more difficult when
building the PHI node, if you get it wrong with another value of
different type.

I don’t understand you concern here, this case would be no different that this:


path1:
%x1 = mul i32 %a, %b
br label %final
path2:
%x2 = mul i32 %c, %d
br label %final
final:
%x = phi i32 [%x1, path1], [%x2, path2]

The “exception” value will always be i8*, it is not possible for it to be anything different.

In the end, this a minor parser detail and it is not terribly important to me one way or the other.

cheers,
–renato

http://systemcall.org/

Reclaim your digital rights, eliminate DRM, learn more at
http://www.defectivebydesign.org/what_is_drm

thanks,
-Nathan

I know, it is rather silly. It has more to do with debugging front-end
code than syntactic or semantics.

When building PHI nodes, I often found that I was trying to join
different types. The assert tells me that the types are different, but
obviously not which types I was joining (only the value and basic
block).

Normally, I would see the values and understand instantly what was
going wrong, but if you don't have the types explicitly, you have to
remember (or read the docs) to know what that type really is and then,
understand what you failed to do. As I said, this is a very minor
issues.

Ok, I see it. Works for me.

How about this syntax:

invoke @method(i32 %arg)
ret i32 %return_value to label %success_branch
unwind i8* %exception_pointer to label %error_branch

this makes the instruction self consistent, and self documenting

I like it.

What if the function returns void? Would you omit the whole type+value?

yea, I think it would go

invoke @method(i32 %arg)
ret to label %success_branch
unwind i8* %exception_pointer to label %error_branch