refactor(cmd): consolidate cross-arch verify and drop dead code
This commit is contained in:
@@ -84,7 +84,7 @@ REPL commands:
|
|||||||
if *timeout > 0 {
|
if *timeout > 0 {
|
||||||
go func() {
|
go func() {
|
||||||
time.Sleep(*timeout)
|
time.Sleep(*timeout)
|
||||||
fmt.Fprintf(os.Stderr, "gasm debug: timeout (%s) — killing the debuggee\n", *timeout)
|
fmt.Fprintf(os.Stderr, "gasm debug: timeout (%s), killing the debuggee\n", *timeout)
|
||||||
os.Exit(3)
|
os.Exit(3)
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
@@ -140,7 +140,6 @@ REPL commands:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Construct the argument block with buffer pointers at the correct positions.
|
// Construct the argument block with buffer pointers at the correct positions.
|
||||||
bufIdx := 0
|
|
||||||
for _, arg := range layout {
|
for _, arg := range layout {
|
||||||
if !arg.IsPtr {
|
if !arg.IsPtr {
|
||||||
continue
|
continue
|
||||||
@@ -170,12 +169,10 @@ REPL commands:
|
|||||||
argBlock[off+16+j] = byte(size >> (j * 8))
|
argBlock[off+16+j] = byte(size >> (j * 8))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bufIdx++
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = bufIdx
|
|
||||||
} else {
|
} else {
|
||||||
argBlock = make([]byte, fl.Args)
|
argBlock = make([]byte, fl.Args)
|
||||||
sess, err = debug.Launch("", path, *funcName, argBlock)
|
sess, err = debug.Launch("", path, *funcName, argBlock)
|
||||||
|
|||||||
+105
-296
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
// Command gasm is the developer frontend for GAsm — Go's Plan 9 assembler.
|
// Command gasm is the developer frontend for GAsm, Go's Plan 9 assembler.
|
||||||
// It bundles a token dumper, a parser, a formatter, a linter and a language
|
// It bundles a token dumper, a parser, a formatter, a linter and a language
|
||||||
// server into one binary. Every subcommand works headlessly so it can be
|
// server into one binary. Every subcommand works headlessly so it can be
|
||||||
// driven from scripts and CI as well as from an editor.
|
// driven from scripts and CI as well as from an editor.
|
||||||
@@ -81,7 +81,7 @@ func main() {
|
|||||||
case "help", "--help", "-h":
|
case "help", "--help", "-h":
|
||||||
usage(os.Stdout)
|
usage(os.Stdout)
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "gasm: unknown command %q — run \"gasm --help\" for usage\n", os.Args[1])
|
fmt.Fprintf(os.Stderr, "gasm: unknown command %q; run \"gasm --help\" for usage\n", os.Args[1])
|
||||||
os.Exit(2)
|
os.Exit(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +117,7 @@ func usage(w io.Writer) {
|
|||||||
bold, cyan, yellow, gray, reset = colorBold, colorCyan, colorYellow, colorGray, colorReset
|
bold, cyan, yellow, gray, reset = colorBold, colorCyan, colorYellow, colorGray, colorReset
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(w, "%sgasm %s%s — developer tooling for Go's Plan 9 assembler (GAsm)%s\n\n", bold, version, reset, reset)
|
fmt.Fprintf(w, "%sgasm %s%s: developer tooling for Go's Plan 9 assembler (GAsm)%s\n\n", bold, version, reset, reset)
|
||||||
fmt.Fprintf(w, "gasm bundles a lexer, parser, formatter, linter, standalone assembler and\n")
|
fmt.Fprintf(w, "gasm bundles a lexer, parser, formatter, linter, standalone assembler and\n")
|
||||||
fmt.Fprintf(w, "language server for Plan 9 assembly into one self-contained binary.\n\n")
|
fmt.Fprintf(w, "language server for Plan 9 assembly into one self-contained binary.\n\n")
|
||||||
|
|
||||||
@@ -263,7 +263,7 @@ standard input.
|
|||||||
funcs++
|
funcs++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf("%s: OK — %d declarations, %d functions\n", path, len(file.Decls), funcs)
|
fmt.Printf("%s: OK, %d declarations, %d functions\n", path, len(file.Decls), funcs)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +274,7 @@ spacing, per-function mnemonic alignment and blank-line layout (exactly one
|
|||||||
blank line before each label, TEXT and GLOBL block). Formatting is
|
blank line before each label, TEXT and GLOBL block). Formatting is
|
||||||
idempotent and preserves every line, comments included.
|
idempotent and preserves every line, comments included.
|
||||||
|
|
||||||
With no paths — or a directory path — every .s file below it is reformatted
|
With no paths, or a directory path, every .s file below it is reformatted
|
||||||
in place and the changed files are listed, the way go fmt does; "." and "_"
|
in place and the changed files are listed, the way go fmt does; "." and "_"
|
||||||
directories are skipped. Explicit file paths print to stdout unless -w is
|
directories are skipped. Explicit file paths print to stdout unless -w is
|
||||||
given.
|
given.
|
||||||
@@ -337,7 +337,7 @@ given.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// asmFiles collects the .s files below dir, skipping directories whose name
|
// asmFiles collects the .s files below dir, skipping directories whose name
|
||||||
// starts with "." or "_" — as the go tooling does, which keeps .git and
|
// starts with "." or "_", as the go tooling does, which keeps .git and
|
||||||
// scratch or reference trees (e.g. _refs) untouched.
|
// scratch or reference trees (e.g. _refs) untouched.
|
||||||
func asmFiles(dir string) ([]string, error) {
|
func asmFiles(dir string) ([]string, error) {
|
||||||
var out []string
|
var out []string
|
||||||
@@ -419,6 +419,7 @@ hover, document symbols, diagnostics and semantic-token highlighting.
|
|||||||
`)
|
`)
|
||||||
fs.Parse(args)
|
fs.Parse(args)
|
||||||
srv := lsp.New(os.Stdin, os.Stdout)
|
srv := lsp.New(os.Stdin, os.Stdout)
|
||||||
|
srv.SetVersion(version)
|
||||||
if err := srv.Run(); err != nil {
|
if err := srv.Run(); err != nil {
|
||||||
fmt.Fprintln(os.Stderr, "gasm lsp:", err)
|
fmt.Fprintln(os.Stderr, "gasm lsp:", err)
|
||||||
return 1
|
return 1
|
||||||
@@ -765,9 +766,11 @@ gasm verify --fuzz which exercises the code paths.
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdVerifyRISCV handles the verify subcommand for RISC-V files.
|
// cmdVerifyNonJIT handles the verify subcommand for files whose architecture
|
||||||
// JIT requires RISC-V hardware; only ground-truth and profile are available.
|
// the host cannot execute: only the ground-truth comparison and the static
|
||||||
func cmdVerifyRISCV(path string, groundTruth, profile bool) int {
|
// profile are available there. Relocation sites are masked before the byte
|
||||||
|
// comparison, as the toolchain leaves them zero for the linker.
|
||||||
|
func cmdVerifyNonJIT(path string, targetArch arch.Arch, groundTruth, profile bool) int {
|
||||||
src, err := readSource(path)
|
src, err := readSource(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||||
@@ -780,63 +783,34 @@ func cmdVerifyRISCV(path string, groundTruth, profile bool) int {
|
|||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
img, err := asm.AssembleFileRISCV(f)
|
img, err := assembleFile(targetArch, f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if groundTruth {
|
if groundTruth {
|
||||||
gt, err := verify.GroundTruthRISCV(path)
|
var gt map[string][]byte
|
||||||
|
switch targetArch {
|
||||||
|
case arch.RISCV:
|
||||||
|
gt, err = verify.GroundTruthRISCV(path)
|
||||||
|
case arch.LOONG64:
|
||||||
|
gt, err = verify.GroundTruthLOONG64(path)
|
||||||
|
case arch.ARM64:
|
||||||
|
gt, err = verify.GroundTruthARM64(path)
|
||||||
|
default:
|
||||||
|
gt, err = verify.GroundTruth(path)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "gasm verify: ground truth: %v\n", err)
|
fmt.Fprintf(os.Stderr, "gasm verify: ground truth: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
matched, total := 0, 0
|
matched, total, diffs := compareGroundTruth(img, gt)
|
||||||
for _, fn := range img.Funcs {
|
if diffs > 0 {
|
||||||
gasmCode := img.Code[fn.Offset : fn.Offset+fn.Size]
|
printCodeDiff(img, gt)
|
||||||
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)
|
fmt.Printf("%s: %d/%d matched\n", path, matched, total)
|
||||||
if matched < total {
|
if matched < total || diffs > 0 {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
@@ -856,193 +830,74 @@ func cmdVerifyRISCV(path string, groundTruth, profile bool) int {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdVerifyLOONG64 verifies a loong64 source file against `go tool asm`
|
// compareGroundTruth compares the image's functions against the go tool asm
|
||||||
// (GOARCH=loong64) — the ground-truth oracle — since gasm cannot JIT-load
|
// output byte-for-byte, masking relocation sites (disp32 fields the Go linker
|
||||||
// LoongArch code on an amd64 host. Relocation sites are masked before the
|
// fills at link time). It prints one line per function and returns the
|
||||||
// byte comparison, as the toolchain leaves them zero for the linker.
|
// matched and compared counts plus the number of functions with byte diffs.
|
||||||
func cmdVerifyLOONG64(path string, groundTruth, profile bool) int {
|
func compareGroundTruth(img *asm.Image, gt map[string][]byte) (matched, total, diffs 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.AssembleFileLOONG64(f)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if groundTruth {
|
|
||||||
gt, err := verify.GroundTruthLOONG64(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 {
|
for _, fn := range img.Funcs {
|
||||||
fmt.Printf(" %s: %d bytes\n", fn.Name, fn.Size)
|
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 {
|
||||||
|
diffs++
|
||||||
|
fmt.Printf(" %s: MISMATCH (%d vs %d bytes)\n", fn.Name, fn.Size, len(goCode))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return matched, total, diffs
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdVerifyARM64(path string, groundTruth, profile bool) int {
|
// printCodeDiff shows a 16-byte hex dump per function whose gasm bytes differ
|
||||||
src, err := readSource(path)
|
// from the go tool asm output.
|
||||||
if err != nil {
|
func printCodeDiff(img *asm.Image, gt map[string][]byte) {
|
||||||
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.AssembleFileARM64(f)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err)
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if groundTruth {
|
|
||||||
gt, err := verify.GroundTruthARM64(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 {
|
for _, fn := range img.Funcs {
|
||||||
fmt.Printf(" %s: %d bytes\n", fn.Name, fn.Size)
|
gasmCode := img.Code[fn.Offset : fn.Offset+fn.Size]
|
||||||
|
goCode, ok := gt[fn.Name]
|
||||||
|
if !ok || bytes.Equal(gasmCode, goCode) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func cmdVerify(args []string) int {
|
func cmdVerify(args []string) int {
|
||||||
set := newCommand("verify", "gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] <file.s>", `
|
set := newCommand("verify", "gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] <file.s>", `
|
||||||
Assemble FILE (amd64), map it into executable memory and report the available
|
Assemble FILE (amd64), map it into executable memory and report the available
|
||||||
functions. This confirms the assembled image is self-consistent (no
|
functions. This confirms the assembled image is self-consistent (no
|
||||||
unresolved external symbols) and executable — the prerequisite for dynamic
|
unresolved external symbols) and executable, the prerequisite for dynamic
|
||||||
testing.
|
testing.
|
||||||
|
|
||||||
With -smoke, each NOSPLIT function is called with a zeroed argument block to
|
With -smoke, each NOSPLIT function is called with a zeroed argument block to
|
||||||
@@ -1100,17 +955,12 @@ each entry reproduces.
|
|||||||
// under the available loong64 emulators), so those kernels take the
|
// under the available loong64 emulators), so those kernels take the
|
||||||
// toolchain-comparison path.
|
// toolchain-comparison path.
|
||||||
if targetArch != hostArch() || targetArch == arch.LOONG64 {
|
if targetArch != hostArch() || targetArch == arch.LOONG64 {
|
||||||
|
// No JIT on this host: ground truth and profile remain available.
|
||||||
|
// (loong64 is ground-truth-only everywhere for now: its trampoline
|
||||||
|
// is implemented but not yet validated against real hardware.)
|
||||||
switch targetArch {
|
switch targetArch {
|
||||||
case arch.RISCV:
|
case arch.RISCV, arch.LOONG64, arch.ARM64:
|
||||||
// RISC-V: ground-truth only (no JIT on non-RISC-V hosts).
|
return cmdVerifyNonJIT(path, targetArch, *groundTruth, *profile)
|
||||||
return cmdVerifyRISCV(path, *groundTruth, *profile)
|
|
||||||
case arch.LOONG64:
|
|
||||||
// LoongArch: ground-truth only (trampoline not yet
|
|
||||||
// hardware-validated).
|
|
||||||
return cmdVerifyLOONG64(path, *groundTruth, *profile)
|
|
||||||
case arch.ARM64:
|
|
||||||
// AArch64: ground-truth only (no JIT on non-ARM64 hosts).
|
|
||||||
return cmdVerifyARM64(path, *groundTruth, *profile)
|
|
||||||
case arch.AMD64:
|
case arch.AMD64:
|
||||||
fmt.Fprintln(os.Stderr, "gasm verify: JIT-based checks need an amd64 host; use --ground-truth here")
|
fmt.Fprintln(os.Stderr, "gasm verify: JIT-based checks need an amd64 host; use --ground-truth here")
|
||||||
return 1
|
return 1
|
||||||
@@ -1224,50 +1074,13 @@ each entry reproduces.
|
|||||||
fmt.Fprintf(os.Stderr, "gasm verify: ground truth: %v\n", err)
|
fmt.Fprintf(os.Stderr, "gasm verify: ground truth: %v\n", err)
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
matched, total := 0, 0
|
matched, total, diffs := compareGroundTruth(k.Image(), gt)
|
||||||
for _, name := range names {
|
if diffs > 0 {
|
||||||
fl, _ := k.Func(name)
|
printCodeDiff(k.Image(), gt)
|
||||||
gasmCode := k.Image().Code[fl.Offset : fl.Offset+fl.Size]
|
rc = 1
|
||||||
goCode, ok := gt[name]
|
|
||||||
if !ok {
|
|
||||||
fmt.Printf(" %s: SKIP (not in go tool asm output)\n", name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
total++
|
|
||||||
// Compare, masking relocation sites (disp32 fields that the
|
|
||||||
// Go linker fills at link time — gasm resolves them internally).
|
|
||||||
gasmCmp := make([]byte, len(gasmCode))
|
|
||||||
goCmp := make([]byte, len(goCode))
|
|
||||||
copy(gasmCmp, gasmCode)
|
|
||||||
copy(goCmp, goCode)
|
|
||||||
for _, r := range fl.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(fl.Relocs) > 0 {
|
|
||||||
fmt.Printf(" %s: MATCH (%d bytes, %d relocs masked)\n", name, fl.Size, len(fl.Relocs))
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" %s: MATCH (%d bytes)\n", name, fl.Size)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Printf(" %s: MISMATCH (gasm %d bytes, go %d bytes)\n", name, fl.Size, len(goCode))
|
|
||||||
for i := 0; i < len(gasmCmp) && i < len(goCmp); i++ {
|
|
||||||
if gasmCmp[i] != goCmp[i] {
|
|
||||||
fmt.Printf(" first diff at byte %d: gasm=%02x go=%02x\n", i, gasmCmp[i], goCmp[i])
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rc = 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fmt.Printf("ground truth: %d/%d functions byte-identical\n", matched, total)
|
fmt.Printf("ground truth: %d/%d functions byte-identical\n", matched, total)
|
||||||
if matched < total {
|
if matched < total || diffs > 0 {
|
||||||
rc = 1
|
rc = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1289,13 +1102,11 @@ each entry reproduces.
|
|||||||
sigs := verify.ExtractSignatures(src)
|
sigs := verify.ExtractSignatures(src)
|
||||||
fuzzed := 0
|
fuzzed := 0
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
sig, ok := sigs[name]
|
if _, ok := sigs[name]; !ok {
|
||||||
if !ok {
|
|
||||||
fmt.Printf(" %s: SKIP (no // func signature)\n", name)
|
fmt.Printf(" %s: SKIP (no // func signature)\n", name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
goCode, ok := gt[name]
|
if _, ok := gt[name]; !ok {
|
||||||
if !ok {
|
|
||||||
fmt.Printf(" %s: SKIP (not in go tool asm output)\n", name)
|
fmt.Printf(" %s: SKIP (not in go tool asm output)\n", name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1312,8 +1123,6 @@ each entry reproduces.
|
|||||||
rc = 1
|
rc = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ = sig
|
|
||||||
_ = goCode
|
|
||||||
fuzzed++
|
fuzzed++
|
||||||
}
|
}
|
||||||
fmt.Printf("fuzz: %d functions tested, %d iterations each\n", fuzzed, *fuzzN)
|
fmt.Printf("fuzz: %d functions tested, %d iterations each\n", fuzzed, *fuzzN)
|
||||||
@@ -1338,7 +1147,7 @@ each entry reproduces.
|
|||||||
|
|
||||||
// Run smoke and ABI checks in parallel, each function in its own child
|
// Run smoke and ABI checks in parallel, each function in its own child
|
||||||
// process: the JIT'd code runs with zeroed or fuzzed arguments, and a
|
// process: the JIT'd code runs with zeroed or fuzzed arguments, and a
|
||||||
// function that dereferences them faults — the crash is reported as a
|
// function that dereferences them faults, the crash is reported as a
|
||||||
// CRASH line instead of killing this process (mirrors fuzzInSubprocess).
|
// CRASH line instead of killing this process (mirrors fuzzInSubprocess).
|
||||||
if *smoke || *abi {
|
if *smoke || *abi {
|
||||||
type checkResult struct {
|
type checkResult struct {
|
||||||
@@ -1479,7 +1288,7 @@ func fuzzInSubprocess(path, funcName string, n int, extra ...string) string {
|
|||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
ws := exitErr.Sys().(syscall.WaitStatus)
|
ws := exitErr.Sys().(syscall.WaitStatus)
|
||||||
if ws.Signaled() {
|
if ws.Signaled() {
|
||||||
return fmt.Sprintf("%s: CRASH (%v — partial function, use --ground-truth)", funcName, ws.Signal())
|
return fmt.Sprintf("%s: CRASH (%v; partial function, use --ground-truth)", funcName, ws.Signal())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Non-zero exit without a signal: the fuzz reported mismatches.
|
// Non-zero exit without a signal: the fuzz reported mismatches.
|
||||||
@@ -1504,12 +1313,12 @@ func fuzzInSubprocess(path, funcName string, n int, extra ...string) string {
|
|||||||
// sweepInSubprocess runs the smoke/abi checks for a single function in a
|
// sweepInSubprocess runs the smoke/abi checks for a single function in a
|
||||||
// child process. If the child is killed by a signal (e.g. SIGSEGV from a
|
// child process. If the child is killed by a signal (e.g. SIGSEGV from a
|
||||||
// function that dereferences its zeroed or fuzzed arguments), it returns a
|
// function that dereferences its zeroed or fuzzed arguments), it returns a
|
||||||
// CRASH report instead of dying — the same isolation fuzzInSubprocess
|
// CRASH report instead of dying, the same isolation fuzzInSubprocess
|
||||||
// provides for the fuzz sweep.
|
// provides for the fuzz sweep.
|
||||||
func sweepInSubprocess(path, funcName string, smoke, abi bool, abiN int) (string, bool) {
|
func sweepInSubprocess(path, funcName string, smoke, abi bool, abiN int) (string, bool) {
|
||||||
self, err := os.Executable()
|
self, err := os.Executable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Sprintf(" smoke/abi: FAIL — cannot find self: %v", err), true
|
return fmt.Sprintf(" smoke/abi: FAIL: cannot find self: %v", err), true
|
||||||
}
|
}
|
||||||
args := []string{"verify"}
|
args := []string{"verify"}
|
||||||
if smoke {
|
if smoke {
|
||||||
@@ -1526,7 +1335,7 @@ func sweepInSubprocess(path, funcName string, smoke, abi bool, abiN int) (string
|
|||||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||||
ws, ok := exitErr.Sys().(syscall.WaitStatus)
|
ws, ok := exitErr.Sys().(syscall.WaitStatus)
|
||||||
if ok && ws.Signaled() {
|
if ok && ws.Signaled() {
|
||||||
return fmt.Sprintf(" smoke/abi: CRASH (%v — the function faults on zeroed or fuzzed\n arguments; verify it with -call and valid buffers)", ws.Signal()), true
|
return fmt.Sprintf(" smoke/abi: CRASH (%v: the function faults on zeroed or fuzzed\n arguments; verify it with -call and valid buffers)", ws.Signal()), true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Non-zero exit without a signal: the checks themselves failed and
|
// Non-zero exit without a signal: the checks themselves failed and
|
||||||
@@ -1550,7 +1359,7 @@ func sweepCheckLines(out []byte) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// runSweepChecks performs the in-process smoke and ABI checks for one
|
// runSweepChecks performs the in-process smoke and ABI checks for one
|
||||||
// function — the child half of sweepInSubprocess.
|
// function, the child half of sweepInSubprocess.
|
||||||
func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smoke, abi bool, abiN int) ([]string, bool) {
|
func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smoke, abi bool, abiN int) ([]string, bool) {
|
||||||
var msgs []string
|
var msgs []string
|
||||||
failed := false
|
failed := false
|
||||||
@@ -1559,7 +1368,7 @@ func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smok
|
|||||||
args := make([]byte, fl.Args)
|
args := make([]byte, fl.Args)
|
||||||
_, err := k.CallFunc(name, args)
|
_, err := k.CallFunc(name, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msgs = append(msgs, fmt.Sprintf(" smoke: FAIL — %v", err))
|
msgs = append(msgs, fmt.Sprintf(" smoke: FAIL: %v", err))
|
||||||
failed = true
|
failed = true
|
||||||
} else {
|
} else {
|
||||||
msgs = append(msgs, " smoke: OK")
|
msgs = append(msgs, " smoke: OK")
|
||||||
@@ -1579,7 +1388,7 @@ func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smok
|
|||||||
args := make([]byte, fl.Args)
|
args := make([]byte, fl.Args)
|
||||||
_, report, err := k.CallFuncChecked(name, args)
|
_, report, err := k.CallFuncChecked(name, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msgs = append(msgs, fmt.Sprintf(" abi: FAIL — %v", err))
|
msgs = append(msgs, fmt.Sprintf(" abi: FAIL: %v", err))
|
||||||
failed = true
|
failed = true
|
||||||
} else if !report.OK() {
|
} else if !report.OK() {
|
||||||
msgs = append(msgs, fmt.Sprintf(" abi: %s", report))
|
msgs = append(msgs, fmt.Sprintf(" abi: %s", report))
|
||||||
@@ -1669,7 +1478,7 @@ func cmdVerifyCall(k *verify.Kernel, path, funcName, bufSpec, scalarSpec string,
|
|||||||
for i := range repeat {
|
for i := range repeat {
|
||||||
out, err := k.CallFunc(funcName, args)
|
out, err := k.CallFunc(funcName, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf(" call %d: FAIL — %v\n", i+1, err)
|
fmt.Printf(" call %d: FAIL: %v\n", i+1, err)
|
||||||
rc = 1
|
rc = 1
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user