2026-07-22 09:28:11 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package verify
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
2026-08-05 23:09:54 +02:00
|
|
|
"runtime"
|
2026-07-22 09:28:11 +02:00
|
|
|
"testing"
|
|
|
|
|
"unsafe"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func loadBasic(t *testing.T) *Kernel {
|
|
|
|
|
t.Helper()
|
|
|
|
|
k, err := Load("../testdata/verify/basic_amd64.s")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Load: %v", err)
|
|
|
|
|
}
|
|
|
|
|
t.Cleanup(k.Close)
|
|
|
|
|
return k
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestJITAdd(t *testing.T) {
|
|
|
|
|
k := loadBasic(t)
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
a, b, want int64
|
|
|
|
|
}{
|
|
|
|
|
{0, 0, 0},
|
|
|
|
|
{1, 2, 3},
|
|
|
|
|
{-1, 1, 0},
|
|
|
|
|
{1 << 62, 1 << 62, -9223372036854775808}, // overflow wraps (MinInt64)
|
|
|
|
|
{-100, -200, -300},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
args := make([]byte, 24)
|
|
|
|
|
PutUint64(args, 0, uint64(tt.a))
|
|
|
|
|
PutUint64(args, 8, uint64(tt.b))
|
|
|
|
|
|
|
|
|
|
out, err := k.CallFunc("add", args)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFunc(add, %d, %d): %v", tt.a, tt.b, err)
|
|
|
|
|
}
|
|
|
|
|
got := int64(GetUint64(out, 16))
|
|
|
|
|
if got != tt.want {
|
|
|
|
|
t.Errorf("add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestJITSum(t *testing.T) {
|
|
|
|
|
k := loadBasic(t)
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
data []int64
|
|
|
|
|
want int64
|
|
|
|
|
}{
|
|
|
|
|
{nil, 0},
|
|
|
|
|
{[]int64{1}, 1},
|
|
|
|
|
{[]int64{1, 2, 3, 4, 5}, 15},
|
|
|
|
|
{[]int64{-10, 20, -30, 40}, 20},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
args := make([]byte, 32)
|
|
|
|
|
if len(tt.data) > 0 {
|
|
|
|
|
PutPtr(args, 0, unsafe.Pointer(&tt.data[0]))
|
|
|
|
|
}
|
|
|
|
|
PutUint64(args, 8, uint64(len(tt.data)))
|
|
|
|
|
PutUint64(args, 16, uint64(cap(tt.data)))
|
|
|
|
|
|
|
|
|
|
out, err := k.CallFunc("sum", args)
|
2026-08-05 23:09:54 +02:00
|
|
|
runtime.KeepAlive(tt.data)
|
2026-07-22 09:28:11 +02:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFunc(sum, %v): %v", tt.data, err)
|
|
|
|
|
}
|
|
|
|
|
got := int64(GetUint64(out, 24))
|
|
|
|
|
if got != tt.want {
|
|
|
|
|
t.Errorf("sum(%v) = %d, want %d", tt.data, got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestJITWideCopy(t *testing.T) {
|
|
|
|
|
k := loadBasic(t)
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
n int
|
|
|
|
|
}{
|
|
|
|
|
{"empty", 0},
|
|
|
|
|
{"tiny", 7},
|
|
|
|
|
{"exact32", 32},
|
|
|
|
|
{"overlap_range", 48},
|
|
|
|
|
{"exact64", 64},
|
|
|
|
|
{"unaligned", 45},
|
|
|
|
|
}
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
src := make([]byte, tt.n)
|
|
|
|
|
for i := range src {
|
|
|
|
|
src[i] = byte(i * 7)
|
|
|
|
|
}
|
|
|
|
|
dst := make([]byte, tt.n)
|
|
|
|
|
|
|
|
|
|
args := make([]byte, 48)
|
|
|
|
|
if tt.n > 0 {
|
|
|
|
|
PutPtr(args, 0, unsafe.Pointer(&dst[0]))
|
|
|
|
|
PutPtr(args, 24, unsafe.Pointer(&src[0]))
|
|
|
|
|
}
|
|
|
|
|
PutUint64(args, 8, uint64(tt.n)) // dst_len
|
|
|
|
|
PutUint64(args, 16, uint64(tt.n)) // dst_cap
|
|
|
|
|
PutUint64(args, 32, uint64(tt.n)) // src_len
|
|
|
|
|
PutUint64(args, 40, uint64(tt.n)) // src_cap
|
|
|
|
|
|
|
|
|
|
_, err := k.CallFunc("wideCopy", args)
|
2026-08-05 23:09:54 +02:00
|
|
|
runtime.KeepAlive(dst)
|
|
|
|
|
runtime.KeepAlive(src)
|
2026-07-22 09:28:11 +02:00
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("CallFunc(wideCopy): %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(dst, src) {
|
|
|
|
|
t.Errorf("wideCopy: dst ≠ src\n got %x\n want %x", dst, src)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestKernelFuncNames(t *testing.T) {
|
|
|
|
|
k := loadBasic(t)
|
|
|
|
|
names := k.FuncNames()
|
|
|
|
|
want := []string{"add", "sum", "wideCopy"}
|
|
|
|
|
if len(names) != len(want) {
|
|
|
|
|
t.Fatalf("FuncNames() = %v, want %v", names, want)
|
|
|
|
|
}
|
|
|
|
|
for i, n := range names {
|
|
|
|
|
if n != want[i] {
|
|
|
|
|
t.Errorf("FuncNames()[%d] = %q, want %q", i, n, want[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestKernelFuncNotFound(t *testing.T) {
|
|
|
|
|
k := loadBasic(t)
|
|
|
|
|
_, err := k.CallFunc("nonexistent", make([]byte, 8))
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for nonexistent function")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestKernelArgTooSmall(t *testing.T) {
|
|
|
|
|
k := loadBasic(t)
|
|
|
|
|
_, err := k.CallFunc("add", make([]byte, 8)) // needs 24
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for too-small arg block")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestMapZeroLength(t *testing.T) {
|
|
|
|
|
_, err := Map(nil)
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for zero-length code")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-24 09:57:21 +02:00
|
|
|
|
|
|
|
|
func TestLoadSourceError(t *testing.T) {
|
|
|
|
|
_, err := LoadSource("bad.s", "TEXT ·f(SB), NOSPLIT\n\tBADINSTRUCTION\n")
|
|
|
|
|
// The parser may or may not error on unknown instructions (it's
|
|
|
|
|
// error-tolerant), but the assembler will reject it.
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Log("LoadSource succeeded unexpectedly (parser is error-tolerant)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLoadSourceParseError(t *testing.T) {
|
|
|
|
|
// A completely invalid file that the parser rejects.
|
|
|
|
|
_, err := LoadSource("empty.s", "")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Logf("expected: %v", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFuncLookup(t *testing.T) {
|
|
|
|
|
k := loadBasic(t)
|
|
|
|
|
fl, err := k.Func("add")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("Func(add): %v", err)
|
|
|
|
|
}
|
|
|
|
|
if fl.Name != "add" {
|
|
|
|
|
t.Errorf("Func(add).Name = %q, want %q", fl.Name, "add")
|
|
|
|
|
}
|
|
|
|
|
if fl.Args != 24 {
|
|
|
|
|
t.Errorf("Func(add).Args = %d, want 24", fl.Args)
|
|
|
|
|
}
|
|
|
|
|
_, err = k.Func("nonexistent")
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("expected error for nonexistent function")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestABIReportString(t *testing.T) {
|
|
|
|
|
r := ABIReport{}
|
|
|
|
|
if r.String() != "ABI clean" {
|
|
|
|
|
t.Errorf("clean report = %q", r.String())
|
|
|
|
|
}
|
2026-08-30 11:27:54 +02:00
|
|
|
r.FPClobbered = true
|
2026-07-24 09:57:21 +02:00
|
|
|
if r.OK() {
|
|
|
|
|
t.Error("expected not OK with BP clobbered")
|
|
|
|
|
}
|
|
|
|
|
s := r.String()
|
|
|
|
|
if s == "ABI clean" {
|
|
|
|
|
t.Error("expected violation string, got clean")
|
|
|
|
|
}
|
2026-08-30 11:27:54 +02:00
|
|
|
r.GClobbered = true
|
2026-07-24 09:57:21 +02:00
|
|
|
r.RedZoneHit = true
|
|
|
|
|
s = r.String()
|
|
|
|
|
if s == "ABI clean" {
|
|
|
|
|
t.Error("expected violation string for all flags")
|
|
|
|
|
}
|
|
|
|
|
}
|