// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package asm import ( "fmt" "sourcedock.dev/petrbalvin/gasm-devkit/ast" ) // Image is an assembled file: the function bodies laid out in source order, // followed by the file's static data section (GLOBL/DATA). Static-symbol // references are encoded RIP-relative and resolved within the image, so the // bytes are self-consistent and executable at any base address. type Image struct { Code []byte // concatenated function bodies Data []byte // static data section Funcs []FuncLayout // function positions, in source order Symbols map[string]int // static symbol → byte offset within the image } // FuncLayout describes one assembled function within an Image. type FuncLayout struct { Name string Offset int // start offset within the image (== offset within Code) Size int Labels map[string]int // local labels, function-relative } // Bytes returns the whole image: code, then data. func (img *Image) Bytes() []byte { out := make([]byte, 0, len(img.Code)+len(img.Data)) out = append(out, img.Code...) return append(out, img.Data...) } // AssembleFile assembles every TEXT function of a parsed file and lays out // its static symbols (GLOBL/DATA) in a data section behind the code. Each // static-symbol reference becomes a RIP-relative load whose displacement is // resolved against that layout. External (non-file-local) symbol references // are rejected: they need object-file emission. func AssembleFile(f *ast.File) (*Image, error) { syms, order, err := collectData(f) if err != nil { return nil, err } known := make(map[string]bool, len(syms)) for name := range syms { known[name] = true } link := &linkInfo{symbols: known} img := &Image{Symbols: map[string]int{}} type asmFunc struct { name string patches []sbPatch } var funcs []asmFunc for _, d := range f.Decls { t, ok := d.(*ast.Text) if !ok { continue } code, patches, labels, err := assemble(t, link) if err != nil { return nil, fmt.Errorf("%s: %w", t.Name.Name, err) } img.Funcs = append(img.Funcs, FuncLayout{ Name: t.Name.Name, Offset: len(img.Code), Size: len(code), Labels: labels, }) img.Code = append(img.Code, code...) funcs = append(funcs, asmFunc{name: t.Name.Name, patches: patches}) } // Lay out the data section behind the code, each symbol 16-aligned. dataStart := len(img.Code) for _, name := range order { if pos := dataStart + len(img.Data); pos != align16(pos) { img.Data = append(img.Data, make([]byte, align16(pos)-pos)...) } img.Symbols[name] = dataStart + len(img.Data) img.Data = append(img.Data, syms[name]...) } // Resolve the RIP-relative displacements now that every address is known. for i, fn := range funcs { base := img.Funcs[i].Offset code := img.Code[base : base+img.Funcs[i].Size] for _, p := range fn.patches { rel := int64(img.Symbols[p.name]) + p.addend - int64(base+p.after) if rel < -1<<31 || rel >= 1<<31 { return nil, fmt.Errorf("%s: displacement to %q out of rel32 range", fn.name, p.name) } copy(code[p.off:p.off+4], le32(rel)) } } return img, nil } // collectData gathers the file's static symbols (GLOBL) and their initial // contents (DATA) into byte buffers, in declaration order. func collectData(f *ast.File) (map[string][]byte, []string, error) { syms := map[string][]byte{} var order []string for _, d := range f.Decls { switch dd := d.(type) { case *ast.Globl: if dd.Name == nil || dd.Name.Pseudo != "SB" { continue } name := dd.Name.Name if _, dup := syms[name]; dup { return nil, nil, fmt.Errorf("duplicate GLOBL %q", name) } size := 0 if dd.Size != nil && dd.Size.Imm.HasVal { size = int(dd.Size.Imm.Val) } syms[name] = make([]byte, size) order = append(order, name) case *ast.Data: if dd.Name == nil || dd.Name.Pseudo != "SB" { continue } buf, ok := syms[dd.Name.Name] if !ok { return nil, nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name) } if dd.Value == nil || !dd.Value.Imm.HasVal { return nil, nil, fmt.Errorf("DATA %q: value must be an integer immediate", dd.Name.Name) } w := dd.Width switch w { case 1, 2, 4, 8: default: return nil, nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w) } off := dd.Name.Offset if off < 0 || off+int64(w) > int64(len(buf)) { return nil, nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf)) } v := dd.Value.Imm.Val if dd.Value.Imm.Neg { v = -v } for i := 0; i < w; i++ { buf[off+int64(i)] = byte(v >> (8 * i)) } } } return syms, order, nil } // align16 rounds n up to the next multiple of 16. func align16(n int) int { return (n + 15) &^ 15 }