Exception handling question

Hi,

I’m trying to get exception handling working in my compiler targetting LLVM. I’ve been working from the LLVM exception handling documentation (including http://llvm.org/docs/ExceptionHandling.html and http://wiki.llvm.org/HowTo:_Build_JIT_based_Exception_mechanism) and looking at g+±llvm’s output.

I’ve been trying to get a minimal test function to work, which simply invokes _Unwind_RaiseException with a single clean-up landing pad. However. when I run it my personality function is not getting called - _Unwind_RaiseException simply returns apparently doing nothing. Looking at the x86-64 assembly output from llc, I can see this is happening because the personality function is not getting into the DWARF eh table (the landing pad is there though).

I’m stumped as to why not. I’d be grateful if anyone can point out what I’m doing wrong here:

define i32 @_ZN4N0014Main5test5EN2IO6WriterEiA_l(%6*, %4*, i32, %33*) {
entry:
%err = alloca %4* ; <%4**> [#uses=1]
%count = alloca i32 ; <i32*> [#uses=1]
%e = alloca %33* ; <%33**> [#uses=2]
%this = alloca %6* ; <%6**> [#uses=1]
%.ex_value = alloca i8* ; <i8**> [#uses=1]
%.ex_value_l = alloca i8* ; <i8**> [#uses=0]
%.ex_type = alloca i64 ; <i64*> [#uses=1]
br label %4

; :4 ; preds = %entry
store %6* %0, %6** %this
store %4* %1, %4** %err
store i32 %2, i32* %count
store %33* %3, %33** %e
br label %.try_body

.try_body: ; preds = %4
%5 = load %33** %e ; <%33*> [#uses=1]
%6 = getelementptr inbounds %33* %5, i32 0, i32 2, i32 0 ; <i64*> [#uses=1]
%7 = invoke i8* (…)* bitcast (i32 (%struct._Unwind_Exception*)* @_Unwind_RaiseException to i8* (…))(i64 %6)
to label %8 unwind label %.finally_pad ; <i8*> [#uses=0]

; :8 ; preds = %.try_body
br label %.finally_handler

.finally_pad: ; preds = %.try_body
%9 = call i8* @llvm.eh.exception() ; <i8*> [#uses=2]
store i8* %9, i8** %.ex_value
%10 = call i64 (i8*, i8*, …)* @llvm.eh.selector.i64(i8* %9, i8* bitcast (i32 (i32, i32, i64, %struct._Unwind_Exception*, %struct._Unwind_Context*)* @__l_personality to i8*)) ; [#uses=1]
%11 = icmp eq i64 %10, 0 ; [#uses=1]
%12 = select i1 %11, i64 0, i64 1 ; [#uses=1]
store i64 %12, i64* %.ex_type
br label %.finally_handler

.finally_handler: ; preds = %.finally_pad, %8
%13 = call i8* (…)* bitcast (i32 (i8*, …)* @printf to i8* (…))(i8 getelementptr ([9 x i8]* @__string_27, i32 0, i32 0)) ; <i8*> [#uses=0]
ret i32 0
}

Thanks in advance,
– James Williams

PS: Thanks to all LLVM contributers for such a well designed IR and code generation library. Switching my compiler from its existing back end to LLVM has so far been incredibly quick and easy - a matter of a few evenings and a couple of weekends!

I think you’ll need a “i8* null” at the end of this @llvm.eh.selector.i64 call.

-bw

Hi Bill,

I think you'll need a "i8* null" at the end of this @llvm.eh.selector.i64 call.

that is what you would do if (1) you were using the C++ personality function,
and (2) you want invoke to have correct LLVM semantics (i.e. invoke always
branches to the landing pad if an exception is unwinding through it). Using
a selector with no catch info, like this one, should result in a cleanup entry
going in the dwarf eh table and the personality function being recorded IIRC.
This may be all that is needed for a personality function that works differently
to the C++ one.

Ciao,

Duncan.

Hi James,

I've been trying to get a minimal test function to work, which simply invokes _Unwind_RaiseException with a single clean-up landing pad. However. when I run it my personality function is not getting called - _Unwind_RaiseException simply returns apparently doing nothing. Looking at the x86-64 assembly output from llc, I can see this is happening because the personality function is not getting into the DWARF eh table (the landing pad is there though).

do you mean that the personality function doesn't turn up in the assembler
at all? It should be! Consider the following simplified example:

declare void @g()

define void @f() {
e:
   invoke void @g()
           to label %c unwind label %u

c: ; preds = %e
   ret void

u: ; preds = %e
   %ptr = tail call i8* @llvm.eh.exception() nounwind ; <i8*> [#uses=1]
   %select = tail call i32 (i8*, i8*, ...)* @llvm.eh.selector(i8* %ptr, i8* bitcast (i32 (...)* @personality to i8*)) nounwind ; <i32> [#uses=0]
   ret void
}

declare i8* @llvm.eh.exception() nounwind readonly

declare i32 @llvm.eh.selector(i8*, i8*, ...) nounwind

declare i32 @personality(...)

This compiles down to the following, which has a "cleanup" noted in the
dwarf eh table, and has the personality function recorded in the CIE.
This is with llc from top-of-tree. Do you see something different?

Ciao,

Duncan.

  .file "selector.ll"

  .text
  .align 16
.globl f
  .type f,@function
f: # @f
.Leh_func_begin1:
# BB#0: # %e
  subq $8, %rsp
.Llabel4:
.Llabel1:
  callq g
.Llabel2:
# BB#1: # %c
  addq $8, %rsp
  ret
.LBB1_2:
                                                             # %u
.Llabel3:
  addq $8, %rsp
  ret
  .size f, .-f
.Leh_func_end1:
  .section .gcc_except_table,"a",@progbits
  .align 4
GCC_except_table1:
  .byte 0
                                                             # Padding
  .byte 0
                                                             # Padding
.Lexception1:
  .byte 255
                                                             # @LPStart format (omit)
  .byte 0
                                                             # @TType format (absptr)
  .uleb128 15 # @TType base offset
  .byte 3
                                                             # Call site format (udata4)
  .uleb128 13 # Call site table size
  .long .Llabel1-.Leh_func_begin1 # Region start
  .long .Llabel2-.Llabel1 # Region length
  .long .Llabel3-.Leh_func_begin1 # Landing pad
  .uleb128 0 # Action
  .align 4
  .section .eh_frame,"aw",@progbits
.LEH_frame0:
.Lsection_eh_frame:
.Leh_frame_common:
  .long .Leh_frame_common_end-.Leh_frame_common_begin # Length of Common Information Entry
.Leh_frame_common_begin:
  .long 0
                                                             # CIE Identifier Tag
  .byte 1
                                                             # CIE Version
  .asciz "zPLR" # CIE Augmentation
  .uleb128 1 # CIE Code Alignment Factor
  .sleb128 -8 # CIE Data Alignment Factor
  .byte 16
                                                             # CIE Return Address Column
  .uleb128 7 # Augmentation Size
  .byte 27
                                                             # Personality (pcrel sdata4)
.Lpersonalityref_addr1_0:
  .long personality-.Lpersonalityref_addr1_0 # Personality
  .byte 27
                                                             # LSDA Encoding (pcrel sdata4)
  .byte 27
                                                             # FDE Encoding (pcrel sdata4)
  .byte 12
                                                             # DW_CFA_def_cfa
  .uleb128 7 # Register
  .uleb128 8 # Offset
  .byte 144
                                                             # DW_CFA_offset + Reg (16)
  .uleb128 1 # Offset
  .align 8
.Leh_frame_common_end:

.Lf.eh:
  .long .Leh_frame_end1-.Leh_frame_begin1 # Length of Frame Information Entry
.Leh_frame_begin1:
  .long .Leh_frame_begin1-.Leh_frame_common # FDE CIE offset
  .long .Leh_func_begin1-. # FDE initial location
  .long .Leh_func_end1-.Leh_func_begin1 # FDE address range
  .uleb128 8 # Augmentation size
  .quad .Lexception1-. # Language Specific Data Area
  .byte 4
                                                             # DW_CFA_advance_loc4
  .long .Llabel4-.Leh_func_begin1
  .byte 14
                                                             # DW_CFA_def_cfa_offset
  .uleb128 16 # Offset
  .align 8
.Leh_frame_end1:

  .section .note.GNU-stack,"",@progbits

2010/1/22 Duncan Sands <baldrick@free.fr>

Hi James,

I’ve been trying to get a minimal test function to work, which simply invokes _Unwind_RaiseException with a single clean-up landing pad. However. when I run it my personality function is not getting called - _Unwind_RaiseException simply returns apparently doing nothing. Looking at the x86-64 assembly output from llc, I can see this is happening because the personality function is not getting into the DWARF eh table (the landing pad is there though).

do you mean that the personality function doesn’t turn up in the assembler
at all? It should be! Consider the following simplified example:

Yes - no reference to the personality function at all. I tried Bill Wendling’s suggestion to add an i8* null to the llvm.eh.selector.i64 parameter list but I still get no reference to the personality function. Compared to your example my eh table seems to be missing a load of stuff. The complete LLVM generated x86-64 assembler for my function including eh table is:

.text
.align 16
.globl _ZN4N0014Main5test5EN2IO6WriterEiA_l
.type _ZN4N0014Main5test5EN2IO6WriterEiA_l,@function
_ZN4N0014Main5test5EN2IO6WriterEiA_l: # @_ZN4N0014Main5test5EN2IO6WriterEiA_l
.Leh_func_begin153:
.Lfunc_begin153:
.LBB153_0: # %entry
subq $56, %rsp
.Llabel294:
.LBB153_1:
movq %rdi, 24(%rsp)
movq %rsi, 48(%rsp)
movl %edx, 44(%rsp)
movq %rcx, 32(%rsp)
.LBB153_2: # %.try_body
movq 32(%rsp), %rdi
.Llabel291:
addq $16, %rdi
xorb %al, %al
call _Unwind_RaiseException
.Llabel292:
jmp .LBB153_4
.LBB153_3: # %.finally_pad
.Llabel293:
movq %rax, 16(%rsp)
testq %rdx, %rdx
setne %al
movzbl %al, %eax
movq %rax, (%rsp)
.LBB153_4: # %.finally_handler
movl $.L__string_27, %edi
xorb %al, %al
call printf
xorl %eax, %eax
addq $56, %rsp
ret
.size _ZN4N0014Main5test5EN2IO6WriterEiA_l, .-_ZN4N0014Main5test5EN2IO6WriterEiA_l
.Lfunc_end153:
.Leh_func_end153:
.section .gcc_except_table,“a”,@progbits
.align 4
GCC_except_table153:
.byte 0x0 # Padding
.Lexception153:
.byte 0xFF # @LPStart format (DW_EH_PE_omit)
.byte 0x0 # @TType format (DW_EH_PE_absptr)
.uleb128 28 # @TType base offset
.byte 0x3 # Call site format (DW_EH_PE_udata4)
.uleb128 26 # Call site table size
.long .Llabel291-.Leh_func_begin153 # Region start
.long .Llabel292-.Llabel291 # Region length
.long .Llabel293-.Leh_func_begin153 # Landing pad
.uleb128 0 # Action
.long .Llabel292-.Leh_func_begin153 # Region start
.long .Leh_func_end153-.Llabel292 # Region length
.long 0x0 # Landing pad
.uleb128 0 # Action
.align 4

2010/1/22 James Williams <junk@giantblob.com>

2010/1/22 Duncan Sands <baldrick@free.fr>

Hi James,

I’ve been trying to get a minimal test function to work, which simply invokes _Unwind_RaiseException with a single clean-up landing pad. However. when I run it my personality function is not getting called - _Unwind_RaiseException simply returns apparently doing nothing. Looking at the x86-64 assembly output from llc, I can see this is happening because the personality function is not getting into the DWARF eh table (the landing pad is there though).

do you mean that the personality function doesn’t turn up in the assembler
at all? It should be! Consider the following simplified example:

Yes - no reference to the personality function at all. I tried Bill Wendling’s suggestion to add an i8* null to the llvm.eh.selector.i64 parameter list but I still get no reference to the personality function. Compared to your example my eh table seems to be missing a load of stuff. The complete LLVM generated x86-64 assembler for my function including eh table is:

BTW: I’m using LLVM 2.6 compiled from source on Ubuntu 9.10 in case it’s relevant.

Hi James,

BTW: I'm using LLVM 2.6 compiled from source on Ubuntu 9.10 in case it's relevant.

what do you get if you compile my example using llc?

Ciao,

Duncan.

2010/1/22 Duncan Sands <baldrick@free.fr>

Hi James,

BTW: I’m using LLVM 2.6 compiled from source on Ubuntu 9.10 in case it’s relevant.

what do you get if you compile my example using llc?

I get the expected output (after changing i32 @llvm.eh.selector() to i64 llvm.eh.selector.i64())

I’ve now discovered if I remove some other code from the module containing my original test function, I get correct eh tables including personality and cleanup. Hence it looks like some other LLVM bitcode that I’m generating is somehow stuffing up the eh table generation for this function. The problem bitcode is all verified at both at function and module level before I write it out it so it looks like whatver bad construct it is I’m generating is passing the verifier.

Thanks for your help,
– James

Hi James,

I get the expected output (after changing i32 @llvm.eh.selector() to i64 llvm.eh.selector.i64())

in top-of-tree, llvm.eh.selector.i64 has been removed (though bitcode will
autoupgrade) and there is only i32 @llvm.eh.selector(), which is why my example
was written this way.

Ciao,

Duncan.

2010/1/22 Duncan Sands <baldrick@free.fr>

Hi James,

I get the expected output (after changing i32 @llvm.eh.selector() to i64 llvm.eh.selector.i64())

in top-of-tree, llvm.eh.selector.i64 has been removed (though bitcode will
autoupgrade) and there is only i32 @llvm.eh.selector(), which is why my example
was written this way.

OK. I understand.

Thanks again for the help.
– James

2010/1/22 James Williams <junk@giantblob.com>

2010/1/22 Duncan Sands <baldrick@free.fr>

Hi James,

I get the expected output (after changing i32 @llvm.eh.selector() to i64 llvm.eh.selector.i64())

in top-of-tree, llvm.eh.selector.i64 has been removed (though bitcode will
autoupgrade) and there is only i32 @llvm.eh.selector(), which is why my example
was written this way.

OK. I understand.

Thanks again for the help.
– James

Sorry. I think I was being very stupid. It looks like I was reading the tables wrong and the eh tables are probably actually correct for all my tests. I didn’t realise the personality function went in a separate shared entry. I’m still not sure why it’s not getting called by _Unwind_RaiseException but I don’t think it’s due to any problem with LLVM eh table output

– James

Hi James,

Looking at:

%7 = invoke i8* (…)* bitcast (i32 (%struct._Unwind_Exception*)* @_Unwind_RaiseException to i8* (…))(i64 %6)
to label %8 unwind label %.finally_pad ; <i8*> [#uses=0]

I am not sure this is going to work, at least from the way I’ve played with the system.
In my examples the _Unwind_RaiseException(…) is called from a frame (function) called via
the invoke instruction, not from a frame that contains the invoke instruction.

Garrison

Hi Garrison,

%7 = invoke i8* (...)* bitcast (i32 (%struct._Unwind_Exception*)* @_Unwind_RaiseException to i8* (...)*)(i64* %6)
          to label %8 unwind label %.finally_pad ; <i8*> [#uses=0]

I am not sure this is going to work, at least from the way I've played with the system. In my examples the _Unwind_RaiseException(...) is called from a frame (function) called via
the invoke instruction, not from a frame that contains the invoke instruction.

I'm pretty sure this doesn't matter. It seems to me more likely that the
exception object was not initialized properly.

Ciao,

Duncan.

Hey Duncan,

Yup, you are correct. Just tested to make sure. Invoking _Unwind_RaiseException(...) directly still sets up
personality function (and calls it during exception search/cleanup), in corresponding frame headers for frame
containing invoke. Well ... ok, at least in a JIT execution environment. :slight_smile:

I thought maybe __gcc_personality_v0(...) was being called instead.

However, I also just tested with a garbage exception (offset exception ptr by 1000), being passed in to _Unwind_RaiseException(...),
with the result that the personality function was still called with the cleanup landing pad correctly found. This personality ignored the
exception pointer. So I don't believe the problem is with the exception data since his personality function is not invoked.

I would test the code directly but I don't yet have non-JIT experience with LLVM.

Garrison

Ah! I forgot to mention I'm doing my tests with 2.7, NOT 2.6; also on OS X 10.6.2.

Garrison

2010/1/22 Garrison Venn <gvenn.cfe.dev@gmail.com>

Hi James,

Looking at:

%7 = invoke i8* (…)* bitcast (i32 (%struct._Unwind_Exception*)* @_Unwind_RaiseException to i8* (…))(i64 %6)
to label %8 unwind label %.finally_pad ; <i8*> [#uses=0]

I am not sure this is going to work, at least from the way I’ve played with the system.
In my examples the _Unwind_RaiseException(…) is called from a frame (function) called via
the invoke instruction, not from a frame that contains the invoke instruction.

Garrison

Hi,

Thanks but I’ve also tried calling both a separate C function that then calls _Unwind_RaiseException and also a C++ function that throws an integer with the same results. I’ve also just tried running the example code posted earlier by Duncan and I find that doesn’t reach the landing pad either if g() is replaced with a C++ function containing “throw 1”.

I’m building LLVM from svn head now and I’ll retry Dunan’s example against that.

– James

BTW: I found the exception handling docs on the wiki very helpful

2010/1/22 Duncan Sands <baldrick@free.fr>

Hi Garrison,

%7 = invoke i8* (…)* bitcast (i32 (%struct._Unwind_Exception*)* @_Unwind_RaiseException to i8* (…))(i64 %6)
to label %8 unwind label %.finally_pad ; <i8*> [#uses=0]

I am not sure this is going to work, at least from the way I’ve played with the system. In my examples the _Unwind_RaiseException(…) is called from a frame (function) called via
the invoke instruction, not from a frame that contains the invoke instruction.

I’m pretty sure this doesn’t matter. It seems to me more likely that the
exception object was not initialized properly.

Hmm. I’ve tried a bunch of different ways of creating the exception including calling a C++ function that then does a throw. If I understand right, the personality function should still be offered the foreign exception so it has a chance to call cleanups?

Hi James,

Thanks but I've also tried calling both a separate C function that then calls _Unwind_RaiseException and also a C++ function that throws an integer with the same results. I've also just tried running the example code posted earlier by Duncan and I find that doesn't reach the landing pad either if g() is replaced with a C++ function containing "throw 1".

if all personality functions up the call chain return "cleanup" (i.e. not
_URC_HANDLER_FOUND), then _Unwind_RaiseException returns without doing
anything, i.e. without running any cleanups. I understand that this is
considered to be a feature rather than a bug... If you want to run cleanups
anyway, then you need to do a "forced unwind" when _Unwind_RaiseException
returns like this. Note that this means that the decision to run cleanups is
under the control of whoever is throwing the exception in this case (for example
C++'s throw logic doesn't do this, so cleanups are not run if there are only
cleanups and no handlers - I'm not sure why C++ considers it so important to
have programs exit swiftly that it doesn't bother running potentially vital
cleanups...). Alternatively, have your personality function return
_URC_HANDLER_FOUND when it finds a cleanup [*].

Ciao,

Duncan.

[*] Though since you said your personality function is not called at all,
I guess this won't help solve your problem...

Yes. The issue here, as you pointed out, is that your personality function is not called at all.
Even if you did nothing in the personality function, associated with the setup caused by
llvm.eh.selector, but returned _URC_CONTINUE_UNWIND (8), it should still be called.
Your results are acting like either dwarf exception header info is not emitted (llvm::DwarfExceptionHandling = true;
not executed could affect this), _Unwind_RaiseException(…) is not being called, or the default or another personality
function (_gcc_personality_v0(…)) is called instead by the unwind infrastructure. Although you implied you
dumped the exception headers and found your personality function, I can if you wish instead given you breaks for
gbd so you can see if lvm.eh.selector is correctly prepping your personality function for eventual dwarf emission.

Garrison

Hi Garrison,

Yes. The issue here, as you pointed out, is that your personality function is not called at all.
Even if you did nothing in the personality function, associated with the setup caused by
llvm.eh.selector, but returned _URC_CONTINUE_UNWIND (8), it should still be called.

this is not correct. Unwind_RaiseException will just exit without unwinding
the stack if all personality functions return _URC_CONTINUE_UNWIND.

Ciao,

Duncan.