Skip to content

Commit d2024a0

Browse files
committed
cmd/compile/internal/inline: extend flag calculation via export data
Extend the code that computes various properties and parameter flags to incorporate information from export data in addition to things we can get from the current package. Specifically: - when deciding whether the current function always calls panic/exit, check to see whether it has an unconditional call to some other function that has that flag. - when computing "parameter feeds" properties, look not just for cases where a parameter feeds an interesting construct (if/switch, indirect/interface call, etc) but where it feeds a call whose corresponding param has that flag. - when computing return properties, if a given return is always the results of a call to X, then set the return properties to those of X. Updates #61502. Change-Id: I6472fe98759cccad05b8eed58e4fc568201d88ad Reviewed-on: https://go-review.googlesource.com/c/go/+/511563 Reviewed-by: Matthew Dempsky <mdempsky@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
1 parent 323cf73 commit d2024a0

File tree

7 files changed

+398
-48
lines changed

7 files changed

+398
-48
lines changed

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

+14
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ func runAnalyzersOnFunction(fn *ir.Func, analyzers []propAnalyzer) {
104104
doNode(fn)
105105
}
106106

107+
func propsForFunc(fn *ir.Func) *FuncProps {
108+
if fih, ok := fpmap[fn]; ok {
109+
return fih.props
110+
} else if fn.Inl != nil && fn.Inl.Properties != "" {
111+
// FIXME: considering adding some sort of cache or table
112+
// for deserialized properties of imported functions.
113+
return DeserializeFromString(fn.Inl.Properties)
114+
}
115+
return nil
116+
}
117+
107118
func fnFileLine(fn *ir.Func) (string, uint) {
108119
p := base.Ctxt.InnermostPos(fn.Pos())
109120
return filepath.Base(p.Filename()), p.Line()
@@ -176,6 +187,9 @@ func captureFuncDumpEntry(fn *ir.Func, canInline func(*ir.Func)) {
176187
} else {
177188
AnalyzeFunc(fn, canInline)
178189
fih = fpmap[fn]
190+
if fn.Inl != nil && fn.Inl.Properties == "" {
191+
fn.Inl.Properties = fih.props.SerializeToString()
192+
}
179193
}
180194
if dumpBuffer == nil {
181195
dumpBuffer = make(map[*ir.Func]fnInlHeur)

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -175,9 +175,11 @@ func isExitCall(n ir.Node) bool {
175175
isWellKnownFunc(s, "runtime", "throw") {
176176
return true
177177
}
178-
// FIXME: consult results of flags computation for
179-
// previously analyzed Go functions, including props
180-
// read from export data for functions in other packages.
178+
if fp := propsForFunc(name.Func); fp != nil {
179+
if fp.Flags&FuncPropNeverReturns != 0 {
180+
return true
181+
}
182+
}
181183
return false
182184
}
183185

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

+101-13
Original file line numberDiff line numberDiff line change
@@ -106,21 +106,35 @@ func (pa *paramsAnalyzer) setResults(fp *FuncProps) {
106106
fp.ParamFlags = pa.values
107107
}
108108

109+
func (pa *paramsAnalyzer) findParamIdx(n *ir.Name) int {
110+
if n == nil {
111+
panic("bad")
112+
}
113+
for i := range pa.params {
114+
if pa.params[i] == n {
115+
return i
116+
}
117+
}
118+
return -1
119+
}
120+
121+
type testfType func(x ir.Node, param *ir.Name, idx int) (bool, bool)
122+
109123
// paramsAnalyzer invokes function 'testf' on the specified expression
110124
// 'x' for each parameter, and if the result is TRUE, or's 'flag' into
111125
// the flags for that param.
112-
func (pa *paramsAnalyzer) checkParams(x ir.Node, flag ParamPropBits, mayflag ParamPropBits, testf func(x ir.Node, param *ir.Name) bool) {
126+
func (pa *paramsAnalyzer) checkParams(x ir.Node, flag ParamPropBits, mayflag ParamPropBits, testf testfType) {
113127
for idx, p := range pa.params {
114128
if !pa.top[idx] && pa.values[idx] == ParamNoInfo {
115129
continue
116130
}
117-
result := testf(x, p)
131+
result, may := testf(x, p, idx)
118132
if debugTrace&debugTraceParams != 0 {
119133
fmt.Fprintf(os.Stderr, "=-= test expr %v param %s result=%v flag=%s\n", x, p.Sym().Name, result, flag.String())
120134
}
121135
if result {
122136
v := flag
123-
if pa.condLevel != 0 {
137+
if pa.condLevel != 0 || may {
124138
v = mayflag
125139
}
126140
pa.values[idx] |= v
@@ -134,8 +148,8 @@ func (pa *paramsAnalyzer) checkParams(x ir.Node, flag ParamPropBits, mayflag Par
134148
// specific parameter had a constant value.
135149
func (pa *paramsAnalyzer) foldCheckParams(x ir.Node) {
136150
pa.checkParams(x, ParamFeedsIfOrSwitch, ParamMayFeedIfOrSwitch,
137-
func(x ir.Node, p *ir.Name) bool {
138-
return ShouldFoldIfNameConstant(x, []*ir.Name{p})
151+
func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
152+
return ShouldFoldIfNameConstant(x, []*ir.Name{p}), false
139153
})
140154
}
141155

@@ -158,9 +172,9 @@ func (pa *paramsAnalyzer) callCheckParams(ce *ir.CallExpr) {
158172
}
159173
pa.checkParams(r, ParamFeedsInterfaceMethodCall,
160174
ParamMayFeedInterfaceMethodCall,
161-
func(x ir.Node, p *ir.Name) bool {
175+
func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
162176
name := x.(*ir.Name)
163-
return name == p
177+
return name == p, false
164178
})
165179
case ir.OCALLFUNC:
166180
if ce.X.Op() != ir.ONAME {
@@ -171,15 +185,89 @@ func (pa *paramsAnalyzer) callCheckParams(ce *ir.CallExpr) {
171185
return
172186
}
173187
name := called.(*ir.Name)
188+
if name.Class == ir.PPARAM {
189+
pa.checkParams(called, ParamFeedsIndirectCall,
190+
ParamMayFeedIndirectCall,
191+
func(x ir.Node, p *ir.Name, idx int) (bool, bool) {
192+
name := x.(*ir.Name)
193+
return name == p, false
194+
})
195+
} else {
196+
cname, isFunc, _ := isFuncName(called)
197+
if isFunc {
198+
pa.deriveFlagsFromCallee(ce, cname.Func)
199+
}
200+
}
201+
}
202+
}
203+
204+
// deriveFlagsFromCallee tries to derive flags for the current
205+
// function based on a call this function makes to some other
206+
// function. Example:
207+
//
208+
// /* Simple */ /* Derived from callee */
209+
// func foo(f func(int)) { func foo(f func(int)) {
210+
// f(2) bar(32, f)
211+
// } }
212+
// func bar(x int, f func()) {
213+
// f(x)
214+
// }
215+
//
216+
// Here we can set the "param feeds indirect call" flag for
217+
// foo's param 'f' since we know that bar has that flag set for
218+
// its second param, and we're passing that param a function.
219+
func (pa *paramsAnalyzer) deriveFlagsFromCallee(ce *ir.CallExpr, callee *ir.Func) {
220+
calleeProps := propsForFunc(callee)
221+
if calleeProps == nil {
222+
return
223+
}
224+
if debugTrace&debugTraceParams != 0 {
225+
fmt.Fprintf(os.Stderr, "=-= callee props for %v:\n%s",
226+
callee.Sym().Name, calleeProps.String())
227+
}
228+
229+
must := []ParamPropBits{ParamFeedsInterfaceMethodCall, ParamFeedsIndirectCall, ParamFeedsIfOrSwitch}
230+
may := []ParamPropBits{ParamMayFeedInterfaceMethodCall, ParamMayFeedIndirectCall, ParamMayFeedIfOrSwitch}
231+
232+
for pidx, arg := range ce.Args {
233+
// Does the callee param have any interesting properties?
234+
// If not we can skip this one.
235+
pflag := calleeProps.ParamFlags[pidx]
236+
if pflag == 0 {
237+
continue
238+
}
239+
// See if one of the caller's parameters is flowing unmodified
240+
// into this actual expression.
241+
r := ir.StaticValue(arg)
242+
if r.Op() != ir.ONAME {
243+
return
244+
}
245+
name := r.(*ir.Name)
174246
if name.Class != ir.PPARAM {
175247
return
176248
}
177-
pa.checkParams(called, ParamFeedsIndirectCall,
178-
ParamMayFeedIndirectCall,
179-
func(x ir.Node, p *ir.Name) bool {
180-
name := x.(*ir.Name)
181-
return name == p
182-
})
249+
callerParamIdx := pa.findParamIdx(name)
250+
if callerParamIdx == -1 || pa.params[callerParamIdx] == nil {
251+
panic("something went wrong")
252+
}
253+
if !pa.top[callerParamIdx] &&
254+
pa.values[callerParamIdx] == ParamNoInfo {
255+
continue
256+
}
257+
if debugTrace&debugTraceParams != 0 {
258+
fmt.Fprintf(os.Stderr, "=-= pflag for arg %d is %s\n",
259+
pidx, pflag.String())
260+
}
261+
for i := range must {
262+
mayv := may[i]
263+
mustv := must[i]
264+
if pflag&mustv != 0 && pa.condLevel == 0 {
265+
pa.values[callerParamIdx] |= mustv
266+
} else if pflag&(mustv|mayv) != 0 {
267+
pa.values[callerParamIdx] |= mayv
268+
}
269+
}
270+
pa.top[callerParamIdx] = false
183271
}
184272
}
185273

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

+76-28
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ type returnsAnalyzer struct {
2828
// the same function, etc. This container stores info on a the specific
2929
// scenarios we're looking for.
3030
type resultVal struct {
31-
lit constant.Value
32-
fn *ir.Name
33-
fnClo bool
34-
top bool
31+
lit constant.Value
32+
fn *ir.Name
33+
fnClo bool
34+
top bool
35+
derived bool // see deriveReturnFlagsFromCallee below
3536
}
3637

3738
func makeResultsAnalyzer(fn *ir.Func, canInline func(*ir.Func)) *returnsAnalyzer {
@@ -62,7 +63,7 @@ func makeResultsAnalyzer(fn *ir.Func, canInline func(*ir.Func)) *returnsAnalyzer
6263
func (ra *returnsAnalyzer) setResults(fp *FuncProps) {
6364
// Promote ResultAlwaysSameFunc to ResultAlwaysSameInlinableFunc
6465
for i := range ra.values {
65-
if ra.props[i] == ResultAlwaysSameFunc {
66+
if ra.props[i] == ResultAlwaysSameFunc && !ra.values[i].derived {
6667
f := ra.values[i].fn.Func
6768
// If the function being returns is a closure that hasn't
6869
// yet been checked by CanInline, invoke it now. NB: this
@@ -149,6 +150,7 @@ func (ra *returnsAnalyzer) analyzeResult(ii int, n ir.Node) {
149150
lit, isConst := isLiteral(n)
150151
rfunc, isFunc, isClo := isFuncName(n)
151152
curp := ra.props[ii]
153+
dprops, isDerivedFromCall := deriveReturnFlagsFromCallee(n)
152154
newp := ResultNoInfo
153155
var newlit constant.Value
154156
var newfunc *ir.Name
@@ -172,30 +174,35 @@ func (ra *returnsAnalyzer) analyzeResult(ii int, n ir.Node) {
172174
case isConst:
173175
newp = ResultAlwaysSameConstant
174176
newlit = lit
177+
case isDerivedFromCall:
178+
newp = dprops
179+
ra.values[ii].derived = true
175180
}
176181
} else {
177-
// this is not the first return we've seen; apply
178-
// what amounts of a "meet" operator to combine
179-
// the properties we see here with what we saw on
180-
// the previous returns.
181-
switch curp {
182-
case ResultIsAllocatedMem:
183-
if isAllocatedMem(n) {
184-
newp = ResultIsAllocatedMem
185-
}
186-
case ResultIsConcreteTypeConvertedToInterface:
187-
if isConcreteConvIface(n) {
188-
newp = ResultIsConcreteTypeConvertedToInterface
189-
}
190-
case ResultAlwaysSameConstant:
191-
if isConst && isSameLiteral(lit, ra.values[ii].lit) {
192-
newp = ResultAlwaysSameConstant
193-
newlit = lit
194-
}
195-
case ResultAlwaysSameFunc:
196-
if isFunc && isSameFuncName(rfunc, ra.values[ii].fn) {
197-
newp = ResultAlwaysSameFunc
198-
newfunc = rfunc
182+
if !ra.values[ii].derived {
183+
// this is not the first return we've seen; apply
184+
// what amounts of a "meet" operator to combine
185+
// the properties we see here with what we saw on
186+
// the previous returns.
187+
switch curp {
188+
case ResultIsAllocatedMem:
189+
if isAllocatedMem(n) {
190+
newp = ResultIsAllocatedMem
191+
}
192+
case ResultIsConcreteTypeConvertedToInterface:
193+
if isConcreteConvIface(n) {
194+
newp = ResultIsConcreteTypeConvertedToInterface
195+
}
196+
case ResultAlwaysSameConstant:
197+
if isConst && isSameLiteral(lit, ra.values[ii].lit) {
198+
newp = ResultAlwaysSameConstant
199+
newlit = lit
200+
}
201+
case ResultAlwaysSameFunc:
202+
if isFunc && isSameFuncName(rfunc, ra.values[ii].fn) {
203+
newp = ResultAlwaysSameFunc
204+
newfunc = rfunc
205+
}
199206
}
200207
}
201208
}
@@ -208,7 +215,6 @@ func (ra *returnsAnalyzer) analyzeResult(ii int, n ir.Node) {
208215
fmt.Fprintf(os.Stderr, "=-= %v: analyzeResult newp=%s\n",
209216
ir.Line(n), newp)
210217
}
211-
212218
}
213219

214220
func isAllocatedMem(n ir.Node) bool {
@@ -220,6 +226,48 @@ func isAllocatedMem(n ir.Node) bool {
220226
return false
221227
}
222228

229+
// deriveReturnFlagsFromCallee tries to set properties for a given
230+
// return result where we're returning call expression; return value
231+
// is a return property value and a boolean indicating whether the
232+
// prop is valid. Examples:
233+
//
234+
// func foo() int { return bar() }
235+
// func bar() int { return 42 }
236+
// func blix() int { return 43 }
237+
// func two(y int) int {
238+
// if y < 0 { return bar() } else { return blix() }
239+
// }
240+
//
241+
// Since "foo" always returns the result of a call to "bar", we can
242+
// set foo's return property to that of bar. In the case of "two", however,
243+
// even though each return path returns a constant, we don't know
244+
// whether the constants are identical, hence we need to be conservative.
245+
func deriveReturnFlagsFromCallee(n ir.Node) (ResultPropBits, bool) {
246+
if n.Op() != ir.OCALLFUNC {
247+
return 0, false
248+
}
249+
ce := n.(*ir.CallExpr)
250+
if ce.X.Op() != ir.ONAME {
251+
return 0, false
252+
}
253+
called := ir.StaticValue(ce.X)
254+
if called.Op() != ir.ONAME {
255+
return 0, false
256+
}
257+
cname, isFunc, _ := isFuncName(called)
258+
if !isFunc {
259+
return 0, false
260+
}
261+
calleeProps := propsForFunc(cname.Func)
262+
if calleeProps == nil {
263+
return 0, false
264+
}
265+
if len(calleeProps.ResultFlags) != 1 {
266+
return 0, false
267+
}
268+
return calleeProps.ResultFlags[0], true
269+
}
270+
223271
func isLiteral(n ir.Node) (constant.Value, bool) {
224272
sv := ir.StaticValue(n)
225273
switch sv.Op() {

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ var remasterflag = flag.Bool("update-expected", false, "if true, generate update
2222

2323
func TestFuncProperties(t *testing.T) {
2424
td := t.TempDir()
25-
//td = "/tmp/qqq"
26-
//os.RemoveAll(td)
27-
//os.Mkdir(td, 0777)
25+
// td = "/tmp/qqq"
26+
// os.RemoveAll(td)
27+
// os.Mkdir(td, 0777)
2828
testenv.MustHaveGoBuild(t)
2929

3030
// NOTE: this testpoint has the unfortunate characteristic that it
@@ -35,7 +35,7 @@ func TestFuncProperties(t *testing.T) {
3535
// to building a fresh compiler on the fly, or using some other
3636
// scheme.
3737

38-
testcases := []string{"funcflags", "returns", "params"}
38+
testcases := []string{"funcflags", "returns", "params", "acrosscall"}
3939

4040
for _, tc := range testcases {
4141
dumpfile, err := gatherPropsDumpForFile(t, tc, td)

0 commit comments

Comments
 (0)