Hi, This patch implements exception-handling support for Objective-C on ARM EABI Linux systems, using the ARM-defined EHABI. This patch gets things working fairly well, though at least one issue remains -- but this is a start, at least. Test results are clean, apart from the following: FAIL: objc/execute/exceptions/foward-1.m execution, -O3 -fomit-frame-pointer -funroll-loops -fgnu-runtime FAIL: objc/execute/exceptions/foward-1.m execution, -O3 -fomit-frame-pointer -funroll-all-loops -finline-functions -fgnu-runtime AFAICT, these seem to be caused by data-flow information being corrupted somehow: the failure happens during the regrename pass, but I don't think the underlying cause is there. On entry to the landing pad: @catch (id error) { i++; [error free]; } The "error" pointer gets scrambled, because the register it should be passed in, r0, is not marked as live-on-entry to the associated block, so register-renaming thinks it's safe to overwrite that register. On the 4.3 branch, defining the EH_USES macro in arm.h as follows fixed this issue: /* r0 may be used by catch handlers. Make sure it's marked live on entry to those. */ #define EH_USES(N) ((N) == 0) But, this solution doesn't seem to work on mainline. Does anyone know why that might be? Is the attached patch OK for mainline, despite the above shortcoming? (Tested with cross to ARM EABI Linux). Cheers, Julian ChangeLog * configure.ac (arm*-*-linux-gnueabi): Don't disable building of libobjc for ARM EABI Linux. * configure: Regenerate. libobjc/ * exception.c (__objc_exception_class): Initialise as constant array for ARM EABI. Change macro to static const for non-ARM EABI. (ObjcException): Add note about structure layout. Remove landingPad and handlerSwitchValue for ARM EABI. (get_ttype_entry): Add __ARM_EABI_UNWINDER__ version of function. (CONTINUE_UNWINDING): Define for ARM EABI/otherwise cases. (PERSONALITY_FUNCTION): Use ARM EABI-specific arguments, and add ARM EABI unwinding support. (objc_exception_throw): Use memcpy to initialise exception class. --MP_/JBpQpWs5CG=5JB9NzgrXUnG Content-Type: text/x-patch; name=libobjc-arm-eabi-support-1.diff Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename=libobjc-arm-eabi-support-1.diff --- a/libobjc/exception.c 2008-04-30 05:15:45.000000000 -0700 +++ b/libobjc/exception.c 2008-05-06 05:41:51.000000000 -0700 @@ -31,16 +31,25 @@ Boston, MA 02110-1301, USA. */ #include "unwind-pe.h" +#ifdef __ARM_EABI_UNWINDER__ + +const _Unwind_Exception_Class __objc_exception_class + = {'G', 'N', 'U', 'C', 'O', 'B', 'J', 'C'}; + +#else + /* This is the exception class we report -- "GNUCOBJC". */ -#define __objc_exception_class \ - ((((((((_Unwind_Exception_Class) 'G' \ - << 8 | (_Unwind_Exception_Class) 'N') \ - << 8 | (_Unwind_Exception_Class) 'U') \ - << 8 | (_Unwind_Exception_Class) 'C') \ - << 8 | (_Unwind_Exception_Class) 'O') \ - << 8 | (_Unwind_Exception_Class) 'B') \ - << 8 | (_Unwind_Exception_Class) 'J') \ - << 8 | (_Unwind_Exception_Class) 'C') +static const _Unwind_Exception_Class __objc_exception_class + = ((((((((_Unwind_Exception_Class) 'G' + << 8 | (_Unwind_Exception_Class) 'N') + << 8 | (_Unwind_Exception_Class) 'U') + << 8 | (_Unwind_Exception_Class) 'C') + << 8 | (_Unwind_Exception_Class) 'O') + << 8 | (_Unwind_Exception_Class) 'B') + << 8 | (_Unwind_Exception_Class) 'J') + << 8 | (_Unwind_Exception_Class) 'C'); + +#endif /* This is the object that is passed around by the Objective C runtime to represent the exception in flight. */ @@ -50,12 +59,18 @@ struct ObjcException /* This bit is needed in order to interact with the unwind runtime. */ struct _Unwind_Exception base; - /* The actual object we want to throw. */ + /* The actual object we want to throw. Note: must come immediately after + unwind header. */ id value; +#ifdef __ARM_EABI_UNWINDER__ + /* Note: we use the barrier cache defined in the unwind control block for + ARM EABI. */ +#else /* Cache some internal unwind data between phase 1 and phase 2. */ _Unwind_Ptr landingPad; int handlerSwitchValue; +#endif }; @@ -106,6 +121,24 @@ parse_lsda_header (struct _Unwind_Contex return p; } +#ifdef __ARM_EABI_UNWINDER__ + +static Class +get_ttype_entry (struct lsda_header_info *info, _uleb128_t i) +{ + _Unwind_Ptr ptr; + + ptr = (_Unwind_Ptr) (info->TType - (i * 4)); + ptr = _Unwind_decode_target2 (ptr); + + if (ptr) + return objc_get_class ((const char *) ptr); + else + return 0; +} + +#else + static Class get_ttype_entry (struct lsda_header_info *info, _Unwind_Word i) { @@ -122,6 +155,8 @@ get_ttype_entry (struct lsda_header_info return 0; } +#endif + /* Like unto the method of the same name on Object, but takes an id. */ /* ??? Does this bork the meta-type system? Can/should we look up an isKindOf method on the id? */ @@ -150,12 +185,32 @@ isKindOf (id value, Class target) #define PERSONALITY_FUNCTION __gnu_objc_personality_v0 #endif +#ifdef __ARM_EABI_UNWINDER__ + +#define CONTINUE_UNWINDING \ + do \ + { \ + if (__gnu_unwind_frame(ue_header, context) != _URC_OK) \ + return _URC_FAILURE; \ + return _URC_CONTINUE_UNWIND; \ + } \ + while (0) + +_Unwind_Reason_Code +PERSONALITY_FUNCTION (_Unwind_State state, + struct _Unwind_Exception *ue_header, + struct _Unwind_Context *context) +#else + +#define CONTINUE_UNWINDING return _URC_CONTINUE_UNWIND + _Unwind_Reason_Code PERSONALITY_FUNCTION (int version, _Unwind_Action actions, _Unwind_Exception_Class exception_class, struct _Unwind_Exception *ue_header, struct _Unwind_Context *context) +#endif { struct ObjcException *xh = (struct ObjcException *) ue_header; @@ -165,19 +220,65 @@ PERSONALITY_FUNCTION (int version, const unsigned char *p; _Unwind_Ptr landing_pad, ip; int handler_switch_value; - int saw_cleanup = 0, saw_handler; + int saw_cleanup = 0, saw_handler, foreign_exception; void *return_object; + int ip_before_insn = 0; + +#ifdef __ARM_EABI_UNWINDER__ + _Unwind_Action actions; + + switch (state & _US_ACTION_MASK) + { + case _US_VIRTUAL_UNWIND_FRAME: + actions = _UA_SEARCH_PHASE; + break; + + case _US_UNWIND_FRAME_STARTING: + actions = _UA_CLEANUP_PHASE; + if (!(state & _US_FORCE_UNWIND) + && ue_header->barrier_cache.sp == _Unwind_GetGR (context, 13)) + actions |= _UA_HANDLER_FRAME; + break; + + case _US_UNWIND_FRAME_RESUME: + CONTINUE_UNWINDING; + break; + + default: + abort(); + } + actions |= state & _US_FORCE_UNWIND; + /* TODO: Foreign exceptions need some attention (e.g. rethrowing doesn't + work). */ + foreign_exception = 0; + + /* The dwarf unwinder assumes the context structure holds things like the + function and LSDA pointers. The ARM implementation caches these in + the exception header (UCB). To avoid rewriting everything we make the + virtual IP register point at the UCB. */ + ip = (_Unwind_Ptr) ue_header; + _Unwind_SetGR (context, 12, ip); + +#else /* !__ARM_EABI_UNWINDER. */ /* Interface version check. */ if (version != 1) return _URC_FATAL_PHASE1_ERROR; + + foreign_exception = (exception_class != __objc_exception_class); +#endif /* Shortcut for phase 2 found handler for domestic exception. */ if (actions == (_UA_CLEANUP_PHASE | _UA_HANDLER_FRAME) - && exception_class == __objc_exception_class) + && !foreign_exception) { +#ifdef __ARM_EABI_UNWINDER__ + handler_switch_value = (int) ue_header->barrier_cache.bitpattern[1]; + landing_pad = (_Unwind_Ptr) ue_header->barrier_cache.bitpattern[3]; +#else handler_switch_value = xh->handlerSwitchValue; landing_pad = xh->landingPad; +#endif goto install_context; } @@ -186,12 +287,18 @@ PERSONALITY_FUNCTION (int version, /* If no LSDA, then there are no handlers or cleanups. */ if (! language_specific_data) - return _URC_CONTINUE_UNWIND; + CONTINUE_UNWINDING; /* Parse the LSDA header. */ p = parse_lsda_header (context, language_specific_data, &info); info.ttype_base = base_of_encoded_value (info.ttype_encoding, context); +#ifdef HAVE_GETIPINFO + ip = _Unwind_GetIPInfo (context, &ip_before_insn); +#else ip = _Unwind_GetIP (context) - 1; +#endif + if (!ip_before_insn) + --ip; landing_pad = 0; action_record = 0; handler_switch_value = 0; @@ -250,7 +357,7 @@ PERSONALITY_FUNCTION (int version, /* If ip is not present in the table, C++ would call terminate. */ /* ??? As with Java, it's perhaps better to tweek the LSDA to that no-action is mapped to no-entry. */ - return _URC_CONTINUE_UNWIND; + CONTINUE_UNWINDING; found_something: saw_cleanup = 0; @@ -287,8 +394,7 @@ PERSONALITY_FUNCTION (int version, /* During forced unwinding, we only run cleanups. With a foreign exception class, we have no class info to match. */ - else if ((actions & _UA_FORCE_UNWIND) - || exception_class != __objc_exception_class) + else if ((actions & _UA_FORCE_UNWIND) || foreign_exception) ; else if (ar_filter > 0) @@ -318,18 +424,24 @@ PERSONALITY_FUNCTION (int version, } if (! saw_handler && ! saw_cleanup) - return _URC_CONTINUE_UNWIND; + CONTINUE_UNWINDING; if (actions & _UA_SEARCH_PHASE) { if (!saw_handler) - return _URC_CONTINUE_UNWIND; + CONTINUE_UNWINDING; /* For domestic exceptions, we cache data from phase 1 for phase 2. */ - if (exception_class == __objc_exception_class) + if (!foreign_exception) { +#ifdef __ARM_EABI_UNWINDER__ + ue_header->barrier_cache.sp = _Unwind_GetGR (context, 13); + ue_header->barrier_cache.bitpattern[1] = (_uw) handler_switch_value; + ue_header->barrier_cache.bitpattern[3] = (_uw) landing_pad; +#else xh->handlerSwitchValue = handler_switch_value; xh->landingPad = landing_pad; +#endif } return _URC_HANDLER_FOUND; } @@ -361,7 +473,9 @@ void objc_exception_throw (id value) { struct ObjcException *header = calloc (1, sizeof (*header)); - header->base.exception_class = __objc_exception_class; + + memcpy (&header->base.exception_class, &__objc_exception_class, + sizeof (__objc_exception_class)); header->base.exception_cleanup = __objc_exception_cleanup; header->value = value; --MP_/JBpQpWs5CG=5JB9NzgrXUnG--