feat(cli): add gasm profile command for basic-block structure

Assisted-by: DeepSeek V4 Pro
This commit is contained in:
2026-08-05 16:41:00 +02:00
parent c0e280ee3c
commit 6228d77566
+65
View File
@@ -16,6 +16,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"sort"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
@@ -57,6 +58,8 @@ func main() {
os.Exit(cmdDebug(os.Args[2:])) os.Exit(cmdDebug(os.Args[2:]))
case "diff": case "diff":
os.Exit(cmdDiff(os.Args[2:])) os.Exit(cmdDiff(os.Args[2:]))
case "profile":
os.Exit(cmdProfile(os.Args[2:]))
case "lsp": case "lsp":
os.Exit(cmdLSP(os.Args[2:])) os.Exit(cmdLSP(os.Args[2:]))
case "version", "--version", "-V": case "version", "--version", "-V":
@@ -118,6 +121,7 @@ func usage(w io.Writer) {
{"verify", "JIT-assemble and run dynamic checks (amd64, riscv64)"}, {"verify", "JIT-assemble and run dynamic checks (amd64, riscv64)"},
{"debug", "interactive source-level debugger (amd64)"}, {"debug", "interactive source-level debugger (amd64)"},
{"diff", "compare machine code of two .s files"}, {"diff", "compare machine code of two .s files"},
{"profile", "show basic-block structure of functions"},
{"lsp", "run the language server over stdio"}, {"lsp", "run the language server over stdio"},
{"version", "print the version (same as --version)"}, {"version", "print the version (same as --version)"},
} }
@@ -637,6 +641,67 @@ func printByteDiff(a, b []byte) {
} }
} }
// cmdProfile shows the basic-block structure of functions in an assembly file.
func cmdProfile(args []string) int {
fs := newCommand("profile", "gasm profile <file.s>", `
Show the basic-block structure of functions in an assembly file.
Lists each function's labels, their offsets, and the block boundaries.
This is the static structure; for runtime execution counts, use
gasm verify --fuzz which exercises the code paths.
`)
fs.Parse(args)
if fs.NArg() != 1 {
fmt.Fprintln(os.Stderr, "usage: gasm profile <file.s>")
return 2
}
path := fs.Arg(0)
// Load the file to get function metadata.
k, err := verify.Load(path)
if err != nil {
fmt.Fprintf(os.Stderr, "gasm profile: %v\n", err)
return 1
}
defer k.Close()
for _, name := range k.FuncNames() {
fl, err := k.Func(name)
if err != nil {
continue
}
fmt.Printf("%s: %d bytes, args=%d, frame=%d", name, fl.Size, fl.Args, fl.Frame)
if fl.NoSplit {
fmt.Printf(" NOSPLIT")
}
fmt.Println()
// Show labels and their offsets.
if len(fl.Labels) > 0 {
fmt.Println(" labels:")
// Sort labels by offset.
type labelOff struct {
name string
off int
}
var labels []labelOff
for name, off := range fl.Labels {
labels = append(labels, labelOff{name, off})
}
sort.Slice(labels, func(i, j int) bool { return labels[i].off < labels[j].off })
for _, l := range labels {
fmt.Printf(" %-20s +%#04x\n", l.name, l.off)
}
}
// Show basic blocks.
blocks, err := k.Blocks(name)
if err == nil && len(blocks) > 0 {
fmt.Printf(" basic blocks: %d\n", len(blocks))
}
}
return 0
}
// cmdVerifyRISCV handles the verify subcommand for RISC-V files. // cmdVerifyRISCV handles the verify subcommand for RISC-V files.
// JIT requires RISC-V hardware; only ground-truth and profile are available. // JIT requires RISC-V hardware; only ground-truth and profile are available.
func cmdVerifyRISCV(path string, groundTruth, profile bool) int { func cmdVerifyRISCV(path string, groundTruth, profile bool) int {