From 4a6efa0a34caad81d854f18237c1c488887cdfa0 Mon Sep 17 00:00:00 2001 From: Peter Collingbourne Date: Mon, 16 Jun 2014 21:34:51 -0400 Subject: [PATCH] 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 --- cmd/ssadump/main.go | 3 ++ go/ssa/builder.go | 44 +++++++++++++---------- go/ssa/builder_test.go | 79 ++++++++++++++++++++++++++++++++++++++++++ go/ssa/create.go | 15 ++++---- 4 files changed, 116 insertions(+), 25 deletions(-) diff --git a/cmd/ssadump/main.go b/cmd/ssadump/main.go index 484ceee5..773b573d 100644 --- a/cmd/ssadump/main.go +++ b/cmd/ssadump/main.go @@ -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) } diff --git a/go/ssa/builder.go b/go/ssa/builder.go index 532a7993..12c31496 100644 --- a/go/ssa/builder.go +++ b/go/ssa/builder.go @@ -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() diff --git a/go/ssa/builder_test.go b/go/ssa/builder_test.go index d69ca85c..9b969071 100644 --- a/go/ssa/builder_test.go +++ b/go/ssa/builder_test.go @@ -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("", 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) + } + } +} diff --git a/go/ssa/create.go b/go/ssa/create.go index 7e3b1f8f..e3191f04 100644 --- a/go/ssa/create.go +++ b/go/ssa/create.go @@ -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)