2026-07-09 15:56:03 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package asm
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
2026-09-21 00:44:47 +02:00
|
|
|
"math"
|
2026-07-16 20:52:20 +02:00
|
|
|
"sort"
|
2026-09-14 18:22:00 +02:00
|
|
|
"strconv"
|
2026-07-09 15:56:03 +02:00
|
|
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Image is an assembled file: the function bodies laid out in source order,
|
2026-07-16 20:52:20 +02:00
|
|
|
// followed by the file's static data section (GLOBL/DATA). References to
|
|
|
|
|
// file-local static symbols are encoded RIP-relative and resolved within the
|
|
|
|
|
// image, so the raw bytes are self-consistent and executable at any base
|
|
|
|
|
// address; references to external symbols are recorded as relocations
|
2026-09-14 18:22:00 +02:00
|
|
|
// (Funcs[i].Relocs, Externals) and left unresolved, the object-file
|
2026-07-16 20:52:20 +02:00
|
|
|
// emitters turn them into linker relocations.
|
2026-07-09 15:56:03 +02:00
|
|
|
type Image struct {
|
2026-07-16 20:52:20 +02:00
|
|
|
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
|
|
|
|
|
DataSyms []DataSymbol // GLOBL symbols, in layout order
|
|
|
|
|
Externals []string // referenced but undefined symbols, sorted
|
2026-09-19 23:49:13 +02:00
|
|
|
// SourcePath is the assembled file's path, recorded in the DWARF
|
|
|
|
|
// sections in place of a placeholder name. Empty when the image was
|
|
|
|
|
// not built from a named file.
|
|
|
|
|
SourcePath string
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// FuncLayout describes one assembled function within an Image.
|
|
|
|
|
type FuncLayout struct {
|
2026-07-17 18:57:04 +02:00
|
|
|
Name string
|
|
|
|
|
Pkg string // explicit package prefix ("" = the current package)
|
|
|
|
|
Static bool // the <> marker: file-local, not exported
|
|
|
|
|
Offset int // start offset within the image (== offset within Code)
|
|
|
|
|
Size int
|
|
|
|
|
Args int // declared argument/result area (the TEXT size suffix)
|
|
|
|
|
Frame int // local frame size (the TEXT $framesize)
|
|
|
|
|
NoSplit bool // the NOSPLIT flag
|
|
|
|
|
SPWrite bool // the SPWRITE flag: writes an arbitrary value to SP
|
|
|
|
|
Line int // source line of the TEXT directive
|
|
|
|
|
Labels map[string]int // local labels, function-relative
|
|
|
|
|
Relocs []Reloc // static-symbol references, in emission order
|
|
|
|
|
Spadj []SpadjStep // stack-adjustment boundaries, ascending by PC
|
2026-08-01 09:47:00 +02:00
|
|
|
Lines []LineEntry // source-line table: byte offset → source line
|
2026-07-17 18:57:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SpadjStep is one stack-adjustment boundary: Value is the SP delta from the
|
|
|
|
|
// entry state in effect from PC (function-relative) until the next step.
|
|
|
|
|
type SpadjStep struct {
|
|
|
|
|
PC int
|
|
|
|
|
Value int
|
2026-07-16 20:52:20 +02:00
|
|
|
}
|
|
|
|
|
|
2026-08-01 09:47:00 +02:00
|
|
|
// LineEntry maps a byte offset (function-relative) to a source line number.
|
|
|
|
|
type LineEntry struct {
|
|
|
|
|
Offset int
|
|
|
|
|
Line int
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LineAt returns the source line number for the given function-relative byte
|
|
|
|
|
// offset, using a binary search on the line table. Returns 0 if the offset
|
|
|
|
|
// is before the first instruction or the table is empty.
|
|
|
|
|
func (fl *FuncLayout) LineAt(offset int) int {
|
|
|
|
|
if len(fl.Lines) == 0 {
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
// Binary search: find the last entry with Offset <= offset.
|
|
|
|
|
lo, hi := 0, len(fl.Lines)-1
|
|
|
|
|
for lo < hi {
|
|
|
|
|
mid := (lo + hi + 1) / 2
|
|
|
|
|
if fl.Lines[mid].Offset <= offset {
|
|
|
|
|
lo = mid
|
|
|
|
|
} else {
|
|
|
|
|
hi = mid - 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if fl.Lines[lo].Offset <= offset {
|
|
|
|
|
return fl.Lines[lo].Line
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
// RelocKind discriminates the relocation a static-symbol reference needs;
|
|
|
|
|
// the encoders record one per SB reference, and the object-file emitters map
|
|
|
|
|
// it to their format's relocation type.
|
2026-08-03 08:51:00 +02:00
|
|
|
type RelocKind int
|
|
|
|
|
|
|
|
|
|
const (
|
2026-08-13 12:07:29 +02:00
|
|
|
RelPCRel32 RelocKind = iota // 32-bit PC-relative (amd64)
|
2026-09-14 20:35:55 +02:00
|
|
|
RelCall // R_CALL: CALL to a function symbol (amd64)
|
|
|
|
|
RelTLSLE // R_TLS_LE: local-exec TLS load, no symbol (amd64 guard)
|
2026-08-13 12:07:29 +02:00
|
|
|
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
|
|
|
|
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
2026-08-13 18:12:22 +02:00
|
|
|
RelRISCVJal // R_RISCV_JAL (J-type call)
|
2026-08-13 12:07:29 +02:00
|
|
|
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
|
|
|
|
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
2026-09-14 18:22:00 +02:00
|
|
|
RelArm64Addr // R_ADDRARM64 (ADRP + ADD pair)
|
2026-08-20 23:22:34 +02:00
|
|
|
RelArm64Branch // R_CALLARM64 (BL instruction)
|
2026-09-14 18:22:00 +02:00
|
|
|
RelArm64LDST64 // R_ARM64_PCREL_LDST64 (ADRP + 64-bit LDR/STR pair)
|
2026-09-14 21:21:58 +02:00
|
|
|
RelLoong64Branch // R_CALLLOONG64 (BL instruction)
|
2026-09-20 19:15:05 +02:00
|
|
|
RelAddr // R_ADDR: the absolute address of a symbol held in a DATA field
|
2026-08-03 08:51:00 +02:00
|
|
|
)
|
|
|
|
|
|
2026-07-16 20:52:20 +02:00
|
|
|
type Reloc struct {
|
2026-09-19 23:49:13 +02:00
|
|
|
// Off is the function-relative offset of the field the linker patches
|
|
|
|
|
// and After the address just past the instruction, the base the
|
|
|
|
|
// assembler measures PC-relative displacements from. Name plus
|
|
|
|
|
// Addend select the target: the symbol plus the byte offset. An
|
|
|
|
|
// External relocation names a symbol no GLOBL in the file defines;
|
|
|
|
|
// the object-file emitters carry it into the output's relocation
|
2026-09-20 19:15:05 +02:00
|
|
|
// table. Siz is the width of the patched field and is set only for
|
|
|
|
|
// data-field relocations (RelAddr, Off relative to the data symbol),
|
|
|
|
|
// whose width is the DATA line's; code relocations take their width
|
|
|
|
|
// from the architecture's instruction encoding.
|
2026-07-16 20:52:20 +02:00
|
|
|
Off int
|
|
|
|
|
After int
|
|
|
|
|
Name string
|
|
|
|
|
Addend int64
|
|
|
|
|
External bool
|
2026-08-03 08:51:00 +02:00
|
|
|
Kind RelocKind
|
2026-09-20 19:15:05 +02:00
|
|
|
Siz uint8
|
2026-07-16 20:52:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DataSymbol describes one GLOBL symbol laid out in the data section.
|
|
|
|
|
type DataSymbol struct {
|
|
|
|
|
Name string
|
|
|
|
|
Pkg string // explicit package prefix ("" = the current package)
|
|
|
|
|
Offset int // byte offset within Data
|
|
|
|
|
Size int
|
|
|
|
|
Static bool // the <> marker: file-local, not exported
|
2026-07-17 18:57:04 +02:00
|
|
|
Rodata bool // the RODATA flag: read-only data
|
|
|
|
|
Dupok bool // the DUPOK flag: duplicate-OK
|
2026-09-20 19:15:05 +02:00
|
|
|
// Relocs carries the symbol-valued DATA initialisers ("DATA s+0(SB)/8,
|
|
|
|
|
// $other(SB)"): fields of this symbol's data that hold another symbol's
|
|
|
|
|
// address, resolved by the linker. Off is relative to the symbol's
|
|
|
|
|
// data start.
|
|
|
|
|
Relocs []Reloc
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
2026-07-16 20:52:20 +02:00
|
|
|
// reference to a file-local static symbol becomes a RIP-relative load whose
|
|
|
|
|
// displacement is resolved against that layout; a reference to a symbol no
|
|
|
|
|
// GLOBL defines is recorded as an external relocation (Externals) with its
|
2026-09-14 18:22:00 +02:00
|
|
|
// displacement left zero, the object-file emitters resolve it at link
|
2026-07-16 20:52:20 +02:00
|
|
|
// time, while the raw image (Bytes) cannot represent it.
|
2026-07-09 15:56:03 +02:00
|
|
|
func AssembleFile(f *ast.File) (*Image, error) {
|
2026-07-16 20:52:20 +02:00
|
|
|
dataSyms, err := collectData(f)
|
2026-07-09 15:56:03 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
known := make(map[string]bool, len(dataSyms))
|
|
|
|
|
for _, d := range dataSyms {
|
|
|
|
|
known[d.name] = true
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
2026-09-21 21:06:42 +02:00
|
|
|
// TEXT symbols are file-level definitions too: a symbol immediate
|
|
|
|
|
// ($fn(SB)) may name one, exactly as a data reference names a GLOBL.
|
|
|
|
|
for _, d := range f.Decls {
|
|
|
|
|
if t, ok := d.(*ast.Text); ok {
|
|
|
|
|
known[t.Name.Name] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
link := &linkInfo{symbols: known, allowExternal: true}
|
2026-09-21 02:04:44 +02:00
|
|
|
poolSeen := map[string]bool{}
|
2026-07-09 15:56:03 +02:00
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
2026-09-14 20:35:55 +02:00
|
|
|
textOff := map[string]int{}
|
2026-07-09 15:56:03 +02:00
|
|
|
type asmFunc struct {
|
|
|
|
|
name string
|
|
|
|
|
patches []sbPatch
|
|
|
|
|
}
|
|
|
|
|
var funcs []asmFunc
|
|
|
|
|
for _, d := range f.Decls {
|
|
|
|
|
t, ok := d.(*ast.Text)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-09-21 02:04:44 +02:00
|
|
|
code, patches, labels, steps, lines, pool, err := assemble(t, link)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
|
|
|
|
}
|
|
|
|
|
// The pooled floating-point constants join the declared data as
|
|
|
|
|
// read-only symbols, deduplicated across the file (the toolchain
|
|
|
|
|
// synthesises the same symbols into its rodata).
|
|
|
|
|
for _, entry := range pool {
|
|
|
|
|
if poolSeen[entry.name] {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
poolSeen[entry.name] = true
|
|
|
|
|
dataSyms = append(dataSyms, dataSym{
|
|
|
|
|
name: entry.name,
|
|
|
|
|
buf: entry.data,
|
|
|
|
|
size: len(entry.data),
|
|
|
|
|
rodata: true,
|
|
|
|
|
dupok: true,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-07-09 15:56:03 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
|
|
|
|
}
|
2026-07-17 18:57:04 +02:00
|
|
|
fl := FuncLayout{
|
2026-07-09 15:56:03 +02:00
|
|
|
Name: t.Name.Name,
|
2026-07-16 20:52:20 +02:00
|
|
|
Pkg: t.Name.Pkg,
|
|
|
|
|
Static: t.Name.Static,
|
2026-07-09 15:56:03 +02:00
|
|
|
Offset: len(img.Code),
|
|
|
|
|
Size: len(code),
|
2026-07-17 18:57:04 +02:00
|
|
|
Frame: frameSize(t),
|
|
|
|
|
Args: argsSize(t),
|
|
|
|
|
Line: t.Pos().Line,
|
2026-07-09 15:56:03 +02:00
|
|
|
Labels: labels,
|
2026-08-01 09:47:00 +02:00
|
|
|
Lines: lines,
|
2026-07-17 18:57:04 +02:00
|
|
|
}
|
|
|
|
|
for _, f := range t.Flags {
|
|
|
|
|
switch f {
|
|
|
|
|
case "NOSPLIT":
|
|
|
|
|
fl.NoSplit = true
|
|
|
|
|
case "SPWRITE":
|
|
|
|
|
fl.SPWrite = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, s := range steps {
|
|
|
|
|
fl.Spadj = append(fl.Spadj, SpadjStep{PC: s.pc, Value: s.value})
|
|
|
|
|
}
|
2026-09-14 20:35:55 +02:00
|
|
|
textOff[t.Name.Name] = len(img.Code)
|
2026-07-17 18:57:04 +02:00
|
|
|
img.Funcs = append(img.Funcs, fl)
|
2026-07-09 15:56:03 +02:00
|
|
|
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)
|
2026-07-16 20:52:20 +02:00
|
|
|
for _, d := range dataSyms {
|
2026-07-09 15:56:03 +02:00
|
|
|
if pos := dataStart + len(img.Data); pos != align16(pos) {
|
|
|
|
|
img.Data = append(img.Data, make([]byte, align16(pos)-pos)...)
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
img.Symbols[d.name] = dataStart + len(img.Data)
|
|
|
|
|
img.DataSyms = append(img.DataSyms, DataSymbol{
|
|
|
|
|
Name: d.name,
|
|
|
|
|
Pkg: d.pkg,
|
|
|
|
|
Offset: len(img.Data),
|
|
|
|
|
Size: len(d.buf),
|
|
|
|
|
Static: d.static,
|
2026-07-17 18:57:04 +02:00
|
|
|
Rodata: d.rodata,
|
|
|
|
|
Dupok: d.dupok,
|
2026-07-16 20:52:20 +02:00
|
|
|
})
|
|
|
|
|
img.Data = append(img.Data, d.buf...)
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-16 20:52:20 +02:00
|
|
|
// Resolve the RIP-relative displacements of file-local references now
|
|
|
|
|
// that every address is known, and record every reference (resolved or
|
|
|
|
|
// external) for the object-file emitters.
|
|
|
|
|
externals := map[string]bool{}
|
2026-07-09 15:56:03 +02:00
|
|
|
for i, fn := range funcs {
|
|
|
|
|
base := img.Funcs[i].Offset
|
|
|
|
|
code := img.Code[base : base+img.Funcs[i].Size]
|
|
|
|
|
for _, p := range fn.patches {
|
2026-09-14 20:35:55 +02:00
|
|
|
reloc := Reloc{Off: p.off, After: p.after, Name: p.name, Addend: p.addend, Kind: p.kind}
|
|
|
|
|
if p.kind == RelTLSLE {
|
|
|
|
|
// The TLS slot has no symbol: the linker fills the offset
|
|
|
|
|
// from the runtime's TLS layout.
|
|
|
|
|
img.Funcs[i].Relocs = append(img.Funcs[i].Relocs, reloc)
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
if imgOff, ok := img.Symbols[p.name]; ok {
|
|
|
|
|
rel := int64(imgOff) + 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))
|
2026-09-14 20:35:55 +02:00
|
|
|
} else if imgOff, ok := textOff[p.name]; ok {
|
|
|
|
|
// A CALL to a TEXT function of the same file: resolve the
|
|
|
|
|
// displacement against the function's layout position.
|
|
|
|
|
rel := int64(imgOff) + 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))
|
2026-07-16 20:52:20 +02:00
|
|
|
} else {
|
|
|
|
|
reloc.External = true
|
|
|
|
|
externals[p.name] = true
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
img.Funcs[i].Relocs = append(img.Funcs[i].Relocs, reloc)
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-09-20 19:15:05 +02:00
|
|
|
// The data symbols' symbol-valued DATA fields resolve the same way the
|
|
|
|
|
// code references do: a name the file defines (GLOBL or TEXT) stays an
|
|
|
|
|
// internal reference the emitters resolve, anything else is external.
|
|
|
|
|
// img.DataSyms was laid out in dataSyms order, so the indexes line up.
|
|
|
|
|
for i := range img.DataSyms {
|
|
|
|
|
for _, r := range dataSyms[i].relocs {
|
|
|
|
|
reloc := r
|
|
|
|
|
if _, ok := img.Symbols[reloc.Name]; !ok {
|
|
|
|
|
if _, ok := textOff[reloc.Name]; !ok {
|
|
|
|
|
reloc.External = true
|
|
|
|
|
externals[reloc.Name] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
img.DataSyms[i].Relocs = append(img.DataSyms[i].Relocs, reloc)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
for name := range externals {
|
|
|
|
|
img.Externals = append(img.Externals, name)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(img.Externals)
|
2026-07-09 15:56:03 +02:00
|
|
|
return img, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-03 08:51:00 +02:00
|
|
|
// AssembleFileRISCV assembles every TEXT function of a parsed RISC-V file
|
|
|
|
|
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
|
|
|
|
// code. SB references in the code are encoded as AUIPC pairs with zero
|
|
|
|
|
// immediates; the object-file emitters record relocations for the linker.
|
2026-08-02 00:18:00 +02:00
|
|
|
func AssembleFileRISCV(f *ast.File) (*Image, error) {
|
2026-08-03 08:51:00 +02:00
|
|
|
dataSyms, err := collectData(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2026-09-21 00:44:47 +02:00
|
|
|
// The pooled $i64 constants the wide MOV immediate loads refer to join
|
|
|
|
|
// the declared data as read-only symbols, deduplicated across the file
|
|
|
|
|
// (the toolchain synthesises the same symbols into its rodata).
|
|
|
|
|
litSeen := map[string]bool{}
|
2026-08-03 08:51:00 +02:00
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
2026-08-02 00:18:00 +02:00
|
|
|
for _, d := range f.Decls {
|
|
|
|
|
t, ok := d.(*ast.Text)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-09-21 00:44:47 +02:00
|
|
|
code, labels, relocs, lines, spadj, lits, err := assembleRISCV(t)
|
2026-08-02 00:18:00 +02:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
|
|
|
|
}
|
2026-09-21 00:44:47 +02:00
|
|
|
for _, lit := range lits {
|
|
|
|
|
if litSeen[lit.Name] {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
litSeen[lit.Name] = true
|
|
|
|
|
dataSyms = append(dataSyms, dataSym{
|
|
|
|
|
name: lit.Name,
|
|
|
|
|
buf: lit.Data,
|
|
|
|
|
size: len(lit.Data),
|
|
|
|
|
rodata: true,
|
|
|
|
|
dupok: true,
|
|
|
|
|
})
|
|
|
|
|
}
|
2026-08-02 00:18:00 +02:00
|
|
|
fl := FuncLayout{
|
|
|
|
|
Name: t.Name.Name,
|
|
|
|
|
Pkg: t.Name.Pkg,
|
|
|
|
|
Static: t.Name.Static,
|
|
|
|
|
Offset: len(img.Code),
|
|
|
|
|
Size: len(code),
|
|
|
|
|
Frame: frameSize(t),
|
|
|
|
|
Args: argsSize(t),
|
|
|
|
|
Line: t.Pos().Line,
|
|
|
|
|
Labels: labels,
|
2026-08-13 14:41:57 +02:00
|
|
|
Lines: lines,
|
|
|
|
|
Spadj: spadj,
|
2026-08-03 08:51:00 +02:00
|
|
|
Relocs: relocs,
|
2026-08-02 00:18:00 +02:00
|
|
|
}
|
|
|
|
|
for _, f := range t.Flags {
|
|
|
|
|
switch f {
|
|
|
|
|
case "NOSPLIT":
|
|
|
|
|
fl.NoSplit = true
|
|
|
|
|
case "SPWRITE":
|
|
|
|
|
fl.SPWrite = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
img.Funcs = append(img.Funcs, fl)
|
|
|
|
|
img.Code = append(img.Code, code...)
|
|
|
|
|
}
|
2026-08-03 08:51:00 +02:00
|
|
|
|
|
|
|
|
// Lay out the data section behind the code, 16-aligned.
|
|
|
|
|
dataStart := len(img.Code)
|
|
|
|
|
for _, d := range dataSyms {
|
|
|
|
|
pos := dataStart + len(img.Data)
|
|
|
|
|
for pos%16 != 0 {
|
|
|
|
|
img.Data = append(img.Data, 0)
|
|
|
|
|
pos++
|
|
|
|
|
}
|
|
|
|
|
img.Symbols[d.name] = pos
|
|
|
|
|
img.Data = append(img.Data, d.buf...)
|
|
|
|
|
img.DataSyms = append(img.DataSyms, DataSymbol{
|
|
|
|
|
Name: d.name,
|
|
|
|
|
Pkg: d.pkg,
|
2026-08-13 11:24:44 +02:00
|
|
|
Offset: len(img.Data) - len(d.buf), // relative to the data section
|
|
|
|
|
Size: d.size,
|
|
|
|
|
Static: d.static,
|
|
|
|
|
Rodata: d.rodata,
|
|
|
|
|
Dupok: d.dupok,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-20 23:22:34 +02:00
|
|
|
markExternals(img, dataSyms)
|
2026-08-13 11:24:44 +02:00
|
|
|
return img, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// AssembleFileLOONG64 assembles every TEXT function of a parsed loong64 file
|
|
|
|
|
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
|
|
|
|
// code. SB references in the code are encoded as pcalau12i pairs with zero
|
|
|
|
|
// immediates; the object-file emitters record R_LOONG64_ADDR_HI/LO
|
|
|
|
|
// relocations for the linker.
|
|
|
|
|
func AssembleFileLOONG64(f *ast.File) (*Image, error) {
|
|
|
|
|
dataSyms, err := collectData(f)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 23:49:13 +02:00
|
|
|
img := &Image{Symbols: map[string]int{}, SourcePath: f.Path}
|
2026-08-13 11:24:44 +02:00
|
|
|
for _, d := range f.Decls {
|
|
|
|
|
t, ok := d.(*ast.Text)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
code, labels, relocs, lines, spadj, err := assembleLOONG64(t)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("%s: %w", t.Name.Name, err)
|
|
|
|
|
}
|
|
|
|
|
fl := FuncLayout{
|
|
|
|
|
Name: t.Name.Name,
|
|
|
|
|
Pkg: t.Name.Pkg,
|
|
|
|
|
Static: t.Name.Static,
|
|
|
|
|
Offset: len(img.Code),
|
|
|
|
|
Size: len(code),
|
|
|
|
|
Frame: frameSize(t),
|
|
|
|
|
Args: argsSize(t),
|
|
|
|
|
Line: t.Pos().Line,
|
|
|
|
|
Labels: labels,
|
|
|
|
|
Lines: lines,
|
|
|
|
|
Spadj: spadj,
|
|
|
|
|
Relocs: relocs,
|
|
|
|
|
}
|
|
|
|
|
for _, f := range t.Flags {
|
|
|
|
|
switch f {
|
|
|
|
|
case "NOSPLIT":
|
|
|
|
|
fl.NoSplit = true
|
|
|
|
|
case "SPWRITE":
|
|
|
|
|
fl.SPWrite = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
img.Funcs = append(img.Funcs, fl)
|
|
|
|
|
img.Code = append(img.Code, code...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lay out the data section behind the code, 16-aligned.
|
|
|
|
|
dataStart := len(img.Code)
|
|
|
|
|
for _, d := range dataSyms {
|
|
|
|
|
pos := dataStart + len(img.Data)
|
|
|
|
|
for pos%16 != 0 {
|
|
|
|
|
img.Data = append(img.Data, 0)
|
|
|
|
|
pos++
|
|
|
|
|
}
|
|
|
|
|
img.Symbols[d.name] = pos
|
|
|
|
|
img.Data = append(img.Data, d.buf...)
|
|
|
|
|
img.DataSyms = append(img.DataSyms, DataSymbol{
|
|
|
|
|
Name: d.name,
|
|
|
|
|
Pkg: d.pkg,
|
|
|
|
|
Offset: len(img.Data) - len(d.buf), // relative to the data section
|
2026-08-03 08:51:00 +02:00
|
|
|
Size: d.size,
|
|
|
|
|
Static: d.static,
|
|
|
|
|
Rodata: d.rodata,
|
|
|
|
|
Dupok: d.dupok,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-20 23:22:34 +02:00
|
|
|
markExternals(img, dataSyms)
|
2026-08-02 00:18:00 +02:00
|
|
|
return img, nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-20 23:22:34 +02:00
|
|
|
// markExternals identifies relocations that reference symbols not defined in
|
|
|
|
|
// the file (neither a GLOBL/DATA symbol nor a TEXT function) and records them
|
|
|
|
|
// as external. The non-amd64 architectures emit relocations for every SB
|
|
|
|
|
// reference; this post-processing step distinguishes file-local from external.
|
|
|
|
|
func markExternals(img *Image, dataSyms []dataSym) {
|
|
|
|
|
known := make(map[string]bool, len(dataSyms)+len(img.Funcs))
|
|
|
|
|
for _, d := range dataSyms {
|
|
|
|
|
known[d.name] = true
|
|
|
|
|
}
|
|
|
|
|
for _, fn := range img.Funcs {
|
|
|
|
|
known[fn.Name] = true
|
|
|
|
|
}
|
|
|
|
|
externals := map[string]bool{}
|
|
|
|
|
for i := range img.Funcs {
|
|
|
|
|
for j := range img.Funcs[i].Relocs {
|
|
|
|
|
r := &img.Funcs[i].Relocs[j]
|
|
|
|
|
if !known[r.Name] {
|
|
|
|
|
r.External = true
|
|
|
|
|
externals[r.Name] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-20 19:15:05 +02:00
|
|
|
// The declared data symbols carry the file's own relocations (the
|
|
|
|
|
// symbol-valued DATA fields); the layouts appended img.DataSyms in
|
|
|
|
|
// dataSyms order, so the indexes line up. The trailing entries (the
|
|
|
|
|
// pooled arm64 literals) have no source relocations.
|
|
|
|
|
for i := range img.DataSyms {
|
|
|
|
|
if i >= len(dataSyms) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
for _, r := range dataSyms[i].relocs {
|
|
|
|
|
reloc := r
|
|
|
|
|
if !known[reloc.Name] {
|
|
|
|
|
reloc.External = true
|
|
|
|
|
externals[reloc.Name] = true
|
|
|
|
|
}
|
|
|
|
|
img.DataSyms[i].Relocs = append(img.DataSyms[i].Relocs, reloc)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-20 23:22:34 +02:00
|
|
|
for name := range externals {
|
|
|
|
|
img.Externals = append(img.Externals, name)
|
|
|
|
|
}
|
|
|
|
|
sort.Strings(img.Externals)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-16 20:52:20 +02:00
|
|
|
// dataSym is one GLOBL symbol and its DATA initialiser.
|
|
|
|
|
type dataSym struct {
|
|
|
|
|
name string
|
|
|
|
|
pkg string
|
|
|
|
|
buf []byte
|
2026-08-03 08:51:00 +02:00
|
|
|
size int
|
2026-07-16 20:52:20 +02:00
|
|
|
static bool
|
2026-07-17 18:57:04 +02:00
|
|
|
rodata bool
|
|
|
|
|
dupok bool
|
2026-09-20 19:15:05 +02:00
|
|
|
// relocs are the symbol-valued DATA fields, in declaration order; Off
|
|
|
|
|
// is relative to the symbol's data start.
|
|
|
|
|
relocs []Reloc
|
2026-07-16 20:52:20 +02:00
|
|
|
}
|
|
|
|
|
|
2026-07-09 15:56:03 +02:00
|
|
|
// collectData gathers the file's static symbols (GLOBL) and their initial
|
2026-09-19 19:58:43 +02:00
|
|
|
// contents (DATA) into byte buffers. Two passes: the Plan 9 convention puts
|
|
|
|
|
// every DATA line before its symbol's GLOBL, so the symbols are registered
|
|
|
|
|
// before the initialisers are applied.
|
2026-07-16 20:52:20 +02:00
|
|
|
func collectData(f *ast.File) ([]dataSym, error) {
|
|
|
|
|
index := map[string]int{}
|
|
|
|
|
var syms []dataSym
|
2026-07-09 15:56:03 +02:00
|
|
|
for _, d := range f.Decls {
|
2026-09-19 19:58:43 +02:00
|
|
|
gd, ok := d.(*ast.Globl)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if gd.Name == nil || gd.Name.Pseudo != "SB" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
name := gd.Name.Name
|
|
|
|
|
if _, dup := index[name]; dup {
|
|
|
|
|
return nil, fmt.Errorf("duplicate GLOBL %q", name)
|
|
|
|
|
}
|
|
|
|
|
size := 0
|
|
|
|
|
if gd.Size != nil && gd.Size.Imm.HasVal {
|
|
|
|
|
size = int(gd.Size.Imm.Val)
|
|
|
|
|
}
|
|
|
|
|
index[name] = len(syms)
|
|
|
|
|
ds := dataSym{
|
|
|
|
|
name: name,
|
|
|
|
|
pkg: gd.Name.Pkg,
|
|
|
|
|
buf: make([]byte, size),
|
|
|
|
|
size: size,
|
|
|
|
|
static: gd.Name.Static,
|
|
|
|
|
}
|
|
|
|
|
for _, f := range gd.Flags {
|
|
|
|
|
switch f {
|
|
|
|
|
case "RODATA":
|
|
|
|
|
ds.rodata = true
|
|
|
|
|
case "DUPOK":
|
|
|
|
|
ds.dupok = true
|
|
|
|
|
default:
|
|
|
|
|
// Legacy numeric flag constants (runtime/textflag.h):
|
|
|
|
|
// DUPOK is 2, RODATA is 8; combinations arrive as one
|
|
|
|
|
// number (e.g. 10 = RODATA|DUPOK).
|
|
|
|
|
if n, err := strconv.Atoi(f); err == nil {
|
|
|
|
|
if n&2 != 0 {
|
|
|
|
|
ds.dupok = true
|
|
|
|
|
}
|
|
|
|
|
if n&8 != 0 {
|
|
|
|
|
ds.rodata = true
|
2026-09-14 18:22:00 +02:00
|
|
|
}
|
2026-07-17 18:57:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-09-19 19:58:43 +02:00
|
|
|
}
|
|
|
|
|
syms = append(syms, ds)
|
|
|
|
|
}
|
|
|
|
|
for _, d := range f.Decls {
|
|
|
|
|
dd, ok := d.(*ast.Data)
|
|
|
|
|
if !ok {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if dd.Name == nil || dd.Name.Pseudo != "SB" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
i, ok := index[dd.Name.Name]
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: no matching GLOBL", dd.Name.Name)
|
|
|
|
|
}
|
2026-09-20 19:15:05 +02:00
|
|
|
if dd.Value == nil {
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: missing value", dd.Name.Name)
|
2026-09-19 19:58:43 +02:00
|
|
|
}
|
|
|
|
|
w := dd.Width
|
|
|
|
|
off := dd.Name.Offset
|
|
|
|
|
buf := syms[i].buf
|
|
|
|
|
if off < 0 || off+int64(w) > int64(len(buf)) {
|
|
|
|
|
return nil, fmt.Errorf("DATA %q+%d/%d exceeds GLOBL size %d", dd.Name.Name, off, w, len(buf))
|
|
|
|
|
}
|
2026-09-20 19:15:05 +02:00
|
|
|
// A symbol value ("DATA s+0(SB)/8, $other(SB)", the rt0 spelling)
|
|
|
|
|
// leaves the field zero and records a relocation against the named
|
|
|
|
|
// symbol: the linker patches the absolute address at this data
|
|
|
|
|
// offset. The toolchain emits the same shape, an R_ADDR of the
|
|
|
|
|
// DATA width with the value's offset as the addend, on every
|
|
|
|
|
// architecture.
|
|
|
|
|
if sym := dd.Value.Imm.Sym; !dd.Value.Imm.HasVal && sym != nil {
|
|
|
|
|
syms[i].relocs = append(syms[i].relocs, Reloc{
|
|
|
|
|
Off: int(off),
|
|
|
|
|
Name: sym.Name,
|
|
|
|
|
Addend: sym.Offset,
|
|
|
|
|
Kind: RelAddr,
|
|
|
|
|
Siz: uint8(w),
|
|
|
|
|
})
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-09-21 00:44:47 +02:00
|
|
|
// A string or rune value ("DATA s+0(SB)/20, $"text"") writes its
|
|
|
|
|
// bytes into the field and leaves the rest zero, the toolchain's
|
|
|
|
|
// WriteString: the declared width must hold every byte, and any
|
|
|
|
|
// width is legal.
|
|
|
|
|
if s := dd.Value.Imm.Str; s != "" && !dd.Value.Imm.HasVal {
|
|
|
|
|
text, err := strconv.Unquote(s)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: invalid string value %s", dd.Name.Name, s)
|
|
|
|
|
}
|
|
|
|
|
if len(text) > w {
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: string of %d bytes does not fit width %d", dd.Name.Name, len(text), w)
|
|
|
|
|
}
|
|
|
|
|
copy(buf[off:], text)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
// A floating-point value stores its IEEE-754 bits: /4 the float32
|
|
|
|
|
// rounding of the parsed double, /8 the full 64 bits, the
|
|
|
|
|
// toolchain's WriteFloat32 and WriteFloat64.
|
|
|
|
|
if f := dd.Value.Imm.Float; f != "" && !dd.Value.Imm.HasVal {
|
|
|
|
|
num, err := strconv.ParseFloat(f, 64)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: invalid floating-point value %q", dd.Name.Name, f)
|
|
|
|
|
}
|
|
|
|
|
if dd.Value.Imm.Neg {
|
|
|
|
|
num = -num
|
|
|
|
|
}
|
|
|
|
|
var v uint64
|
|
|
|
|
switch w {
|
|
|
|
|
case 4:
|
|
|
|
|
v = uint64(math.Float32bits(float32(num)))
|
|
|
|
|
case 8:
|
|
|
|
|
v = math.Float64bits(num)
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: invalid width %d for a float (want 4 or 8)", dd.Name.Name, w)
|
|
|
|
|
}
|
|
|
|
|
for j := range w {
|
|
|
|
|
buf[off+int64(j)] = byte(v >> (8 * j))
|
|
|
|
|
}
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-09-20 19:15:05 +02:00
|
|
|
if !dd.Value.Imm.HasVal {
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: value must be an integer immediate or a symbol address", dd.Name.Name)
|
|
|
|
|
}
|
2026-09-21 00:44:47 +02:00
|
|
|
switch w {
|
|
|
|
|
case 1, 2, 4, 8:
|
|
|
|
|
default:
|
|
|
|
|
return nil, fmt.Errorf("DATA %q: invalid width %d (want 1, 2, 4 or 8)", dd.Name.Name, w)
|
|
|
|
|
}
|
2026-09-19 19:58:43 +02:00
|
|
|
v := dd.Value.Imm.Val
|
|
|
|
|
if dd.Value.Imm.Neg {
|
|
|
|
|
v = -v
|
|
|
|
|
}
|
|
|
|
|
for j := range w {
|
|
|
|
|
buf[off+int64(j)] = byte(v >> (8 * j))
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-07-16 20:52:20 +02:00
|
|
|
return syms, nil
|
2026-07-09 15:56:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// align16 rounds n up to the next multiple of 16.
|
|
|
|
|
func align16(n int) int {
|
|
|
|
|
return (n + 15) &^ 15
|
|
|
|
|
}
|
2026-07-17 18:57:04 +02:00
|
|
|
|
|
|
|
|
// frameSize returns the local frame size declared on the TEXT directive.
|
|
|
|
|
func frameSize(t *ast.Text) int {
|
|
|
|
|
if t.Frame != nil && t.Frame.Imm.HasVal {
|
|
|
|
|
return int(t.Frame.Imm.Val)
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// argsSize returns the argument/result area declared on the TEXT directive.
|
|
|
|
|
func argsSize(t *ast.Text) int {
|
|
|
|
|
if t.Args != nil && t.Args.Imm.HasVal {
|
|
|
|
|
return int(t.Args.Imm.Val)
|
|
|
|
|
}
|
|
|
|
|
return 0
|
|
|
|
|
}
|