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)