feat(asm): add RISC-V ELF relocatable object emission and SB relocation
support
This commit is contained in:
+107
-6
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user