Skip to content

Commit dc0548f

Browse files
committed
cmd/compile/internal/inline: add call site flag generation
Add code to detect call sites that are nested in loops, call sites that are on an unconditional path to panic/exit, and call sites within "init" functions. The panic-path processing reuses some of the logic+state already present for the function flag version of "calls panic/exit". Updates #61502. Change-Id: I1d728e0763282d3616a9cbc0a07c5cda115660f3 Reviewed-on: https://go-review.googlesource.com/c/go/+/511565 Reviewed-by: Matthew Dempsky <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 19ca233 commit dc0548f

File tree

7 files changed

+305
-29
lines changed

7 files changed

+305
-29
lines changed

src/cmd/compile/internal/inline/inlheur/analyze.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func computeFuncProps(fn *ir.Func, canInline func(*ir.Func)) (*FuncProps, CallSi
9898
a.setResults(fp)
9999
}
100100
// Now build up a partial table of callsites for this func.
101-
cstab := computeCallSiteTable(fn)
101+
cstab := computeCallSiteTable(fn, ffa.panicPathTable())
102102
disableDebugTrace()
103103
return fp, cstab
104104
}

src/cmd/compile/internal/inline/inlheur/analyze_func_callsites.go

+129-8
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,34 @@ import (
99
"cmd/compile/internal/pgo"
1010
"fmt"
1111
"os"
12+
"strings"
1213
)
1314

1415
type callSiteAnalyzer struct {
15-
cstab CallSiteTab
16-
nstack []ir.Node
16+
cstab CallSiteTab
17+
fn *ir.Func
18+
ptab map[ir.Node]pstate
19+
nstack []ir.Node
20+
loopNest int
21+
isInit bool
1722
}
1823

