diff --git a/src/cmd/compile/internal/base/flag.go b/src/cmd/compile/internal/base/flag.go index eb346e29fc..f1685104b1 100644 --- a/src/cmd/compile/internal/base/flag.go +++ b/src/cmd/compile/internal/base/flag.go @@ -80,8 +80,8 @@ type CmdFlags struct { LowerV *bool "help:\"increase debug verbosity\"" // Special characters - Percent int "flag:\"%\" help:\"debug non-static initializers\"" - CompilingRuntime bool "flag:\"+\" help:\"compiling runtime\"" + Percent CountFlag "flag:\"%\" help:\"debug non-static initializers\"" + CompilingRuntime bool "flag:\"+\" help:\"compiling runtime\"" // Longer names AsmHdr string "help:\"write assembly header to `file`\"" diff --git a/src/cmd/compile/internal/ssagen/phi.go b/src/cmd/compile/internal/ssagen/phi.go index 01ad211282..3e31ac7fd6 100644 --- a/src/cmd/compile/internal/ssagen/phi.go +++ b/src/cmd/compile/internal/ssagen/phi.go @@ -483,7 +483,7 @@ loop: var_ := v.Aux.(fwdRefAux).N if b == s.f.Entry { // No variable should be live at entry. - s.s.Fatalf("Value live at entry. It shouldn't be. func %s, node %v, value %v", s.f.Name, var_, v) + s.s.Fatalf("value %v (%v) incorrectly live at entry", var_, v) } if !s.reachable[b.ID] { // This block is dead. diff --git a/src/cmd/compile/internal/staticinit/sched.go b/src/cmd/compile/internal/staticinit/sched.go index e9b97e6c87..8ad340c046 100644 --- a/src/cmd/compile/internal/staticinit/sched.go +++ b/src/cmd/compile/internal/staticinit/sched.go @@ -48,7 +48,7 @@ func (s *Schedule) append(n ir.Node) { func (s *Schedule) StaticInit(n ir.Node) { if !s.tryStaticInit(n) { if base.Flag.Percent != 0 { - ir.Dump("nonstatic", n) + ir.Dump("StaticInit failed", n) } s.append(n) } @@ -364,9 +364,15 @@ func (s *Schedule) StaticAssign(l *ir.Name, loff int64, r ir.Node, typ *types.Ty } return true + + case ir.OINLCALL: + r := r.(*ir.InlinedCallExpr) + return s.staticAssignInlinedCall(l, loff, r, typ) } - //dump("not static", r); + if base.Flag.Percent != 0 { + ir.Dump("not static", r) + } return false } @@ -443,6 +449,163 @@ func (s *Schedule) addvalue(p *Plan, xoffset int64, n ir.Node) { p.E = append(p.E, Entry{Xoffset: xoffset, Expr: n}) } +func (s *Schedule) staticAssignInlinedCall(l *ir.Name, loff int64, call *ir.InlinedCallExpr, typ *types.Type) bool { + // Handle the special case of an inlined call of + // a function body with a single return statement, + // which turns into a single assignment plus a goto. + // + // For example code like this: + // + // type T struct{ x int } + // func F(x int) *T { return &T{x} } + // var Global = F(400) + // + // turns into IR like this: + // + // INLCALL-init + // . AS2-init + // . . DCL # x.go:18:13 + // . . . NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13 + // . AS2 Def tc(1) # x.go:18:13 + // . AS2-Lhs + // . . NAME-p.x Class:PAUTO Offset:0 InlFormal OnStack Used int tc(1) # x.go:14:9,x.go:18:13 + // . AS2-Rhs + // . . LITERAL-400 int tc(1) # x.go:18:14 + // . INLMARK Index:1 # +x.go:18:13 + // INLCALL PTR-*T tc(1) # x.go:18:13 + // INLCALL-Body + // . BLOCK tc(1) # x.go:18:13 + // . BLOCK-List + // . . DCL tc(1) # x.go:18:13 + // . . . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 + // . . AS2 tc(1) # x.go:18:13 + // . . AS2-Lhs + // . . . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 + // . . AS2-Rhs + // . . . INLINED RETURN ARGUMENT HERE + // . . GOTO p..i1 tc(1) # x.go:18:13 + // . LABEL p..i1 # x.go:18:13 + // INLCALL-ReturnVars + // . NAME-p.~R0 Class:PAUTO Offset:0 OnStack Used PTR-*T tc(1) # x.go:18:13 + // + // In non-unified IR, the tree is slightly different: + // - if there are no arguments to the inlined function, + // the INLCALL-init omits the AS2. + // - the DCL inside BLOCK is on the AS2's init list, + // not its own statement in the top level of the BLOCK. + // + // If the init values are side-effect-free and each either only + // appears once in the function body or is safely repeatable, + // then we inline the value expressions into the return argument + // and then call StaticAssign to handle that copy. + // + // This handles simple cases like + // + // var myError = errors.New("mine") + // + // where errors.New is + // + // func New(text string) error { + // return &errorString{text} + // } + // + // We could make things more sophisticated but this kind of initializer + // is the most important case for us to get right. + + init := call.Init() + var as2init *ir.AssignListStmt + if len(init) == 2 && init[0].Op() == ir.OAS2 && init[1].Op() == ir.OINLMARK { + as2init = init[0].(*ir.AssignListStmt) + } else if len(init) == 1 && init[0].Op() == ir.OINLMARK { + as2init = new(ir.AssignListStmt) + } else { + return false + } + if len(call.Body) != 2 || call.Body[0].Op() != ir.OBLOCK || call.Body[1].Op() != ir.OLABEL { + return false + } + label := call.Body[1].(*ir.LabelStmt).Label + block := call.Body[0].(*ir.BlockStmt) + list := block.List + var dcl *ir.Decl + if len(list) == 3 && list[0].Op() == ir.ODCL { + dcl = list[0].(*ir.Decl) + list = list[1:] + } + if len(list) != 2 || + list[0].Op() != ir.OAS2 || + list[1].Op() != ir.OGOTO || + list[1].(*ir.BranchStmt).Label != label { + return false + } + as2body := list[0].(*ir.AssignListStmt) + if dcl == nil { + ainit := as2body.Init() + if len(ainit) != 1 || ainit[0].Op() != ir.ODCL { + return false + } + dcl = ainit[0].(*ir.Decl) + } + if len(as2body.Lhs) != 1 || as2body.Lhs[0] != dcl.X { + return false + } + + // Can't remove the parameter variables if an address is taken. + for _, v := range as2init.Lhs { + if v.(*ir.Name).Addrtaken() { + return false + } + } + // Can't move the computation of the args if they have side effects. + for _, r := range as2init.Rhs { + if AnySideEffects(r) { + return false + } + } + + // Can only substitute arg for param if param is used + // at most once or is repeatable. + count := make(map[*ir.Name]int) + for _, x := range as2init.Lhs { + count[x.(*ir.Name)] = 0 + } + ir.Visit(as2body.Rhs[0], func(n ir.Node) { + if name, ok := n.(*ir.Name); ok { + if c, ok := count[name]; ok { + count[name] = c + 1 + } + } + }) + for name, c := range count { + if c > 1 { + // Check whether corresponding initializer can be repeated. + // Something like 1 can be; make(chan int) or &T{} cannot, + // because they need to evaluate to the same result in each use. + for i, n := range as2init.Lhs { + if n == name && !canRepeat(as2init.Rhs[i]) { + return false + } + } + } + } + + // Possible static init. + // Build tree with args substituted for params and try it. + args := make(map[*ir.Name]ir.Node) + for i, v := range as2init.Lhs { + args[v.(*ir.Name)] = as2init.Rhs[i] + } + r := subst(as2body.Rhs[0], args) + ok := s.StaticAssign(l, loff, r, typ) + + if ok && base.Flag.Percent != 0 { + ir.Dump("static inlined-LEFT", l) + ir.Dump("static inlined-ORIG", call) + ir.Dump("static inlined-RIGHT", r) + } + return ok +} + // from here down is the walk analysis // of composite literals. // most of the work is to generate @@ -510,91 +673,118 @@ func StaticLoc(n ir.Node) (name *ir.Name, offset int64, ok bool) { return nil, 0, false } +func isSideEffect(n ir.Node) bool { + switch n.Op() { + // Assume side effects unless we know otherwise. + default: + return true + + // No side effects here (arguments are checked separately). + case ir.ONAME, + ir.ONONAME, + ir.OTYPE, + ir.OLITERAL, + ir.ONIL, + ir.OADD, + ir.OSUB, + ir.OOR, + ir.OXOR, + ir.OADDSTR, + ir.OADDR, + ir.OANDAND, + ir.OBYTES2STR, + ir.ORUNES2STR, + ir.OSTR2BYTES, + ir.OSTR2RUNES, + ir.OCAP, + ir.OCOMPLIT, + ir.OMAPLIT, + ir.OSTRUCTLIT, + ir.OARRAYLIT, + ir.OSLICELIT, + ir.OPTRLIT, + ir.OCONV, + ir.OCONVIFACE, + ir.OCONVNOP, + ir.ODOT, + ir.OEQ, + ir.ONE, + ir.OLT, + ir.OLE, + ir.OGT, + ir.OGE, + ir.OKEY, + ir.OSTRUCTKEY, + ir.OLEN, + ir.OMUL, + ir.OLSH, + ir.ORSH, + ir.OAND, + ir.OANDNOT, + ir.ONEW, + ir.ONOT, + ir.OBITNOT, + ir.OPLUS, + ir.ONEG, + ir.OOROR, + ir.OPAREN, + ir.ORUNESTR, + ir.OREAL, + ir.OIMAG, + ir.OCOMPLEX: + return false + + // Only possible side effect is division by zero. + case ir.ODIV, ir.OMOD: + n := n.(*ir.BinaryExpr) + if n.Y.Op() != ir.OLITERAL || constant.Sign(n.Y.Val()) == 0 { + return true + } + + // Only possible side effect is panic on invalid size, + // but many makechan and makemap use size zero, which is definitely OK. + case ir.OMAKECHAN, ir.OMAKEMAP: + n := n.(*ir.MakeExpr) + if !ir.IsConst(n.Len, constant.Int) || constant.Sign(n.Len.Val()) != 0 { + return true + } + + // Only possible side effect is panic on invalid size. + // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp). + case ir.OMAKESLICE, ir.OMAKESLICECOPY: + return true + } + return false +} + // AnySideEffects reports whether n contains any operations that could have observable side effects. func AnySideEffects(n ir.Node) bool { - return ir.Any(n, func(n ir.Node) bool { - switch n.Op() { - // Assume side effects unless we know otherwise. - default: + return ir.Any(n, isSideEffect) +} + +// canRepeat reports whether executing n multiple times has the same effect as +// assigning n to a single variable and using that variable multiple times. +func canRepeat(n ir.Node) bool { + bad := func(n ir.Node) bool { + if isSideEffect(n) { return true - - // No side effects here (arguments are checked separately). - case ir.ONAME, - ir.ONONAME, - ir.OTYPE, - ir.OLITERAL, - ir.ONIL, - ir.OADD, - ir.OSUB, - ir.OOR, - ir.OXOR, - ir.OADDSTR, - ir.OADDR, - ir.OANDAND, - ir.OBYTES2STR, - ir.ORUNES2STR, - ir.OSTR2BYTES, - ir.OSTR2RUNES, - ir.OCAP, - ir.OCOMPLIT, + } + switch n.Op() { + case ir.OMAKECHAN, + ir.OMAKEMAP, + ir.OMAKESLICE, + ir.OMAKESLICECOPY, ir.OMAPLIT, - ir.OSTRUCTLIT, - ir.OARRAYLIT, - ir.OSLICELIT, - ir.OPTRLIT, - ir.OCONV, - ir.OCONVIFACE, - ir.OCONVNOP, - ir.ODOT, - ir.OEQ, - ir.ONE, - ir.OLT, - ir.OLE, - ir.OGT, - ir.OGE, - ir.OKEY, - ir.OSTRUCTKEY, - ir.OLEN, - ir.OMUL, - ir.OLSH, - ir.ORSH, - ir.OAND, - ir.OANDNOT, ir.ONEW, - ir.ONOT, - ir.OBITNOT, - ir.OPLUS, - ir.ONEG, - ir.OOROR, - ir.OPAREN, - ir.ORUNESTR, - ir.OREAL, - ir.OIMAG, - ir.OCOMPLEX: - return false - - // Only possible side effect is division by zero. - case ir.ODIV, ir.OMOD: - n := n.(*ir.BinaryExpr) - if n.Y.Op() != ir.OLITERAL || constant.Sign(n.Y.Val()) == 0 { - return true - } - - // Only possible side effect is panic on invalid size, - // but many makechan and makemap use size zero, which is definitely OK. - case ir.OMAKECHAN, ir.OMAKEMAP: - n := n.(*ir.MakeExpr) - if !ir.IsConst(n.Len, constant.Int) || constant.Sign(n.Len.Val()) != 0 { - return true - } - - // Only possible side effect is panic on invalid size. - // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp). - case ir.OMAKESLICE, ir.OMAKESLICECOPY: + ir.OPTRLIT, + ir.OSLICELIT, + ir.OSTR2BYTES, + ir.OSTR2RUNES: return true } return false - }) + } + return !ir.Any(n, bad) } func getlit(lit ir.Node) int { @@ -607,3 +797,23 @@ func getlit(lit ir.Node) int { func isvaluelit(n ir.Node) bool { return n.Op() == ir.OARRAYLIT || n.Op() == ir.OSTRUCTLIT } + +func subst(n ir.Node, m map[*ir.Name]ir.Node) ir.Node { + var edit func(ir.Node) ir.Node + edit = func(x ir.Node) ir.Node { + switch x.Op() { + case ir.ONAME: + x := x.(*ir.Name) + if v, ok := m[x]; ok { + return ir.DeepCopy(v.Pos(), v) + } + return x + case ir.ONONAME, ir.OLITERAL, ir.ONIL, ir.OTYPE: + return x + } + x = ir.Copy(x) + ir.EditChildren(x, edit) + return typecheck.EvalConst(x) + } + return edit(n) +} diff --git a/test/initempty.go b/test/initempty.go deleted file mode 100644 index 60bd9fb35e..0000000000 --- a/test/initempty.go +++ /dev/null @@ -1,40 +0,0 @@ -// run - -// Copyright 2019 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. - -// Test that empty init functions are skipped. - -package main - -import _ "unsafe" // for go:linkname - -type initTask struct { - state uintptr - ndeps uintptr - nfns uintptr -} - -//go:linkname main_inittask main..inittask -var main_inittask initTask - -func main() { - if nfns := main_inittask.nfns; nfns != 0 { - println(nfns) - panic("unexpected init funcs") - } -} - -func init() { -} - -func init() { - if false { - } -} - -func init() { - for false { - } -} diff --git a/test/initialize.go b/test/initialize.go index 1307e02096..bbf73d9464 100644 --- a/test/initialize.go +++ b/test/initialize.go @@ -8,8 +8,10 @@ package main -import "fmt" -import "reflect" +import ( + "fmt" + "reflect" +) type S struct { A, B, C, X, Y, Z int @@ -19,43 +21,82 @@ type T struct { S } -var a1 = S { 0, 0, 0, 1, 2, 3 } -var b1 = S { X: 1, Z: 3, Y: 2 } +var a1 = S{0, 0, 0, 1, 2, 3} +var b1 = S{X: 1, Z: 3, Y: 2} -var a2 = S { 0, 0, 0, 0, 0, 0, } -var b2 = S { } +var a2 = S{0, 0, 0, 0, 0, 0} +var b2 = S{} -var a3 = T { S { 1, 2, 3, 0, 0, 0, } } -var b3 = T { S: S{ A: 1, B: 2, C: 3 } } +var a3 = T{S{1, 2, 3, 0, 0, 0}} +var b3 = T{S: S{A: 1, B: 2, C: 3}} -var a4 = &[16]byte { 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, } -var b4 = &[16]byte { 4: 1, 1, 1, 1, 12: 1, 1, } +var a4 = &[16]byte{0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0} +var b4 = &[16]byte{4: 1, 1, 1, 1, 12: 1, 1} -var a5 = &[16]byte { 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, } -var b5 = &[16]byte { 1, 4: 1, 1, 1, 1, 12: 1, 1, } +var a5 = &[16]byte{1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0} +var b5 = &[16]byte{1, 4: 1, 1, 1, 1, 12: 1, 1} -var a6 = &[16]byte { 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, } -var b6 = &[...]byte { 1, 4: 1, 1, 1, 1, 12: 1, 1, 0, 0,} +var a6 = &[16]byte{1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0} +var b6 = &[...]byte{1, 4: 1, 1, 1, 1, 12: 1, 1, 0, 0} + +func f7(ch chan int) [2]chan int { return [2]chan int{ch, ch} } + +var a7 = f7(make(chan int)) + +func f8(m map[string]string) [2]map[string]string { return [2]map[string]string{m, m} } +func m8(m [2]map[string]string) string { + m[0]["def"] = "ghi" + return m[1]["def"] +} + +var a8 = f8(make(map[string]string)) +var a9 = f8(map[string]string{"abc": "def"}) + +func f10(s *S) [2]*S { return [2]*S{s, s} } + +var a10 = f10(new(S)) +var a11 = f10(&S{X: 1}) + +func f12(b []byte) [2][]byte { return [2][]byte{b, b} } + +var a12 = f12([]byte("hello")) +var a13 = f12([]byte{1, 2, 3}) +var a14 = f12(make([]byte, 1)) + +func f15(b []rune) [2][]rune { return [2][]rune{b, b} } + +var a15 = f15([]rune("hello")) +var a16 = f15([]rune{1, 2, 3}) type Same struct { a, b interface{} } -var same = []Same { - Same{ a1, b1 }, - Same{ a2, b2 }, - Same{ a3, b3 }, - Same{ a4, b4 }, - Same{ a5, b5 }, - Same{ a6, b6 }, +var same = []Same{ + {a1, b1}, + {a2, b2}, + {a3, b3}, + {a4, b4}, + {a5, b5}, + {a6, b6}, + {a7[0] == a7[1], true}, + {m8(a8) == "ghi", true}, + {m8(a9) == "ghi", true}, + {a10[0] == a10[1], true}, + {a11[0] == a11[1], true}, + {&a12[0][0] == &a12[1][0], true}, + {&a13[0][0] == &a13[1][0], true}, + {&a14[0][0] == &a14[1][0], true}, + {&a15[0][0] == &a15[1][0], true}, + {&a16[0][0] == &a16[1][0], true}, } func main() { ok := true - for _, s := range same { + for i, s := range same { if !reflect.DeepEqual(s.a, s.b) { ok = false - fmt.Printf("not same: %v and %v\n", s.a, s.b) + fmt.Printf("#%d not same: %v and %v\n", i+1, s.a, s.b) } } if !ok { diff --git a/test/inline.go b/test/inline.go index 04ba16858f..cf2cd8cd60 100644 --- a/test/inline.go +++ b/test/inline.go @@ -10,6 +10,7 @@ package foo import ( + "errors" "runtime" "unsafe" ) @@ -55,6 +56,8 @@ func f2() int { // ERROR "can inline f2" return tmp2(0) // ERROR "inlining call to h" } +var abc = errors.New("abc") // ERROR "inlining call to errors.New" + var somethingWrong error // local closures can be inlined diff --git a/test/sinit.go b/test/noinit.go similarity index 80% rename from test/sinit.go rename to test/noinit.go index df4d50d367..8bcda1a5ce 100644 --- a/test/sinit.go +++ b/test/noinit.go @@ -1,4 +1,4 @@ -// skip +// run // Copyright 2010 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style @@ -6,13 +6,15 @@ // Test that many initializations can be done at link time and // generate no executable init functions. -// This test is run by sinit_run.go. +// Also test that trivial func init are optimized away. -package p +package main -import "unsafe" +import ( + "errors" + "unsafe" +) -// Should be no init func in the assembly. // All these initializations should be done at link time. type S struct{ a, b, c int } @@ -108,7 +110,7 @@ var ( copy_pi = pi copy_slice = slice copy_sliceInt = sliceInt - copy_hello = hello + // copy_hello = hello // static init of copied strings defeats link -X; see #34675 // Could be handled without an initialization function, but // requires special handling for "a = []byte("..."); b = a" @@ -118,12 +120,13 @@ var ( // make this special case work. copy_four, copy_five = four, five - copy_x, copy_y = x, y - copy_nilslice = nilslice - copy_nilmap = nilmap - copy_nilfunc = nilfunc - copy_nilchan = nilchan - copy_nilptr = nilptr + copy_x = x + // copy_y = y // static init of copied strings defeats link -X; see #34675 + copy_nilslice = nilslice + copy_nilmap = nilmap + copy_nilfunc = nilfunc + copy_nilchan = nilchan + copy_nilptr = nilptr ) var copy_a = a @@ -283,3 +286,58 @@ var _ Mer = (*T1)(nil) var Byte byte var PtrByte unsafe.Pointer = unsafe.Pointer(&Byte) + +var LitSXInit = &S{1, 2, 3} +var LitSAnyXInit any = &S{4, 5, 6} + +func FS(x, y, z int) *S { return &S{x, y, z} } +func FSA(x, y, z int) any { return &S{x, y, z} } +func F3(x int) *S { return &S{x, x, x} } + +var LitSCallXInit = FS(7, 8, 9) +var LitSAnyCallXInit any = FSA(10, 11, 12) + +var LitSRepeat = F3(1 + 2) + +func F0() *S { return &S{1, 2, 3} } + +var LitSNoArgs = F0() + +var myError = errors.New("mine") + +func gopherize(s string) string { return "gopher gopher gopher " + s } + +var animals = gopherize("badger") + +// These init funcs should optimize away. + +func init() { +} + +func init() { + if false { + } +} + +func init() { + for false { + } +} + +// Actual test: check for init funcs in runtime data structures. + +type initTask struct { + state uintptr + ndeps uintptr + nfns uintptr +} + +//go:linkname main_inittask main..inittask +var main_inittask initTask + +func main() { + if nfns := main_inittask.nfns; nfns != 0 { + println(nfns) + panic("unexpected init funcs") + } +} diff --git a/test/sinit_run.go b/test/sinit_run.go deleted file mode 100644 index e01502bd56..0000000000 --- a/test/sinit_run.go +++ /dev/null @@ -1,45 +0,0 @@ -// +build !nacl,!js,gc -// run - -// Copyright 2014 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. - -// Run the sinit test. - -package main - -import ( - "bytes" - "fmt" - "io/ioutil" - "os" - "os/exec" -) - -func main() { - f, err := ioutil.TempFile("", "sinit-*.o") - if err != nil { - fmt.Println(err) - os.Exit(1) - } - f.Close() - - cmd := exec.Command("go", "tool", "compile", "-p=sinit", "-o", f.Name(), "-S", "sinit.go") - out, err := cmd.CombinedOutput() - os.Remove(f.Name()) - if err != nil { - fmt.Println(string(out)) - fmt.Println(err) - os.Exit(1) - } - - if len(bytes.TrimSpace(out)) == 0 { - fmt.Println("'go tool compile -S sinit.go' printed no output") - os.Exit(1) - } - if bytes.Contains(out, []byte("initdone")) { - fmt.Println("sinit generated an init function") - os.Exit(1) - } -}