Skip to content

Commit a245ace

Browse files
mdempskygopherbot
authored andcommitted
cmd/compile/internal/inline: refactor fixpoint algorithm
[Re-land of CL 567695 without further changes.] This CL refactors the interleaved fixpoint algorithm so that calls can be inlined in any order. This has no immediate effect, but it will allow a subsequent CL to prioritize calls by inlheur score. Change-Id: I8fd6748f0347fd696daee815bf7c9c183572c1ea Reviewed-on: https://go-review.googlesource.com/c/go/+/573096 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Matthew Dempsky <[email protected]> Reviewed-by: Than McIntosh <[email protected]>
1 parent 6af27c4 commit a245ace

File tree

2 files changed

+106
-31
lines changed

2 files changed

+106
-31
lines changed

src/cmd/compile/internal/inline/interleaved/interleaved.go

+93-24
Original file line numberDiff line numberDiff line change
@@ -83,39 +83,108 @@ func DevirtualizeAndInlineFunc(fn *ir.Func, profile *pgo.Profile) {
8383
fmt.Printf("%v: function %v considered 'big'; reducing max cost of inlinees\n", ir.Line(fn), fn)
8484
}
8585

86-
// Walk fn's body and apply devirtualization and inlining.
87-
var inlCalls []*ir.InlinedCallExpr
88-
var edit func(ir.Node) ir.Node
89-
edit = func(n ir.Node) ir.Node {
86+
match := func(n ir.Node) bool {
9087
switch n := n.(type) {
88+
case *ir.CallExpr:
89+
return true
9190
case *ir.TailCallStmt:
9291
n.Call.NoInline = true // can't inline yet
9392
}
93+
return false
94+
}
95+
96+
edit := func(n ir.Node) ir.Node {
97+
call, ok := n.(*ir.CallExpr)
98+
if !ok { // previously inlined
99+
return nil
100+
}
101+
102+
devirtualize.StaticCall(call)
103+
if inlCall := inline.TryInlineCall(fn, call, bigCaller, profile); inlCall != nil {
104+
return inlCall
105+
}
106+
return nil
107+
}
108+
109+
fixpoint(fn, match, edit)
110+
})
111+
}
112+
113+
// fixpoint repeatedly edits a function until it stabilizes.
114+
//
115+
// First, fixpoint applies match to every node n within fn. Then it
116+
// iteratively applies edit to each node satisfying match(n).
117+
//
118+
// If edit(n) returns nil, no change is made. Otherwise, the result
119+
// replaces n in fn's body, and fixpoint iterates at least once more.
120+
//
121+
// After an iteration where all edit calls return nil, fixpoint
122+
// returns.
123+
func fixpoint(fn *ir.Func, match func(ir.Node) bool, edit func(ir.Node) ir.Node) {
124+
// Consider the expression "f(g())". We want to be able to replace
125+
// "g()" in-place with its inlined representation. But if we first
126+
// replace "f(...)" with its inlined representation, then "g()" will
127+
// instead appear somewhere within this new AST.
128+
//
129+
// To mitigate this, each matched node n is wrapped in a ParenExpr,
130+
// so we can reliably replace n in-place by assigning ParenExpr.X.
131+
// It's safe to use ParenExpr here, because typecheck already
132+
// removed them all.
133+
134+
var parens []*ir.ParenExpr
135+
var mark func(ir.Node) ir.Node
136+
mark = func(n ir.Node) ir.Node {
137+
if _, ok := n.(*ir.ParenExpr); ok {
138+
return n // already visited n.X before wrapping
139+
}
94140

95-
ir.EditChildren(n, edit)
141+
ok := match(n)
96142

97-
if call, ok := n.(*ir.CallExpr); ok {
98-
devirtualize.StaticCall(call)
143+
ir.EditChildren(n, mark)
99144

100-
if inlCall := inline.TryInlineCall(fn, call, bigCaller, profile); inlCall != nil {
101-
inlCalls = append(inlCalls, inlCall)
102-
n = inlCall
103-
}
145+
if ok {
146+
paren := ir.NewParenExpr(n.Pos(), n)
147+
paren.SetType(n.Type())
148+
paren.SetTypecheck(n.Typecheck())
149+
150+
parens = append(parens, paren)
151+
n = paren
152+
}
153+
154+
return n
155+
}
156+
ir.EditChildren(fn, mark)
157+
158+
// Edit until stable.
159+
for {
160+
done := true
161+
162+
for i := 0; i < len(parens); i++ { // can't use "range parens" here
163+
paren := parens[i]
164+
if new := edit(paren.X); new != nil {
165+
// Update AST and recursively mark nodes.
166+
paren.X = new
167+
ir.EditChildren(new, mark) // mark may append to parens
168+
done = false
104169
}
170+
}
105171

106-
return n
172+
if done {
173+
break
107174
}
108-
ir.EditChildren(fn, edit)
109-
110-
// If we inlined any calls, we want to recursively visit their
111-
// bodies for further devirtualization and inlining. However, we
112-
// need to wait until *after* the original function body has been
113-
// expanded, or else inlCallee can have false positives (e.g.,
114-
// #54632).
115-
for len(inlCalls) > 0 {
116-
call := inlCalls[0]
117-
inlCalls = inlCalls[1:]
118-
ir.EditChildren(call, edit)
175+
}
176+
177+
// Finally, remove any parens we inserted.
178+
if len(parens) == 0 {
179+
return // short circuit
180+
}
181+
var unparen func(ir.Node) ir.Node
182+
unparen = func(n ir.Node) ir.Node {
183+
if paren, ok := n.(*ir.ParenExpr); ok {
184+
n = paren.X
119185
}
120-
})
186+
ir.EditChildren(n, unparen)
187+
return n
188+
}
189+
ir.EditChildren(fn, unparen)
121190
}

src/cmd/compile/internal/ir/expr.go

+13-7
Original file line numberDiff line numberDiff line change
@@ -856,13 +856,19 @@ func IsAddressable(n Node) bool {
856856
// "g()" expression.
857857
func StaticValue(n Node) Node {
858858
for {
859-
if n.Op() == OCONVNOP {
860-
n = n.(*ConvExpr).X
861-
continue
862-
}
863-
864-
if n.Op() == OINLCALL {
865-
n = n.(*InlinedCallExpr).SingleResult()
859+
switch n1 := n.(type) {
860+
case *ConvExpr:
861+
if n1.Op() == OCONVNOP {
862+
n = n1.X
863+
continue
864+
}
865+
case *InlinedCallExpr:
866+
if n1.Op() == OINLCALL {
867+
n = n1.SingleResult()
868+
continue
869+
}
870+
case *ParenExpr:
871+
n = n1.X
866872
continue
867873
}
868874

0 commit comments

Comments
 (0)