ARM EABI Exceptions

Hi,

I was comparing the way LLVM generates the exception table and it looks
a bit different from what GCC (arm-none-eabi-g++) generates.

Maybe that's because clang is not generating ARM IR when I do:

$ clang -c -emit-llvm -march=arm -mcpu=cortex-a8 -mtriple=arm-none-eabi
exception.cpp -o exception.clang.bc
clang: warning: argument unused during compilation: '-mcpu=cortex-a8'
clang: warning: argument unused during compilation:
'-mtriple=arm-none-eabi'

The IR triple is "x86_64-unknown-linux-gnu". Is there a way I can force
the triple?

Then I compiled with llc:

$ llc -march=arm -mcpu=cortex-a8 -mtriple=arm-none-eabi
exception.clang.ll -o exception.clang.s

It doesn't generate the tables because ARMELFMCAsmInfo doesn't set
ExceptionsType. If I set it to Dwarf and fix the missing lowering
operations (EXCEPTIONADDR and EHSELECTION), it does generate a table
which is slightly different than what GCC is doing.

I've compared the assembly generated and it's close, but not perfect.
Some EABI issues (frame pointer, some intrinsics mismatch, EH call
table) were present, but the general execution flow seems very similar.

If I compile the resulting asm with GCC ("-T generic-hosted.ld") and
run, it breaks completely. GCC's run flawlessly.

Anyone has any idea on the status of exception handling in clang/LLVM?
DwarfException cannot be easily overwritten, and adding target specific
code to it seems wrong...

Attached is my example compiled with Codesourcery's GCC (2009q3) and
clang (2.7).

cheers,
--renato

exception-clang-example.tar.gz (9.4 KB)

Hello, Renato

Anyone has any idea on the status of exception handling in clang/LLVM?
DwarfException cannot be easily overwritten, and adding target specific
code to it seems wrong...

Neither llvm-gcc nor clang support exceptions on ARM (except, maybe,
sjlj excheptions on arm/darwin). I have some patched uncommitted for
EH on ARM but they are too far from being complete.

We do support SJLJ exceptions on ARM for Darwin at least.

-bw

Hi Anton,

Are you actively working in that area? I did some experiments and managed to understand how LLVM does the exception handling, but my changes are also far from working.

My main concern is that DwarfException is not extensible at all. I can't inherit from it (DwarfWriter creates it directly) and there are no call backs to target-specific code (nor registration of such mechanism). To change that in line with AsmWriter would be a major change and passing a ARMException reference through AsmWriter would pass the object through many places that are not concerned with it.

A simple registration mechanism (DE->registerTargetCode-thingy) would be the least change and more direct approach, but it's damn ugly. :wink:

Apart from that, the format of the table and the calls to intrinsic functions are quite close.

About Sj/Lj exceptions, that's not ARM EHABI compliant. EABI GCC won't compile that, I guess. Not to mention it hampers normal execution...

Thanks,
--renato

-- IMPORTANT NOTICE: The contents of this email and any attachments are confidential and may also be privileged. If you are not the intended recipient, please notify the sender immediately and do not disclose the contents to any other person, use it for any purpose, or store or copy the information in any medium. Thank you.

Hello, Renato

Are you actively working in that area?

No, I started to experiment with ARM EH ~year ago, but never had
anything complete, unfortunately

My main concern is that DwarfException is not extensible at all. I can't inherit from it (DwarfWriter creates it directly) and there are no call backs to target-specific code (nor registration of such mechanism).

Why do you need this? My feeling that this will not be required. I
might be mistaken though.

Hi Anton,

Maybe I'm following the wrong path, then.

When trying to make llc print the EH table for clang-compiled IR, I noticed that there was none. If I turn on the Dwarf exception in ARMMCAsmInfo, I could see a table that looks like GCC's. ARM EHABI tables are quite different and the idea is not to produce them now, but to make clang generated asm/objects to be compiled/linked with arm-none-eabi-gcc.

In that area, I was trying to make LLVM DwarfException more like GCC's, and for that I had to make some changes in the order things appear (the table is inside the function instead of in a new section, thus the size has to appear after the table), and add new things (.fnstart, .fnend, .personality, etc), and make sure all Actions are in conformance with GCC's intrinsics. Also, looks like gcc doesn't produce the CIE nor any of the FDEs, so maybe I have to suppress that as well.

For all those changes, the only place I can implement, AFAIK, is DwarfException. Now, as a local hack, I have added a new ExceptionABI { None, ARM }; enum to MCAsmInfo to if/else inside DwarfException, but that's far from ideal.

I'd like to be able to have with DwarfException the same we have with AsmPrinter, calling target-implemented functions (like EmitFunctionBodyStart, etc), so one could customise the particular differences of a target. But this change is a bit wide-spread, since DwarfWriter will also have to be changed to create a target-specific exception writer rather than the generic one.

But again, maybe the whole thing is pointless, and clang already generates exception tables compatible with eabi-gcc and I was not able to make it work.

