Files
gasm-sdk/lint/liveness_test.go
T
2026-07-11 17:36:52 +02:00

176 lines
5.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package lint
import "testing"
// TestRegisterClobber checks the register-clobber audit is calibrated to the
// Go ABI (cmd/compile/abi-internal.md), not the platform ABI: Go's
// stack-based ABI0 — which hand-written assembly uses — has no System V
// style callee-saved registers, so argument and scratch registers may be
// clobbered freely. Only the registers the ABI fixes across calls (the
// frame pointer, the goroutine pointer, OS-reserved registers) are audited.
func TestRegisterClobber(t *testing.T) {
// amd64: BX, R12, R13 and R15 are argument/permanent-scratch registers in
// Go ABI0 — writing them unsaved is legal (a System V calibration would
// report all of these).
scratch := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOVQ CX, BX\n"+
"\tXORL R12, R12\n"+
"\tXORL R13, R13\n"+
"\tXORL R15, R15\n"+
"\tRET\n")
if codes(scratch)[CodeRegisterClobber] != 0 {
t.Fatalf("Go ABI0 scratch registers must not be flagged: %+v", scratch)
}
// amd64: R14 (the goroutine pointer) in a NOSPLIT function without calls
// is the runtime's own pattern — the ABI0 transition restores it — so it
// is not flagged.
leaf := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tXORL R14, R14\n"+
"\tRET\n")
if codes(leaf)[CodeRegisterClobber] != 0 {
t.Fatalf("R14 in a NOSPLIT leaf must not be flagged: %+v", leaf)
}
// amd64: R14 in a function that makes a call is a genuine hazard.
withCall := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tXORL R14, R14\n"+
"\tCALL ·g(SB)\n"+
"\tRET\n")
if codes(withCall)[CodeRegisterClobber] != 1 {
t.Fatalf("unsaved R14 with a call should be flagged: %+v", withCall)
}
// amd64: R14 in a non-NOSPLIT function is a hazard regardless of calls.
split := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), $0\n"+
"\tMOVQ CX, R14\n"+
"\tRET\n")
if codes(split)[CodeRegisterClobber] != 1 {
t.Fatalf("unsaved R14 in a non-NOSPLIT function should be flagged: %+v", split)
}
// amd64: R14 saved and restored around the call is preserved.
saved := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $8\n"+
"\tPUSHQ R14\n"+
"\tXORL R14, R14\n"+
"\tCALL ·g(SB)\n"+
"\tPOPQ R14\n"+
"\tRET\n")
if codes(saved)[CodeRegisterClobber] != 0 {
t.Fatalf("saved/restored R14 must not be flagged: %+v", saved)
}
// amd64: BP maintains the frame chain and is always audited.
bp := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOVQ CX, BP\n"+
"\tRET\n")
if codes(bp)[CodeRegisterClobber] != 1 {
t.Fatalf("unsaved BP write should be flagged: %+v", bp)
}
// arm64: R20 is scratch; R28 (goroutine pointer) and R18 (OS-reserved)
// are fixed by the Go ABI.
armScratch := lintSrcArch(t, "t_arm64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOVD R0, R20\n"+
"\tRET\n")
if codes(armScratch)[CodeRegisterClobber] != 0 {
t.Fatalf("arm64 scratch register must not be flagged: %+v", armScratch)
}
armG := lintSrcArch(t, "t_arm64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOVD R0, R28\n"+
"\tRET\n")
if codes(armG)[CodeRegisterClobber] != 1 {
t.Fatalf("unsaved arm64 R28 write should be flagged: %+v", armG)
}
armReserved := lintSrcArch(t, "t_arm64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOVD R0, R18\n"+
"\tRET\n")
if codes(armReserved)[CodeRegisterClobber] != 1 {
t.Fatalf("arm64 R18 write should be flagged: %+v", armReserved)
}
// riscv64: X27 holds the goroutine; X5–X7 are scratch.
riscScratch := lintSrcArch(t, "t_riscv64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOV X5, X6\n"+
"\tRET\n")
if codes(riscScratch)[CodeRegisterClobber] != 0 {
t.Fatalf("riscv64 scratch register must not be flagged: %+v", riscScratch)
}
riscG := lintSrcArch(t, "t_riscv64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOV X5, X27\n"+
"\tRET\n")
if codes(riscG)[CodeRegisterClobber] != 1 {
t.Fatalf("unsaved riscv64 X27 write should be flagged: %+v", riscG)
}
// loong64: R22 holds the goroutine; R5–R19 are argument/scratch.
loongScratch := lintSrcArch(t, "t_loong64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOVV R5, R6\n"+
"\tRET\n")
if codes(loongScratch)[CodeRegisterClobber] != 0 {
t.Fatalf("loong64 scratch register must not be flagged: %+v", loongScratch)
}
loongG := lintSrcArch(t, "t_loong64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tMOVV R5, R22\n"+
"\tRET\n")
if codes(loongG)[CodeRegisterClobber] != 1 {
t.Fatalf("unsaved loong64 R22 write should be flagged: %+v", loongG)
}
}
// TestFuncdata validates the FUNCDATA/PCDATA structural checks.
func TestFuncdata(t *testing.T) {
// Well formed: no findings.
good := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tFUNCDATA $0, gclocals·abc(SB)\n"+
"\tPCDATA $1, $0\n"+
"\tRET\n")
if codes(good)[CodeFuncdata] != 0 {
t.Fatalf("well-formed FUNCDATA/PCDATA must not be flagged: %+v", good)
}
// FUNCDATA with one operand.
bad1 := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tFUNCDATA $0\n"+
"\tRET\n")
if codes(bad1)[CodeFuncdata] == 0 {
t.Fatal("FUNCDATA with one operand should be flagged")
}
// PCDATA with a non-immediate value.
bad2 := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tPCDATA $0, AX\n"+
"\tRET\n")
if codes(bad2)[CodeFuncdata] == 0 {
t.Fatal("PCDATA with a register value should be flagged")
}
// FUNCDATA index out of range.
bad3 := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tFUNCDATA $99, gclocals·abc(SB)\n"+
"\tRET\n")
if codes(bad3)[CodeFuncdata] == 0 {
t.Fatal("out-of-range FUNCDATA index should be flagged")
}
}