// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import ( "strings" "testing" "sourcedock.dev/petrbalvin/gasm-devkit/parser" ) // TestAssembleFileStaticData checks the whole-image layout — code, padding // and the data section — and that the RIP-relative displacements of static // symbol loads resolve to the right bytes. func TestAssembleFileStaticData(t *testing.T) { f, errs := parser.Parse("d_amd64.s", ` #include "textflag.h" TEXT ·load(SB), NOSPLIT, $0 VMOVDQU mask<>(SB), X15 MOVL small<>(SB), AX RET GLOBL mask<>(SB), RODATA, $16 DATA mask<>+0(SB)/4, $0x80020100 DATA mask<>+4(SB)/4, $0x80050403 DATA mask<>+8(SB)/4, $0x80080706 DATA mask<>+12(SB)/4, $0x800B0A09 GLOBL small<>(SB), RODATA, $4 DATA small<>+0(SB)/4, $0x1234 `) if len(errs) > 0 { t.Fatalf("parse: %v", errs) } img, err := AssembleFile(f) if err != nil { t.Fatalf("AssembleFile: %v", err) } // Code (15 bytes) + 1 pad byte to align the data section to 16: // VMOVDQU mask<>(SB), X15 c5 7a 6f 3d 08 00 00 00 (disp = 16 − 8) // MOVL small<>(SB), AX 8b 05 12 00 00 00 (disp = 32 − 14) // RET c3 // Data: pad, mask (16 bytes), small (4 bytes). want := "c57a6f3d080000008b0512000000c300" + "000102800304058006070880090a0b80" + "34120000" if got := strings.ReplaceAll(hexBytes(img.Bytes()), " ", ""); got != want { t.Errorf("image bytes:\n got %s\n want %s", got, want) } if img.Symbols["mask"] != 16 || img.Symbols["small"] != 32 { t.Errorf("symbol offsets = %v, want mask=16 small=32", img.Symbols) } if len(img.Funcs) != 1 || img.Funcs[0].Name != "load" || img.Funcs[0].Size != 15 { t.Errorf("funcs = %+v", img.Funcs) } } // TestAssembleFileErrors checks the static-symbol error paths. func TestAssembleFileErrors(t *testing.T) { cases := []struct { name string src string want string // substring of the error }{ { "undefined symbol", ` #include "textflag.h" TEXT ·f(SB), NOSPLIT, $0 VMOVDQU nope<>(SB), X0 RET `, "undefined symbol", }, { "DATA without GLOBL", ` #include "textflag.h" TEXT ·f(SB), NOSPLIT, $0 RET DATA orphan<>+0(SB)/4, $1 `, "no matching GLOBL", }, { "DATA exceeds size", ` #include "textflag.h" TEXT ·f(SB), NOSPLIT, $0 RET GLOBL tiny<>(SB), RODATA, $4 DATA tiny<>+0(SB)/8, $1 `, "exceeds GLOBL size", }, { "DATA bad width", ` #include "textflag.h" TEXT ·f(SB), NOSPLIT, $0 RET GLOBL odd<>(SB), RODATA, $4 DATA odd<>+0(SB)/3, $1 `, "invalid width", }, } for _, c := range cases { f, errs := parser.Parse("e_amd64.s", c.src) if len(errs) > 0 { t.Fatalf("%s: parse: %v", c.name, errs) } if _, err := AssembleFile(f); err == nil || !strings.Contains(err.Error(), c.want) { t.Errorf("%s: error %v, want substring %q", c.name, err, c.want) } } // A static-symbol operand is unresolvable in single-function assembly. fn := firstText(t, ` #include "textflag.h" TEXT ·f(SB), NOSPLIT, $0 MOVQ x<>(SB), AX RET GLOBL x<>(SB), RODATA, $8 DATA x<>+0(SB)/4, $1 `) if _, _, err := Assemble(fn); err == nil || !strings.Contains(err.Error(), "file-level assembly") { t.Errorf("single-function SB: error %v, want a file-level-assembly error", err) } } // TestCollectDataNumericFlags pins the numeric GLOBL flag constants from // runtime/textflag.h: DUPOK is 2, RODATA is 8, and combinations arrive as // one number (9 = NOPROF|RODATA, 10 = RODATA|DUPOK). func TestCollectDataNumericFlags(t *testing.T) { tests := []struct { flags string rodata bool dupok bool }{ {"2", false, true}, {"8", true, false}, {"9", true, false}, // NOPROF|RODATA, not DUPOK {"10", true, true}, // RODATA|DUPOK {"RODATA", true, false}, {"DUPOK", false, true}, {"RODATA|DUPOK", true, true}, } for _, tt := range tests { src := "TEXT \u00b7f(SB), NOSPLIT, $0\n\tRET\nGLOBL sym(SB), " + tt.flags + ", $8\n" f, errs := parser.Parse("f_amd64.s", src) if len(errs) > 0 { t.Fatalf("parse %q: %v", tt.flags, errs) } img, err := AssembleFile(f) if err != nil { t.Fatalf("assemble %q: %v", tt.flags, err) } if len(img.DataSyms) != 1 { t.Fatalf("%q: data syms = %d, want 1", tt.flags, len(img.DataSyms)) } d := img.DataSyms[0] if d.Rodata != tt.rodata || d.Dupok != tt.dupok { t.Errorf("flags %q: rodata=%v dupok=%v, want rodata=%v dupok=%v", tt.flags, d.Rodata, d.Dupok, tt.rodata, tt.dupok) } } }