// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause #include "textflag.h" // ABI-checking trampoline for LoongArch 64. Sets a sentinel value in the // register the Go ABI fixes across calls before entering the JIT function // and checks whether it survived on return. // // The return trampoline (leaveJITCheckedRaw) is a raw TEXT symbol with no // Go function declaration, so the toolchain does NOT interpose an // ABIInternal wrapper; the JIT function RETs directly into the check // code, which sees the registers exactly as the function left them. // // Go ABI on loong64 guarantees: // - R22 holds the goroutine pointer (g) and must survive across any // call. Go keeps no hardware frame pointer on loong64. The assembler // spells this register "g"; R22 is not accepted. // Sentinel value chosen to be unlikely in normal execution. #define SENTINEL_G 0x0BADF00DDEADBEEF // GLOBL holding the raw address of the leave trampoline, read by Go. GLOBL ·leaveCheckedPtr(SB), NOPTR, $8 DATA ·leaveCheckedPtr(SB)/8, $·leaveJITCheckedRaw(SB) // func enterJITChecked(fn uintptr, stack uintptr) // Sets a sentinel in g (R22), switches to the prepared stack and jumps to // fn. The prepared stack's first word must be the address of // leaveJITCheckedRaw (read from leaveCheckedPtr). Only R4 and R5 are used // as scratch: caller-saved, and R22 is not among them. TEXT ·enterJITChecked(SB), NOSPLIT, $0-16 MOVV fn+0(FP), R4 // target function address (A0) MOVV R1, savedRA(SB) // save return address (RA) MOVV R3, savedSP(SB) // save Go stack pointer (SP) MOVV g, savedG(SB) // save g MOVV $SENTINEL_G, g // sentinel in g MOVV stack+8(FP), R5 // load prepared stack pointer (A1) MOVV 0(R5), R1 // load leaveJITCheckedRaw into RA MOVV R5, R3 // SP stays on the leave slot: the kernel // reads its first argument at SP+8 JMP (R4) // jump to JIT function // leaveJITCheckedRaw is the raw return trampoline. It has NO Go function // declaration, so no ABIInternal wrapper is generated; the JIT function's // RET lands here directly, seeing g exactly as the function left it. It // checks the sentinel, records violations in abiResult, then restores the // Go stack and returns. TEXT ·leaveJITCheckedRaw(SB), NOSPLIT, $0-0 // Check g against the sentinel. MOVV $SENTINEL_G, R5 BEQ g, R5, g_ok MOVV ·abiResult(SB), R4 MOVV $2, R6 OR R6, R4, R4 MOVV R4, ·abiResult(SB) g_ok: MOVV savedSP(SB), R5 // restore Go stack pointer MOVV R5, R3 MOVV savedRA(SB), R1 // restore return address MOVV savedG(SB), g // restore g: Go code needs it the moment it // resumes, violation or not JMP (R1) // return to Go caller GLOBL savedG(SB), NOPTR, $8 // leaveRawAddr holds the raw .abi0 address of leaveJIT, read by // call_loong64.go in preference to reflect.ValueOf(leaveJIT), which returns // the address of the ABIInternal wrapper the linker interposes: the // wrapper's prologue clobbers the saved-register window the JIT call // depends on. It lives here rather than beside leaveJIT in // trampoline_loong64.s because that file is assembled by gasm itself in // the ground-truth byte tests, and gasm cannot yet encode a symbol-valued // DATA word. GLOBL ·leaveRawAddr(SB), NOPTR, $8 DATA ·leaveRawAddr(SB)/8, $·leaveJIT(SB)