diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index 0ee0612..30509ba 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -16,6 +16,7 @@ import ( "os" "os/exec" "path/filepath" + "sort" "strconv" "strings" "syscall" @@ -57,6 +58,8 @@ func main() { os.Exit(cmdDebug(os.Args[2:])) case "diff": os.Exit(cmdDiff(os.Args[2:])) + case "profile": + os.Exit(cmdProfile(os.Args[2:])) case "lsp": os.Exit(cmdLSP(os.Args[2:])) case "version", "--version", "-V": @@ -118,6 +121,7 @@ func usage(w io.Writer) { {"verify", "JIT-assemble and run dynamic checks (amd64, riscv64)"}, {"debug", "interactive source-level debugger (amd64)"}, {"diff", "compare machine code of two .s files"}, + {"profile", "show basic-block structure of functions"}, {"lsp", "run the language server over stdio"}, {"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 ", ` +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 ") + 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. // JIT requires RISC-V hardware; only ground-truth and profile are available. func cmdVerifyRISCV(path string, groundTruth, profile bool) int {