]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/compile: handle simple inlined calls in staticinit
authorRuss Cox <rsc@golang.org>
Sun, 13 Nov 2022 14:22:35 +0000 (09:22 -0500)
committerGopher Robot <gobot@golang.org>
Wed, 16 Nov 2022 04:04:52 +0000 (04:04 +0000)
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 <cherryyz@google.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Joedian Reid <joedian@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
Auto-Submit: Russ Cox <rsc@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Russ Cox <rsc@golang.org>

src/cmd/compile/internal/base/flag.go
src/cmd/compile/internal/ssagen/phi.go
src/cmd/compile/internal/staticinit/sched.go
test/initempty.go [deleted file]
test/initialize.go
test/inline.go
test/noinit.go [moved from test/sinit.go with 80% similarity]
test/sinit_run.go [deleted file]

index eb346e29fc2713b9150a2b9df153b6fd1eb23990..f1685104b1d4e203c4d98ff5b743170e193c4e0c 100644 (file)
@@ -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`\""
index 01ad211282cf3436310e49a325ec8586f8147351..3e31ac7fd61fad0ff26aa3fc0368725433c6a4be 100644 (file)
@@ -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.
index e9b97e6c87e9e70f651909f1273a22ba08689dfa..8ad340c046df8e0f3507fd732c0c447918be6b2e 100644 (file)
@@ -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
 }
 
-// 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 {
@@ -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 (file)
index 60bd9fb..0000000
+++ /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 {
-       }
-}
index 1307e0209615b9355a44a76632119d72d9f748e7..bbf73d9464e96154baa8e7a3ef802b2b50b73ae1 100644 (file)
@@ -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 {
index 04ba16858fa1bcb61a2b892ffc1b316333f85209..cf2cd8cd60326e537ce28188feba0783e5189b88 100644 (file)
@@ -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
similarity index 80%
rename from test/sinit.go
rename to test/noinit.go
index df4d50d36769ce0f9649f7f3855b156f39eeaaf5..8bcda1a5ce51a95f8456b6112fcabd5f703eb517 100644 (file)
@@ -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 (file)
index e01502b..0000000
+++ /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)
-       }
-}