refactor(cmd): consolidate cross-arch verify and drop dead code

This commit is contained in:
2026-09-14 18:22:00 +02:00
parent 4f190ee1a2
commit 2db563be07
2 changed files with 106 additions and 300 deletions
+1 -4
View File
@@ -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
View File
@@ -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
} }