X-Git-Url: https://git.saurik.com/apple/objc4.git/blobdiff_plain/7257e56cc9570231fcb1a302702a85f51f9a9790..8070259c3936ee823b758fc1ad1645ae016ba500:/test/msgSend.m diff --git a/test/msgSend.m b/test/msgSend.m index fd71539..833f53f 100644 --- a/test/msgSend.m +++ b/test/msgSend.m @@ -18,6 +18,19 @@ int main() #include #include +#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 #include #include @@ -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, ®); + testassert(!err); + testassert(reg == eip); + + err = unw_get_reg(&curs, UNW_X86_ESP, ®); + testassert(!err); + testassert(reg == esp); + + err = unw_get_reg(&curs, UNW_X86_EBX, ®); + testassert(!err); + testassert(reg == ebx); + + err = unw_get_reg(&curs, UNW_X86_EBP, ®); + testassert(!err); + testassert(reg == ebp); + + err = unw_get_reg(&curs, UNW_X86_EDI, ®); + testassert(!err); + testassert(reg == edi); + + err = unw_get_reg(&curs, UNW_X86_ESI, ®); + 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 + +// 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, ®); + testassert(!err); + testassert(reg == x19); + + err = unw_get_reg(&curs, UNW_ARM64_X20, ®); + testassert(!err); + testassert(reg == x20); + + err = unw_get_reg(&curs, UNW_ARM64_X21, ®); + testassert(!err); + testassert(reg == x21); + + err = unw_get_reg(&curs, UNW_ARM64_X22, ®); + testassert(!err); + testassert(reg == x22); + + err = unw_get_reg(&curs, UNW_ARM64_X23, ®); + testassert(!err); + testassert(reg == x23); + + err = unw_get_reg(&curs, UNW_ARM64_X24, ®); + testassert(!err); + testassert(reg == x24); + + err = unw_get_reg(&curs, UNW_ARM64_X25, ®); + testassert(!err); + testassert(reg == x25); + + err = unw_get_reg(&curs, UNW_ARM64_X26, ®); + testassert(!err); + testassert(reg == x26); + + err = unw_get_reg(&curs, UNW_ARM64_X27, ®); + testassert(!err); + testassert(reg == x27); + + err = unw_get_reg(&curs, UNW_ARM64_X28, ®); + testassert(!err); + testassert(reg == x28); + + err = unw_get_reg(&curs, UNW_ARM64_FP, ®); + testassert(!err); + testassert(reg == fp); + + err = unw_get_reg(&curs, UNW_ARM64_SP, ®); + testassert(!err); + testassert(reg == sp); + + err = unw_get_reg(&curs, UNW_REG_IP, ®); + testassert(!err); + testassert(reg == pc); + + // libunwind restores PC into LR and doesn't track LR + // err = unw_get_reg(&curs, UNW_ARM64_LR, ®); + // 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__); }