feat(asm): emit RISC-V GOOBJ with the shared emitter
Assisted-by: DeepSeek V4 Pro
This commit is contained in:
+20
-327
@@ -5,7 +5,6 @@ package asm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
@@ -13,343 +12,37 @@ import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// GOObjectRISCV emits a GOOBJ object file for RISC-V.
|
||||
// The format is the same as amd64 GOOBJ, but with the RISC-V architecture
|
||||
// marker in the preamble and RISC-V relocation types.
|
||||
// GOObjectRISCV emits a GOOBJ object file for RISC-V. The layout is the
|
||||
// shared one in goobj.go — the toolchain preamble, the go120ld header with
|
||||
// its block offsets, the string table, the symbol definitions and the
|
||||
// reloc/aux/data index arrays — with the RISC-V preamble, the MinLC of 2 for
|
||||
// the pc-value deltas, and the single R_RISCV_PCREL_ITYPE/STYPE relocation
|
||||
// per AUIPC pair, matching `go tool asm`'s model (each pair is one 8-byte
|
||||
// relocation, not the ELF HI20/LO12 pair).
|
||||
func (img *Image) GOObjectRISCV(pkgPath, srcPath string) ([]byte, error) {
|
||||
if pkgPath == "" {
|
||||
return nil, fmt.Errorf("GOOBJ emission requires a package path (-p)")
|
||||
}
|
||||
pre, err := toolchainObjectPreambleRISCV()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The symbol tables. Package definitions: the GLOBL symbols, then one
|
||||
// anonymous FuncInfo symbol per function. Non-package definitions: the
|
||||
// pc-value tables and the functions themselves, as cmd/asm lays them
|
||||
// out. defIdx maps a GLOBL's bare name to its definition index for the
|
||||
// relocations; fnNpIdx maps a function to its non-package index.
|
||||
var defs []goSym
|
||||
var defData [][]byte
|
||||
defIdx := map[string]int{}
|
||||
for _, d := range img.DataSyms {
|
||||
name := d.Name
|
||||
if !d.Static {
|
||||
name = pkgPath + "." + name
|
||||
return img.emitGOObject(pkgPath, srcPath, pre, 2, func(r Reloc) (uint16, uint8) {
|
||||
if r.Kind == RelRISCVPCRELSType {
|
||||
return relocRISCVPcrelStype, 8
|
||||
}
|
||||
typ := uint8(kindSDATA)
|
||||
if d.Rodata {
|
||||
typ = kindSRODATA
|
||||
}
|
||||
flag := uint8(0)
|
||||
if d.Dupok {
|
||||
flag = symFlagDupok
|
||||
}
|
||||
abi := uint16(0)
|
||||
if d.Static {
|
||||
abi = symABIStatic
|
||||
}
|
||||
defIdx[d.Name] = len(defs)
|
||||
defs = append(defs, goSym{name: name, abi: abi, typ: typ, flag: flag, flag2: symFlag2Link, size: uint32(d.Size)})
|
||||
defData = append(defData, img.Data[d.Offset:d.Offset+d.Size])
|
||||
}
|
||||
fnFiIdx := make([]int, len(img.Funcs))
|
||||
for i := range img.Funcs {
|
||||
data := marshalFuncInfo(img.Funcs[i])
|
||||
fnFiIdx[i] = len(defs)
|
||||
defs = append(defs, goSym{typ: kindSDATA, size: uint32(len(data))})
|
||||
defData = append(defData, data)
|
||||
}
|
||||
|
||||
type npSym struct {
|
||||
sym goSym
|
||||
data []byte
|
||||
}
|
||||
var nps []npSym
|
||||
type pcRefs struct{ sp, file, line, inl int }
|
||||
pcIdx := make([]pcRefs, len(img.Funcs))
|
||||
fnNpIdx := make([]int, len(img.Funcs))
|
||||
for i, fn := range img.Funcs {
|
||||
tables := []struct {
|
||||
data []byte
|
||||
dst *int
|
||||
}{
|
||||
{pcspTable(fn, 2), &pcIdx[i].sp},
|
||||
{pcValueFlat(0, fn.Size, 2), &pcIdx[i].file},
|
||||
{pcValueFlat(int32(fn.Line), fn.Size, 2), &pcIdx[i].line},
|
||||
{pcValueFlat(-1, fn.Size, 2), &pcIdx[i].inl},
|
||||
}
|
||||
for _, t := range tables {
|
||||
*t.dst = len(nps)
|
||||
nps = append(nps, npSym{
|
||||
sym: goSym{typ: kindSRODATA, size: uint32(len(t.data)), align: 1},
|
||||
data: t.data,
|
||||
})
|
||||
}
|
||||
name := fn.Name
|
||||
abi := uint16(0)
|
||||
if fn.Static {
|
||||
abi = symABIStatic
|
||||
} else {
|
||||
name = pkgPath + "." + name
|
||||
}
|
||||
flag := uint8(0)
|
||||
if fn.NoSplit {
|
||||
flag |= symFlagNoSplit
|
||||
}
|
||||
fnNpIdx[i] = len(nps)
|
||||
code := append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...)
|
||||
for _, r := range fn.Relocs {
|
||||
// The linker writes the resolved displacement into the field;
|
||||
// leave it zero, as cmd/asm's object does.
|
||||
if r.Off >= 0 && r.Off+4 <= len(code) {
|
||||
code[r.Off], code[r.Off+1], code[r.Off+2], code[r.Off+3] = 0, 0, 0, 0
|
||||
}
|
||||
}
|
||||
nps = append(nps, npSym{
|
||||
sym: goSym{name: name, abi: abi, typ: kindSTEXT, flag: flag, flag2: symFlag2Link, size: uint32(fn.Size)},
|
||||
data: code,
|
||||
})
|
||||
}
|
||||
|
||||
// Resolve external symbol references (cross-package).
|
||||
var extPkgTable []string
|
||||
var extPkgIdx map[string]int
|
||||
var extSymIdx map[string]int
|
||||
if len(img.Externals) > 0 {
|
||||
var err error
|
||||
extPkgTable, extPkgIdx, extSymIdx, err = resolveExternalSymbols(img.Externals)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GOOBJ emission: resolving external symbols: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Relocations, per defined symbol in definition order (package defs,
|
||||
// then non-package defs).
|
||||
nsyms := len(defs) + len(nps)
|
||||
symRelocs := make([][]byte, nsyms) // flat 23-byte records
|
||||
for i, fn := range img.Funcs {
|
||||
si := len(defs) + fnNpIdx[i]
|
||||
for _, r := range fn.Relocs {
|
||||
if r.External {
|
||||
pkg, name := splitQualified(r.Name)
|
||||
if pkg == "" {
|
||||
return nil, fmt.Errorf("GOOBJ emission: external symbol %q has no package prefix", r.Name)
|
||||
}
|
||||
pIdx, ok := extPkgIdx[pkg]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("GOOBJ emission: package %q not resolved", pkg)
|
||||
}
|
||||
sIdx, ok := extSymIdx[pkg+"·"+name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("GOOBJ emission: symbol %s·%s not resolved", pkg, name)
|
||||
}
|
||||
var rec [23]byte
|
||||
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
||||
rec[4] = 4 // field width
|
||||
binary.LittleEndian.PutUint16(rec[5:], relocRISCVPcrelHi20)
|
||||
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
||||
binary.LittleEndian.PutUint32(rec[15:], uint32(pIdx))
|
||||
binary.LittleEndian.PutUint32(rec[19:], uint32(sIdx))
|
||||
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
||||
continue
|
||||
}
|
||||
di, ok := defIdx[r.Name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("GOOBJ emission: reference to unknown symbol %q", r.Name)
|
||||
}
|
||||
var rec [23]byte
|
||||
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
||||
rec[4] = 4 // field width
|
||||
binary.LittleEndian.PutUint16(rec[5:], relocRISCVPcrelHi20)
|
||||
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
||||
binary.LittleEndian.PutUint32(rec[15:], pkgIdxSelf)
|
||||
binary.LittleEndian.PutUint32(rec[19:], uint32(di))
|
||||
symRelocs[si] = append(symRelocs[si], rec[:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// Aux entries per function: FuncInfo, then the four pc tables.
|
||||
// References into the non-package table use pkgIdxNone.
|
||||
symAux := make([][]byte, nsyms)
|
||||
for i := range img.Funcs {
|
||||
si := len(defs) + fnNpIdx[i]
|
||||
aux := func(typ uint8, pkg, idx uint32) {
|
||||
var rec [9]byte
|
||||
rec[0] = typ
|
||||
binary.LittleEndian.PutUint32(rec[1:], pkg)
|
||||
binary.LittleEndian.PutUint32(rec[5:], idx)
|
||||
symAux[si] = append(symAux[si], rec[:]...)
|
||||
}
|
||||
aux(auxFuncInfo, pkgIdxSelf, uint32(fnFiIdx[i]))
|
||||
aux(auxPcsp, pkgIdxNone, uint32(len(defs)+pcIdx[i].sp))
|
||||
aux(auxPcfile, pkgIdxNone, uint32(len(defs)+pcIdx[i].file))
|
||||
aux(auxPcline, pkgIdxNone, uint32(len(defs)+pcIdx[i].line))
|
||||
aux(auxPcinline, pkgIdxNone, uint32(len(defs)+pcIdx[i].inl))
|
||||
}
|
||||
|
||||
// --- Serialise ---
|
||||
|
||||
// String table: all symbol names, NUL-terminated.
|
||||
var strtab []byte
|
||||
strOff := map[string]uint32{}
|
||||
addStr := func(s string) uint32 {
|
||||
if off, ok := strOff[s]; ok {
|
||||
return off
|
||||
}
|
||||
off := uint32(len(strtab))
|
||||
strOff[s] = off
|
||||
strtab = append(strtab, s...)
|
||||
strtab = append(strtab, 0)
|
||||
return off
|
||||
}
|
||||
for _, s := range defs {
|
||||
addStr(s.name)
|
||||
}
|
||||
for _, s := range nps {
|
||||
addStr(s.sym.name)
|
||||
}
|
||||
for _, pkg := range extPkgTable {
|
||||
addStr(pkg)
|
||||
}
|
||||
|
||||
// Package index block: index 0 is the dummy invalid package, followed by
|
||||
// external packages.
|
||||
var pkgIdxBlk bytes.Buffer
|
||||
pkgIdxBlk.Write(binary.LittleEndian.AppendUint32(nil, 0)) // len("")
|
||||
pkgIdxBlk.Write(binary.LittleEndian.AppendUint32(nil, 0)) // offset of ""
|
||||
for _, pkg := range extPkgTable {
|
||||
off := strOff[pkg]
|
||||
pkgIdxBlk.Write(binary.LittleEndian.AppendUint32(nil, uint32(len(pkg))))
|
||||
pkgIdxBlk.Write(binary.LittleEndian.AppendUint32(nil, off))
|
||||
}
|
||||
|
||||
// Symbol definition records (21 bytes each).
|
||||
var symdef, nonpkgdef []byte
|
||||
for _, s := range defs {
|
||||
symdef = s.append(symdef, strOff)
|
||||
}
|
||||
for _, s := range nps {
|
||||
nonpkgdef = s.sym.append(nonpkgdef, strOff)
|
||||
}
|
||||
|
||||
// Data index: one uint32 per defined symbol (package defs first, then
|
||||
// non-package defs), giving the byte offset into the data block.
|
||||
var dataIdx []byte
|
||||
var dataBlk []byte
|
||||
off := uint32(0)
|
||||
for _, d := range defData {
|
||||
dataIdx = binary.LittleEndian.AppendUint32(dataIdx, off)
|
||||
dataBlk = append(dataBlk, d...)
|
||||
off += uint32(len(d))
|
||||
}
|
||||
for _, s := range nps {
|
||||
dataIdx = binary.LittleEndian.AppendUint32(dataIdx, off)
|
||||
dataBlk = append(dataBlk, s.data...)
|
||||
off += uint32(len(s.data))
|
||||
}
|
||||
dataIdx = binary.LittleEndian.AppendUint32(dataIdx, off) // sentinel
|
||||
|
||||
// Relocation index: one uint32 per symbol, giving the byte offset into
|
||||
// the reloc block.
|
||||
var relocIdx []byte
|
||||
roff := uint32(0)
|
||||
for i := 0; i < nsyms; i++ {
|
||||
relocIdx = binary.LittleEndian.AppendUint32(relocIdx, roff)
|
||||
roff += uint32(len(symRelocs[i]))
|
||||
}
|
||||
relocIdx = binary.LittleEndian.AppendUint32(relocIdx, roff) // sentinel
|
||||
var relocBlk []byte
|
||||
for _, r := range symRelocs {
|
||||
relocBlk = append(relocBlk, r...)
|
||||
}
|
||||
|
||||
// Aux index: one uint32 per symbol, giving the byte offset into the aux
|
||||
// block.
|
||||
var auxIdx []byte
|
||||
aoff := uint32(0)
|
||||
for i := 0; i < nsyms; i++ {
|
||||
auxIdx = binary.LittleEndian.AppendUint32(auxIdx, aoff)
|
||||
aoff += uint32(len(symAux[i]))
|
||||
}
|
||||
auxIdx = binary.LittleEndian.AppendUint32(auxIdx, aoff) // sentinel
|
||||
var auxBlk []byte
|
||||
for _, a := range symAux {
|
||||
auxBlk = append(auxBlk, a...)
|
||||
}
|
||||
|
||||
// File table: one entry, the source file.
|
||||
var fileBlk []byte
|
||||
fileOff := addStr(srcPath)
|
||||
fileBlk = binary.LittleEndian.AppendUint32(fileBlk, uint32(len(srcPath)))
|
||||
fileBlk = binary.LittleEndian.AppendUint32(fileBlk, fileOff)
|
||||
|
||||
// Assemble the object.
|
||||
var out bytes.Buffer
|
||||
out.Write(pre)
|
||||
out.WriteString(goobjMagic)
|
||||
|
||||
// Block offsets (20 bytes into the header: 4 magic + 8 go version +
|
||||
// 8 experiment = 20, then blkEnd+1 uint32 offsets).
|
||||
// We'll fill these in after we know the sizes.
|
||||
hdrStart := out.Len()
|
||||
out.Write(make([]byte, 4*(blkEnd+1)))
|
||||
|
||||
writeBlock := func(data []byte) {
|
||||
out.Write(data)
|
||||
}
|
||||
|
||||
// Blocks in order: autolib, pkgidx, file, symdef, hashed64def, hasheddef,
|
||||
// nonpkgdef, nonpkgref, refflags, hash64, hash, relocidx, auxidx, dataidx,
|
||||
// reloc, aux, data, refname.
|
||||
writeBlock(nil) // autolib
|
||||
writeBlock(pkgIdxBlk.Bytes()) // pkgidx
|
||||
writeBlock(fileBlk) // file
|
||||
writeBlock(symdef) // symdef
|
||||
writeBlock(nil) // hashed64def
|
||||
writeBlock(nil) // hasheddef
|
||||
writeBlock(nonpkgdef) // nonpkgdef
|
||||
writeBlock(nil) // nonpkgref
|
||||
writeBlock(nil) // refflags
|
||||
writeBlock(nil) // hash64
|
||||
writeBlock(nil) // hash
|
||||
writeBlock(relocIdx) // relocidx
|
||||
writeBlock(auxIdx) // auxidx
|
||||
writeBlock(dataIdx) // dataidx
|
||||
writeBlock(relocBlk) // reloc
|
||||
writeBlock(auxBlk) // aux
|
||||
writeBlock(dataBlk) // data
|
||||
writeBlock(nil) // refname
|
||||
|
||||
// Fill in the block offsets.
|
||||
le := binary.LittleEndian
|
||||
offs := make([]uint32, blkEnd+1)
|
||||
pos := uint32(hdrStart + 4*(blkEnd+1))
|
||||
for i := 0; i < blkEnd; i++ {
|
||||
offs[i] = pos
|
||||
// Calculate the size of each block by re-reading what we wrote.
|
||||
// This is a simplification; a real implementation would track sizes.
|
||||
}
|
||||
offs[blkEnd] = uint32(out.Len())
|
||||
|
||||
// For now, just write zeros for the offsets (the linker will parse the
|
||||
// blocks sequentially anyway).
|
||||
for i := 0; i <= blkEnd; i++ {
|
||||
le.PutUint32(out.Bytes()[hdrStart+4*i:], offs[i])
|
||||
}
|
||||
|
||||
return out.Bytes(), nil
|
||||
return relocRISCVPcrelItype, 8
|
||||
})
|
||||
}
|
||||
|
||||
// RISC-V relocation types (cmd/internal/objabi).
|
||||
// RISC-V relocation types (cmd/internal/objabi). The Go linker applies
|
||||
// R_RISCV_PCREL_ITYPE/STYPE to an AUIPC + I/S-type instruction pair as a
|
||||
// single 8-byte field.
|
||||
const (
|
||||
relocRISCVPcrelHi20 = 23
|
||||
relocRISCVPcrelLo12I = 24
|
||||
relocRISCVPcrelLo12S = 25
|
||||
relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE
|
||||
relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE
|
||||
)
|
||||
|
||||
// toolchainObjectPreambleRISCV returns the RISC-V object preamble.
|
||||
// toolchainObjectPreambleRISCV returns the "go object ...\n!\n" header
|
||||
// the installed go tool asm writes for riscv64, captured by assembling a
|
||||
// one-instruction probe (see toolchainObjectPreamble).
|
||||
var (
|
||||
preambleRISCVOnce sync.Once
|
||||
preambleRISCV []byte
|
||||
|
||||
Reference in New Issue
Block a user