From 6206fb932bf16e143d735843ae6ab7be96297220 Mon Sep 17 00:00:00 2001 From: immanuwell Date: Thu, 16 Jul 2026 21:15:27 +0400 Subject: [PATCH] fix: include inferred stack usage in collection summaries Signed-off-by: immanuwell --- cmd/stackwhere/list_test.go | 16 ++++++++++++++++ cmd/stackwhere/web.go | 5 ++++- cmd/stackwhere/web_test.go | 18 ++++++++++++++++++ internal/stackview/stackview.go | 31 +++++++++++++++++++++++++++++-- 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/cmd/stackwhere/list_test.go b/cmd/stackwhere/list_test.go index d25b119..ca20e85 100644 --- a/cmd/stackwhere/list_test.go +++ b/cmd/stackwhere/list_test.go @@ -94,6 +94,22 @@ func TestCollectionSummaryIncludesProgramUsage(t *testing.T) { } } +func TestListCollectionIncludesInstructionOnlyStackUsage(t *testing.T) { + cmd := root() + cmd.SetArgs([]string{"list", "../../testdata/noinline.o"}) + + var stdout bytes.Buffer + cmd.SetOut(&stdout) + + if err := cmd.Execute(); err != nil { + t.Fatalf("list command failed: %v", err) + } + + if got := stdout.String(); !strings.Contains(got, "8 bytes - entry") { + t.Fatalf("expected instruction-derived stack usage in collection output, got %q", got) + } +} + func TestListCollectionWritesToConfiguredOutput(t *testing.T) { cmd := root() cmd.SetArgs([]string{"list", "../../testdata/basic.o"}) diff --git a/cmd/stackwhere/web.go b/cmd/stackwhere/web.go index 7e4ff90..e2cb2d5 100644 --- a/cmd/stackwhere/web.go +++ b/cmd/stackwhere/web.go @@ -158,7 +158,10 @@ func newWebApp(collectionPath string, sourceDirs []string) (*webApp, error) { return nil, err } - summary := analyzer.CollectionSummary() + summary, err := analyzer.CollectionSummaryInCollection() + if err != nil { + return nil, err + } spec, functions, err := loadCollectionFunctions(collectionPath) if err != nil { return nil, err diff --git a/cmd/stackwhere/web_test.go b/cmd/stackwhere/web_test.go index 60aef36..f1c2aec 100644 --- a/cmd/stackwhere/web_test.go +++ b/cmd/stackwhere/web_test.go @@ -78,6 +78,24 @@ func TestWebHandlerServesLandingPage(t *testing.T) { } } +func TestWebHandlerIncludesInstructionOnlyStackUsage(t *testing.T) { + app, err := newWebApp("../../testdata/noinline.o", nil) + if err != nil { + t.Fatalf("failed to initialize web app: %v", err) + } + + req := httptest.NewRequest("GET", "/", nil) + rr := httptest.NewRecorder() + app.handler().ServeHTTP(rr, req) + + if rr.Code != 200 { + t.Fatalf("unexpected status code: got %d want 200", rr.Code) + } + if body := rr.Body.String(); !strings.Contains(body, "8 bytes") { + t.Fatalf("expected instruction-derived stack usage in landing page, got %q", body) + } +} + func TestWebHandlerServesProgramPage(t *testing.T) { app, err := newWebApp("../../testdata/basic.o", nil) if err != nil { diff --git a/internal/stackview/stackview.go b/internal/stackview/stackview.go index 1481089..e3bf2b2 100644 --- a/internal/stackview/stackview.go +++ b/internal/stackview/stackview.go @@ -113,15 +113,42 @@ func (a *Analyzer) CollectionSummaryInCollection() ([]ProgramStackUsage, error) summary := a.CollectionSummary() filtered := make([]ProgramStackUsage, 0, len(summary)) + subProgsDwarf := a.tree.ByType(dbgdwarf.TagSubprogram) for _, prog := range summary { - if fn, ok := a.functions[prog.Name]; ok && fn.fn != nil { - filtered = append(filtered, prog) + fn, ok := a.functions[prog.Name] + if !ok || fn.fn == nil { + continue } + + subProgDwarfIdx := slices.IndexFunc(subProgsDwarf, func(n *dbgdwarf.Node) bool { + return n.Name() == prog.Name + }) + if subProgDwarfIdx != -1 { + inferredUsage := stackUsageFromSlots(stackSlotsFromInsns(fn, subProgsDwarf[subProgDwarfIdx])) + prog.StackUsage = max(prog.StackUsage, inferredUsage) + } + + filtered = append(filtered, prog) } return filtered, nil } +func stackUsageFromSlots(slots slotList) int64 { + var largestOffset int64 + for _, group := range slots { + for _, slot := range group { + largestOffset = max(largestOffset, slot.Offset) + } + } + + if largestOffset%8 != 0 { + largestOffset = ((largestOffset / 8) + 1) * 8 + } + + return largestOffset +} + // ProgramDetails returns grouped stack slot usage for a single program. func (a *Analyzer) ProgramDetails(functionName string) ([][]SlotUsage, error) { if err := a.loadFunctions(); err != nil {