110 lines
3.6 KiB
Go
110 lines
3.6 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// writeTree writes a directory of files and returns its root.
|
|
func writeTree(t *testing.T, files map[string]string) string {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
for name, content := range files {
|
|
path := filepath.Join(dir, name)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
return dir
|
|
}
|
|
|
|
// TestAsmMacroAndIncludeEndToEnd drives `gasm asm` over a source with an
|
|
// in-file parameterised macro and an include resolved through -I, and checks
|
|
// the assembled bytes came from the expansion (the loop body counts six
|
|
// increments, two per expanded iteration).
|
|
func TestAsmMacroAndIncludeEndToEnd(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("runs the assembler end to end")
|
|
}
|
|
dir := writeTree(t, map[string]string{
|
|
"inc/consts.h": "#define NITER 3\n",
|
|
"main_amd64.s": "#include \"textflag.h\"\n" +
|
|
"#include \"consts.h\"\n" +
|
|
"#define STEP(r) ADDQ $1, r; ADDQ $1, r\n" +
|
|
"TEXT ·f(SB), NOSPLIT, $0-8\n" +
|
|
"\tXORQ AX, AX\n" +
|
|
"\tMOVQ $NITER, CX\n" +
|
|
"loop:\n" +
|
|
"\tSTEP(AX)\n" +
|
|
"\tDECQ CX\n" +
|
|
"\tJNZ loop\n" +
|
|
"\tMOVQ AX, ret+0(FP)\n" +
|
|
"\tRET\n",
|
|
})
|
|
stdout, stderr, code := capture(func() int {
|
|
return cmdAsm([]string{"-I", filepath.Join(dir, "inc"), "-GOARCH", "amd64", filepath.Join(dir, "main_amd64.s")})
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("gasm asm exited %d: %s%s", code, stdout, stderr)
|
|
}
|
|
// The macro expanded to two ADDQ $1 encodings in the static body; the
|
|
// iteration count lives in the runtime loop.
|
|
if n := strings.Count(stdout, "83 c0 01"); n != 2 {
|
|
t.Errorf("found %d ADDQ $1 encodings in the image, want 2:\n%s", n, stdout)
|
|
}
|
|
}
|
|
|
|
// TestAsmIncludeResolutionOrder pins the -I search order end to end: the
|
|
// including file's directory wins over the -I directories.
|
|
func TestAsmIncludeResolutionOrder(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("runs the assembler end to end")
|
|
}
|
|
dir := writeTree(t, map[string]string{
|
|
"src/main_amd64.s": "#include \"textflag.h\"\n" +
|
|
"#include \"vals.h\"\n" +
|
|
"TEXT ·f(SB), NOSPLIT, $0\n" +
|
|
"\tMOVQ $VAL, AX\n" +
|
|
"\tRET\n",
|
|
"src/vals.h": "#define VAL 1\n",
|
|
"late/vals.h": "#define VAL 2\n",
|
|
"early/vals.h": "#define VAL 3\n",
|
|
})
|
|
stdout, stderr, code := capture(func() int {
|
|
return cmdAsm([]string{"-I", filepath.Join(dir, "early"), "-I", filepath.Join(dir, "late"),
|
|
"-GOARCH", "amd64", filepath.Join(dir, "src", "main_amd64.s")})
|
|
})
|
|
if code != 0 {
|
|
t.Fatalf("gasm asm exited %d: %s%s", code, stdout, stderr)
|
|
}
|
|
// VAL came from src/vals.h, not from either -I directory: the image
|
|
// loads the immediate 1.
|
|
if !strings.Contains(stdout, "b8 01 00 00 00") {
|
|
t.Errorf("expected the source-directory VAL (immediate 1) in:\n%s", stdout)
|
|
}
|
|
}
|
|
|
|
// TestAsmMissingIncludeIsAnError pins the diagnostic for an include that
|
|
// resolves nowhere on the assembly path.
|
|
func TestAsmMissingIncludeIsAnError(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("runs the assembler end to end")
|
|
}
|
|
path := writeTemp(t, "main_amd64.s", "#include \"textflag.h\"\n#include \"nothere.h\"\nTEXT ·f(SB), NOSPLIT, $0\n\tRET\n")
|
|
_, stderr, code := capture(func() int { return cmdAsm([]string{"-GOARCH", "amd64", path}) })
|
|
if code == 0 {
|
|
t.Fatal("gasm asm accepted a file whose include resolves nowhere")
|
|
}
|
|
if !strings.Contains(stderr, `#include "nothere.h"`) {
|
|
t.Errorf("stderr does not name the failing include: %s", stderr)
|
|
}
|
|
}
|