| // Copyright 2018 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // +build race |
| |
| #include "go_asm.h" |
| #include "funcdata.h" |
| #include "textflag.h" |
| #include "tls_arm64.h" |
| |
| // The following thunks allow calling the gcc-compiled race runtime directly |
| // from Go code without going all the way through cgo. |
| // First, it's much faster (up to 50% speedup for real Go programs). |
| // Second, it eliminates race-related special cases from cgocall and scheduler. |
| // Third, in long-term it will allow to remove cyclic runtime/race dependency on cmd/go. |
| |
| // A brief recap of the arm64 calling convention. |
| // Arguments are passed in R0...R7, the rest is on stack. |
| // Callee-saved registers are: R19...R28. |
| // Temporary registers are: R9...R15 |
| // SP must be 16-byte aligned. |
| |
| // When calling racecalladdr, R9 is the call target address. |
| |
| // The race ctx, ThreadState *thr below, is passed in R0 and loaded in racecalladdr. |
| |
| #define load_g \ |
| MRS_TPIDR_R0 \ |
| MOVD runtime·tls_g(SB), R11 \ |
| ADD R11, R0 \ |
| MOVD 0(R0), g |
| |
| // func runtime·raceread(addr uintptr) |
| // Called from instrumented code. |
| TEXT runtime·raceread(SB), NOSPLIT, $0-8 |
| MOVD addr+0(FP), R1 |
| MOVD LR, R2 |
| // void __tsan_read(ThreadState *thr, void *addr, void *pc); |
| MOVD $__tsan_read(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // func runtime·RaceRead(addr uintptr) |
| TEXT runtime·RaceRead(SB), NOSPLIT, $0-8 |
| // This needs to be a tail call, because raceread reads caller pc. |
| JMP runtime·raceread(SB) |
| |
| // func runtime·racereadpc(void *addr, void *callpc, void *pc) |
| TEXT runtime·racereadpc(SB), NOSPLIT, $0-24 |
| MOVD addr+0(FP), R1 |
| MOVD callpc+8(FP), R2 |
| MOVD pc+16(FP), R3 |
| // void __tsan_read_pc(ThreadState *thr, void *addr, void *callpc, void *pc); |
| MOVD $__tsan_read_pc(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // func runtime·racewrite(addr uintptr) |
| // Called from instrumented code. |
| TEXT runtime·racewrite(SB), NOSPLIT, $0-8 |
| MOVD addr+0(FP), R1 |
| MOVD LR, R2 |
| // void __tsan_write(ThreadState *thr, void *addr, void *pc); |
| MOVD $__tsan_write(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // func runtime·RaceWrite(addr uintptr) |
| TEXT runtime·RaceWrite(SB), NOSPLIT, $0-8 |
| // This needs to be a tail call, because racewrite reads caller pc. |
| JMP runtime·racewrite(SB) |
| |
| // func runtime·racewritepc(void *addr, void *callpc, void *pc) |
| TEXT runtime·racewritepc(SB), NOSPLIT, $0-24 |
| MOVD addr+0(FP), R1 |
| MOVD callpc+8(FP), R2 |
| MOVD pc+16(FP), R3 |
| // void __tsan_write_pc(ThreadState *thr, void *addr, void *callpc, void *pc); |
| MOVD $__tsan_write_pc(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // func runtime·racereadrange(addr, size uintptr) |
| // Called from instrumented code. |
| TEXT runtime·racereadrange(SB), NOSPLIT, $0-16 |
| MOVD addr+0(FP), R1 |
| MOVD size+8(FP), R2 |
| MOVD LR, R3 |
| // void __tsan_read_range(ThreadState *thr, void *addr, uintptr size, void *pc); |
| MOVD $__tsan_read_range(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // func runtime·RaceReadRange(addr, size uintptr) |
| TEXT runtime·RaceReadRange(SB), NOSPLIT, $0-16 |
| // This needs to be a tail call, because racereadrange reads caller pc. |
| JMP runtime·racereadrange(SB) |
| |
| // func runtime·racereadrangepc1(void *addr, uintptr sz, void *pc) |
| TEXT runtime·racereadrangepc1(SB), NOSPLIT, $0-24 |
| MOVD addr+0(FP), R1 |
| MOVD size+8(FP), R2 |
| MOVD pc+16(FP), R3 |
| ADD $4, R3 // pc is function start, tsan wants return address. |
| // void __tsan_read_range(ThreadState *thr, void *addr, uintptr size, void *pc); |
| MOVD $__tsan_read_range(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // func runtime·racewriterange(addr, size uintptr) |
| // Called from instrumented code. |
| TEXT runtime·racewriterange(SB), NOSPLIT, $0-16 |
| MOVD addr+0(FP), R1 |
| MOVD size+8(FP), R2 |
| MOVD LR, R3 |
| // void __tsan_write_range(ThreadState *thr, void *addr, uintptr size, void *pc); |
| MOVD $__tsan_write_range(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // func runtime·RaceWriteRange(addr, size uintptr) |
| TEXT runtime·RaceWriteRange(SB), NOSPLIT, $0-16 |
| // This needs to be a tail call, because racewriterange reads caller pc. |
| JMP runtime·racewriterange(SB) |
| |
| // func runtime·racewriterangepc1(void *addr, uintptr sz, void *pc) |
| TEXT runtime·racewriterangepc1(SB), NOSPLIT, $0-24 |
| MOVD addr+0(FP), R1 |
| MOVD size+8(FP), R2 |
| MOVD pc+16(FP), R3 |
| ADD $4, R3 // pc is function start, tsan wants return address. |
| // void __tsan_write_range(ThreadState *thr, void *addr, uintptr size, void *pc); |
| MOVD $__tsan_write_range(SB), R9 |
| JMP racecalladdr<>(SB) |
| |
| // If addr (R1) is out of range, do nothing. |
| // Otherwise, setup goroutine context and invoke racecall. Other arguments already set. |
| TEXT racecalladdr<>(SB), NOSPLIT, $0-0 |
| load_g |
| MOVD g_racectx(g), R0 |
| // Check that addr is within [arenastart, arenaend) or within [racedatastart, racedataend). |
| MOVD runtime·racearenastart(SB), R10 |
| CMP R10, R1 |
| BLT data |
| MOVD runtime·racearenaend(SB), R10 |
| CMP R10, R1 |
| BLT call |
| data: |
| MOVD runtime·racedatastart(SB), R10 |
| CMP R10, R1 |
| BLT ret |
| MOVD runtime·racedataend(SB), R10 |
| CMP R10, R1 |
| BGT ret |
| call: |
| JMP racecall<>(SB) |
| ret: |
| RET |
| |
| // func runtime·racefuncenterfp(fp uintptr) |
| // Called from instrumented code. |
| // Like racefuncenter but doesn't passes an arg, uses the caller pc |
| // from the first slot on the stack |
| TEXT runtime·racefuncenterfp(SB), NOSPLIT, $0-0 |
| MOVD 0(RSP), R9 |
| JMP racefuncenter<>(SB) |
| |
| // func runtime·racefuncenter(pc uintptr) |
| // Called from instrumented code. |
| TEXT runtime·racefuncenter(SB), NOSPLIT, $0-8 |
| MOVD callpc+0(FP), R9 |
| JMP racefuncenter<>(SB) |
| |
| // Common code for racefuncenter/racefuncenterfp |
| // R9 = caller's return address |
| TEXT racefuncenter<>(SB), NOSPLIT, $0-0 |
| load_g |
| MOVD g_racectx(g), R0 // goroutine racectx |
| MOVD R9, R1 |
| // void __tsan_func_enter(ThreadState *thr, void *pc); |
| MOVD $__tsan_func_enter(SB), R9 |
| BL racecall<>(SB) |
| RET |
| |
| // func runtime·racefuncexit() |
| // Called from instrumented code. |
| TEXT runtime·racefuncexit(SB), NOSPLIT, $0-0 |
| load_g |
| MOVD g_racectx(g), R0 // race context |
| // void __tsan_func_exit(ThreadState *thr); |
| MOVD $__tsan_func_exit(SB), R9 |
| JMP racecall<>(SB) |
| |
| // Atomic operations for sync/atomic package. |
| // R3 = addr of arguments passed to this function, it can |
| // be fetched at 40(RSP) in racecallatomic after two times BL |
| // R0, R1, R2 set in racecallatomic |
| |
| // Load |
| TEXT sync∕atomic·LoadInt32(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic32_load(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·LoadInt64(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic64_load(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·LoadUint32(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·LoadInt32(SB) |
| |
| TEXT sync∕atomic·LoadUint64(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·LoadInt64(SB) |
| |
| TEXT sync∕atomic·LoadUintptr(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·LoadInt64(SB) |
| |
| TEXT sync∕atomic·LoadPointer(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·LoadInt64(SB) |
| |
| // Store |
| TEXT sync∕atomic·StoreInt32(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic32_store(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·StoreInt64(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic64_store(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·StoreUint32(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·StoreInt32(SB) |
| |
| TEXT sync∕atomic·StoreUint64(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·StoreInt64(SB) |
| |
| TEXT sync∕atomic·StoreUintptr(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·StoreInt64(SB) |
| |
| // Swap |
| TEXT sync∕atomic·SwapInt32(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic32_exchange(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·SwapInt64(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic64_exchange(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·SwapUint32(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·SwapInt32(SB) |
| |
| TEXT sync∕atomic·SwapUint64(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·SwapInt64(SB) |
| |
| TEXT sync∕atomic·SwapUintptr(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·SwapInt64(SB) |
| |
| // Add |
| TEXT sync∕atomic·AddInt32(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic32_fetch_add(SB), R9 |
| BL racecallatomic<>(SB) |
| MOVW add+8(FP), R0 // convert fetch_add to add_fetch |
| MOVW ret+16(FP), R1 |
| ADD R0, R1, R0 |
| MOVW R0, ret+16(FP) |
| RET |
| |
| TEXT sync∕atomic·AddInt64(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic64_fetch_add(SB), R9 |
| BL racecallatomic<>(SB) |
| MOVD add+8(FP), R0 // convert fetch_add to add_fetch |
| MOVD ret+16(FP), R1 |
| ADD R0, R1, R0 |
| MOVD R0, ret+16(FP) |
| RET |
| |
| TEXT sync∕atomic·AddUint32(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·AddInt32(SB) |
| |
| TEXT sync∕atomic·AddUint64(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·AddInt64(SB) |
| |
| TEXT sync∕atomic·AddUintptr(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·AddInt64(SB) |
| |
| // CompareAndSwap |
| TEXT sync∕atomic·CompareAndSwapInt32(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic32_compare_exchange(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·CompareAndSwapInt64(SB), NOSPLIT, $0 |
| GO_ARGS |
| MOVD $__tsan_go_atomic64_compare_exchange(SB), R9 |
| BL racecallatomic<>(SB) |
| RET |
| |
| TEXT sync∕atomic·CompareAndSwapUint32(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·CompareAndSwapInt32(SB) |
| |
| TEXT sync∕atomic·CompareAndSwapUint64(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·CompareAndSwapInt64(SB) |
| |
| TEXT sync∕atomic·CompareAndSwapUintptr(SB), NOSPLIT, $0 |
| GO_ARGS |
| JMP sync∕atomic·CompareAndSwapInt64(SB) |
| |
| // Generic atomic operation implementation. |
| // R9 = addr of target function |
| TEXT racecallatomic<>(SB), NOSPLIT, $0 |
| // Set up these registers |
| // R0 = *ThreadState |
| // R1 = caller pc |
| // R2 = pc |
| // R3 = addr of incoming arg list |
| |
| // Trigger SIGSEGV early. |
| MOVD 40(RSP), R3 // 1st arg is addr. after two times BL, get it at 40(RSP) |
| MOVD (R3), R13 // segv here if addr is bad |
| // Check that addr is within [arenastart, arenaend) or within [racedatastart, racedataend). |
| MOVD runtime·racearenastart(SB), R10 |
| CMP R10, R3 |
| BLT racecallatomic_data |
| MOVD runtime·racearenaend(SB), R10 |
| CMP R10, R3 |
| BLT racecallatomic_ok |
| racecallatomic_data: |
| MOVD runtime·racedatastart(SB), R10 |
| CMP R10, R3 |
| BLT racecallatomic_ignore |
| MOVD runtime·racedataend(SB), R10 |
| CMP R10, R3 |
| BGE racecallatomic_ignore |
| racecallatomic_ok: |
| // Addr is within the good range, call the atomic function. |
| load_g |
| MOVD g_racectx(g), R0 // goroutine context |
| MOVD 16(RSP), R1 // caller pc |
| MOVD R9, R2 // pc |
| ADD $40, RSP, R3 |
| JMP racecall<>(SB) // does not return |
| racecallatomic_ignore: |
| // Addr is outside the good range. |
| // Call __tsan_go_ignore_sync_begin to ignore synchronization during the atomic op. |
| // An attempt to synchronize on the address would cause crash. |
| MOVD R9, R20 // remember the original function |
| MOVD $__tsan_go_ignore_sync_begin(SB), R9 |
| load_g |
| MOVD g_racectx(g), R0 // goroutine context |
| BL racecall<>(SB) |
| MOVD R20, R9 // restore the original function |
| // Call the atomic function. |
| // racecall will call LLVM race code which might clobber R28 (g) |
| load_g |
| MOVD g_racectx(g), R0 // goroutine context |
| MOVD 16(RSP), R1 // caller pc |
| MOVD R9, R2 // pc |
| ADD $40, RSP, R3 // arguments |
| BL racecall<>(SB) |
| // Call __tsan_go_ignore_sync_end. |
| MOVD $__tsan_go_ignore_sync_end(SB), R9 |
| MOVD g_racectx(g), R0 // goroutine context |
| BL racecall<>(SB) |
| RET |
| |
| // func runtime·racecall(void(*f)(...), ...) |
| // Calls C function f from race runtime and passes up to 4 arguments to it. |
| // The arguments are never heap-object-preserving pointers, so we pretend there are no arguments. |
| TEXT runtime·racecall(SB), NOSPLIT, $0-0 |
| MOVD fn+0(FP), R9 |
| MOVD arg0+8(FP), R0 |
| MOVD arg1+16(FP), R1 |
| MOVD arg2+24(FP), R2 |
| MOVD arg3+32(FP), R3 |
| JMP racecall<>(SB) |
| |
| // Switches SP to g0 stack and calls (R9). Arguments already set. |
| TEXT racecall<>(SB), NOSPLIT, $0-0 |
| MOVD g_m(g), R10 |
| // Switch to g0 stack. |
| MOVD RSP, R19 // callee-saved, preserved across the CALL |
| MOVD m_g0(R10), R11 |
| CMP R11, g |
| BEQ call // already on g0 |
| MOVD (g_sched+gobuf_sp)(R11), R12 |
| MOVD R12, RSP |
| call: |
| BL R9 |
| MOVD R19, RSP |
| RET |
| |
| // C->Go callback thunk that allows to call runtime·racesymbolize from C code. |
| // Direct Go->C race call has only switched SP, finish g->g0 switch by setting correct g. |
| // The overall effect of Go->C->Go call chain is similar to that of mcall. |
| // R0 contains command code. R1 contains command-specific context. |
| // See racecallback for command codes. |
| TEXT runtime·racecallbackthunk(SB), NOSPLIT|NOFRAME, $0 |
| // Handle command raceGetProcCmd (0) here. |
| // First, code below assumes that we are on curg, while raceGetProcCmd |
| // can be executed on g0. Second, it is called frequently, so will |
| // benefit from this fast path. |
| CBNZ R0, rest |
| MOVD g, R13 |
| load_g |
| MOVD g_m(g), R0 |
| MOVD m_p(R0), R0 |
| MOVD p_raceprocctx(R0), R0 |
| MOVD R0, (R1) |
| MOVD R13, g |
| JMP (LR) |
| rest: |
| // Save callee-saved registers (Go code won't respect that). |
| // 8(RSP) and 16(RSP) are for args passed through racecallback |
| SUB $112, RSP |
| MOVD LR, 0(RSP) |
| STP (R19, R20), 24(RSP) |
| STP (R21, R22), 40(RSP) |
| STP (R23, R24), 56(RSP) |
| STP (R25, R26), 72(RSP) |
| STP (R27, g), 88(RSP) |
| // Set g = g0. |
| // load_g will clobber R0, Save R0 |
| MOVD R0, R13 |
| load_g |
| // restore R0 |
| MOVD R13, R0 |
| MOVD g_m(g), R13 |
| MOVD m_g0(R13), R14 |
| CMP R14, g |
| BEQ noswitch // branch if already on g0 |
| MOVD R14, g |
| |
| MOVD R0, 8(RSP) // func arg |
| MOVD R1, 16(RSP) // func arg |
| BL runtime·racecallback(SB) |
| |
| // All registers are smashed after Go code, reload. |
| MOVD g_m(g), R13 |
| MOVD m_curg(R13), g // g = m->curg |
| ret: |
| // Restore callee-saved registers. |
| MOVD 0(RSP), LR |
| LDP 24(RSP), (R19, R20) |
| LDP 40(RSP), (R21, R22) |
| LDP 56(RSP), (R23, R24) |
| LDP 72(RSP), (R25, R26) |
| LDP 88(RSP), (R27, g) |
| ADD $112, RSP |
| JMP (LR) |
| |
| noswitch: |
| // already on g0 |
| MOVD R0, 8(RSP) // func arg |
| MOVD R1, 16(RSP) // func arg |
| BL runtime·racecallback(SB) |
| JMP ret |
| |
| // tls_g, g value for each thread in TLS |
| GLOBL runtime·tls_g+0(SB), TLSBSS+DUPOK, $8 |