]> git.saurik.com Git - apple/objc4.git/blobdiff - test/msgSend.m
objc4-646.tar.gz
[apple/objc4.git] / test / msgSend.m
index fd7153913f3bb9237dc470b019e033a24cb50d56..833f53f76fc53162b1656256e77ad540ed8437e8 100644 (file)
@@ -18,6 +18,19 @@ int main()
 #include <objc/objc-internal.h>
 #include <objc/objc-abi.h>
 
+#if __arm64__
+    // no stret dispatchers
+#   define SUPPORT_STRET 0
+#   define objc_msgSend_stret objc_msgSend
+#   define objc_msgSendSuper2_stret objc_msgSendSuper2
+#   define objc_msgSend_stret_debug objc_msgSend_debug
+#   define objc_msgSendSuper2_stret_debug objc_msgSendSuper2_debug
+#   define method_invoke_stret method_invoke
+#else
+#   define SUPPORT_STRET 1
+#endif
 #if defined(__arm__) 
 // rdar://8331406
 #   define ALIGN_() 
@@ -90,6 +103,130 @@ long double LFP_RESULT = __LDBL_MIN__ + __LDBL_EPSILON__;
 
 static struct stret zero;
 
+struct stret_i1 {
+    uintptr_t i1;
+};
+struct stret_i2 {
+    uintptr_t i1;
+    uintptr_t i2;
+};
+struct stret_i3 {
+    uintptr_t i1;
+    uintptr_t i2;
+    uintptr_t i3;
+};
+struct stret_i4 {
+    uintptr_t i1;
+    uintptr_t i2;
+    uintptr_t i3;
+};
+struct stret_i5 {
+    uintptr_t i1;
+    uintptr_t i2;
+    uintptr_t i3;
+    uintptr_t i4;
+    uintptr_t i5;
+};
+struct stret_i6 {
+    uintptr_t i1;
+    uintptr_t i2;
+    uintptr_t i3;
+    uintptr_t i4;
+    uintptr_t i5;
+    uintptr_t i6;
+};
+struct stret_i7 {
+    uintptr_t i1;
+    uintptr_t i2;
+    uintptr_t i3;
+    uintptr_t i4;
+    uintptr_t i5;
+    uintptr_t i6;
+    uintptr_t i7;
+};
+struct stret_i8 {
+    uintptr_t i1;
+    uintptr_t i2;
+    uintptr_t i3;
+    uintptr_t i4;
+    uintptr_t i5;
+    uintptr_t i8;
+    uintptr_t i9;
+};
+struct stret_i9 {
+    uintptr_t i1;
+    uintptr_t i2;
+    uintptr_t i3;
+    uintptr_t i4;
+    uintptr_t i5;
+    uintptr_t i6;
+    uintptr_t i7;
+    uintptr_t i8;
+    uintptr_t i9;
+};
+
+struct stret_d1 {
+    double d1;
+};
+struct stret_d2 {
+    double d1;
+    double d2;
+};
+struct stret_d3 {
+    double d1;
+    double d2;
+    double d3;
+};
+struct stret_d4 {
+    double d1;
+    double d2;
+    double d3;
+};
+struct stret_d5 {
+    double d1;
+    double d2;
+    double d3;
+    double d4;
+    double d5;
+};
+struct stret_d6 {
+    double d1;
+    double d2;
+    double d3;
+    double d4;
+    double d5;
+    double d6;
+};
+struct stret_d7 {
+    double d1;
+    double d2;
+    double d3;
+    double d4;
+    double d5;
+    double d6;
+    double d7;
+};
+struct stret_d8 {
+    double d1;
+    double d2;
+    double d3;
+    double d4;
+    double d5;
+    double d8;
+    double d9;
+};
+struct stret_d9 {
+    double d1;
+    double d2;
+    double d3;
+    double d4;
+    double d5;
+    double d6;
+    double d7;
+    double d8;
+    double d9;
+};
+
 
 @implementation Super
 -(struct stret)stret { return STRET_RESULT; }
@@ -176,6 +313,11 @@ static struct stret zero;
     return;
 }
 
+-(void)voidret_nop2
+{
+    return;
+}
+
 -(id)idret_nop
 {
     return ID_RESULT;
@@ -201,6 +343,39 @@ static struct stret zero;
     return LFP_RESULT;
 }
 
