feat(verify): add universal --ground-truth verification against go tool asm

Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
2026-08-02 23:11:30 +02:00
parent f8d28a42ba
commit 382efe538a
5 changed files with 314 additions and 2 deletions
+58 -1
View File
@@ -8,6 +8,7 @@
package main
import (
"bytes"
"flag"
"fmt"
"io"
@@ -29,7 +30,7 @@ import (
// version is the release version, stamped at build time via
// -ldflags "-X main.version=…" (defaulting to the current release).
var version = "0.24.0"
var version = "0.25.0"
func main() {
if len(os.Args) < 2 {
@@ -490,6 +491,7 @@ With -profile, the static basic-block structure is listed for each function.
smoke := fs.Bool("smoke", false, "call each NOSPLIT function with zeroed args")
abi := fs.Bool("abi", false, "run ABI-checking calls (sentinel registers + red zone)")
profile := fs.Bool("profile", false, "list basic-block structure per function")
groundTruth := fs.Bool("ground-truth", false, "compare machine code byte-for-byte against go tool asm")
fs.Parse(args)
if fs.NArg() != 1 {
fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] [-abi] [-profile] <file.s>")
@@ -511,6 +513,61 @@ With -profile, the static basic-block structure is listed for each function.
names := k.FuncNames()
fmt.Printf("%s: %d functions JIT-loaded\n", path, len(names))
rc := 0
// Ground-truth comparison: assemble with go tool asm and compare bytes.
if *groundTruth {
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 _, 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
}
}
fmt.Printf("ground truth: %d/%d functions byte-identical\n", matched, total)
if matched < total {
rc = 1
}
}
for _, name := range names {
fl, _ := k.Func(name)
flags := ""