From b1678e508bf04b32fcd8153d09c39ff25b51d287 Mon Sep 17 00:00:00 2001 From: Russ Cox Date: Sun, 13 Nov 2022 09:22:35 -0500 Subject: [PATCH] cmd/compile: handle simple inlined calls in staticinit Global variable initializers like var myErr error = &myError{"msg"} have been converted to statically initialized data from the earliest days of Go: there is no init-time execution or allocation for that line of code. But if the expression is moved into an inlinable function, the static initialization no longer happens. That is, this code has always executed and allocated at init time, even after we added inlining to the compiler, which should in theory make this code equivalent to the original: func NewError(s string) error { return &myError{s} } var myErr2 = NewError("msg") This CL makes the static initialization rewriter understand inlined functions consisting of a single return statement, like in this example, so that myErr2 can be implemented as statically initialized data too, just like myErr, with no init-time execution or allocation. A real example of code that benefits from this rewrite is all globally declared errors created with errors.New, like package io var EOF = errors.New("EOF") Package io no longer has to allocate and initialize EOF each time a program starts. Another example of code that benefits is any globally declared godebug setting (using the API from CL 449504), like package http var http2server = godebug.New("http2server") These are no longer allocated and initialized at program startup either. The list of functions that are inlined into static initializers when compiling std and cmd (along with how many times each occurs) is: cmd/compile/internal/ssa.StringToAux (3) cmd/compile/internal/walk.mkmapnames (4) errors.New (360) go/ast.NewIdent (1) go/constant.MakeBool (4) go/constant.MakeInt64 (3) image.NewUniform (4) image/color.ModelFunc (11) internal/godebug.New (12) vendor/golang.org/x/text/unicode/bidi.newBidiTrie (1) vendor/golang.org/x/text/unicode/norm.newNfcTrie (1) vendor/golang.org/x/text/unicode/norm.newNfkcTrie (1) For the cmd/go binary, this CL cuts the number of init-time allocations from about 1920 to about 1620 (a 15% reduction). The total executable code footprint of init functions is reduced by 24kB, from 137kB to 113kB (an 18% reduction). The overall binary size is reduced by 45kB, from 15.335MB to 15.290MB (a 0.3% reduction). (The binary size savings is larger than the executable code savings because every byte of executable code also requires corresponding runtime tables for unwinding, source-line mapping, and so on.) Also merge test/sinit_run.go, which had stopped testing anything at all as of CL 161337 (Feb 2019) and initempty.go into a new test noinit.go. Fixes #30820. Change-Id: I52f7275b1ac2a0a32e22c29f9095071c7b1fac20 Reviewed-on: https://go-review.googlesource.com/c/go/+/450136 Reviewed-by: Cherry Mui Reviewed-by: Cuong Manh Le Reviewed-by: Joedian Reid Reviewed-by: Keith Randall Reviewed-by: Than McIntosh Auto-Submit: Russ Cox TryBot-Result: Gopher Robot Run-TryBot: Russ Cox --- src/cmd/compile/internal/base/flag.go | 4 +- src/cmd/compile/internal/ssagen/phi.go | 2 +- src/cmd/compile/internal/staticinit/sched.go | 368 +++++++++++++++---- test/initempty.go | 40 -- test/initialize.go | 87 +++-- test/inline.go | 3 + test/{sinit.go => noinit.go} | 82 ++++- test/sinit_run.go | 45 --- 8 files changed, 429 insertions(+), 202 deletions(-) delete mode 100644 test/initempty.go rename test/{sinit.go => noinit.go} (80%) delete mode 100644 test/sinit_run.go 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) - } -}