2026-08-30 11:27:54 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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
|
2026-08-30 22:27:20 +02:00
|
|
|
MOV g, savedG(SB) // save g
|
2026-08-30 11:27:54 +02:00
|
|
|
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
|
2026-08-31 12:13:43 +02:00
|
|
|
MOV X6, X2 // SP stays on the leave slot: the kernel
|
|
|
|
|
// reads its first argument at SP+8
|
2026-08-30 11:27:54 +02:00
|
|
|
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
|
2026-08-30 22:27:20 +02:00
|
|
|
MOV savedG(SB), g // restore g: Go code needs it the moment it
|
|
|
|
|
// resumes, violation or not
|
2026-08-30 11:27:54 +02:00
|
|
|
JALR X0, 0(X1) // return to Go caller
|
2026-08-30 22:27:20 +02:00
|
|
|
|
|
|
|
|
GLOBL savedG(SB), NOPTR, $8
|