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`\""
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.
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)
}
}
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
}
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
return nil, 0, 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:
+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
+ }
- // 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 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 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.
+ // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp).
+ case ir.OMAKESLICE, ir.OMAKESLICECOPY:
+ return true
+ }
+ return false
+}
- // 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
- }
+// AnySideEffects reports whether n contains any operations that could have observable side effects.
+func AnySideEffects(n ir.Node) bool {
+ return ir.Any(n, isSideEffect)
+}
- // Only possible side effect is panic on invalid size.
- // TODO(rsc): Merge with previous case (probably breaks toolstash -cmp).
- case ir.OMAKESLICE, ir.OMAKESLICECOPY:
+// 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
+ }
+ switch n.Op() {
+ case ir.OMAKECHAN,
+ ir.OMAKEMAP,
+ ir.OMAKESLICE,
+ ir.OMAKESLICECOPY,
+ ir.OMAPLIT,
+ ir.ONEW,
+ ir.OPTRLIT,
+ ir.OSLICELIT,
+ ir.OSTR2BYTES,
+ ir.OSTR2RUNES:
return true
}
return false
- })
+ }
+ return !ir.Any(n, bad)
}
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)
+}
+++ /dev/null
-// 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 {
- }
-}
package main
-import "fmt"
-import "reflect"
+import (
+ "fmt"
+ "reflect"
+)
type S struct {
A, B, C, X, Y, Z int
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 {
package foo
import (
+ "errors"
"runtime"
"unsafe"
)
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
-// skip
+// run
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// 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 }
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"
// 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
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")
+ }
+}
+++ /dev/null
-// +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)
- }
-}