Skip to content

Commit afa3f8e

Browse files
mdempskygopherbot
authored andcommitted
cmd/compile/internal/staticinit: make staticopy safe
Currently, cmd/compile optimizes `var a = true; var b = a` into `var a = true; var b = true`. But this may not be safe if we need to initialize any other global variables between `a` and `b`, and the initialization involves calling a user-defined function that reassigns `a`. This CL changes staticinit to keep track of the initialization expressions that we've seen so far, and to stop applying the staticcopy optimization once we've seen an initialization expression that might have modified another global variable within this package. To help identify affected initializers, this CL adds a -d=staticcopy flag to warn when a staticcopy is suppressed and turned into a dynamic copy. Currently, `go build -gcflags=all=-d=staticcopy std` reports only four instances: ``` encoding/xml/xml.go:1600:5: skipping static copy of HTMLEntity+0 with map[string]string{...} encoding/xml/xml.go:1869:5: skipping static copy of HTMLAutoClose+0 with []string{...} net/net.go:661:5: skipping static copy of .stmp_31+0 with poll.ErrNetClosing net/http/transport.go:2566:5: skipping static copy of errRequestCanceled+0 with ~R0 ``` Fixes #51913. Change-Id: Iab41cf6f84c44f7f960e4e62c28a8aeaade4fbcf Reviewed-on: https://go-review.googlesource.com/c/go/+/395541 Auto-Submit: Matthew Dempsky <mdempsky@google.com> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com> Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Heschi Kreinick <heschi@google.com>
1 parent 1cdabf0 commit afa3f8e

File tree

6 files changed

+130
-26
lines changed

6 files changed

+130
-26
lines changed

