feat(asm): add RISC-V ELF relocatable object emission and SB relocation

support
This commit is contained in:
2026-08-03 08:51:00 +02:00
parent 7721353d44
commit f41a86b660
8 changed files with 824 additions and 43 deletions
+107 -6
View File
@@ -88,8 +88,8 @@ Commands:
parse parse and report syntax errors
fmt canonicalise formatting (gofmt for assembly)
lint run static checks
asm assemble .s files to machine code (amd64)
verify JIT-assemble and run dynamic checks (amd64)
asm assemble .s files to machine code (amd64, riscv64)
verify JIT-assemble and run dynamic checks (amd64, riscv64)
lsp run the language server over stdio
version print the version (same as --version)
@@ -352,7 +352,7 @@ hover, document symbols, diagnostics and semantic-token highlighting.
func cmdAsm(args []string) int {
fs := newCommand("asm", "gasm asm [--format raw|elf|macho|goobj] [-p pkg] [-o out] <file>", `
Assemble FILE (amd64) without the Go toolchain: every TEXT function is
Assemble FILE (amd64 or riscv64) without the Go toolchain: every TEXT function is
encoded to machine code — scalar, VEX/AVX2 and EVEX/AVX-512 instructions,
FP/SP frame mapping, local labels and file-local static symbols (GLOBL/DATA)
resolved RIP-relative — and printed as a hex dump.
@@ -458,7 +458,11 @@ requires -p, the package path, and the installed Go toolchain).
}
obj, kind = img.Bytes(), "raw image"
case "elf":
obj, err = img.ELFObject()
if targetArch == arch.RISCV {
obj, err = img.ELFRISCVObject()
} else {
obj, err = img.ELFObject()
}
kind = "ELF object"
case "macho":
obj, err = img.MachOObject()
@@ -483,6 +487,97 @@ requires -p, the package path, and the installed Go toolchain).
return 0
}
// cmdVerifyRISCV handles the verify subcommand for RISC-V files.
// JIT requires RISC-V hardware; only ground-truth and profile are available.
func cmdVerifyRISCV(path string, groundTruth, profile bool) int {
src, err := readSource(path)
if err != nil {
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
return 1
}
f, errs := parser.Parse(path, src)
for _, e := range errs {
fmt.Fprintf(os.Stderr, "%s: %v\n", path, e)
}
if len(errs) > 0 {
return 1
}
img, err := asm.AssembleFileRISCV(f)
if err != nil {
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
return 1
}
if groundTruth {
gt, err := verify.GroundTruthRISCV(path)
if err != nil {
fmt.Fprintf(os.Stderr, "gasm verify: ground truth: %v\n", err)
return 1
}
matched, total := 0, 0
for _, fn := range img.Funcs {
gasmCode := img.Code[fn.Offset : fn.Offset+fn.Size]
goCode, ok := gt[fn.Name]
if !ok {
fmt.Printf(" %s: SKIP (not in go tool asm output)\n", fn.Name)
continue
}
total++
gasmCmp := make([]byte, len(gasmCode))
goCmp := make([]byte, len(goCode))
copy(gasmCmp, gasmCode)
copy(goCmp, goCode)
for _, r := range fn.Relocs {
for j := r.Off; j < r.Off+4 && j < len(gasmCmp); j++ {
gasmCmp[j] = 0
}
for j := r.Off; j < r.Off+4 && j < len(goCmp); j++ {
goCmp[j] = 0
}
}
if bytes.Equal(gasmCmp, goCmp) {
matched++
if len(fn.Relocs) > 0 {
fmt.Printf(" %s: MATCH (%d bytes, %d relocs masked)\n", fn.Name, fn.Size, len(fn.Relocs))
} else {
fmt.Printf(" %s: MATCH (%d bytes)\n", fn.Name, fn.Size)
}
} else {
fmt.Printf(" %s: MISMATCH (%d vs %d bytes)\n", fn.Name, fn.Size, len(goCode))
for i := 0; i < len(gasmCode) || i < len(goCode); i += 16 {
var gb, gs string
for j := i; j < i+16 && j < len(gasmCode); j++ {
gb += fmt.Sprintf(" %02x", gasmCode[j])
}
for j := i; j < i+16 && j < len(goCode); j++ {
gs += fmt.Sprintf(" %02x", goCode[j])
}
fmt.Printf(" %04x: gasm:%s\n", i, gb)
fmt.Printf(" %04x: gt: %s\n", i, gs)
}
}
}
fmt.Printf("%s: %d/%d matched\n", path, matched, total)
if matched < total {
return 1
}
return 0
}
if profile {
for _, fn := range img.Funcs {
fmt.Printf("%s: %d bytes, labels: %v\n", fn.Name, fn.Size, fn.Labels)
}
return 0
}
fmt.Printf("%s: %d functions assembled\n", path, len(img.Funcs))
for _, fn := range img.Funcs {
fmt.Printf(" %s: %d bytes\n", fn.Name, fn.Size)
}
return 0
}
func cmdVerify(args []string) int {
fs := newCommand("verify", "gasm verify [-smoke] [-abi] [-profile] <file.s>", `
Assemble FILE (amd64), map it into executable memory and report the available
@@ -512,11 +607,17 @@ With -profile, the static basic-block structure is listed for each function.
return 2
}
path := fs.Arg(0)
if arch.FromFilename(path) != arch.AMD64 {
fmt.Fprintln(os.Stderr, "gasm verify: only amd64 is supported")
targetArch := arch.FromFilename(path)
if targetArch != arch.AMD64 && targetArch != arch.RISCV {
fmt.Fprintln(os.Stderr, "gasm verify: only amd64 and riscv64 are supported")
return 1
}
// RISC-V: ground-truth only (no JIT on non-RISC-V hosts).
if targetArch == arch.RISCV {
return cmdVerifyRISCV(path, *groundTruth, *profile)
}
k, err := verify.Load(path)
if err != nil {
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)