+#define STRET_IMP(n)                            \
++(struct stret_##n)stret_##n##_zero             \
+{                                               \
+    struct stret_##n ret;                       \
+    bzero(&ret, sizeof(ret));                   \
+    return ret;                                 \
+}                                               \
++(struct stret_##n)stret_##n##_nonzero          \
+{                                               \
+    struct stret_##n ret;                       \
+    memset(&ret, 0xff, sizeof(ret));            \
+    return ret;                                 \
+}
+
+STRET_IMP(i1)
+STRET_IMP(i2)
+STRET_IMP(i3)
+STRET_IMP(i4)
+STRET_IMP(i5)
+STRET_IMP(i6)
+STRET_IMP(i7)
+STRET_IMP(i8)
+STRET_IMP(i9)
+
+STRET_IMP(d1)
+STRET_IMP(d2)
+STRET_IMP(d3)
+STRET_IMP(d4)
+STRET_IMP(d5)
+STRET_IMP(d6)
+STRET_IMP(d7)
+STRET_IMP(d8)
+STRET_IMP(d9)
 
 
 +(id)idret: 
@@ -415,8 +590,33 @@ static struct stret zero;
 
 @end
 
+#endif
+
+
 // DWARF checking machinery
 
+#if TARGET_OS_WIN32
+// unimplemented on this platform
+#elif !__OBJC2__
+// 32-bit Mac doesn't use DWARF unwind
+#elif TARGET_OS_IPHONE && __arm__
+// 32-bit iOS device doesn't use DWARF unwind
+#elif __has_feature(objc_arc)
+// ARC's extra RR calls hit the traps at the wrong times
+#else
+
+#define TEST_DWARF 1
+
+// Classes with no implementations and no cache contents from elsewhere.
+@interface SuperDW : TestRoot @end
+@implementation SuperDW @end
+
+@interface Sub0DW : SuperDW @end
+@implementation Sub0DW @end
+
+@interface SubDW : Sub0DW @end
+@implementation SubDW @end
+
 #include <dlfcn.h>
 #include <signal.h>
 #include <sys/mman.h>
@@ -428,6 +628,18 @@ static struct stret zero;
 bool caught = false;
 uintptr_t clobbered;
 
+__BEGIN_DECLS
+extern void callit(void *obj, void *sel, void *fn);
+extern struct stret callit_stret(void *obj, void *sel, void *fn);
+__END_DECLS
+
+#if __x86_64__
+
+#define OTOOL "/usr/bin/xcrun otool -arch x86_64 "
+
+typedef uint8_t insn_t;
+#define BREAK_INSN ((insn_t)0xcc)  // int3
+
 uintptr_t r12 = 0;
 uintptr_t r13 = 0;
 uintptr_t r14 = 0;
@@ -511,39 +723,11 @@ void sigtrap(int sig, siginfo_t *info, void *cc)
     // handle_exception changed register state for continuation
 }
 
-
-uint8_t set(uintptr_t dst, uint8_t newvalue)
-{
-    uintptr_t start = dst & ~(PAGE_SIZE-1);
-    mprotect((void*)start, PAGE_SIZE, PROT_READ|PROT_WRITE);
-    // int3
-    uint8_t oldvalue = *(uint8_t *)dst;
-    *(uint8_t *)dst = newvalue;
-    mprotect((void*)start, PAGE_SIZE, PROT_READ|PROT_EXEC);
-    return oldvalue;
-}
-
-uint8_t clobber(void *fn, uintptr_t offset)
-{
-    clobbered = (uintptr_t)fn + offset;
-    return set((uintptr_t)fn + offset, 0xcc /*int3*/);
-}
-
-void unclobber(void *fn, uintptr_t offset, uint8_t oldvalue)
-{
-    set((uintptr_t)fn + offset, oldvalue);
-}
-
-__BEGIN_DECLS
-extern void callit(void *obj, void *sel, void *fn);
-extern struct stret callit_stret(void *obj, void *sel, void *fn);
-__END_DECLS
-
 __asm__(
 "\n  .text"
 "\n  .globl _callit"
 "\n  _callit:"
-// save rsp and rip registers to variables
+// save sp and return address to variables
 "\n      movq  (%rsp), %r10"
 "\n      movq  %r10, _rip(%rip)"
 "\n      movq  %rsp, _rsp(%rip)"
@@ -562,7 +746,7 @@ __asm__(
 "\n  .text"
 "\n  .globl _callit_stret"
 "\n  _callit_stret:"
-// save rsp and rip registers to variables
+// save sp and return address to variables
 "\n      movq  (%rsp), %r10"
 "\n      movq  %r10, _rip(%rip)"
 "\n      movq  %rsp, _rsp(%rip)"
@@ -577,10 +761,330 @@ __asm__(
 "\n      jmpq  *%rcx"
         );
 
-uintptr_t *getOffsets(void *symbol, const char *symname)
+
+// x86_64
+
+#elif __i386__
+
+#define OTOOL "/usr/bin/xcrun otool -arch i386 "
+
+typedef uint8_t insn_t;
+#define BREAK_INSN ((insn_t)0xcc)  // int3
+
+uintptr_t eip = 0;
+uintptr_t esp = 0;
+uintptr_t ebx = 0;
+uintptr_t ebp = 0;
+uintptr_t edi = 0;
+uintptr_t esi = 0;
+uintptr_t espfix = 0;
+
+void handle_exception(i386_thread_state_t *state)
+{
+    unw_cursor_t curs;
+    unw_word_t reg;
+    int err;
+    int step;
+
+    err = unw_init_local(&curs, (unw_context_t *)state);
+    testassert(!err);
+
+    step = unw_step(&curs);
+    testassert(step == UNW_STEP_SUCCESS);
+
+    err = unw_get_reg(&curs, UNW_REG_IP, &reg);
+    testassert(!err);
+    testassert(reg == eip);
+
+    err = unw_get_reg(&curs, UNW_X86_ESP, &reg);
+    testassert(!err);
+    testassert(reg == esp);
+
+    err = unw_get_reg(&curs, UNW_X86_EBX, &reg);
+    testassert(!err);
+    testassert(reg == ebx);
+
+    err = unw_get_reg(&curs, UNW_X86_EBP, &reg);
+    testassert(!err);
+    testassert(reg == ebp);
+
+    err = unw_get_reg(&curs, UNW_X86_EDI, &reg);
+    testassert(!err);
+    testassert(reg == edi);
+
+    err = unw_get_reg(&curs, UNW_X86_ESI, &reg);
+    testassert(!err);
+    testassert(reg == esi);
+
+
+    // set thread state to unwound state
+    state->__eip = eip;
+    state->__esp = esp + espfix;
+    state->__ebx = ebx;
+    state->__ebp = ebp;
+    state->__edi = edi;
+    state->__esi = esi;
+
+    caught = true;
+}
+
+
+void sigtrap(int sig, siginfo_t *info, void *cc)
+{
+    ucontext_t *uc = (ucontext_t *)cc;
+    mcontext_t mc = (mcontext_t)uc->uc_mcontext;
+
+    testprintf("    handled\n");
+
+    testassert(sig == SIGTRAP);
+    testassert((uintptr_t)info->si_addr-1 == clobbered);
+
+    handle_exception(&mc->__ss);
+    // handle_exception changed register state for continuation
+}
+
+__asm__(
+"\n  .text"
+"\n  .globl _callit"
+"\n  _callit:"
+// save sp and return address to variables
+"\n      call  1f"
+"\n  1:  popl  %edx"
+"\n      movl  (%esp), %eax"
+"\n      movl  %eax, _eip-1b(%edx)"
+"\n      movl  %esp, _esp-1b(%edx)"
+"\n      addl  $4,   _esp-1b(%edx)"   // rewind to pre-call value
+"\n      movl  $0,   _espfix-1b(%edx)"
+// save other non-volatile registers to variables
+"\n      movl  %ebx, _ebx-1b(%edx)"
+"\n      movl  %ebp, _ebp-1b(%edx)"
+"\n      movl  %edi, _edi-1b(%edx)"
+"\n      movl  %esi, _esi-1b(%edx)"
+"\n      jmpl  *12(%esp)"
+        );
+
+__asm__(
+"\n  .text"
+"\n  .globl _callit_stret"
+"\n  _callit_stret:"
+// save sp and return address to variables
+"\n      call  1f"
+"\n  1:  popl  %edx"
+"\n      movl  (%esp), %eax"
+"\n      movl  %eax, _eip-1b(%edx)"
+"\n      movl  %esp, _esp-1b(%edx)"
+"\n      addl  $4,   _esp-1b(%edx)"   // rewind to pre-call value
+"\n      movl  $4,   _espfix-1b(%edx)"
+// save other non-volatile registers to variables
+"\n      movl  %ebx, _ebx-1b(%edx)"
+"\n      movl  %ebp, _ebp-1b(%edx)"
+"\n      movl  %edi, _edi-1b(%edx)"
+"\n      movl  %esi, _esi-1b(%edx)"
+"\n      jmpl  *16(%esp)"
+        );
+
+
+// i386
+#elif __arm64__
+
+#include <sys/ucontext.h>
+
+// runs on iOS device, no xcrun command present
+#define OTOOL "/usr/bin/otool -arch arm64 "
+
+typedef uint32_t insn_t;
+#define BREAK_INSN ((insn_t)0xd4200020)  // brk #1
+
+uintptr_t x19 = 0;
+uintptr_t x20 = 0;
+uintptr_t x21 = 0;
+uintptr_t x22 = 0;
+uintptr_t x23 = 0;
+uintptr_t x24 = 0;
+uintptr_t x25 = 0;
+uintptr_t x26 = 0;
+uintptr_t x27 = 0;
+uintptr_t x28 = 0;
+uintptr_t fp = 0;
+uintptr_t sp = 0;
+uintptr_t pc = 0;
+
+void handle_exception(arm_thread_state64_t *state)
+{
+    unw_cursor_t curs;
+    unw_word_t reg;
+    int err;
+    int step;
+
+    err = unw_init_local(&curs, (unw_context_t *)state);
+    testassert(!err);
+
+    step = unw_step(&curs);
+    testassert(step == UNW_STEP_SUCCESS);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X19, &reg);
+    testassert(!err);
+    testassert(reg == x19);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X20, &reg);
+    testassert(!err);
+    testassert(reg == x20);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X21, &reg);
+    testassert(!err);
+    testassert(reg == x21);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X22, &reg);
+    testassert(!err);
+    testassert(reg == x22);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X23, &reg);
+    testassert(!err);
+    testassert(reg == x23);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X24, &reg);
+    testassert(!err);
+    testassert(reg == x24);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X25, &reg);
+    testassert(!err);
+    testassert(reg == x25);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X26, &reg);
+    testassert(!err);
+    testassert(reg == x26);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X27, &reg);
+    testassert(!err);
+    testassert(reg == x27);
+
+    err = unw_get_reg(&curs, UNW_ARM64_X28, &reg);
+    testassert(!err);
+    testassert(reg == x28);
+
+    err = unw_get_reg(&curs, UNW_ARM64_FP, &reg);
+    testassert(!err);
+    testassert(reg == fp);
+
+    err = unw_get_reg(&curs, UNW_ARM64_SP, &reg);
+    testassert(!err);
+    testassert(reg == sp);
+
+    err = unw_get_reg(&curs, UNW_REG_IP, &reg);
+    testassert(!err);
+    testassert(reg == pc);
+
+    // libunwind restores PC into LR and doesn't track LR
+    // err = unw_get_reg(&curs, UNW_ARM64_LR, &reg);
+    // testassert(!err);
+    // testassert(reg == lr);
+
+    // set thread state to unwound state
+    state->__x[19] = x19;
+    state->__x[20] = x20;
+    state->__x[20] = x21;
+    state->__x[22] = x22;
+    state->__x[23] = x23;
+    state->__x[24] = x24;
+    state->__x[25] = x25;
+    state->__x[26] = x26;
+    state->__x[27] = x27;
+    state->__x[28] = x28;
+    state->__fp = fp;
+    state->__lr = pc;  // libunwind restores PC into LR
+    state->__sp = sp;
+    state->__pc = pc;
+
+    caught = true;
+}
+
+
+void sigtrap(int sig, siginfo_t *info, void *cc)
+{
+    ucontext_t *uc = (ucontext_t *)cc;
+    struct __darwin_mcontext64 *mc = (struct __darwin_mcontext64 *)uc->uc_mcontext;
+
+    testprintf("    handled\n");
+
+    testassert(sig == SIGTRAP);
+    testassert((uintptr_t)info->si_addr == clobbered);
+
+    handle_exception(&mc->__ss);
+    // handle_exception changed register state for continuation
+}
+
+
+__asm__(
+"\n  .text"
+"\n  .globl _callit"
+"\n  _callit:"
+// save sp and return address to variables
+"\n      mov   x16, sp"
+"\n      adrp  x17, _sp@PAGE"
+"\n      str   x16, [x17, _sp@PAGEOFF]"
+"\n      adrp  x17, _pc@PAGE"
+"\n      str   lr, [x17, _pc@PAGEOFF]"
+// save other non-volatile registers to variables
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x19, [x17, _x19@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x20, [x17, _x20@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x21, [x17, _x21@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x22, [x17, _x22@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x23, [x17, _x23@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x24, [x17, _x24@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x25, [x17, _x25@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x26, [x17, _x26@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x27, [x17, _x27@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   x28, [x17, _x28@PAGEOFF]"
+"\n      adrp  x17, _x19@PAGE"
+"\n      str   fp,  [x17, _fp@PAGEOFF]"
+"\n      br    x2"
+        );
+
+
+// arm64
+#else
+
+#error unknown architecture
+
+#endif
+
+
+insn_t set(uintptr_t dst, insn_t newvalue)
 {
-    uintptr_t *result = (uintptr_t *)malloc(PAGE_SIZE * sizeof(uintptr_t));
-    uintptr_t *end = result + PAGE_SIZE;
+    uintptr_t start = dst & ~(PAGE_MAX_SIZE-1);
+    mprotect((void*)start, PAGE_MAX_SIZE, PROT_READ|PROT_WRITE);
+    insn_t oldvalue = *(insn_t *)dst;
+    *(insn_t *)dst = newvalue;
+    mprotect((void*)start, PAGE_MAX_SIZE, PROT_READ|PROT_EXEC);
+    return oldvalue;
+}
+
+insn_t clobber(void *fn, uintptr_t offset)
+{
+    clobbered = (uintptr_t)fn + offset;
+    return set((uintptr_t)fn + offset, BREAK_INSN);
+}
+
+void unclobber(void *fn, uintptr_t offset, insn_t oldvalue)
+{
+    set((uintptr_t)fn + offset, oldvalue);
+}
+
+
+uintptr_t *getOffsets(void *symbol, const char *symname, uintptr_t *outBase)
+{
+    uintptr_t *result = (uintptr_t *)malloc(1000 * sizeof(uintptr_t));
+    uintptr_t *end = result + 1000;
     uintptr_t *p = result;
 
     // find library
@@ -591,8 +1095,11 @@ uintptr_t *getOffsets(void *symbol, const char *symname)
     unsetenv("DYLD_LIBRARY_PATH");
     unsetenv("DYLD_ROOT_PATH");
     unsetenv("DYLD_INSERT_LIBRARIES");
+    unsetenv("DYLD_SHARED_REGION");
+    unsetenv("DYLD_SHARED_CACHE_DIR");
+    unsetenv("DYLD_SHARED_CACHE_DONT_VALIDATE");
     char *cmd;
-    asprintf(&cmd, "/usr/bin/xcrun otool -arch x86_64 -tv -p _%s %s", 
+    asprintf(&cmd, OTOOL "-tv -p _%s %s", 
              symname, dl.dli_fname);
     testprintf("%s\n", cmd);
     FILE *disa = popen(cmd, "r");
@@ -621,17 +1128,39 @@ uintptr_t *getOffsets(void *symbol, const char *symname)
     }
     pclose(disa);
 
+#if __arm64__
+    // Also add breakpoints in _objc_msgSend_uncached_impcache
+    // (which is the slow path and has a frame to unwind)
+    if (0 != strcmp(symname, "_objc_msgSend_uncached_impcache")) {
+        uintptr_t base2;
+        uintptr_t *more_offsets = getOffsets(symbol, "_objc_msgSend_uncached_impcache", &base2);
+        uintptr_t *q = more_offsets;
+        // Skip prologue because it's imprecisely modeled in compact unwind
+        testassert(*q != ~0UL);
+        q++;
+        testassert(*q != ~0UL);
+        q++;
+        while (*q != ~0UL) *p++ = *q++ + base2 - base;
+        // Skip return because it's imprecisely modeled in compact unwind
+        p--;
+        free(more_offsets);
+    }
+#endif
+
     testassert(p > result);
     testassert(p < end);
     *p = ~0UL;
+#if __x86_64__
     // hack: skip last instruction because libunwind blows up if it's 
     // one byte long and followed by the next function with no NOPs first
-    if (p > result) p[-1] = ~0UL;
+    p[-1] = ~0UL;
+#endif
+    if (outBase) *outBase = base;
     return result;
 }
 
-void CALLIT(void *o, void *sel_arg, SEL s, void *f) __attribute__((noinline));
-void CALLIT(void *o, void *sel_arg, SEL s, void *f)
+void CALLIT(void *o, void *sel_arg, SEL s, void *f, bool stret) __attribute__((noinline));
+void CALLIT(void *o, void *sel_arg, SEL s, void *f, bool stret)
 {
     uintptr_t message_ref[2];
     if (sel_arg != s) {
@@ -640,10 +1169,22 @@ void CALLIT(void *o, void *sel_arg, SEL s, void *f)
         memcpy(message_ref, sel_arg, sizeof(message_ref));
         sel_arg = message_ref;
     }
-    if (s == @selector(idret_nop)) callit(o, sel_arg, f);
-    else if (s == @selector(fpret_nop)) callit(o, sel_arg, f);
-    else if (s == @selector(stret_nop)) callit_stret(o, sel_arg, f);
-    else fail("test_dw selector");
+    if (!stret) callit(o, sel_arg, f);
+#if SUPPORT_STRET
+    else callit_stret(o, sel_arg, f);
+#else
+    else fail("stret?");
+#endif
+}
+
+void test_dw_forward(void)
+{
+    return;
+}
+
+struct stret test_dw_forward_stret(void)
+{
+    return zero;
 }
 
 // sub = ordinary receiver object
@@ -652,9 +1193,60 @@ void CALLIT(void *o, void *sel_arg, SEL s, void *f)
 // sub_arg = arg to pass in receiver register (may be objc_super struct)
 // tagged_arg = arg to pass in receiver register (may be objc_super struct)
 // sel_arg = arg to pass in sel register (may be message_ref)
-void test_dw(const char *name, id sub, id tagged, SEL sel)
+// uncaughtAllowed is the number of acceptable unreachable instructions
+//   (for example, the ones that handle the corrupt-cache-error case)
+void test_dw(const char *name, id sub, id tagged, bool stret, 
+             int uncaughtAllowed)
 {
-    testprintf("DWARF FOR %s\n", name);
+    SEL sel = @selector(a);
+
+    testprintf("DWARF FOR %s%s\n", name, stret ? " (stret)" : "");
+
+    // We need 2 SELs of each alignment so we can generate hash collisions.
+    // sel_registerName() never returns those alignments because they 
+    // differ from malloc's alignment. So we create lots of compiled-in 
+    // SELs here and hope something fits.
+    SEL lotsOfSels[] = {
+        @selector(a1), @selector(a2), @selector(a3), @selector(a4), 
+        @selector(a5), @selector(a6), @selector(a7), @selector(a8), 
+        @selector(aa), @selector(ab), @selector(ac), @selector(ad), 
+        @selector(ae), @selector(af), @selector(ag), @selector(ah), 
+        @selector(A1), @selector(A2), @selector(A3), @selector(A4), 
+        @selector(A5), @selector(A6), @selector(A7), @selector(A8), 
+        @selector(AA), @selector(Ab), @selector(Ac), @selector(Ad), 
+        @selector(Ae), @selector(Af), @selector(Ag), @selector(Ah), 
+        @selector(bb1), @selector(bb2), @selector(bb3), @selector(bb4), 
+        @selector(bb5), @selector(bb6), @selector(bb7), @selector(bb8), 
+        @selector(bba), @selector(bbb), @selector(bbc), @selector(bbd), 
+        @selector(bbe), @selector(bbf), @selector(bbg), @selector(bbh), 
+        @selector(BB1), @selector(BB2), @selector(BB3), @selector(BB4), 
+        @selector(BB5), @selector(BB6), @selector(BB7), @selector(BB8), 
+        @selector(BBa), @selector(BBb), @selector(BBc), @selector(BBd), 
+        @selector(BBe), @selector(BBf), @selector(BBg), @selector(BBh), 
+        @selector(ccc1), @selector(ccc2), @selector(ccc3), @selector(ccc4), 
+        @selector(ccc5), @selector(ccc6), @selector(ccc7), @selector(ccc8), 
+        @selector(ccca), @selector(cccb), @selector(cccc), @selector(cccd), 
+        @selector(ccce), @selector(cccf), @selector(cccg), @selector(ccch), 
+        @selector(CCC1), @selector(CCC2), @selector(CCC3), @selector(CCC4), 
+        @selector(CCC5), @selector(CCC6), @selector(CCC7), @selector(CCC8), 
+        @selector(CCCa), @selector(CCCb), @selector(CCCc), @selector(CCCd), 
+        @selector(CCCe), @selector(CCCf), @selector(CCCg), @selector(CCCh), 
+    };
+    #define ALIGNCOUNT 16
+    SEL sels[ALIGNCOUNT][2] = {{0}};
+    for (int align = 0; align < ALIGNCOUNT; align++) {
+        for (size_t i = 0; i < sizeof(lotsOfSels)/sizeof(lotsOfSels[0]); i++) {
+            if ((uintptr_t)(void*)lotsOfSels[i] % ALIGNCOUNT == align) {
+                if (sels[align][0]) {
+                    sels[align][1] = lotsOfSels[i];
+                } else {
+                    sels[align][0] = lotsOfSels[i];
+                }
+            }
+        }
+        if (!sels[align][0]) fail("no SEL with alignment %d", align);
+        if (!sels[align][1]) fail("only one SEL with alignment %d", align);
+    }
 
     void *fn = dlsym(RTLD_DEFAULT, name);
     testassert(fn);
@@ -669,8 +1261,12 @@ void test_dw(const char *name, id sub, id tagged, SEL sel)
     struct objc_super tagged_sup_st = { tagged, object_getClass(tagged) };
     struct { void *imp; SEL sel; } message_ref = { fn, sel };
 
+    Class cache_cls = object_getClass(sub);
+
     if (strstr(name, "Super")) {
         // super version - replace receiver with objc_super
+        // clear caches of superclass
+        cache_cls = class_getSuperclass(cache_cls);
         sub_arg = &sup_st;
         tagged_arg = &tagged_sup_st;
     }
@@ -681,60 +1277,109 @@ void test_dw(const char *name, id sub, id tagged, SEL sel)
     }
 
 
-    uintptr_t *insnOffsets = getOffsets(fn, name);
-    uintptr_t *offsetp = insnOffsets;
+    uintptr_t *insnOffsets = getOffsets(fn, name, nil);
     uintptr_t offset;
-    while ((offset = *offsetp++) != ~0UL) {
+    int uncaughtCount = 0;
+    for (int oo = 0; insnOffsets[oo] != ~0UL; oo++) {
+        offset = insnOffsets[oo];
         testprintf("OFFSET %lu\n", offset);
 
-        uint8_t insn_byte = clobber(fn, offset);
+        insn_t saved_insn = clobber(fn, offset);
         caught = false;
 
         // nil
         if ((void*)objc_unretainedPointer(sub) == sub_arg) {
             SELF = nil;
             testprintf("  nil\n");
-            CALLIT(nil, sel_arg, sel, fn);
-            CALLIT(nil, sel_arg, sel, fn);
+            CALLIT(nil, sel_arg, sel, fn, stret);
+            CALLIT(nil, sel_arg, sel, fn, stret);
         }
 
         // uncached
         SELF = sub;
         testprintf("  uncached\n");
-        _objc_flush_caches(object_getClass(sub));
-        CALLIT(sub_arg, sel_arg, sel, fn);
-        _objc_flush_caches(object_getClass(sub));
-        CALLIT(sub_arg, sel_arg, sel, fn);
+        _objc_flush_caches(cache_cls);
+        CALLIT(sub_arg, sel_arg, sel, fn, stret);
+        _objc_flush_caches(cache_cls);
+        CALLIT(sub_arg, sel_arg, sel, fn, stret);
 
         // cached
         SELF = sub;
         testprintf("  cached\n");
-        CALLIT(sub_arg, sel_arg, sel, fn);
-        CALLIT(sub_arg, sel_arg, sel, fn);
+        CALLIT(sub_arg, sel_arg, sel, fn, stret);
+        CALLIT(sub_arg, sel_arg, sel, fn, stret);
         
         // uncached,tagged
         SELF = tagged;
         testprintf("  uncached,tagged\n");
-        _objc_flush_caches(object_getClass(tagged));
-        CALLIT(tagged_arg, sel_arg, sel, fn);
-        _objc_flush_caches(object_getClass(tagged));
-        CALLIT(tagged_arg, sel_arg, sel, fn);
+        _objc_flush_caches(cache_cls);
+        CALLIT(tagged_arg, sel_arg, sel, fn, stret);
+        _objc_flush_caches(cache_cls);
+        CALLIT(tagged_arg, sel_arg, sel, fn, stret);
 
         // cached,tagged
         SELF = tagged;
         testprintf("  cached,tagged\n");
-        CALLIT(tagged_arg, sel_arg, sel, fn);
-        CALLIT(tagged_arg, sel_arg, sel, fn);
+        CALLIT(tagged_arg, sel_arg, sel, fn, stret);
+        CALLIT(tagged_arg, sel_arg, sel, fn, stret);
+
+        // multiple SEL alignments, collisions, wraps
+        SELF = sub;
+        for (int a = 0; a < ALIGNCOUNT; a++) {
+            testprintf("  cached, SEL alignment %d\n", a);
+
+            // Count both up and down to be independent of 
+            // implementation's cache scan direction
+
+            _objc_flush_caches(cache_cls);
+            for (int x2 = 0; x2 < 1; x2++) {
+                for (int s = 0; s < 4; s++) {
+                    int align = (a+s) % ALIGNCOUNT;
+                    CALLIT(sub_arg, sels[align][0], sels[align][0], fn, stret);
+                    CALLIT(sub_arg, sels[align][1], sels[align][1], fn, stret);
+                }
+            }
+
+            _objc_flush_caches(cache_cls);
+            for (int x2 = 0; x2 < 1; x2++) {
+                for (int s = 0; s < 4; s++) {
+                    int align = abs(a-s) % ALIGNCOUNT;
+                    CALLIT(sub_arg, sels[align][0], sels[align][0], fn, stret);
+                    CALLIT(sub_arg, sels[align][1], sels[align][1], fn, stret);
+                }
+            }
+        }
         
-        unclobber(fn, offset, insn_byte);
+        unclobber(fn, offset, saved_insn);
+
+        // remember offsets that were caught by none of the above
+        if (caught) {
+            insnOffsets[oo] = 0;
+        } else {
+            uncaughtCount++;
+            testprintf("offset %s+%lu not caught (%d/%d)\n", 
+                       name, offset, uncaughtCount, uncaughtAllowed);
+        }
+    }
 
-        // require at least one path above to trip this offset
-        if (!caught) fprintf(stderr, "OFFSET %s+%lu NOT CAUGHT\n", name, offset);
+    // Complain if too many offsets went uncaught.
+    // Acceptably-uncaught offsets include the corrupt-cache-error handler.
+    if (uncaughtCount != uncaughtAllowed) {
+        for (int oo = 0; insnOffsets[oo] != ~0UL; oo++) {
+            if (insnOffsets[oo]) {
+                fprintf(stderr, "BAD: offset %s+%lu not caught\n", 
+                        name, insnOffsets[oo]);
+            }
+        }
+        fail("wrong instructions not reached for %s (missed %d, expected %d)",
+             name, uncaughtCount, uncaughtAllowed);
     }
+
     free(insnOffsets);
 }
 
-// x86_64
+
+// TEST_DWARF
 #endif
 
 
@@ -761,27 +1406,32 @@ void test_basic(id receiver)
     // fixme verify that uncached lookup didn't happen the 2nd time?
     SELF = receiver;
     for (int i = 0; i < 5; i++) {
+        testprintf("idret\n");
         state = 0;
         idval = nil;
         idval = [receiver idret :1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0];
         testassert(state == 101);
         testassert(idval == ID_RESULT);
         
+        testprintf("llret\n");
         llval = 0;
         llval = [receiver llret :1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0];
         testassert(state == 102);
         testassert(llval == LL_RESULT);
         
+        testprintf("stret\n");
         stretval = zero;
         stretval = [receiver stret :1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0];
         testassert(state == 103);
         testassert(stret_equal(stretval, STRET_RESULT));
         
+        testprintf("fpret\n");
         fpval = 0;
         fpval = [receiver fpret :1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0];
         testassert(state == 104);
         testassert(fpval == FP_RESULT);
         
+        testprintf("lfpret\n");
         lfpval = 0;
         lfpval = [receiver lfpret :1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0];
         testassert(state == 105);
@@ -790,12 +1440,14 @@ void test_basic(id receiver)
 #if __OBJC2__
         // explicitly call noarg messenger, even if compiler doesn't emit it
         state = 0;
+        testprintf("idret noarg\n");
         idval = nil;
         idval = ((typeof(idmsg0))objc_msgSend_noarg)(receiver, @selector(idret_noarg));
         testassert(state == 111);
         testassert(idval == ID_RESULT);
         
         llval = 0;
+        testprintf("llret noarg\n");
         llval = ((typeof(llmsg0))objc_msgSend_noarg)(receiver, @selector(llret_noarg));
         testassert(state == 112);
         testassert(llval == LL_RESULT);
@@ -808,12 +1460,14 @@ void test_basic(id receiver)
         testassert(stret_equal(stretval, STRET_RESULT));
         */
 # if !__i386__
+        testprintf("fpret noarg\n");
         fpval = 0;
         fpval = ((typeof(fpmsg0))objc_msgSend_noarg)(receiver, @selector(fpret_noarg));
         testassert(state == 114);
         testassert(fpval == FP_RESULT);
 # endif
 # if !__i386__ && !__x86_64__
+        testprintf("lfpret noarg\n");
         lfpval = 0;
         lfpval = ((typeof(lfpmsg0))objc_msgSend_noarg)(receiver, @selector(lfpret_noarg));
         testassert(state == 115);
@@ -821,6 +1475,8 @@ void test_basic(id receiver)
 # endif
 #endif
     }
+
+    testprintf("basic done\n");
 }
 
 int main()
@@ -911,16 +1567,19 @@ int main()
 
     SELF = sub;
     [sub voidret_nop];
+    [sub voidret_nop2];
     [sub llret_nop];
     [sub stret_nop];
     [sub fpret_nop];
     [sub lfpret_nop];
     [sub voidret_nop];
+    [sub voidret_nop2];
     [sub llret_nop];
     [sub stret_nop];
     [sub fpret_nop];
     [sub lfpret_nop];
     [sub voidret_nop];
+    [sub voidret_nop2];
     [sub llret_nop];
     [sub stret_nop];
     [sub fpret_nop];
@@ -930,24 +1589,30 @@ int main()
     // The errors we're trying to catch should be catastrophically slow, 
     // so the margins here are generous to avoid false failures.
 
+    // Use voidret because id return is too slow for perf test with ARC.
+
+    // Pick smallest of voidret_nop and voidret_nop2 time
+    // in the hopes that one of them didn't collide in the method cache.
+
 #define COUNT 1000000
+
     startTime = mach_absolute_time();
     ALIGN_();
     for (i = 0; i < COUNT; i++) {
-        [sub voidret_nop];  // id return is too slow for perf test with ARC
+        [sub voidret_nop];
     }
     totalTime = mach_absolute_time() - startTime;
-    testprintf("time: idret  %llu\n", totalTime);
+    testprintf("time: voidret  %llu\n", totalTime);
     targetTime = totalTime;
 
     startTime = mach_absolute_time();
     ALIGN_();
     for (i = 0; i < COUNT; i++) {
-        [sub voidret_nop];  // id return is too slow for perf test with ARC
+        [sub voidret_nop2];  
     }
     totalTime = mach_absolute_time() - startTime;
-    testprintf("time: idret  %llu\n", totalTime);
-    targetTime = totalTime;
+    testprintf("time: voidret2  %llu\n", totalTime);
+    if (totalTime < targetTime) targetTime = totalTime;
 
     startTime = mach_absolute_time();
     ALIGN_();
@@ -980,6 +1645,13 @@ int main()
     }
     totalTime = mach_absolute_time() - startTime;
     timecheck("lfpret", totalTime, targetTime * 0.7, targetTime * 4.0);
+
+#if __arm64__
+    // Removing this testwarn(), or changing voidret_nop to nop;ret, 
+    // changes the voidret_nop and stret_nop times above by a factor of 2.
+    testwarn("rdar://13896922 nop;ret is faster than ret?");
+#endif
+
 #undef COUNT
 
     // method_invoke 
@@ -1059,6 +1731,15 @@ int main()
     // no stret result guarantee for hand-written calls, even with clang
 #endif
 
+#if __i386__
+    // check struct-return address stack pop
+    for (int i = 0; i < 10000000; i++) {
+        state = 0;
+        ((struct stret (*)(id, SEL))objc_msgSend_stret)
+            (nil, @selector(stret_nop));
+    }
+#endif
+
     state = 0;
     fpval = FP_RESULT;
     fpval = [(id)NIL_RECEIVER fpret :1:2:3:4:5:6:7:8:9:10:11:12:13:1.0:2.0:3.0:4.0:5.0:6.0:7.0:8.0:9.0:10.0:11.0:12.0:13.0:14.0:15.0];
@@ -1071,6 +1752,44 @@ int main()
     testassert(state == 0);
     testassert(lfpval == 0.0);
 
+    // message to nil, different struct types
+    // This verifies that ordinary objc_msgSend() erases enough registers 
+    // for structs that return in registers.
+#define TEST_NIL_STRUCT(i,n)                                            \
+    do {                                                                \
+        struct stret_##i##n z;                                          \
+        bzero(&z, sizeof(z));                                           \
+        [Super stret_i##n##_nonzero];                                   \
+        [Super stret_d##n##_nonzero];                                   \
+        struct stret_##i##n val = [(id)NIL_RECEIVER stret_##i##n##_zero]; \
+        testassert(0 == memcmp(&z, &val, sizeof(val)));             \
+    } while (0)
+
+    TEST_NIL_STRUCT(i,1);
+    TEST_NIL_STRUCT(i,2);
+    TEST_NIL_STRUCT(i,3);
+    TEST_NIL_STRUCT(i,4);
+    TEST_NIL_STRUCT(i,5);
+    TEST_NIL_STRUCT(i,6);
+    TEST_NIL_STRUCT(i,7);
+    TEST_NIL_STRUCT(i,8);
+    TEST_NIL_STRUCT(i,9);
+
+#if __i386__
+    testwarn("rdar://16267205 i386 struct{float} and struct{double}");
+#else
+    TEST_NIL_STRUCT(d,1);
+#endif
+    TEST_NIL_STRUCT(d,2);
+    TEST_NIL_STRUCT(d,3);
+    TEST_NIL_STRUCT(d,4);
+    TEST_NIL_STRUCT(d,5);
+    TEST_NIL_STRUCT(d,6);
+    TEST_NIL_STRUCT(d,7);
+    TEST_NIL_STRUCT(d,8);
+    TEST_NIL_STRUCT(d,9);
+
+
 #if __OBJC2__
     // message to nil noarg
     // explicitly call noarg messenger, even if compiler doesn't emit it
@@ -1132,7 +1851,7 @@ int main()
     testassert(sup_st.super_class == object_getClass(sub));
 #endif
 
-#if __OBJC2__
+#if __OBJC2__  &&  !__arm64__
     // Debug messengers.
     testprintf("debug messengers\n");
 
@@ -1198,9 +1917,10 @@ int main()
 #endif
 
 
-#if __x86_64__  &&  !__has_feature(objc_arc)
+#if !TEST_DWARF
+    testwarn("no unwind tables in this configuration");
+#else
     // DWARF unwind tables
-    // Not for ARC because the extra RR calls hit the traps at the wrong times
     testprintf("unwind tables\n");
 
     // install exception handler
@@ -1210,20 +1930,38 @@ int main()
     act.sa_flags = SA_SIGINFO;
     sigaction(SIGTRAP, &act, NULL);
 
-    // use _nop methods because other methods make more calls
-    // which can die in the trapped messenger
-
-    test_dw("objc_msgSend",                   sub,tagged,@selector(idret_nop));
-    test_dw("objc_msgSend_stret",             sub,tagged,@selector(stret_nop));
-    test_dw("objc_msgSend_fpret",             sub,tagged,@selector(fpret_nop));
-    // fixme fp2ret
-    test_dw("objc_msgSendSuper",              sub,tagged,@selector(idret_nop));
-    test_dw("objc_msgSendSuper2",             sub,tagged,@selector(idret_nop));
-    test_dw("objc_msgSendSuper_stret",        sub,tagged,@selector(stret_nop));
-    test_dw("objc_msgSendSuper2_stret",       sub,tagged,@selector(stret_nop));
+    SubDW *dw = [[SubDW alloc] init];
+
+    objc_setForwardHandler((void*)test_dw_forward, (void*)test_dw_forward_stret);
+
+# if __x86_64__
+    test_dw("objc_msgSend",             dw, tagged, false, 2);
+    test_dw("objc_msgSend_stret",       dw, tagged, true,  4);
+    test_dw("objc_msgSend_fpret",       dw, tagged, false, 2);
+    test_dw("objc_msgSend_fp2ret",      dw, tagged, false, 2);
+    test_dw("objc_msgSendSuper",        dw, tagged, false, 2);
+    test_dw("objc_msgSendSuper2",       dw, tagged, false, 2);
+    test_dw("objc_msgSendSuper_stret",  dw, tagged, true,  4);
+    test_dw("objc_msgSendSuper2_stret", dw, tagged, true,  4);
+# elif __i386__
+    test_dw("objc_msgSend",             dw, dw, false, 10);
+    test_dw("objc_msgSend_stret",       dw, dw, true,  10);
+    test_dw("objc_msgSend_fpret",       dw, dw, false, 10);
+    test_dw("objc_msgSendSuper",        dw, dw, false, 10);
+    test_dw("objc_msgSendSuper2",       dw, dw, false, 10);
+    test_dw("objc_msgSendSuper_stret",  dw, dw, true,  10);
+    test_dw("objc_msgSendSuper2_stret", dw, dw, true,  10);
+# elif __arm64__
+    test_dw("objc_msgSend",             dw, tagged, false, 2);
+    test_dw("objc_msgSendSuper",        dw, tagged, false, 2);
+    test_dw("objc_msgSendSuper2",       dw, tagged, false, 2);
+# else
+#   error unknown architecture
+# endif
 
     // DWARF unwind tables
 #endif
+
   } POP_POOL;
     succeed(__FILE__);
 }