Files
gasm-sdk/lint/abi_test.go
T

130 lines
4.2 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package lint
import "testing"
// TestABIExpectedArgSize checks the Go ABI0 argument-area size computation
// against hand-verified signatures (the same layouts the go-flac kernels use).
func TestABIExpectedArgSize(t *testing.T) {
cases := []struct {
sig string
want int64
}{
{"func f()", 0},
{"func f(a int, b int)", 16},
{"func f(a int32)", 4}, // no results: no word-boundary padding
{"func f(a int32) (r int32)", 12}, // results begin on a word boundary: 4 -> 8, +4
{"func f(a int) int", 16},
{"func f(s []int32, p *[32]uint16) (x uint64, ok bool)", 41},
{"func f(left, right []int32, sums *[4]uint64)", 56},
{"func f(src []byte, dst []int32)", 48},
{"func f(a bool, b int64)", 16}, // bool at 0, int64 aligned to 8
{"func f(x struct{ a int32; b int64 })", 16},
}
for _, c := range cases {
got, ok := abiExpectedArgSize(c.sig)
if !ok {
t.Errorf("%s: could not compute size", c.sig)
continue
}
if got != c.want {
t.Errorf("%s: size = %d, want %d", c.sig, got, c.want)
}
}
}
func TestABIUnknownTypeSkipped(t *testing.T) {
// A bare named type of unknown size must abort the check rather than guess.
// (A *pointer* to a named type is still 8 bytes and is fine.)
if _, ok := abiExpectedArgSize("func f(s Stream)"); ok {
t.Error("bare named type should make the size undecidable")
}
if _, ok := abiExpectedArgSize("func f(s *Stream)"); !ok {
t.Error("pointer to a named type is decidable (8 bytes)")
}
}
// TestABIArgSizeRule checks the lint rule end to end.
func TestABIArgSizeRule(t *testing.T) {
// Matching: the declared arg size agrees with the signature.
clean := lintSrc(t, "#include \"textflag.h\"\n"+
"// func f(a int, b int)\n"+
"TEXT ·f(SB), NOSPLIT, $0-16\n"+
"\tMOVQ a+0(FP), AX\n"+
"\tRET\n")
if codes(clean)[CodeABIArgSize] != 0 {
t.Fatalf("matching arg size should not warn: %+v", clean)
}
// Mismatching: declared 8, signature implies 16.
bad := lintSrc(t, "#include \"textflag.h\"\n"+
"// func f(a int, b int)\n"+
"TEXT ·f(SB), NOSPLIT, $0-8\n"+
"\tMOVQ a+0(FP), AX\n"+
"\tRET\n")
if codes(bad)[CodeABIArgSize] != 1 {
t.Fatalf("mismatching arg size should warn once: %+v", bad)
}
}
// TestABIArgSizeSkipsRegisterABI verifies the check does not fire for functions
// that declare a zero arg area (register ABI) or never touch FP.
func TestABIArgSizeSkipsRegisterABI(t *testing.T) {
diags := lintSrc(t, "#include \"textflag.h\"\n"+
"// func f(a int, b int)\n"+
"TEXT ·f(SB), NOSPLIT, $0-0\n"+
"\tMOVQ AX, BX\n"+
"\tRET\n")
if codes(diags)[CodeABIArgSize] != 0 {
t.Fatalf("register-ABI function must not be checked: %+v", diags)
}
}
// TestUnreachableCode exercises the dead-code detection and its guard rails.
func TestUnreachableCode(t *testing.T) {
// Code after a RET is unreachable.
dead := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tRET\n"+
"\tMOVQ AX, BX\n")
if codes(dead)[CodeUnreachable] != 1 {
t.Fatalf("code after RET should be unreachable: %+v", dead)
}
// A label after the RET makes the following code reachable again.
live := lintSrc(t, "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tRET\n"+
"again:\n"+
"\tJMP again\n")
if codes(live)[CodeUnreachable] != 0 {
t.Fatalf("code after a label is reachable: %+v", live)
}
}
// TestUnreachableGuards verifies the analysis is suppressed where reachability
// cannot be determined statically.
func TestUnreachableGuards(t *testing.T) {
// A PC-relative jump defeats the analysis for the whole function.
pcrel := lintSrcArch(t, "f_amd64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tJCC 2(PC)\n"+
"\tRET\n"+
"\tMOVQ AX, BX\n")
if codes(pcrel)[CodeUnreachable] != 0 {
t.Fatalf("PC-relative functions must be skipped: %+v", pcrel)
}
// A register-indirect branch (riscv JALR) defeats the analysis too.
indirect := lintSrcArch(t, "f_riscv64.s", "#include \"textflag.h\"\n"+
"TEXT ·f(SB), NOSPLIT, $0\n"+
"\tJALR X1, X5\n"+
"\tRET\n"+
"\tMOV X1, X2\n")
if codes(indirect)[CodeUnreachable] != 0 {
t.Fatalf("indirect-branch functions must be skipped: %+v", indirect)
}
}