feat(verify): add universal --ground-truth verification against go tool asm
Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
+58
-1
@@ -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 := ""
|
||||
|
||||
Reference in New Issue
Block a user