feat(asm): emit RISC-V GOOBJ with the shared emitter
Assisted-by: DeepSeek V4 Pro
This commit is contained in:
@@ -26,6 +26,14 @@ Unreleased changes on the `development` branch.
|
|||||||
in the architecture's MinLC units as the runtime expects — the amd64 link
|
in the architecture's MinLC units as the runtime expects — the amd64 link
|
||||||
test now genuinely substitutes the gasm object, and the amd64/loong64
|
test now genuinely substitutes the gasm object, and the amd64/loong64
|
||||||
end-to-end GOOBJ link tests pass.
|
end-to-end GOOBJ link tests pass.
|
||||||
|
- **RISC-V GOOBJ emission via the shared emitter.** RISC-V GOOBJ output is
|
||||||
|
now written by the same shared emitter as amd64 and LoongArch, modelling
|
||||||
|
each AUIPC + second-instruction pair as a single R_RISCV_PCREL_ITYPE/STYPE
|
||||||
|
relocation (the layout `cmd/asm` writes, not the ELF HI20/LO12 pair), so the
|
||||||
|
object links into a cross-compiled `go build` for `GOARCH=riscv64`. An
|
||||||
|
end-to-end link test substitutes the gasm object and reads the symbol back
|
||||||
|
with `go tool nm`; the rewrite also corrects the relocation `after` field
|
||||||
|
and the compressed `RET` encoding (C.JR ra).
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
+17
-18
@@ -79,11 +79,10 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
symIdx[s.name] = i
|
symIdx[s.name] = i
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build relocations. Each SB reference produces a pair:
|
// Build relocations. Each SB reference is an AUIPC + second-instruction
|
||||||
// AUIPC rd, 0 → R_RISCV_PCREL_HI20
|
// pair carrying a single relocation kind; the ELF writer expands it into
|
||||||
// ADDI/LD/SD → R_RISCV_PCREL_LO12_I or _S
|
// the R_RISCV_PCREL_HI20 + R_RISCV_PCREL_LO12_I/S pair the psABI expects,
|
||||||
// For now we record them as individual entries; at link time
|
// with the same addend on both.
|
||||||
// the linker must pair HI20 with its matching LO12.
|
|
||||||
type elfRela struct {
|
type elfRela struct {
|
||||||
off uint64
|
off uint64
|
||||||
typ uint32
|
typ uint32
|
||||||
@@ -97,22 +96,22 @@ func (img *Image) ELFRISCVObject() ([]byte, error) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
return nil, fmt.Errorf("relocation references unknown symbol %q", r.Name)
|
||||||
}
|
}
|
||||||
// Determine relocation type from the relocation kind.
|
|
||||||
typ := uint32(rRISCVPCRELHI20) // default: AUIPC
|
|
||||||
switch r.Kind {
|
switch r.Kind {
|
||||||
case RelPCRelLO12:
|
case RelRISCVPCRELIType:
|
||||||
typ = rRISCVPCRELLO12I
|
relas = append(relas,
|
||||||
case RelPCRelLO12S:
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVPCRELHI20, sym: idx, addend: r.Addend},
|
||||||
typ = rRISCVPCRELLO12S
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVPCRELLO12I, sym: idx, addend: r.Addend},
|
||||||
|
)
|
||||||
|
case RelRISCVPCRELSType:
|
||||||
|
relas = append(relas,
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCVPCRELHI20, sym: idx, addend: r.Addend},
|
||||||
|
elfRela{off: uint64(fn.Offset + r.Off + 4), typ: rRISCVPCRELLO12S, sym: idx, addend: r.Addend},
|
||||||
|
)
|
||||||
case RelPCRelAbs:
|
case RelPCRelAbs:
|
||||||
typ = rRISCV32
|
relas = append(relas, elfRela{off: uint64(fn.Offset + r.Off), typ: rRISCV32, sym: idx, addend: r.Addend})
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("relocation kind %v unsupported in ELF emission", r.Kind)
|
||||||
}
|
}
|
||||||
relas = append(relas, elfRela{
|
|
||||||
off: uint64(fn.Offset + r.Off),
|
|
||||||
typ: typ,
|
|
||||||
sym: idx,
|
|
||||||
addend: r.Addend - int64(r.After-r.Off),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-10
@@ -143,14 +143,15 @@ func (img *Image) GOObject(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// amd64: MinLC 1, R_PCREL for the code relocations.
|
// amd64: MinLC 1, R_PCREL for the code relocations.
|
||||||
return img.emitGOObject(pkgPath, srcPath, pre, 1, func(Reloc) uint16 { return relocPCRel })
|
return img.emitGOObject(pkgPath, srcPath, pre, 1, func(Reloc) (uint16, uint8) { return relocPCRel, 4 })
|
||||||
}
|
}
|
||||||
|
|
||||||
// emitGOObject assembles the GOOBJ payload for any architecture. pre is
|
// emitGOObject assembles the GOOBJ payload for any architecture. pre is
|
||||||
// the toolchain's object preamble; minLC is the architecture's minimum
|
// the toolchain's object preamble; minLC is the architecture's minimum
|
||||||
// instruction length, the unit of the pc-value table deltas; relocType
|
// instruction length, the unit of the pc-value table deltas; relocField
|
||||||
// maps a code relocation to its objabi relocation type.
|
// maps a code relocation to its objabi relocation type and the width of
|
||||||
func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, relocType func(Reloc) uint16) ([]byte, error) {
|
// the instruction field the linker writes.
|
||||||
|
func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, relocField func(Reloc) (uint16, uint8)) ([]byte, error) {
|
||||||
if pkgPath == "" {
|
if pkgPath == "" {
|
||||||
return nil, fmt.Errorf("GOOBJ emission requires a package path (-p)")
|
return nil, fmt.Errorf("GOOBJ emission requires a package path (-p)")
|
||||||
}
|
}
|
||||||
@@ -197,8 +198,16 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
|||||||
fnNpIdx[i] = len(nps)
|
fnNpIdx[i] = len(nps)
|
||||||
code := append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...)
|
code := append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...)
|
||||||
for _, r := range fn.Relocs {
|
for _, r := range fn.Relocs {
|
||||||
// The linker writes the resolved displacement into the field;
|
// Only the amd64 encoder resolves file-local static symbols
|
||||||
// leave it zero, as cmd/asm's object does.
|
// into a disp32 field at assemble time; GOOBJ must leave that
|
||||||
|
// field zero for the linker to fill. The RISC-V and LoongArch
|
||||||
|
// encoders emit zero immediates with a relocation instead, and
|
||||||
|
// their relocations cover whole AUIPC/pcalau12i pairs, so
|
||||||
|
// zeroing r.Off would erase the opcode/register bits the linker
|
||||||
|
// preserves when it patches only the immediate.
|
||||||
|
if r.Kind != RelPCRel32 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
if r.Off >= 0 && r.Off+4 <= len(code) {
|
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
|
code[r.Off], code[r.Off+1], code[r.Off+2], code[r.Off+3] = 0, 0, 0, 0
|
||||||
}
|
}
|
||||||
@@ -290,6 +299,7 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
|||||||
for i, fn := range img.Funcs {
|
for i, fn := range img.Funcs {
|
||||||
si := len(defs) + fnNpIdx[i]
|
si := len(defs) + fnNpIdx[i]
|
||||||
for _, r := range fn.Relocs {
|
for _, r := range fn.Relocs {
|
||||||
|
typ, size := relocField(r)
|
||||||
if r.External {
|
if r.External {
|
||||||
// Split package-qualified name: "runtime·morestack" → runtime, morestack.
|
// Split package-qualified name: "runtime·morestack" → runtime, morestack.
|
||||||
pkg, name := splitQualified(r.Name)
|
pkg, name := splitQualified(r.Name)
|
||||||
@@ -306,8 +316,8 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
|||||||
}
|
}
|
||||||
var rec [23]byte
|
var rec [23]byte
|
||||||
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
||||||
rec[4] = 4 // field width
|
rec[4] = size // field width
|
||||||
binary.LittleEndian.PutUint16(rec[5:], relocType(r))
|
binary.LittleEndian.PutUint16(rec[5:], typ)
|
||||||
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
||||||
binary.LittleEndian.PutUint32(rec[15:], uint32(pIdx))
|
binary.LittleEndian.PutUint32(rec[15:], uint32(pIdx))
|
||||||
binary.LittleEndian.PutUint32(rec[19:], uint32(sIdx))
|
binary.LittleEndian.PutUint32(rec[19:], uint32(sIdx))
|
||||||
@@ -320,8 +330,8 @@ func (img *Image) emitGOObject(pkgPath, srcPath string, pre []byte, minLC int, r
|
|||||||
}
|
}
|
||||||
var rec [23]byte
|
var rec [23]byte
|
||||||
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
binary.LittleEndian.PutUint32(rec[0:], uint32(int32(r.Off)))
|
||||||
rec[4] = 4 // field width
|
rec[4] = size // field width
|
||||||
binary.LittleEndian.PutUint16(rec[5:], relocType(r))
|
binary.LittleEndian.PutUint16(rec[5:], typ)
|
||||||
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
binary.LittleEndian.PutUint64(rec[7:], uint64(r.Addend))
|
||||||
binary.LittleEndian.PutUint32(rec[15:], pkgIdxSelf)
|
binary.LittleEndian.PutUint32(rec[15:], pkgIdxSelf)
|
||||||
binary.LittleEndian.PutUint32(rec[19:], uint32(di))
|
binary.LittleEndian.PutUint32(rec[19:], uint32(di))
|
||||||
|
|||||||
+3
-3
@@ -23,13 +23,13 @@ func (img *Image) GOObjectLOONG64(pkgPath, srcPath string) ([]byte, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return img.emitGOObject(pkgPath, srcPath, pre, 4, func(r Reloc) uint16 {
|
return img.emitGOObject(pkgPath, srcPath, pre, 4, func(r Reloc) (uint16, uint8) {
|
||||||
// A pcalau12i+addi.d pair: the high part carries
|
// A pcalau12i+addi.d pair: the high part carries
|
||||||
// R_LOONG64_ADDR_HI, the low part R_LOONG64_ADDR_LO.
|
// R_LOONG64_ADDR_HI, the low part R_LOONG64_ADDR_LO.
|
||||||
if r.Kind == RelLoong64AddrLo {
|
if r.Kind == RelLoong64AddrLo {
|
||||||
return relocLoong64AddrLo
|
return relocLoong64AddrLo, 4
|
||||||
}
|
}
|
||||||
return relocLoong64AddrHi
|
return relocLoong64AddrHi, 4
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-327
@@ -5,7 +5,6 @@ package asm
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -13,343 +12,37 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GOObjectRISCV emits a GOOBJ object file for RISC-V.
|
// GOObjectRISCV emits a GOOBJ object file for RISC-V. The layout is the
|
||||||
// The format is the same as amd64 GOOBJ, but with the RISC-V architecture
|
// shared one in goobj.go — the toolchain preamble, the go120ld header with
|
||||||
// marker in the preamble and RISC-V relocation types.
|
// 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) {
|
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()
|
pre, err := toolchainObjectPreambleRISCV()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
return img.emitGOObject(pkgPath, srcPath, pre, 2, func(r Reloc) (uint16, uint8) {
|
||||||
// The symbol tables. Package definitions: the GLOBL symbols, then one
|
if r.Kind == RelRISCVPCRELSType {
|
||||||
// anonymous FuncInfo symbol per function. Non-package definitions: the
|
return relocRISCVPcrelStype, 8
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
typ := uint8(kindSDATA)
|
return relocRISCVPcrelItype, 8
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 (
|
const (
|
||||||
relocRISCVPcrelHi20 = 23
|
relocRISCVPcrelItype = 62 // R_RISCV_PCREL_ITYPE
|
||||||
relocRISCVPcrelLo12I = 24
|
relocRISCVPcrelStype = 63 // R_RISCV_PCREL_STYPE
|
||||||
relocRISCVPcrelLo12S = 25
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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 (
|
var (
|
||||||
preambleRISCVOnce sync.Once
|
preambleRISCVOnce sync.Once
|
||||||
preambleRISCV []byte
|
preambleRISCV []byte
|
||||||
|
|||||||
+6
-7
@@ -89,13 +89,12 @@ func (fl *FuncLayout) LineAt(offset int) int {
|
|||||||
type RelocKind int
|
type RelocKind int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
RelPCRel32 RelocKind = iota // 32-bit PC-relative (amd64)
|
RelPCRel32 RelocKind = iota // 32-bit PC-relative (amd64)
|
||||||
RelPCRelHI20 // R_RISCV_PCREL_HI20 (AUIPC)
|
RelRISCVPCRELIType // R_RISCV_PCREL_ITYPE (AUIPC + I-type pair)
|
||||||
RelPCRelLO12 // R_RISCV_PCREL_LO12_I (ADDI, LD)
|
RelRISCVPCRELSType // R_RISCV_PCREL_STYPE (AUIPC + S-type pair)
|
||||||
RelPCRelLO12S // R_RISCV_PCREL_LO12_S (SD)
|
RelPCRelAbs // 32-bit absolute (R_RISCV_32)
|
||||||
RelPCRelAbs // 32-bit absolute (R_RISCV_32)
|
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
||||||
RelLoong64AddrHi // R_LOONG64_ADDR_HI (pcalau12i)
|
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
||||||
RelLoong64AddrLo // R_LOONG64_ADDR_LO (addi.d/ld/st)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Reloc struct {
|
type Reloc struct {
|
||||||
|
|||||||
+15
-14
@@ -91,9 +91,12 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, error) {
|
|||||||
code = []byte{byte(c16), byte(c16 >> 8)}
|
code = []byte{byte(c16), byte(c16 >> 8)}
|
||||||
}
|
}
|
||||||
// Make newly added relocation offsets absolute (subtract prologue to make
|
// Make newly added relocation offsets absolute (subtract prologue to make
|
||||||
// them function-relative, then the caller adds fn.Offset).
|
// them function-relative, then the caller adds fn.Offset). After
|
||||||
|
// points just past the AUIPC+second-instruction pair, which is
|
||||||
|
// always 8 bytes wide for these static-symbol references.
|
||||||
for j := preCount; j < len(relocs); j++ {
|
for j := preCount; j < len(relocs); j++ {
|
||||||
relocs[j].Off += pc - len(prologue)
|
relocs[j].Off += pc - len(prologue)
|
||||||
|
relocs[j].After = relocs[j].Off + 8
|
||||||
}
|
}
|
||||||
preCount = len(relocs)
|
preCount = len(relocs)
|
||||||
out = append(out, code...)
|
out = append(out, code...)
|
||||||
@@ -608,39 +611,37 @@ func encodeRISCVLoadImm(rd int, imm int32) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// encodeRISCVSBAddr emits AUIPC + ADDI to load the address of a static
|
// encodeRISCVSBAddr emits AUIPC + ADDI to load the address of a static
|
||||||
// symbol into rd. Records R_RISCV_PCREL_HI20 + R_RISCV_PCREL_LO12_I relocs.
|
// symbol into rd, recording the single R_RISCV_PCREL_ITYPE relocation the Go
|
||||||
|
// toolchain uses for the pair (the object-file emitters expand or map it).
|
||||||
func encodeRISCVSBAddr(sym *ast.Symbol, rd int, relocs *[]Reloc) []byte {
|
func encodeRISCVSBAddr(sym *ast.Symbol, rd int, relocs *[]Reloc) []byte {
|
||||||
name := sym.Name
|
name := sym.Name
|
||||||
if relocs != nil {
|
if relocs != nil {
|
||||||
*relocs = append(*relocs, Reloc{Off: 0, After: 0, Name: name, Kind: RelPCRelHI20})
|
*relocs = append(*relocs, Reloc{Off: 0, After: 8, Name: name, Kind: RelRISCVPCRELIType, Addend: sym.Offset})
|
||||||
*relocs = append(*relocs, Reloc{Off: 4, After: 4, Name: name, Kind: RelPCRelLO12})
|
|
||||||
}
|
}
|
||||||
auipc := riscvUType(riscvEnc{0x17, 0x0, 0x00}, rd, 0)
|
auipc := riscvUType(riscvEnc{0x17, 0x0, 0x00}, rd, 0)
|
||||||
addi := riscvIType(riscvEnc{0x13, 0x0, 0x00}, rd, rd, 0)
|
addi := riscvIType(riscvEnc{0x13, 0x0, 0x00}, rd, rd, 0)
|
||||||
return append(wordLE(auipc), wordLE(addi)...)
|
return append(wordLE(auipc), wordLE(addi)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeRISCVSBLoad emits AUIPC + LD to load from a static symbol into rd.
|
// encodeRISCVSBLoad emits AUIPC + LD to load from a static symbol into rd,
|
||||||
// Records R_RISCV_PCREL_HI20 + R_RISCV_PCREL_LO12_I relocs.
|
// recording the single R_RISCV_PCREL_ITYPE relocation for the pair.
|
||||||
func encodeRISCVSBLoad(sym *ast.Symbol, rd int, relocs *[]Reloc) []byte {
|
func encodeRISCVSBLoad(sym *ast.Symbol, rd int, relocs *[]Reloc) []byte {
|
||||||
name := sym.Name
|
name := sym.Name
|
||||||
if relocs != nil {
|
if relocs != nil {
|
||||||
*relocs = append(*relocs, Reloc{Off: 0, After: 0, Name: name, Kind: RelPCRelHI20})
|
*relocs = append(*relocs, Reloc{Off: 0, After: 8, Name: name, Kind: RelRISCVPCRELIType, Addend: sym.Offset})
|
||||||
*relocs = append(*relocs, Reloc{Off: 4, After: 4, Name: name, Kind: RelPCRelLO12})
|
|
||||||
}
|
}
|
||||||
auipc := riscvUType(riscvEnc{0x17, 0x0, 0x00}, rd, 0)
|
auipc := riscvUType(riscvEnc{0x17, 0x0, 0x00}, rd, 0)
|
||||||
ld := riscvIType(riscvEnc{0x03, 0x3, 0x00}, rd, rd, 0)
|
ld := riscvIType(riscvEnc{0x03, 0x3, 0x00}, rd, rd, 0)
|
||||||
return append(wordLE(auipc), wordLE(ld)...)
|
return append(wordLE(auipc), wordLE(ld)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeRISCVSBStore emits AUIPC + SD to store a register into a static symbol.
|
// encodeRISCVSBStore emits AUIPC + SD to store a register into a static symbol,
|
||||||
// Records R_RISCV_PCREL_HI20 + R_RISCV_PCREL_LO12_S relocs.
|
// recording the single R_RISCV_PCREL_STYPE relocation for the pair.
|
||||||
func encodeRISCVSBStore(sym *ast.Symbol, rs2 int, relocs *[]Reloc) []byte {
|
func encodeRISCVSBStore(sym *ast.Symbol, rs2 int, relocs *[]Reloc) []byte {
|
||||||
tmp := 31 // X31 = T6
|
tmp := 31 // X31 = T6
|
||||||
name := sym.Name
|
name := sym.Name
|
||||||
if relocs != nil {
|
if relocs != nil {
|
||||||
*relocs = append(*relocs, Reloc{Off: 0, After: 0, Name: name, Kind: RelPCRelHI20})
|
*relocs = append(*relocs, Reloc{Off: 0, After: 8, Name: name, Kind: RelRISCVPCRELSType, Addend: sym.Offset})
|
||||||
*relocs = append(*relocs, Reloc{Off: 4, After: 4, Name: name, Kind: RelPCRelLO12S})
|
|
||||||
}
|
}
|
||||||
auipc := riscvUType(riscvEnc{0x17, 0x0, 0x00}, tmp, 0)
|
auipc := riscvUType(riscvEnc{0x17, 0x0, 0x00}, tmp, 0)
|
||||||
sd := riscvSType(riscvEnc{0x23, 0x3, 0x00}, tmp, rs2, 0)
|
sd := riscvSType(riscvEnc{0x23, 0x3, 0x00}, tmp, rs2, 0)
|
||||||
@@ -687,8 +688,8 @@ func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) {
|
|||||||
|
|
||||||
switch mnem {
|
switch mnem {
|
||||||
case "RET":
|
case "RET":
|
||||||
// RET = JALR X0, 0(X1) → C.JR RA (CR-type: funct4=0x8, rd=0, rs2=1)
|
// RET = JALR X0, 0(X1) → C.JR RA (CR-type: funct4=0x8, rs1=ra, rs2=0).
|
||||||
return rvcCR(0x8, 0, 1), true
|
return rvcCR(0x8, 1, 0), true
|
||||||
|
|
||||||
case "LD", "MOV":
|
case "LD", "MOV":
|
||||||
// LD rd, offset(SP) → C.LDSP when rd≠0 and uimm[8:3] fits.
|
// LD rd, offset(SP) → C.LDSP when rd≠0 and uimm[8:3] fits.
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGOObjectRISCVStructure checks the emitted RISC-V object's relocations:
|
||||||
|
// each AUIPC + second-instruction pair carries a single 8-byte
|
||||||
|
// R_RISCV_PCREL_ITYPE/STYPE relocation, exactly as `go tool asm` models it
|
||||||
|
// (rather than the ELF HI20/LO12 pair).
|
||||||
|
func TestGOObjectRISCVStructure(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("k_riscv64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·sb(SB), NOSPLIT, $0-0
|
||||||
|
MOV $answer<>(SB), X10
|
||||||
|
MOV answer<>(SB), X11
|
||||||
|
MOV X12, answer<>(SB)
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL answer<>(SB), RODATA, $8
|
||||||
|
DATA answer<>+0(SB)/8, $42
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileRISCV(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||||
|
}
|
||||||
|
fn := img.Funcs[0]
|
||||||
|
if fn.Size != 26 {
|
||||||
|
t.Fatalf("function size = %d, want 26", fn.Size)
|
||||||
|
}
|
||||||
|
if len(fn.Relocs) != 3 {
|
||||||
|
t.Fatalf("relocs = %d, want 3", len(fn.Relocs))
|
||||||
|
}
|
||||||
|
wantKind := []RelocKind{RelRISCVPCRELIType, RelRISCVPCRELIType, RelRISCVPCRELSType}
|
||||||
|
wantOff := []int{0, 8, 16}
|
||||||
|
for i, r := range fn.Relocs {
|
||||||
|
if r.Kind != wantKind[i] || r.Off != wantOff[i] || r.After != r.Off+8 || r.Name != "answer" || r.Addend != 0 {
|
||||||
|
t.Errorf("reloc %d = {kind %v off %d after %d name %q addend %d}", i, r.Kind, r.Off, r.After, r.Name, r.Addend)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
obj, err := img.GOObjectRISCV("testpkg", "k_riscv64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObjectRISCV: %v", err)
|
||||||
|
}
|
||||||
|
v := openGoobj(t, obj)
|
||||||
|
|
||||||
|
// Package defs: the static GLOBL, the FuncInfo, then the two DWARF
|
||||||
|
// symbols.
|
||||||
|
defs := v.syms(blkSymdef)
|
||||||
|
if len(defs) != 4 {
|
||||||
|
t.Fatalf("symdefs = %d, want 4", len(defs))
|
||||||
|
}
|
||||||
|
if defs[0].name != "answer" || defs[0].abi != 0xffff || defs[0].typ != kindSRODATA || defs[0].size != 8 {
|
||||||
|
t.Errorf("answer symbol = %+v", defs[0])
|
||||||
|
}
|
||||||
|
if defs[2].typ != kindSDWARFLINES || defs[3].typ != kindSDWARFFCN {
|
||||||
|
t.Errorf("dwarf symbols = %+v, %+v", defs[2], defs[3])
|
||||||
|
}
|
||||||
|
|
||||||
|
// The three code relocations, in definition order: ITYPE, ITYPE, STYPE,
|
||||||
|
// each 8 bytes wide against the GLOBL (package symbol 0).
|
||||||
|
relocIdx := v.blk(blkRelocIdx)
|
||||||
|
relocs := v.blk(blkReloc)
|
||||||
|
if len(relocs) != 5*23 {
|
||||||
|
t.Fatalf("relocs = %d bytes, want 5 entries", len(relocs))
|
||||||
|
}
|
||||||
|
// The function is the last non-package symbol; its relocs start after
|
||||||
|
// the DWARF symbols' (defs 2 and 3 each carry one).
|
||||||
|
le := binary.LittleEndian
|
||||||
|
first := int(le.Uint32(relocIdx[4*(4+4):]))
|
||||||
|
wantType := []uint16{relocRISCVPcrelItype, relocRISCVPcrelItype, relocRISCVPcrelStype}
|
||||||
|
wantOffAbs := []int{0, 8, 16}
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
e := relocs[(first+i)*23:]
|
||||||
|
if int32(le.Uint32(e[0:])) != int32(wantOffAbs[i]) || e[4] != 8 || le.Uint16(e[5:]) != wantType[i] ||
|
||||||
|
le.Uint32(e[15:]) != pkgIdxSelf || le.Uint32(e[19:]) != 0 {
|
||||||
|
t.Errorf("reloc %d = off %d size %d type %d pkg %d sym %d", i, int32(le.Uint32(e[0:])), e[4], le.Uint16(e[5:]), le.Uint32(e[15:]), le.Uint32(e[19:]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The function code: three AUIPC+second-instruction pairs with zero
|
||||||
|
// immediates, then the compressed return.
|
||||||
|
code := img.Code[fn.Offset : fn.Offset+fn.Size]
|
||||||
|
want := append(wordLE(riscvUType(riscvEnc{0x17, 0x0, 0x00}, 10, 0)), wordLE(riscvIType(riscvEnc{0x13, 0x0, 0x00}, 10, 10, 0))...)
|
||||||
|
want = append(want, wordLE(riscvUType(riscvEnc{0x17, 0x0, 0x00}, 11, 0))...)
|
||||||
|
want = append(want, wordLE(riscvIType(riscvEnc{0x03, 0x3, 0x00}, 11, 11, 0))...)
|
||||||
|
want = append(want, wordLE(riscvUType(riscvEnc{0x17, 0x0, 0x00}, 31, 0))...)
|
||||||
|
want = append(want, wordLE(riscvSType(riscvEnc{0x23, 0x3, 0x00}, 31, 12, 0))...)
|
||||||
|
want = append(want, 0x82, 0x80) // C.JR ra
|
||||||
|
if !bytes.Equal(code, want) {
|
||||||
|
t.Errorf("code = % x\nwant % x", code, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The same bytes must survive into the object's data block intact: the
|
||||||
|
// linker patches only the immediate fields of the AUIPC pairs, so the
|
||||||
|
// opcode/register bits of every instruction must not be zeroed.
|
||||||
|
dataIdx := v.blk(blkDataIdx)
|
||||||
|
dataBlk := v.blk(blkData)
|
||||||
|
dOff := int(le.Uint32(dataIdx[8*4:])) // the function is the last symbol
|
||||||
|
emitted := dataBlk[dOff : dOff+fn.Size]
|
||||||
|
if !bytes.Equal(emitted, want) {
|
||||||
|
t.Errorf("emitted data = % x\nwant % x", emitted, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestGOObjectRISCVLink cross-compiles a Go program with the gasm-produced
|
||||||
|
// object substituted into the package archive, proving cmd/link accepts the
|
||||||
|
// emitted RISC-V GOOBJ. The binary is not executed (no riscv64 host or
|
||||||
|
// qemu). Skipped when no Go toolchain is available.
|
||||||
|
func TestGOObjectRISCVLink(t *testing.T) {
|
||||||
|
goBin, err := exec.LookPath("go")
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no Go toolchain available")
|
||||||
|
}
|
||||||
|
dir := t.TempDir()
|
||||||
|
asmSrc := `#include "textflag.h"
|
||||||
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
|
MOV a+0(FP), X10
|
||||||
|
MOV b+8(FP), X11
|
||||||
|
ADD X11, X10, X10
|
||||||
|
MOV X10, ret+16(FP)
|
||||||
|
RET
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main_riscv64.s"), []byte(asmSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
mainSrc := `package main
|
||||||
|
|
||||||
|
func add(a, b int64) int64
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if add(20, 22) != 42 {
|
||||||
|
panic("bad add")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "main.go"), []byte(mainSrc), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module rvlink\n\ngo 1.21\n"), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
build := exec.Command(goBin, "build", "-x", "-work", "-o", filepath.Join(dir, "prog"), ".")
|
||||||
|
build.Dir = dir
|
||||||
|
build.Env = append(os.Environ(), "GOARCH=riscv64")
|
||||||
|
buildLog, err := build.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("baseline build: %v\n%s", err, buildLog)
|
||||||
|
}
|
||||||
|
var pkgArch, work, linkLine, asmObj string
|
||||||
|
for _, line := range strings.Split(string(buildLog), "\n") {
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(line, "WORK="):
|
||||||
|
work = strings.TrimPrefix(line, "WORK=")
|
||||||
|
case strings.Contains(line, "/asm ") && strings.Contains(line, "main_riscv64.s") && !strings.Contains(line, "-gensymabis"):
|
||||||
|
asmObj = fieldAfter(line, "-o")
|
||||||
|
case strings.Contains(line, "pack r") && strings.Contains(line, "_pkg_.a"):
|
||||||
|
pkgArch = strings.TrimSpace(strings.SplitN(line, "pack r", 2)[1])
|
||||||
|
pkgArch = strings.Fields(strings.SplitN(pkgArch, "#", 2)[0])[0]
|
||||||
|
case strings.Contains(line, "/link ") && strings.Contains(line, "-importcfg"):
|
||||||
|
linkLine = line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pkgArch == "" || linkLine == "" || asmObj == "" {
|
||||||
|
t.Skip("could not locate the archive, asm output or link line in the build log")
|
||||||
|
}
|
||||||
|
pkgArch = strings.ReplaceAll(pkgArch, "$WORK", work)
|
||||||
|
asmMember := filepath.Base(strings.ReplaceAll(asmObj, "$WORK", work))
|
||||||
|
|
||||||
|
pf, perrs := parser.Parse(filepath.Join(dir, "main_riscv64.s"), asmSrc)
|
||||||
|
if len(perrs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", perrs)
|
||||||
|
}
|
||||||
|
pimg, err := AssembleFileRISCV(pf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := pimg.GOObjectRISCV("main", filepath.Join(dir, "main_riscv64.s"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObjectRISCV: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
membersDir := filepath.Join(dir, "members")
|
||||||
|
if err := os.MkdirAll(membersDir, 0o755); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
extract := exec.Command(goBin, "tool", "pack", "x", pkgArch)
|
||||||
|
extract.Dir = membersDir
|
||||||
|
extract.Env = append(os.Environ(), "GOARCH=riscv64")
|
||||||
|
if out, err := extract.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack x: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
member := filepath.Join(membersDir, asmMember)
|
||||||
|
if err := os.Chmod(member, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(member, obj, 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
listCmd := exec.Command(goBin, "tool", "pack", "t", pkgArch)
|
||||||
|
listCmd.Env = append(os.Environ(), "GOARCH=riscv64")
|
||||||
|
listOut, err := listCmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pack t: %v\n%s", err, listOut)
|
||||||
|
}
|
||||||
|
newArch := filepath.Join(dir, "pkg.a")
|
||||||
|
args := []string{"tool", "pack", "c", newArch}
|
||||||
|
seen := map[string]bool{}
|
||||||
|
for _, m := range strings.Fields(string(listOut)) {
|
||||||
|
if seen[m] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[m] = true
|
||||||
|
if err := os.Chmod(filepath.Join(membersDir, m), 0o644); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
args = append(args, filepath.Join(membersDir, m))
|
||||||
|
}
|
||||||
|
pack := exec.Command(goBin, args...)
|
||||||
|
pack.Dir = membersDir
|
||||||
|
pack.Env = append(os.Environ(), "GOARCH=riscv64")
|
||||||
|
if out, err := pack.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("pack c: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, "$WORK", work)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(work, "b001", "_pkg_.a"), newArch)
|
||||||
|
linkLine = strings.ReplaceAll(linkLine, filepath.Join(work, "b001", "exe", "a.out"), filepath.Join(dir, "app2"))
|
||||||
|
link := exec.Command("sh", "-c", linkLine)
|
||||||
|
link.Dir = dir
|
||||||
|
goExp, _ := exec.Command(goBin, "env", "GOEXPERIMENT").Output()
|
||||||
|
link.Env = append(os.Environ(), "GOEXPERIMENT="+strings.TrimSpace(string(goExp)), "GOARCH=riscv64")
|
||||||
|
if out, err := link.CombinedOutput(); err != nil {
|
||||||
|
t.Fatalf("link with gasm object: %v\n%s", err, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
nm := exec.Command(goBin, "tool", "nm", filepath.Join(dir, "app2"))
|
||||||
|
nm.Env = append(os.Environ(), "GOARCH=riscv64")
|
||||||
|
nmOut, err := nm.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("nm gasm-linked binary: %v\n%s", err, nmOut)
|
||||||
|
}
|
||||||
|
if !strings.Contains(string(nmOut), "main.add") {
|
||||||
|
t.Errorf("main.add not found in linked binary:\n%s", nmOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -299,10 +299,12 @@ boundaries, plus flat `pcfile`, `pcline` and `pcinline` tables — so a
|
|||||||
gasm-assembled object drops into a `go build` in place of the toolchain's.
|
gasm-assembled object drops into a `go build` in place of the toolchain's.
|
||||||
The object preamble (the version-and-experiment header the linker compares
|
The object preamble (the version-and-experiment header the linker compares
|
||||||
verbatim) is captured from the installed `go tool asm`, so the output is
|
verbatim) is captured from the installed `go tool asm`, so the output is
|
||||||
always consistent with the toolchain that links it. LoongArch GOOBJ emission
|
always consistent with the toolchain that links it. RISC-V and LoongArch
|
||||||
uses the same shared emitter with the loong64 marker and R_LOONG64_ADDR_HI/LO
|
GOOBJ emission share this emitter: the loong64 marker with
|
||||||
relocation types, and the emitted object links into a real `go build` for
|
R_LOONG64_ADDR_HI/LO relocation types, and the riscv64 marker with a single
|
||||||
`GOARCH=loong64`. Per function, the emitter also writes the two DWARF
|
R_RISCV_PCREL_ITYPE/STYPE relocation per AUIPC pair — the model `cmd/asm`
|
||||||
|
writes, not the ELF HI20/LO12 pair — and both link into a real `go build` for
|
||||||
|
their `GOARCH`. Per function, the emitter also writes the two DWARF
|
||||||
symbols the linker's DWARF pass reads verbatim — the subprogram DIE
|
symbols the linker's DWARF pass reads verbatim — the subprogram DIE
|
||||||
(`SDWARFFCN`) and the `.debug_line` state-machine program (`SDWARFLINES`),
|
(`SDWARFFCN`) and the `.debug_line` state-machine program (`SDWARFLINES`),
|
||||||
both built the way `cmd/asm` builds them (the DIE carries the
|
both built the way `cmd/asm` builds them (the DIE carries the
|
||||||
|
|||||||
Reference in New Issue
Block a user