Cheers,
--renato

It's likely there will be some work to get DWARF EH working for ARM. That said, keep in mind that the goal is to get the code to conform to the ABI, not to produce code and tables that look the same as what GCC produces. The former is reasonable, the latter is going to feel like tilting at windmills, as the internal EH representations in gcc are very different than what llvm IR represents. GCC uses a side-table of EH "regions" that it references to determine call sites and such, for example. LLVM doesn't have that concept and uses the invoke instruction instead. One result of this is that the tables that come out often look quite different. So long as they adhere to the spec, though, the code will interoperate correctly.

Regards,
  Jim

Hi Jim,

It's likely there will be some work to get DWARF EH working for ARM.

Tell me about it! :wink:

That said, keep in mind that the goal is to get the code to conform to
the ABI, not to produce code and tables that look the same as what GCC
produces.

Absolutely. I'm only trying to get exception handling working on ARM. Right
now, I'm linking with eabi-gcc, and because clang's table is similar, I
thought I could adapt it. Unfortunately, the testability is nil and the end
goal is not good, so the task is probably not worth it.

The former is reasonable, the latter is going to feel like
tilting at windmills, as the internal EH representations in gcc are
very different than what llvm IR represents. GCC uses a side-table of
EH "regions" that it references to determine call sites and such, for
example. LLVM doesn't have that concept and uses the invoke instruction
instead. One result of this is that the tables that come out often look
quite different.

As I gathered, the invoke calls were populating the call site table, similar
to the .LEH regions GCC does, and the call site table of both look quite
similar, to me. I noticed more differences around the call table, for
instance, the headers, the type info actions and the relocation information.

Also, the normal flow (try/catch blocks, with cleanup regions and intrinsic
calls) didn't seem that different.

So long as they adhere to the spec, though, the code
will interoperate correctly.

The spec is quite vague when expressing the personality routines, AFAICT.

If the tables are different, when you link code compiled with Clang and GCC,
both using the same personality routine (GCC's or Clang's, but not both),
how do you expect them to work together?

Nevertheless, I digress. I may have started it the wrong way, but my
immediate goal is to compile a simple exception example to ARM. Do you know
a better path?

Cheers,
--renato

Hello, Renato

If the tables are different, when you link code compiled with Clang and GCC,
both using the same personality routine (GCC's or Clang's, but not both),
how do you expect them to work together?

Because both tables are "standard" ?

Nevertheless, I digress. I may have started it the wrong way, but my
immediate goal is to compile a simple exception example to ARM. Do you know
a better path?

Here is how EH was implemented first for x86:
1. LLVM was made to emit tables for some "easy" examples, this
involves some low-level stuff like register moves, encoding, etc. At
this point one can use LLVM-generated .s files with gcc-provided
unwinding library.
2. Necessary EH intrinsics was implemented
3. More refinements (PIC mode, etc.)

Hi Anton,

Because both tables are "standard" ?

Ok, we're in different pages, I think.

I trust that LLVM is binary compatible with GCC, including the exception tables, for x86. What I was referring to is about the LLVM's x86 EH table compatibility with ARM GCC's personality routine.

I'm not an expert in exception handling, so I don't know all the idiosyncrasies of both standards, but I guessed that hoping ARM PRs to understand x86 tables was a bit too far. As far as I read and dug, both standards are very similar, but I can't yet guarantee that all PRs will read all tables, not without spending months reading and testing every combination.

Here is how EH was implemented first for x86:
1. LLVM was made to emit tables for some "easy" examples, this
involves some low-level stuff like register moves, encoding, etc. At
this point one can use LLVM-generated .s files with gcc-provided
unwinding library.

That's my road...

Just by turning on Dwarf debugging and lowering exception address and selection, I could get an ARM assembly with the default exception table. I've investigated the assembly code and it seems that clang is preparing the intrinsics the same way arm-gcc does, which is good.

Up until __cxa_throw, everything is fine. The parameters are the same, in the same registers and the table header is similar. The failures come when getting into __cxa_begin_catch, when the PR recognizes the type thrown ('int') but still calls terminate. I'm guessing the action table is different, but there is no documentation on neither LLVM nor GCC intrinsics I could find.

Does LLVM have implementations of __cxa_* intrinsics? If so, where are they?

2. Necessary EH intrinsics was implemented
3. More refinements (PIC mode, etc.)

I didn't want to go that far, for now.

Cheers,
--renato

Hi Renato,

Up until __cxa_throw, everything is fine. The parameters are the same, in the same registers and the table header is similar. The failures come when getting into __cxa_begin_catch, when the PR recognizes the type thrown ('int') but still calls terminate. I'm guessing the action table is different, but there is no documentation on neither LLVM nor GCC intrinsics I could find.

if you compile with -fverbose-asm you should get some helpful comments in the
action table. You could also send the tables to the mailing list.

Does LLVM have implementations of __cxa_* intrinsics? If so, where are they?

These aren't intrinsics, they are C++ standard library functions.

Ciao,

Duncan.

