// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause #include "textflag.h" // ABI-checking trampoline for riscv64. 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 riscv64 guarantees: // - X27 holds the goroutine pointer (g) and must survive across any // call. Go keeps no hardware frame pointer on riscv64. The assembler // spells this register "g"; X27 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 (X27), 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 X5 and X6 are used // as scratch: caller-saved, and X27 is not among them. TEXT ·enterJITChecked(SB), NOSPLIT, $0-16 MOV fn+0(FP), X5 // target function address (T0) MOV X1, savedRA(SB) // save return address MOV X2, savedSP(SB) // save Go stack pointer MOV g, savedG(SB) // save g MOV $SENTINEL_G, g // sentinel in g MOV stack+8(FP), X6 // load prepared stack pointer (T1) LD 0(X6), X1 // load leaveJITCheckedRaw into RA MOV X6, X2 // SP stays on the leave slot: the kernel // reads its first argument at SP+8 JALR X0, 0(X5) // 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. MOV $SENTINEL_G, X6 BEQ g, X6, g_ok MOV ·abiResult(SB), X7 MOV $2, X5 OR X5, X7, X7 MOV X7, ·abiResult(SB) g_ok: MOV savedSP(SB), X6 // restore Go stack pointer MOV X6, X2 MOV savedRA(SB), X1 // restore return address MOV savedG(SB), g // restore g: Go code needs it the moment it // resumes, violation or not JALR X0, 0(X1) // return to Go caller GLOBL savedG(SB), NOPTR, $8