19-
func makeCallSiteAnalyzer(fn *ir.Func) *callSiteAnalyzer {
24+
func makeCallSiteAnalyzer(fn *ir.Func, ptab map[ir.Node]pstate) *callSiteAnalyzer {
25+
isInit := fn.IsPackageInit() || strings.HasPrefix(fn.Sym().Name, "init.")
2026
return &callSiteAnalyzer{
21-
cstab: make(CallSiteTab),
27+
fn: fn,
28+
cstab: make(CallSiteTab),
29+
ptab: ptab,
30+
isInit: isInit,
2231
}
2332
}
2433

25-
func computeCallSiteTable(fn *ir.Func) CallSiteTab {
26-
if debugTrace&debugTraceCalls != 0 {
34+
func computeCallSiteTable(fn *ir.Func, ptab map[ir.Node]pstate) CallSiteTab {
35+
if debugTrace != 0 {
2736
fmt.Fprintf(os.Stderr, "=-= making callsite table for func %v:\n",
2837
fn.Sym().Name)
2938
}
30-
csa := makeCallSiteAnalyzer(fn)
39+
csa := makeCallSiteAnalyzer(fn, ptab)
3140
var doNode func(ir.Node) bool
3241
doNode = func(n ir.Node) bool {
3342
csa.nodeVisitPre(n)
@@ -40,7 +49,74 @@ func computeCallSiteTable(fn *ir.Func) CallSiteTab {
4049
}
4150

4251
func (csa *callSiteAnalyzer) flagsForNode(call *ir.CallExpr) CSPropBits {
43-
return 0
52+
var r CSPropBits
53+
54+
if debugTrace&debugTraceCalls != 0 {
55+
fmt.Fprintf(os.Stderr, "=-= analyzing call at %s\n",
56+
fmtFullPos(call.Pos()))
57+
}
58+
59+
// Set a bit if this call is within a loop.
60+
if csa.loopNest > 0 {
61+
r |= CallSiteInLoop
62+
}
63+
64+
// Set a bit if the call is within an init function (either
65+
// compiler-generated or user-written).
66+
if csa.isInit {
67+
r |= CallSiteInInitFunc
68+
}
69+
70+
// Decide whether to apply the panic path heuristic. Hack: don't
71+
// apply this heuristic in the function "main.main" (mostly just
72+
// to avoid annoying users).
73+
if !isMainMain(csa.fn) {
74+
r = csa.determinePanicPathBits(call, r)
75+
}
76+
77+
return r
78+
}
79+
80+
// determinePanicPathBits updates the CallSiteOnPanicPath bit within
81+
// "r" if we think this call is on an unconditional path to
82+
// panic/exit. Do this by walking back up the node stack to see if we
83+
// can find either A) an enclosing panic, or B) a statement node that
84+
// we've determined leads to a panic/exit.
85+
func (csa *callSiteAnalyzer) determinePanicPathBits(call ir.Node, r CSPropBits) CSPropBits {
86+
csa.nstack = append(csa.nstack, call)
87+
defer func() {
88+
csa.nstack = csa.nstack[:len(csa.nstack)-1]
89+
}()
90+
91+
for ri := range csa.nstack[:len(csa.nstack)-1] {
92+
i := len(csa.nstack) - ri - 1
93+
n := csa.nstack[i]
94+
_, isCallExpr := n.(*ir.CallExpr)
95+
_, isStmt := n.(ir.Stmt)
96+
if isCallExpr {
97+
isStmt = false
98+
}
99+
100+
if debugTrace&debugTraceCalls != 0 {
101+
ps, inps := csa.ptab[n]
102+
fmt.Fprintf(os.Stderr, "=-= callpar %d op=%s ps=%s inptab=%v stmt=%v\n", i, n.Op().String(), ps.String(), inps, isStmt)
103+
}
104+
105+
if n.Op() == ir.OPANIC {
106+
r |= CallSiteOnPanicPath
107+
break
108+
}
109+
if v, ok := csa.ptab[n]; ok {
110+
if v == psCallsPanic {
111+
r |= CallSiteOnPanicPath
112+
break
113+
}
114+
if isStmt {
115+
break
116+
}
117+
}
118+
}
119+
return r
44120
}
45121

46122
func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
@@ -68,6 +144,10 @@ func (csa *callSiteAnalyzer) addCallSite(callee *ir.Func, call *ir.CallExpr) {
68144

69145
func (csa *callSiteAnalyzer) nodeVisitPre(n ir.Node) {
70146
switch n.Op() {
147+
case ir.ORANGE, ir.OFOR:
148+
if !hasTopLevelLoopBodyReturnOrBreak(loopBody(n)) {
149+
csa.loopNest++
150+
}
71151
case ir.OCALLFUNC:
72152
ce := n.(*ir.CallExpr)
73153
callee := pgo.DirectCallee(ce.X)
@@ -80,6 +160,47 @@ func (csa *callSiteAnalyzer) nodeVisitPre(n ir.Node) {
80160

81161
func (csa *callSiteAnalyzer) nodeVisitPost(n ir.Node) {
82162
csa.nstack = csa.nstack[:len(csa.nstack)-1]
163+
switch n.Op() {
164+
case ir.ORANGE, ir.OFOR:
165+
if !hasTopLevelLoopBodyReturnOrBreak(loopBody(n)) {
166+
csa.loopNest--
167+
}
168+
}
169+
}
170+
171+
func loopBody(n ir.Node) ir.Nodes {
172+
if forst, ok := n.(*ir.ForStmt); ok {
173+
return forst.Body
174+
}
175+
if rst, ok := n.(*ir.RangeStmt); ok {
176+
return rst.Body
177+
}
178+
return nil
179+
}
180+
181+
// hasTopLevelLoopBodyReturnOrBreak examines the body of a "for" or
182+
// "range" loop to try to verify that it is a real loop, as opposed to
183+
// a construct that is syntactically loopy but doesn't actually iterate
184+
// multiple times, like:
185+
//
186+
// for {
187+
// blah()
188+
// return 1
189+
// }
190+
//
191+
// [Remark: the pattern above crops up quite a bit in the source code
192+
// for the compiler itself, e.g. the auto-generated rewrite code]
193+
//
194+
// Note that we don't look for GOTO statements here, so it's possible
195+
// we'll get the wrong result for a loop with complicated control
196+
// jumps via gotos.
197+
func hasTopLevelLoopBodyReturnOrBreak(loopBody ir.Nodes) bool {
198+
for _, n := range loopBody {
199+
if n.Op() == ir.ORETURN || n.Op() == ir.OBREAK {
200+
return true
201+
}
202+
}
203+
return false
83204
}
84205

85206
// containingAssignment returns the top-level assignment statement

src/cmd/compile/internal/inline/inlheur/analyze_func_flags.go

+15-1
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,22 @@ func (ffa *funcFlagsAnalyzer) setstate(n ir.Node, st pstate) {
8282
}
8383
}
8484

85+
func (ffa *funcFlagsAnalyzer) updatestate(n ir.Node, st pstate) {
86+
if _, ok := ffa.nstate[n]; !ok {
87+
base.Fatalf("funcFlagsAnalyzer: fn %q internal error, expected existing setting for node:\n%+v\n", ffa.fn.Sym().Name, n)
88+
} else {
89+
ffa.nstate[n] = st
90+
}
91+
}
92+
8593
func (ffa *funcFlagsAnalyzer) setstateSoft(n ir.Node, st pstate) {
8694
ffa.nstate[n] = st
8795
}
8896

97+
func (ffa *funcFlagsAnalyzer) panicPathTable() map[ir.Node]pstate {
98+
return ffa.nstate
99+
}
100+
89101
// blockCombine merges together states as part of a linear sequence of
90102
// statements, where 'pred' and 'succ' are analysis results for a pair
91103
// of consecutive statements. Examples:
@@ -132,7 +144,8 @@ func branchCombine(p1, p2 pstate) pstate {
132144
}
133145

134146
// stateForList walks through a list of statements and computes the
135-
// state/diposition for the entire list as a whole.
147+
// state/diposition for the entire list as a whole, as well
148+
// as updating disposition of intermediate nodes.
136149
func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate {
137150
st := psTop
138151
for i := range list {
@@ -143,6 +156,7 @@ func (ffa *funcFlagsAnalyzer) stateForList(list ir.Nodes) pstate {
143156
ir.Line(n), n.Op().String(), psi.String())
144157
}
145158
st = blockCombine(st, psi)
159+
ffa.updatestate(n, st)
146160
}
147161
if st == psTop {
148162
st = psNoInfo

src/cmd/compile/internal/inline/inlheur/funcprops_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ func TestFuncProperties(t *testing.T) {
3636
// to building a fresh compiler on the fly, or using some other
3737
// scheme.
3838

39-
testcases := []string{"funcflags", "returns", "params", "acrosscall"}
40-
39+
testcases := []string{"funcflags", "returns", "params",
40+
"acrosscall", "calls"}
4141
for _, tc := range testcases {
4242
dumpfile, err := gatherPropsDumpForFile(t, tc, td)
4343
if err != nil {

src/cmd/compile/internal/inline/inlheur/testdata/props/README.txt

+4-2
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,20 @@ cmd/compile/internal/inline/inlheur/testdata/props:
2727
properties, as well as the JSON for the properties object, each
2828
section separated by a "<>" delimiter.
2929

30-
// funcflags.go T_feeds_if_simple 35 0 1
30+
// params.go T_feeds_if_simple 35 0 1
3131
// RecvrParamFlags:
3232
// 0: ParamFeedsIfOrSwitch
3333
// <endpropsdump>
3434
// {"Flags":0,"RecvrParamFlags":[8],"ReturnFlags":[]}
35+
// callsite: params.go:34:10|0 "CallSiteOnPanicPath" 2
36+
// <endcallsites>
3537
// <endfuncpreamble>
3638
func T_feeds_if_simple(x int) {
3739
if x < 100 {
3840
os.Exit(1)
3941
}
4042
println(x)
41-
}
43+
}
4244

4345
- when the test runs, it will compile the Go source file with an
4446
option to dump out function properties, then compare the new dump

0 commit comments

Comments
 (0)