feat(cli): add gasm diff command for comparing assembly encodings
Assisted-by: DeepSeek V4 Pro
This commit is contained in:
@@ -55,6 +55,8 @@ func main() {
|
||||
os.Exit(cmdVerify(os.Args[2:]))
|
||||
case "debug":
|
||||
os.Exit(cmdDebug(os.Args[2:]))
|
||||
case "diff":
|
||||
os.Exit(cmdDiff(os.Args[2:]))
|
||||
case "lsp":
|
||||
os.Exit(cmdLSP(os.Args[2:]))
|
||||
case "version", "--version", "-V":
|
||||
@@ -115,6 +117,7 @@ func usage(w io.Writer) {
|
||||
{"asm", "assemble .s files to machine code (amd64, riscv64)"},
|
||||
{"verify", "JIT-assemble and run dynamic checks (amd64, riscv64)"},
|
||||
{"debug", "interactive source-level debugger (amd64)"},
|
||||
{"diff", "compare machine code of two .s files"},
|
||||
{"lsp", "run the language server over stdio"},
|
||||
{"version", "print the version (same as --version)"},
|
||||
}
|
||||
@@ -528,6 +531,112 @@ requires -p, the package path, and the installed Go toolchain).
|
||||
return 0
|
||||
}
|
||||
|
||||
// cmdDiff compares the machine code of two assembly files.
|
||||
func cmdDiff(args []string) int {
|
||||
fs := newCommand("diff", "gasm diff <file1.s> <file2.s>", `
|
||||
Compare the machine code produced by assembling two files.
|
||||
Shows which functions differ and the byte-level differences.
|
||||
Useful for verifying that two implementations produce identical code,
|
||||
or for tracking encoding changes between Go assembler versions.
|
||||
`)
|
||||
fs.Parse(args)
|
||||
if fs.NArg() != 2 {
|
||||
fmt.Fprintln(os.Stderr, "usage: gasm diff <file1.s> <file2.s>")
|
||||
return 2
|
||||
}
|
||||
path1, path2 := fs.Arg(0), fs.Arg(1)
|
||||
|
||||
// Assemble both files.
|
||||
img1, err := assembleFile(path1)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gasm diff: %s: %v\n", path1, err)
|
||||
return 1
|
||||
}
|
||||
img2, err := assembleFile(path2)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "gasm diff: %s: %v\n", path2, err)
|
||||
return 1
|
||||
}
|
||||
|
||||
// Compare functions by name.
|
||||
funcs1 := make(map[string][]byte)
|
||||
for _, fn := range img1.Funcs {
|
||||
funcs1[fn.Name] = img1.Code[fn.Offset : fn.Offset+fn.Size]
|
||||
}
|
||||
funcs2 := make(map[string][]byte)
|
||||
for _, fn := range img2.Funcs {
|
||||
funcs2[fn.Name] = img2.Code[fn.Offset : fn.Offset+fn.Size]
|
||||
}
|
||||
|
||||
// Find common functions and compare.
|
||||
diffs := 0
|
||||
for name, code1 := range funcs1 {
|
||||
code2, ok := funcs2[name]
|
||||
if !ok {
|
||||
fmt.Printf("%s: only in %s\n", name, path1)
|
||||
diffs++
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(code1, code2) {
|
||||
fmt.Printf("%s: DIFFERS (%d vs %d bytes)\n", name, len(code1), len(code2))
|
||||
printByteDiff(code1, code2)
|
||||
diffs++
|
||||
} else {
|
||||
fmt.Printf("%s: identical (%d bytes)\n", name, len(code1))
|
||||
}
|
||||
}
|
||||
for name := range funcs2 {
|
||||
if _, ok := funcs1[name]; !ok {
|
||||
fmt.Printf("%s: only in %s\n", name, path2)
|
||||
diffs++
|
||||
}
|
||||
}
|
||||
|
||||
if diffs == 0 {
|
||||
fmt.Println("all functions identical")
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// assembleFile assembles a file and returns the image.
|
||||
func assembleFile(path string) (*asm.Image, error) {
|
||||
src, err := readSource(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, errs := parser.Parse(path, src)
|
||||
for _, e := range errs {
|
||||
fmt.Fprintf(os.Stderr, "%s: %v\n", path, e)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, fmt.Errorf("parse errors")
|
||||
}
|
||||
targetArch := arch.FromFilename(path)
|
||||
if targetArch == arch.RISCV {
|
||||
return asm.AssembleFileRISCV(f)
|
||||
}
|
||||
return asm.AssembleFile(f)
|
||||
}
|
||||
|
||||
// printByteDiff shows the first few byte differences between two code blocks.
|
||||
func printByteDiff(a, b []byte) {
|
||||
maxLen := len(a)
|
||||
if len(b) < maxLen {
|
||||
maxLen = len(b)
|
||||
}
|
||||
shown := 0
|
||||
for i := 0; i < maxLen && shown < 8; i++ {
|
||||
if a[i] != b[i] {
|
||||
fmt.Printf(" offset %#04x: %02x vs %02x\n", i, a[i], b[i])
|
||||
shown++
|
||||
}
|
||||
}
|
||||
if len(a) != len(b) {
|
||||
fmt.Printf(" length: %d vs %d\n", len(a), len(b))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
||||
Reference in New Issue
Block a user