// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package verify import ( "runtime" "testing" ) // requireHost skips the test unless the test binary runs on the named // architecture: the JIT executes native code, so a kernel assembled for // arm64 only runs on an arm64 host. func requireHost(t *testing.T, goarch string) { t.Helper() if runtime.GOARCH != goarch { t.Skipf("runs only on %s hosts (this host is %s)", goarch, runtime.GOARCH) } } func TestABIArm64(t *testing.T) { requireHost(t, "arm64") k, err := Load("../testdata/verify/abi_arm64.s") if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) // cleanAdd preserves everything and computes correctly. args := make([]byte, 24) PutUint64(args, 0, 3) PutUint64(args, 8, 4) out, report, err := k.CallFuncChecked("cleanAdd", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 16)); got != 7 { t.Errorf("cleanAdd(3, 4) = %d, want 7", got) } if !report.OK() { t.Errorf("cleanAdd: %s", report) } // dirtyFP clobbers the frame pointer (R29). The kernel passes its // argument through, so the argument must carry the expected value the // way the amd64 twin test seeds it. args = make([]byte, 16) PutUint64(args, 0, 0x1234) out, report, err = k.CallFuncChecked("dirtyFP", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 8)); got != 0x1234 { t.Errorf("dirtyFP returned %d, want %d", got, int64(0x1234)) } if !report.FPClobbered { t.Error("dirtyFP: expected frame pointer clobbered, but report says clean") } if report.GClobbered { t.Errorf("dirtyFP: only R29 should be clobbered: %s", report) } // dirtyG clobbers the goroutine pointer (R28) and still returns its // argument. args = make([]byte, 16) PutUint64(args, 0, 0x5678) out, report, err = k.CallFuncChecked("dirtyG", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 8)); got != 0x5678 { t.Errorf("dirtyG returned %d, want %d", got, int64(0x5678)) } if !report.GClobbered { t.Error("dirtyG: expected g clobbered, but report says clean") } if report.FPClobbered { t.Errorf("dirtyG: only R28 should be clobbered: %s", report) } } func TestABIRiscv64(t *testing.T) { requireHost(t, "riscv64") k, err := Load("../testdata/verify/abi_riscv64.s") if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) // cleanAdd preserves g and computes correctly. args := make([]byte, 24) PutUint64(args, 0, 3) PutUint64(args, 8, 4) out, report, err := k.CallFuncChecked("cleanAdd", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 16)); got != 7 { t.Errorf("cleanAdd(3, 4) = %d, want 7", got) } if !report.OK() { t.Errorf("cleanAdd: %s", report) } // dirtyG clobbers the goroutine pointer (X27). _, report, err = k.CallFuncChecked("dirtyG", make([]byte, 16)) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if !report.GClobbered { t.Error("dirtyG: expected g clobbered, but report says clean") } if report.FPClobbered || report.RedZoneHit { t.Errorf("dirtyG: unexpected additional violations: %s", report) } } func TestABILoong64(t *testing.T) { requireHost(t, "loong64") k, err := Load("../testdata/verify/abi_loong64.s") if err != nil { t.Fatalf("Load: %v", err) } t.Cleanup(k.Close) // cleanAdd preserves g and computes correctly. args := make([]byte, 24) PutUint64(args, 0, 3) PutUint64(args, 8, 4) out, report, err := k.CallFuncChecked("cleanAdd", args) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if got := int64(GetUint64(out, 16)); got != 7 { t.Errorf("cleanAdd(3, 4) = %d, want 7", got) } if !report.OK() { t.Errorf("cleanAdd: %s", report) } // dirtyG clobbers the goroutine pointer (R22). _, report, err = k.CallFuncChecked("dirtyG", make([]byte, 16)) if err != nil { t.Fatalf("CallFuncChecked: %v", err) } if !report.GClobbered { t.Error("dirtyG: expected g clobbered, but report says clean") } if report.FPClobbered || report.RedZoneHit { t.Errorf("dirtyG: unexpected additional violations: %s", report) } }