From 2db563be07e02fbc80a5a85f1b1c7bd2a4c97c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Mon, 14 Sep 2026 18:22:00 +0200 Subject: [PATCH] refactor(cmd): consolidate cross-arch verify and drop dead code --- cmd/gasm/debug_linux.go | 5 +- cmd/gasm/main.go | 401 +++++++++++----------------------------- 2 files changed, 106 insertions(+), 300 deletions(-) diff --git a/cmd/gasm/debug_linux.go b/cmd/gasm/debug_linux.go index b25eaf6..55fb948 100644 --- a/cmd/gasm/debug_linux.go +++ b/cmd/gasm/debug_linux.go @@ -84,7 +84,7 @@ REPL commands: if *timeout > 0 { go func() { 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) }() } @@ -140,7 +140,6 @@ REPL commands: } // Construct the argument block with buffer pointers at the correct positions. - bufIdx := 0 for _, arg := range layout { if !arg.IsPtr { continue @@ -170,12 +169,10 @@ REPL commands: argBlock[off+16+j] = byte(size >> (j * 8)) } } - bufIdx++ break } } } - _ = bufIdx } else { argBlock = make([]byte, fl.Args) sess, err = debug.Launch("", path, *funcName, argBlock) diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index 9ad6fe9..c096b55 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -1,7 +1,7 @@ // Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) // 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 // server into one binary. Every subcommand works headlessly so it can be // driven from scripts and CI as well as from an editor. @@ -81,7 +81,7 @@ func main() { case "help", "--help", "-h": usage(os.Stdout) 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) } } @@ -117,7 +117,7 @@ func usage(w io.Writer) { 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, "language server for Plan 9 assembly into one self-contained binary.\n\n") @@ -263,7 +263,7 @@ standard input. 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 } @@ -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 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 "_" directories are skipped. Explicit file paths print to stdout unless -w is given. @@ -337,7 +337,7 @@ given. } // 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. func asmFiles(dir string) ([]string, error) { var out []string @@ -419,6 +419,7 @@ hover, document symbols, diagnostics and semantic-token highlighting. `) fs.Parse(args) srv := lsp.New(os.Stdin, os.Stdout) + srv.SetVersion(version) if err := srv.Run(); err != nil { fmt.Fprintln(os.Stderr, "gasm lsp:", err) return 1 @@ -765,9 +766,11 @@ gasm verify --fuzz which exercises the code paths. 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 { +// cmdVerifyNonJIT handles the verify subcommand for files whose architecture +// the host cannot execute: only the ground-truth comparison and the static +// 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) if err != nil { 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 { return 1 } - img, err := asm.AssembleFileRISCV(f) + img, err := assembleFile(targetArch, f) if err != nil { fmt.Fprintf(os.Stderr, "gasm verify: %v\n", err) return 1 } 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 { 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) - } - } + matched, total, diffs := compareGroundTruth(img, gt) + if diffs > 0 { + printCodeDiff(img, gt) } fmt.Printf("%s: %d/%d matched\n", path, matched, total) - if matched < total { + if matched < total || diffs > 0 { return 1 } return 0 @@ -856,193 +830,74 @@ func cmdVerifyRISCV(path string, groundTruth, profile bool) int { return 0 } -// cmdVerifyLOONG64 verifies a loong64 source file against `go tool asm` -// (GOARCH=loong64) — the ground-truth oracle — since gasm cannot JIT-load -// LoongArch code on an amd64 host. Relocation sites are masked before the -// byte comparison, as the toolchain leaves them zero for the linker. -func cmdVerifyLOONG64(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.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)) +// compareGroundTruth compares the image's functions against the go tool asm +// output byte-for-byte, masking relocation sites (disp32 fields the Go linker +// fills at link time). It prints one line per function and returns the +// matched and compared counts plus the number of functions with byte diffs. +func compareGroundTruth(img *asm.Image, gt map[string][]byte) (matched, total, diffs int) { 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 { - 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.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)) +// printCodeDiff shows a 16-byte hex dump per function whose gasm bytes differ +// from the go tool asm output. +func printCodeDiff(img *asm.Image, gt map[string][]byte) { 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 { set := newCommand("verify", "gasm verify [-smoke] [-abi] [-fuzz] [-ground-truth] [-profile] [-call] ", ` Assemble FILE (amd64), map it into executable memory and report the available 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. 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 // toolchain-comparison path. 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 { - case arch.RISCV: - // RISC-V: ground-truth only (no JIT on non-RISC-V hosts). - 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.RISCV, arch.LOONG64, arch.ARM64: + return cmdVerifyNonJIT(path, targetArch, *groundTruth, *profile) case arch.AMD64: fmt.Fprintln(os.Stderr, "gasm verify: JIT-based checks need an amd64 host; use --ground-truth here") return 1 @@ -1224,50 +1074,13 @@ each entry reproduces. fmt.Fprintf(os.Stderr, "gasm verify: ground truth: %v\n", err) return 1 } - matched, total := 0, 0 - for _, name := range names { - fl, _ := k.Func(name) - gasmCode := k.Image().Code[fl.Offset : fl.Offset+fl.Size] - 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 - } + matched, total, diffs := compareGroundTruth(k.Image(), gt) + if diffs > 0 { + printCodeDiff(k.Image(), gt) + rc = 1 } fmt.Printf("ground truth: %d/%d functions byte-identical\n", matched, total) - if matched < total { + if matched < total || diffs > 0 { rc = 1 } } @@ -1289,13 +1102,11 @@ each entry reproduces. sigs := verify.ExtractSignatures(src) fuzzed := 0 for _, name := range names { - sig, ok := sigs[name] - if !ok { + if _, ok := sigs[name]; !ok { fmt.Printf(" %s: SKIP (no // func signature)\n", name) continue } - goCode, ok := gt[name] - if !ok { + if _, ok := gt[name]; !ok { fmt.Printf(" %s: SKIP (not in go tool asm output)\n", name) continue } @@ -1312,8 +1123,6 @@ each entry reproduces. rc = 1 } } - _ = sig - _ = goCode fuzzed++ } 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 // 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). if *smoke || *abi { type checkResult struct { @@ -1479,7 +1288,7 @@ func fuzzInSubprocess(path, funcName string, n int, extra ...string) string { if exitErr, ok := err.(*exec.ExitError); ok { ws := exitErr.Sys().(syscall.WaitStatus) 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. @@ -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 // 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 -// CRASH report instead of dying — the same isolation fuzzInSubprocess +// CRASH report instead of dying, the same isolation fuzzInSubprocess // provides for the fuzz sweep. func sweepInSubprocess(path, funcName string, smoke, abi bool, abiN int) (string, bool) { self, err := os.Executable() 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"} if smoke { @@ -1526,7 +1335,7 @@ func sweepInSubprocess(path, funcName string, smoke, abi bool, abiN int) (string if exitErr, ok := err.(*exec.ExitError); ok { ws, ok := exitErr.Sys().(syscall.WaitStatus) 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 @@ -1550,7 +1359,7 @@ func sweepCheckLines(out []byte) string { } // 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) { var msgs []string failed := false @@ -1559,7 +1368,7 @@ func runSweepChecks(k *verify.Kernel, path, name string, fl asm.FuncLayout, smok args := make([]byte, fl.Args) _, err := k.CallFunc(name, args) if err != nil { - msgs = append(msgs, fmt.Sprintf(" smoke: FAIL — %v", err)) + msgs = append(msgs, fmt.Sprintf(" smoke: FAIL: %v", err)) failed = true } else { 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) _, report, err := k.CallFuncChecked(name, args) if err != nil { - msgs = append(msgs, fmt.Sprintf(" abi: FAIL — %v", err)) + msgs = append(msgs, fmt.Sprintf(" abi: FAIL: %v", err)) failed = true } else if !report.OK() { 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 { out, err := k.CallFunc(funcName, args) 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 continue }