go.tools/go/ssa: add a flag for selecting bare init functions
Bare init functions omit calls to dependent init functions and the use of an init guard. They are useful in cases where the client uses a different calling convention for init functions, or cases where it is easier for a client to analyze bare init functions. LGTM=adonovan R=adonovan CC=golang-codereviews, iant https://golang.org/cl/78780043
This commit is contained in:
parent
f032426134
commit
4a6efa0a34
|
|
@ -29,6 +29,7 @@ S log [S]ource locations as SSA builder progresses.
|
|||
G use binary object files from gc to provide imports (no code).
|
||||
L build distinct packages seria[L]ly instead of in parallel.
|
||||
N build [N]aive SSA form: don't replace local loads/stores with registers.
|
||||
I build bare [I]nit functions: no init guards or calls to dependent inits.
|
||||
`)
|
||||
|
||||
var testFlag = flag.Bool("test", false, "Loads test code (*_test.go) for imported packages.")
|
||||
|
|
@ -117,6 +118,8 @@ func doMain() error {
|
|||
conf.SourceImports = false
|
||||
case 'L':
|
||||
mode |= ssa.BuildSerially
|
||||
case 'I':
|
||||
mode |= ssa.BareInits
|
||||
default:
|
||||
return fmt.Errorf("unknown -build option: '%c'", c)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2171,25 +2171,29 @@ func (p *Package) Build() {
|
|||
init := p.init
|
||||
init.startBody()
|
||||
|
||||
// Make init() skip if package is already initialized.
|
||||
initguard := p.Var("init$guard")
|
||||
doinit := init.newBasicBlock("init.start")
|
||||
done := init.newBasicBlock("init.done")
|
||||
emitIf(init, emitLoad(init, initguard), done, doinit)
|
||||
init.currentBlock = doinit
|
||||
emitStore(init, initguard, vTrue)
|
||||
var done *BasicBlock
|
||||
|
||||
// Call the init() function of each package we import.
|
||||
for _, pkg := range p.info.Pkg.Imports() {
|
||||
prereq := p.Prog.packages[pkg]
|
||||
if prereq == nil {
|
||||
panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Object.Path(), pkg.Path()))
|
||||
if p.Prog.mode&BareInits == 0 {
|
||||
// Make init() skip if package is already initialized.
|
||||
initguard := p.Var("init$guard")
|
||||
doinit := init.newBasicBlock("init.start")
|
||||
done = init.newBasicBlock("init.done")
|
||||
emitIf(init, emitLoad(init, initguard), done, doinit)
|
||||
init.currentBlock = doinit
|
||||
emitStore(init, initguard, vTrue)
|
||||
|
||||
// Call the init() function of each package we import.
|
||||
for _, pkg := range p.info.Pkg.Imports() {
|
||||
prereq := p.Prog.packages[pkg]
|
||||
if prereq == nil {
|
||||
panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Object.Path(), pkg.Path()))
|
||||
}
|
||||
var v Call
|
||||
v.Call.Value = prereq.init
|
||||
v.Call.pos = init.pos
|
||||
v.setType(types.NewTuple())
|
||||
init.emit(&v)
|
||||
}
|
||||
var v Call
|
||||
v.Call.Value = prereq.init
|
||||
v.Call.pos = init.pos
|
||||
v.setType(types.NewTuple())
|
||||
init.emit(&v)
|
||||
}
|
||||
|
||||
var b builder
|
||||
|
|
@ -2233,8 +2237,10 @@ func (p *Package) Build() {
|
|||
}
|
||||
|
||||
// Finish up init().
|
||||
emitJump(init, done)
|
||||
init.currentBlock = done
|
||||
if p.Prog.mode&BareInits == 0 {
|
||||
emitJump(init, done)
|
||||
init.currentBlock = done
|
||||
}
|
||||
init.emit(new(Return))
|
||||
init.finishBody()
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
package ssa_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
|
@ -238,3 +239,81 @@ func TestTypesWithMethodSets(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tests that synthesized init functions are correctly formed.
|
||||
// Bare init functions omit calls to dependent init functions and the use of
|
||||
// an init guard. They are useful in cases where the client uses a different
|
||||
// calling convention for init functions, or cases where it is easier for a
|
||||
// client to analyze bare init functions. Both of these aspects are used by
|
||||
// the llgo compiler for simpler integration with gccgo's runtime library,
|
||||
// and to simplify the analysis whereby it deduces which stores to globals
|
||||
// can be lowered to global initializers.
|
||||
func TestInit(t *testing.T) {
|
||||
tests := []struct {
|
||||
mode ssa.BuilderMode
|
||||
input, want string
|
||||
}{
|
||||
{0, `package A; import _ "errors"; var i int = 42`,
|
||||
`# Name: A.init
|
||||
# Package: A
|
||||
# Synthetic: package initializer
|
||||
func init():
|
||||
0: entry P:0 S:2
|
||||
t0 = *init$guard bool
|
||||
if t0 goto 2 else 1
|
||||
1: init.start P:1 S:1
|
||||
*init$guard = true:bool
|
||||
t1 = errors.init() ()
|
||||
*i = 42:int
|
||||
jump 2
|
||||
2: init.done P:2 S:0
|
||||
return
|
||||
|
||||
`},
|
||||
{ssa.BareInits, `package B; import _ "errors"; var i int = 42`,
|
||||
`# Name: B.init
|
||||
# Package: B
|
||||
# Synthetic: package initializer
|
||||
func init():
|
||||
0: entry P:0 S:0
|
||||
*i = 42:int
|
||||
return
|
||||
|
||||
`},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Create a single-file main package.
|
||||
var conf loader.Config
|
||||
f, err := conf.ParseFile("<input>", test.input)
|
||||
if err != nil {
|
||||
t.Errorf("test %q: %s", test.input[:15], err)
|
||||
continue
|
||||
}
|
||||
conf.CreateFromFiles(f.Name.Name, f)
|
||||
|
||||
iprog, err := conf.Load()
|
||||
if err != nil {
|
||||
t.Errorf("test 'package %s': Load: %s", f.Name.Name, err)
|
||||
continue
|
||||
}
|
||||
prog := ssa.Create(iprog, test.mode)
|
||||
mainPkg := prog.Package(iprog.Created[0].Pkg)
|
||||
prog.BuildAll()
|
||||
initFunc := mainPkg.Func("init")
|
||||
if initFunc == nil {
|
||||
t.Errorf("test 'package %s': no init function", f.Name.Name)
|
||||
continue
|
||||
}
|
||||
|
||||
var initbuf bytes.Buffer
|
||||
_, err = initFunc.WriteTo(&initbuf)
|
||||
if err != nil {
|
||||
t.Errorf("test 'package %s': WriteTo: %s", f.Name.Name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if initbuf.String() != test.want {
|
||||
t.Errorf("test 'package %s': got %q, want %q", f.Name.Name, initbuf.String(), test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ const (
|
|||
NaiveForm // Build naïve SSA form: don't replace local loads/stores with registers
|
||||
BuildSerially // Build packages serially, not in parallel.
|
||||
GlobalDebug // Enable debug info for all packages
|
||||
BareInits // Build init functions without guards or calls to dependent inits
|
||||
)
|
||||
|
||||
// Create returns a new SSA Program. An SSA Package is created for
|
||||
|
|
@ -222,13 +223,15 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package {
|
|||
}
|
||||
}
|
||||
|
||||
// Add initializer guard variable.
|
||||
initguard := &Global{
|
||||
Pkg: p,
|
||||
name: "init$guard",
|
||||
typ: types.NewPointer(tBool),
|
||||
if prog.mode&BareInits == 0 {
|
||||
// Add initializer guard variable.
|
||||
initguard := &Global{
|
||||
Pkg: p,
|
||||
name: "init$guard",
|
||||
typ: types.NewPointer(tBool),
|
||||
}
|
||||
p.Members[initguard.Name()] = initguard
|
||||
}
|
||||
p.Members[initguard.Name()] = initguard
|
||||
|
||||
if prog.mode&GlobalDebug != 0 {
|
||||
p.SetDebugMode(true)
|
||||
|
|
|
|||
Loading…
Reference in New Issue