Hi Duncan,

if you compile with -fverbose-asm you should get some helpful comments
in the
action table.

I can't compile directly with clang, I need first to generate IR than use
LLC to generate the assembly. LLC doesn't accept that option. :frowning:

I don't know why, but my changes only took effect on LLC, maybe I'm doing
something wrong when compiling clang...

You could also send the tables to the mailing list.

I did, my first post. Attached again, just in case.

These aren't intrinsics, they are C++ standard library functions.

Sorry, you're right.

Cheers,
--renato

ehabi.zip (7.73 KB)

Hi Renato,

if you compile with -fverbose-asm you should get some helpful comments
in the
action table.

I can't compile directly with clang, I need first to generate IR than use
LLC to generate the assembly. LLC doesn't accept that option. :frowning:

in llc it's called -asm-verbose

I did, my first post. Attached again, just in case.

OK, I may have time to take a look later.

Best wishes,

Duncan.

in llc it's called -asm-verbose

Hi Duncan,

Didn't make any difference, I think the asm was already quite verbose... :wink:

OK, I may have time to take a look later.

Thanks, I'll be digging it a bit more these days.

Cheers,
--renato

Hi Renato,

I looked at the .s file you sent. We end up at the throw here:

.Ltmp9:
        ldr r1, .LCPI1_2
        bl __cxa_throw
.Ltmp10:

This has an action table of:

        .long .Ltmp9-.Leh_func_begin1 @ Region start
        .long .Ltmp10-.Ltmp9 @ Region length
        .long .Ltmp11-.Leh_func_begin1 @ Landing pad
        .uleb128 3 @ Action

The action record is this:

        .sleb128 1 @ TypeInfo index
        .sleb128 0 @ Next action

(Here are the type infos

        .long _ZTIc @ TypeInfo
        .long _ZTIi @ TypeInfo
        .long 0 @ TypeInfo
)

So the landing pad is a "catch-all", which is how LLVM does things (*mutter*guh*mutter*). That falls into here:

.Ltmp14:
        bl _Unwind_Resume_or_Rethrow
.Ltmp15:

Which has an action table of:

        .long .Ltmp14-.Leh_func_begin1 @ Region start
        .long .Ltmp15-.Ltmp14 @ Region length
        .long .Ltmp21-.Leh_func_begin1 @ Landing pad
        .uleb128 7 @ Action

And action record:

        .sleb128 3 @ TypeInfo index
        .sleb128 -3 @ Next action

So it'll try to catch a character type. If it can't, then it tries this action record:

        .sleb128 2 @ TypeInfo index
        .sleb128 -3 @ Next action

Which is an integer type. It should catch this, so it should go to the landing pad .Ltmp21. It should make its way to this lump of code:

.LBB1_20: @ %catch.next
        cmp r1, #2
        bne .LBB1_27
@ BB#21: @ %match27
        mov r0, r4
        bl __cxa_begin_catch
        ldr r1, [r0]
        str r1, [sp, #8]

And eventually the __cxa_end_catch here:

.LBB1_25: @ %match.end38
.Ltmp30:
        bl __cxa_end_catch
.Ltmp31:

Is this the behavior you're seeing?

-bw

Hi Bill,

So the landing pad is a "catch-all", which is how LLVM does things (*mutter*guh*mutter*). That falls into here:

.Ltmp14:
         bl _Unwind_Resume_or_Rethrow

gcc uses __cxa_end_cleanup rather than _Unwind_Resume when using the ARM EABI.
I think Anton is quite right to advise Renato to use llvm-gcc rather than clang,
since it should output the correct thing here.

Ciao,

Duncan.

Sounds like a good plan. You should file a PR against clang about this.

-bw

gcc uses __cxa_end_cleanup rather than _Unwind_Resume when using the ARM EABI.
I think Anton is quite right to advise Renato to use llvm-gcc rather than clang,
since it should output the correct thing here.

Sounds like a good plan. You should file a PR against clang about this.

You can also file a PR against dragonegg - I was too lazy to handle the ARM case
there :slight_smile: OK, it doesn't support ARM yet, but still!

Ciao,

Duncan.

Is this the behavior you're seeing?

Hi Bill,

The ASM file is perfectly correct. I have compiled both asm files with
eabi-gcc and got very similar tables, including using the EHABI compact mode
for personality routines (generated by eabi-gcc).

But there was one difference in the handler's list (one less entry, the
first one), which agrees with the fact that the code is failing in the
_Unwind_RaiseException while searching for the throw action
(search_EIT_table in gcc).

I think it's something in Clang's code that should take into account that
the table will be converted into an ARM EHABI format, that doesn't affect
x86 codegen. My intent if to fix it.

Thanks!
--renato

Sounds like a good plan. You should file a PR against clang about this.

I've created a bug against clang:

http://llvm.org/bugs/show_bug.cgi?id=7187

And linked this conversation. Any new information/progress I'll ad to the
bug, so if you're interested, please follow the bug.

Cheers,
--renato