// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package inlheur import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" "cmd/internal/src" "fmt" "io" "path/filepath" "sort" "strings" ) // CallSite records useful information about a potentially inlinable // (direct) function call. "Callee" is the target of the call, "Call" // is the ir node corresponding to the call itself, "Assign" is // the top-level assignment statement containing the call (if the call // appears in the form of a top-level statement, e.g. "x := foo()"), // "Flags" contains properties of the call that might be useful for // making inlining decisions, "Score" is the final score assigned to // the site, and "ID" is a numeric ID for the site within its // containing function. type CallSite struct { Callee *ir.Func Call *ir.CallExpr Assign ir.Node Flags CSPropBits Score int ScoreMask scoreAdjustTyp ID uint } // CallSiteTab is a table of call sites, keyed by call expr. // Ideally it would be nice to key the table by src.XPos, but // this results in collisions for calls on very long lines (the // front end saturates column numbers at 255). We also wind up // with many calls that share the same auto-generated pos. type CallSiteTab map[*ir.CallExpr]*CallSite type CSPropBits uint32 const ( CallSiteInLoop CSPropBits = 1 << iota CallSiteOnPanicPath CallSiteInInitFunc ) // encodedCallSiteTab is a table keyed by "encoded" callsite // (stringified src.XPos plus call site ID) mapping to a value of call // property bits and score. type encodedCallSiteTab map[string]propsAndScore type propsAndScore struct { props CSPropBits score int mask scoreAdjustTyp } func (pas propsAndScore) String() string { return fmt.Sprintf("P=%s|S=%d|M=%s", pas.props.String(), pas.score, pas.mask.String()) } func (cst CallSiteTab) merge(other CallSiteTab) error { for k, v := range other { if prev, ok := cst[k]; ok { return fmt.Errorf("internal error: collision during call site table merge, fn=%s callsite=%s", prev.Callee.Sym().Name, fmtFullPos(prev.Call.Pos())) } cst[k] = v } return nil } func fmtFullPos(p src.XPos) string { var sb strings.Builder sep := "" base.Ctxt.AllPos(p, func(pos src.Pos) { fmt.Fprintf(&sb, sep) sep = "|" file := filepath.Base(pos.Filename()) fmt.Fprintf(&sb, "%s:%d:%d", file, pos.Line(), pos.Col()) }) return sb.String() } func EncodeCallSiteKey(cs *CallSite) string { var sb strings.Builder // FIXME: maybe rewrite line offsets relative to function start? sb.WriteString(fmtFullPos(cs.Call.Pos())) fmt.Fprintf(&sb, "|%d", cs.ID) return sb.String() } func buildEncodedCallSiteTab(tab CallSiteTab) encodedCallSiteTab { r := make(encodedCallSiteTab) for _, cs := range tab { k := EncodeCallSiteKey(cs) r[k] = propsAndScore{ props: cs.Flags, score: cs.Score, mask: cs.ScoreMask, } } return r } // dumpCallSiteComments emits comments into the dump file for the // callsites in the function of interest. If "ecst" is non-nil, we use // that, otherwise generated a fresh encodedCallSiteTab from "tab". func dumpCallSiteComments(w io.Writer, tab CallSiteTab, ecst encodedCallSiteTab) { if ecst == nil { ecst = buildEncodedCallSiteTab(tab) } tags := make([]string, 0, len(ecst)) for k := range ecst { tags = append(tags, k) } sort.Strings(tags) for _, s := range tags { v := ecst[s] fmt.Fprintf(w, "// callsite: %s flagstr %q flagval %d score %d mask %d maskstr %q\n", s, v.props.String(), v.props, v.score, v.mask, v.mask.String()) } fmt.Fprintf(w, "// %s\n", csDelimiter) }