1
0
mirror of https://github.com/golang/go synced 2024-07-01 07:56:09 +00:00

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 <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>
This commit is contained in:
Russ Cox 2022-11-13 09:22:35 -05:00 committed by Gopher Robot
parent cb5534c1c6
commit b1678e508b
8 changed files with 429 additions and 202 deletions

View 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`\""

View 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.

View 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
}
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)
}

View File

@ -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 {
}
}

View 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 {

View 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

View 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")
}
}

View File

@ -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)
}
}