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:
Peter Collingbourne 2014-06-16 21:34:51 -04:00 committed by Alan Donovan
parent f032426134
commit 4a6efa0a34
4 changed files with 116 additions and 25 deletions

View File

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

View File

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

View File

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

View File

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