src/cmd/compile/internal/base/debug.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ type DebugFlags struct {
4747
Shapify int `help:"print information about shaping recursive types"`
4848
Slice int `help:"print information about slice compilation"`
4949
SoftFloat int `help:"force compiler to emit soft-float code" concurrent:"ok"`
50+
StaticCopy int `help:"print information about missed static copies" concurrent:"ok"`
5051
SyncFrames int `help:"how many writer stack frames to include at sync points in unified export data"`
5152
TypeAssert int `help:"print information about type assertion inlining"`
5253
WB int `help:"print information about write barriers"`

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,3 +445,13 @@ func NewClosureFunc(fpos, cpos src.XPos, why Op, typ *types.Type, outerfn *Func,
445445

446446
return fn
447447
}
448+
449+
// IsFuncPCIntrinsic returns whether n is a direct call of internal/abi.FuncPCABIxxx functions.
450+
func IsFuncPCIntrinsic(n *CallExpr) bool {
451+
if n.Op() != OCALLFUNC || n.X.Op() != ONAME {
452+
return false
453+
}
454+
fn := n.X.(*Name).Sym()
455+
return (fn.Name == "FuncPCABI0" || fn.Name == "FuncPCABIInternal") &&
456+
fn.Pkg.Path == "internal/abi"
457+
}

src/cmd/compile/internal/staticinit/sched.go

Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ type Schedule struct {
4242

4343
Plans map[ir.Node]*Plan
4444
Temps map[ir.Node]*ir.Name
45+
46+
// seenMutation tracks whether we've seen an initialization
47+
// expression that may have modified other package-scope variables
48+
// within this package.
49+
seenMutation bool
4550
}
4651

4752
func (s *Schedule) append(n ir.Node) {
@@ -80,26 +85,57 @@ func recordFuncForVar(v *ir.Name, fn *ir.Func) {
8085
MapInitToVar[fn] = v
8186
}
8287

88+
// allBlank reports whether every node in exprs is blank.
89+
func allBlank(exprs []ir.Node) bool {
90+
for _, expr := range exprs {
91+
if !ir.IsBlank(expr) {
92+
return false
93+
}
94+
}
95+
return true
96+
}
97+
8398
// tryStaticInit attempts to statically execute an initialization
8499
// statement and reports whether it succeeded.
85-
func (s *Schedule) tryStaticInit(nn ir.Node) bool {
86-
// Only worry about simple "l = r" assignments. Multiple
87-
// variable/expression OAS2 assignments have already been
88-
// replaced by multiple simple OAS assignments, and the other
89-
// OAS2* assignments mostly necessitate dynamic execution
90-
// anyway.
91-
if nn.Op() != ir.OAS {
92-
return false
100+
func (s *Schedule) tryStaticInit(n ir.Node) bool {
101+
var lhs []ir.Node
102+
var rhs ir.Node
103+
104+
switch n.Op() {
105+
default:
106+
base.FatalfAt(n.Pos(), "unexpected initialization statement: %v", n)
107+
case ir.OAS:
108+
n := n.(*ir.AssignStmt)
109+
lhs, rhs = []ir.Node{n.X}, n.Y
110+
case ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
111+
n := n.(*ir.AssignListStmt)
112+
if len(n.Lhs) < 2 || len(n.Rhs) != 1 {
113+
base.FatalfAt(n.Pos(), "unexpected shape for %v: %v", n.Op(), n)
114+
}
115+
lhs, rhs = n.Lhs, n.Rhs[0]
116+
case ir.OCALLFUNC:
117+
return false // outlined map init call; no mutations
93118
}
94-
n := nn.(*ir.AssignStmt)
95-
if ir.IsBlank(n.X) && !AnySideEffects(n.Y) {
96-
// Discard.
97-
return true
119+
120+
if !s.seenMutation {
121+
s.seenMutation = mayModifyPkgVar(rhs)
122+
}
123+
124+
if allBlank(lhs) && !AnySideEffects(rhs) {
125+
return true // discard
98126
}
127+
128+
// Only worry about simple "l = r" assignments. The OAS2*
129+
// assignments mostly necessitate dynamic execution anyway.
130+
if len(lhs) > 1 {
131+
return false
132+
}
133+
99134
lno := ir.SetPos(n)
100135
defer func() { base.Pos = lno }()
101-
nam := n.X.(*ir.Name)
102-
return s.StaticAssign(nam, 0, n.Y, nam.Type())
136+
137+
nam := lhs[0].(*ir.Name)
138+
return s.StaticAssign(nam, 0, rhs, nam.Type())
103139
}
104140

105141
// like staticassign but we are copying an already
@@ -134,6 +170,15 @@ func (s *Schedule) staticcopy(l *ir.Name, loff int64, rn *ir.Name, typ *types.Ty
134170
base.Fatalf("unexpected initializer: %v", rn.Defn)
135171
}
136172

173+
// Variable may have been reassigned by a user-written function call
174+
// that was invoked to initialize another global variable (#51913).
175+
if s.seenMutation {
176+
if base.Debug.StaticCopy != 0 {
177+
base.WarnfAt(l.Pos(), "skipping static copy of %v+%v with %v", l, loff, r)
178+
}
179+
return false
180+
}
181+
137182
for r.Op() == ir.OCONVNOP && !types.Identical(r.Type(), typ) {
138183
r = r.(*ir.ConvExpr).X
139184
}
@@ -830,6 +875,43 @@ func AnySideEffects(n ir.Node) bool {
830875
return ir.Any(n, isSideEffect)
831876
}
832877

878+
// mayModifyPkgVar reports whether expression n may modify any
879+
// package-scope variables declared within the current package.
880+
func mayModifyPkgVar(n ir.Node) bool {
881+
// safeLHS reports whether the assigned-to variable lhs is either a
882+
// local variable or a global from another package.
883+
safeLHS := func(lhs ir.Node) bool {
884+
v, ok := ir.OuterValue(lhs).(*ir.Name)
885+
return ok && v.Op() == ir.ONAME && !(v.Class == ir.PEXTERN && v.Sym().Pkg == types.LocalPkg)
886+
}
887+
888+
return ir.Any(n, func(n ir.Node) bool {
889+
switch n.Op() {
890+
case ir.OCALLFUNC, ir.OCALLINTER:
891+
return !ir.IsFuncPCIntrinsic(n.(*ir.CallExpr))
892+
893+
case ir.OAPPEND, ir.OCLEAR, ir.OCOPY:
894+
return true // could mutate a global array
895+
896+
case ir.OAS:
897+
n := n.(*ir.AssignStmt)
898+
if !safeLHS(n.X) {
899+
return true
900+
}
901+
902+
case ir.OAS2, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
903+
n := n.(*ir.AssignListStmt)
904+
for _, lhs := range n.Lhs {
905+
if !safeLHS(lhs) {
906+
return true
907+
}
908+
}
909+
}
910+
911+
return false
912+
})
913+
}
914+
833915
// canRepeat reports whether executing n multiple times has the same effect as
834916
// assigning n to a single variable and using that variable multiple times.
835917
func canRepeat(n ir.Node) bool {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ func walkCall(n *ir.CallExpr, init *ir.Nodes) ir.Node {
549549
directClosureCall(n)
550550
}
551551

552-
if isFuncPCIntrinsic(n) {
552+
if ir.IsFuncPCIntrinsic(n) {
553553
// For internal/abi.FuncPCABIxxx(fn), if fn is a defined function, rewrite
554554
// it to the address of the function of the ABI fn is defined.
555555
name := n.X.(*ir.Name).Sym().Name

src/cmd/compile/internal/walk/order.go

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -538,7 +538,7 @@ func (o *orderState) call(nn ir.Node) {
538538
n := nn.(*ir.CallExpr)
539539
typecheck.AssertFixedCall(n)
540540

541-
if isFuncPCIntrinsic(n) && isIfaceOfFunc(n.Args[0]) {
541+
if ir.IsFuncPCIntrinsic(n) && isIfaceOfFunc(n.Args[0]) {
542542
// For internal/abi.FuncPCABIxxx(fn), if fn is a defined function,
543543
// do not introduce temporaries here, so it is easier to rewrite it
544544
// to symbol address reference later in walk.
@@ -1500,16 +1500,6 @@ func (o *orderState) as2ok(n *ir.AssignListStmt) {
15001500
o.stmt(typecheck.Stmt(as))
15011501
}
15021502

1503-
// isFuncPCIntrinsic returns whether n is a direct call of internal/abi.FuncPCABIxxx functions.
1504-
func isFuncPCIntrinsic(n *ir.CallExpr) bool {
1505-
if n.Op() != ir.OCALLFUNC || n.X.Op() != ir.ONAME {
1506-
return false
1507-
}
1508-
fn := n.X.(*ir.Name).Sym()
1509-
return (fn.Name == "FuncPCABI0" || fn.Name == "FuncPCABIInternal") &&
1510-
fn.Pkg.Path == "internal/abi"
1511-
}
1512-
15131503
// isIfaceOfFunc returns whether n is an interface conversion from a direct reference of a func.
15141504
func isIfaceOfFunc(n ir.Node) bool {
15151505
return n.Op() == ir.OCONVIFACE && n.(*ir.ConvExpr).X.Op() == ir.ONAME && n.(*ir.ConvExpr).X.(*ir.Name).Class == ir.PFUNC

test/fixedbugs/issue51913.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// run
2+
3+
// Copyright 2022 The Go Authors. All rights reserved.
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file.
6+
7+
package main
8+
9+
var _ = func() int {
10+
a = false
11+
return 0
12+
}()
13+
14+
var a = true
15+
var b = a
16+
17+
func main() {
18+
if b {
19+
panic("FAIL")
20+
}
21+
}

0 commit comments

Comments
 (0)