From 20186168d5994c0682d36c5e57e190cd5b81a79c Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Wed, 8 Apr 2015 16:14:19 -0400 Subject: [PATCH] go/ssa/ssautil: break ssa->loader dependency Remove all dependencies from non-test code in go/ssa to go/loader, except the deprecated Create function which will be eliminated in favor of ssautil.CreateProgram in a mechnanical followup. Add Examples of two main use cases of SSA construction: loading a complete program from source; and building a single package, loading its dependencies from import data. Add tests to ssautil of the two load functions. Suggestions welcome for better names. Planned follow-ups: - replace all references to ssa.Create with ssautil.CreateProgram and eliminate it. - eliminate support in go/loader for the ImportBinary flag, and the PackageCreated hook which is no longer needed since clients can create the package themselves (see Example). Step 1 to fixing issue 9955. Change-Id: I4e64df67fcd5b7f0c0388047e06cea247fddfec5 Reviewed-on: https://go-review.googlesource.com/8669 Reviewed-by: Robert Griesemer --- go/loader/loader.go | 4 ++ go/ssa/builder.go | 11 +++- go/ssa/create.go | 61 +++++++++++--------- go/ssa/example_test.go | 108 +++++++++++++++++++++++------------- go/ssa/source.go | 2 +- go/ssa/ssa.go | 8 +-- go/ssa/ssautil/load.go | 91 ++++++++++++++++++++++++++++++ go/ssa/ssautil/load_test.go | 60 ++++++++++++++++++++ 8 files changed, 271 insertions(+), 74 deletions(-) create mode 100644 go/ssa/ssautil/load.go create mode 100644 go/ssa/ssautil/load_test.go diff --git a/go/loader/loader.go b/go/loader/loader.go index c49ea6d6652..4dd84852461 100644 --- a/go/loader/loader.go +++ b/go/loader/loader.go @@ -257,6 +257,8 @@ type Config struct { // // NB: there is a bug when loading multiple initial packages with // this flag enabled: https://github.com/golang/go/issues/9955. + // + // THIS FEATURE IS DEPRECATED and will be removed shortly (Apr 2015). ImportFromBinary bool // If Build is non-nil, it is used to locate source packages. @@ -321,6 +323,8 @@ type Config struct { // the package scope, for example. // // It must be safe to call concurrently from multiple goroutines. + // + // THIS FEATURE IS DEPRECATED and will be removed shortly (Apr 2015). PackageCreated func(*types.Package) } diff --git a/go/ssa/builder.go b/go/ssa/builder.go index d4c41d4a249..5b8ce0eadd7 100644 --- a/go/ssa/builder.go +++ b/go/ssa/builder.go @@ -2212,8 +2212,13 @@ func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) { // BuildAll calls Package.Build() for each package in prog. // Building occurs in parallel unless the BuildSerially mode flag was set. // +// BuildAll is intended for whole-program analysis; a typical compiler +// need only build a single package. +// // BuildAll is idempotent and thread-safe. // +// TODO(adonovan): rename to Build. +// func (prog *Program) BuildAll() { var wg sync.WaitGroup for _, p := range prog.packages { @@ -2245,7 +2250,7 @@ func (p *Package) Build() { if p.info == nil { return // synthetic package, e.g. "testmain" } - if len(p.info.Files) == 0 { + if p.files == nil { p.info = nil return // package loaded from export data } @@ -2276,7 +2281,7 @@ func (p *Package) Build() { emitStore(init, initguard, vTrue, token.NoPos) // Call the init() function of each package we import. - for _, pkg := range p.info.Pkg.Imports() { + for _, pkg := range p.Object.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())) @@ -2321,7 +2326,7 @@ func (p *Package) Build() { // Build all package-level functions, init functions // and methods, including unreachable/blank ones. // We build them in source order, but it's not significant. - for _, file := range p.info.Files { + for _, file := range p.files { for _, decl := range file.Decls { if decl, ok := decl.(*ast.FuncDecl); ok { b.buildFuncDecl(p, decl) diff --git a/go/ssa/create.go b/go/ssa/create.go index 0c25bf54365..c13faceea3c 100644 --- a/go/ssa/create.go +++ b/go/ssa/create.go @@ -19,17 +19,13 @@ import ( "golang.org/x/tools/go/types/typeutil" ) -// Create returns a new SSA Program. An SSA Package is created for -// each transitively error-free package of iprog. -// -// Code for bodies of functions is not built until Build() is called -// on the result. +// NewProgram returns a new SSA Program. // // mode controls diagnostics and checking during SSA construction. // -func Create(iprog *loader.Program, mode BuilderMode) *Program { +func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { prog := &Program{ - Fset: iprog.Fset, + Fset: fset, imported: make(map[string]*Package), packages: make(map[*types.Package]*Package), thunks: make(map[selectionKey]*Function), @@ -41,11 +37,26 @@ func Create(iprog *loader.Program, mode BuilderMode) *Program { prog.methodSets.SetHasher(h) prog.canon.SetHasher(h) - for _, info := range iprog.AllPackages { - // TODO(adonovan): relax this constraint if the - // program contains only "soft" errors. + return prog +} + +// Create returns a new SSA Program. An SSA Package is created for +// each transitively error-free package of lprog. +// +// Code for bodies of functions is not built until BuildAll() is called +// on the result. +// +// mode controls diagnostics and checking during SSA construction. +// +// TODO(adonovan): move this to ssautil and breaking the go/ssa -> +// go/loader dependency. +// +func Create(lprog *loader.Program, mode BuilderMode) *Program { + prog := NewProgram(lprog.Fset, mode) + + for _, info := range lprog.AllPackages { if info.TransitivelyErrorFree { - prog.CreatePackage(info) + prog.CreatePackage(info.Pkg, info.Files, &info.Info, info.Importable) } } @@ -160,26 +171,24 @@ func membersFromDecl(pkg *Package, decl ast.Decl) { } } -// CreatePackage constructs and returns an SSA Package from an -// error-free package described by info, and populates its Members -// mapping. +// CreatePackage constructs and returns an SSA Package from the +// specified type-checked, error-free file ASTs, and populates its +// Members mapping. // -// Repeated calls with the same info return the same Package. +// importable determines whether this package should be returned by a +// subsequent call to ImportedPackage(pkg.Path()). // // The real work of building SSA form for each function is not done // until a subsequent call to Package.Build(). // -func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package { - if p := prog.packages[info.Pkg]; p != nil { - return p // already loaded - } - +func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package { p := &Package{ Prog: prog, Members: make(map[string]Member), values: make(map[types.Object]Value), - Object: info.Pkg, - info: info, // transient (CREATE and BUILD phases) + Object: pkg, + info: info, // transient (CREATE and BUILD phases) + files: files, // transient (CREATE and BUILD phases) } // Add init() function. @@ -194,9 +203,9 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package { // CREATE phase. // Allocate all package members: vars, funcs, consts and types. - if len(info.Files) > 0 { + if len(files) > 0 { // Go source package. - for _, file := range info.Files { + for _, file := range files { for _, decl := range file.Decls { membersFromDecl(p, decl) } @@ -238,8 +247,8 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package { printMu.Unlock() } - if info.Importable { - prog.imported[info.Pkg.Path()] = p + if importable { + prog.imported[p.Object.Path()] = p } prog.packages[p.Object] = p diff --git a/go/ssa/example_test.go b/go/ssa/example_test.go index 5253c882c89..9e6bcfda02c 100644 --- a/go/ssa/example_test.go +++ b/go/ssa/example_test.go @@ -8,28 +8,17 @@ import ( "fmt" "os" + "go/ast" + "go/parser" + "go/token" + "golang.org/x/tools/go/loader" "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + "golang.org/x/tools/go/types" ) -// This program demonstrates how to run the SSA builder on a "Hello, -// World!" program and shows the printed representation of packages, -// functions and instructions. -// -// Within the function listing, the name of each BasicBlock such as -// ".0.entry" is printed left-aligned, followed by the block's -// Instructions. -// -// For each instruction that defines an SSA virtual register -// (i.e. implements Value), the type of that value is shown in the -// right column. -// -// Build and run the ssadump.go program if you want a standalone tool -// with similar functionality. It is located at -// golang.org/x/tools/cmd/ssadump. -// -func Example() { - const hello = ` +const hello = ` package main import "fmt" @@ -40,49 +29,64 @@ func main() { fmt.Println(message) } ` - var conf loader.Config - // Parse the input file. - file, err := conf.ParseFile("hello.go", hello) +// This program demonstrates how to run the SSA builder on a single +// package of one or more already-parsed files. Its dependencies are +// loaded from compiler export data. This is what you'd typically use +// for a compiler; it does not depend on golang.org/x/tools/go/loader. +// +// It shows the printed representation of packages, functions, and +// instructions. Within the function listing, the name of each +// BasicBlock such as ".0.entry" is printed left-aligned, followed by +// the block's Instructions. +// +// For each instruction that defines an SSA virtual register +// (i.e. implements Value), the type of that value is shown in the +// right column. +// +// Build and run the ssadump.go program if you want a standalone tool +// with similar functionality. It is located at +// golang.org/x/tools/cmd/ssadump. +// +func ExampleLoadPackage() { + // Parse the source files. + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hello.go", hello, parser.ParseComments) if err != nil { fmt.Print(err) // parse error return } + files := []*ast.File{f} - // Create single-file main package. - conf.CreateFromFiles("main", file) + // Create the type-checker's package. + pkg := types.NewPackage("hello", "") - // Load the main package and its dependencies. - iprog, err := conf.Load() + // Type-check the package, load dependencies. + // Create and build the SSA program. + hello, _, err := ssautil.LoadPackage( + new(types.Config), fset, pkg, files, ssa.SanityCheckFunctions) if err != nil { fmt.Print(err) // type error in some package return } - // Create SSA-form program representation. - prog := ssa.Create(iprog, ssa.SanityCheckFunctions) - mainPkg := prog.Package(iprog.Created[0].Pkg) - // Print out the package. - mainPkg.WriteTo(os.Stdout) - - // Build SSA code for bodies of functions in mainPkg. - mainPkg.Build() + hello.WriteTo(os.Stdout) // Print out the package-level functions. - mainPkg.Func("init").WriteTo(os.Stdout) - mainPkg.Func("main").WriteTo(os.Stdout) + hello.Func("init").WriteTo(os.Stdout) + hello.Func("main").WriteTo(os.Stdout) // Output: // - // package main: + // package hello: // func init func() // var init$guard bool // func main func() // const message message = "Hello, World!":untyped string // - // # Name: main.init - // # Package: main + // # Name: hello.init + // # Package: hello // # Synthetic: package initializer // func init(): // 0: entry P:0 S:2 @@ -95,8 +99,8 @@ func main() { // 2: init.done P:2 S:0 // return // - // # Name: main.main - // # Package: main + // # Name: hello.main + // # Package: hello // # Location: hello.go:8:6 // func main(): // 0: entry P:0 S:0 @@ -108,3 +112,27 @@ func main() { // t4 = fmt.Println(t3...) (n int, err error) // return } + +// This program shows how to load a main package (cmd/nm) and all its +// dependencies from source, using the loader, and then build SSA code +// for the entire program. This is what you'd typically use for a +// whole-program analysis. +// +func ExampleLoadProgram() { + // Load cmd/nm and its dependencies. + var conf loader.Config + conf.Import("cmd/nm") + lprog, err := conf.Load() + if err != nil { + fmt.Print(err) // type error in some package + return + } + + // Create SSA-form program representation. + prog := ssautil.CreateProgram(lprog, ssa.SanityCheckFunctions) + + // Build SSA code for the entire cmd/nm program. + prog.BuildAll() + + // Output: +} diff --git a/go/ssa/source.go b/go/ssa/source.go index 0566d23e810..84e6f1d2c23 100644 --- a/go/ssa/source.go +++ b/go/ssa/source.go @@ -140,7 +140,7 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function { // - f was not built with debug information; or // - e is a constant expression. (For efficiency, no debug // information is stored for constants. Use -// loader.PackageInfo.ValueOf(e) instead.) +// go/types.Info.Types[e].Value instead.) // - e is a reference to nil or a built-in function. // - the value was optimised away. // diff --git a/go/ssa/ssa.go b/go/ssa/ssa.go index 75fdff8da9e..01ee46ac888 100644 --- a/go/ssa/ssa.go +++ b/go/ssa/ssa.go @@ -14,7 +14,6 @@ import ( "sync" "golang.org/x/tools/go/exact" - "golang.org/x/tools/go/loader" "golang.org/x/tools/go/types" "golang.org/x/tools/go/types/typeutil" ) @@ -54,9 +53,10 @@ type Package struct { // The following fields are set transiently, then cleared // after building. - started int32 // atomically tested and set at start of build phase - ninit int32 // number of init functions - info *loader.PackageInfo // package ASTs and type information + started int32 // atomically tested and set at start of build phase + ninit int32 // number of init functions + info *types.Info // package type information + files []*ast.File // package ASTs } // A Member is a member of a Go package, implemented by *NamedConst, diff --git a/go/ssa/ssautil/load.go b/go/ssa/ssautil/load.go new file mode 100644 index 00000000000..f3090075863 --- /dev/null +++ b/go/ssa/ssautil/load.go @@ -0,0 +1,91 @@ +// Copyright 2015 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. + +package ssautil + +// This file defines utility functions for constructing programs in SSA form. + +import ( + "go/ast" + "go/token" + + "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/types" +) + +// CreateProgram returns a new program in SSA form, given a program +// loaded from source. An SSA package is created for each transitively +// error-free package of lprog. +// +// Code for bodies of functions is not built until BuildAll() is called +// on the result. +// +// mode controls diagnostics and checking during SSA construction. +// +func CreateProgram(lprog *loader.Program, mode ssa.BuilderMode) *ssa.Program { + // TODO(adonovan): inline and delete ssa.Create. + return ssa.Create(lprog, mode) +} + +// LoadPackage builds SSA code for a single package. +// +// It populates pkg by type-checking the specified file ASTs. All +// dependencies are loaded using the importer specified by tc, which +// typically loads compiler export data; SSA code cannot be built for +// those packages. LoadPackage then constructs an ssa.Program with all +// dependency packages created, and builds and returns the SSA package +// corresponding to pkg. +// +// The caller must have set pkg.Path() to the import path. +// +// The operation fails if there were any type-checking or import errors. +// +// LoadPackage modifies the tc.Import field. +// +func LoadPackage(tc *types.Config, fset *token.FileSet, pkg *types.Package, files []*ast.File, mode ssa.BuilderMode) (*ssa.Package, *types.Info, error) { + if fset == nil { + panic("no token.FileSet") + } + if pkg.Path() == "" { + panic("no package path") + } + + // client's effective import function + clientImport := tc.Import + if clientImport == nil { + clientImport = types.DefaultImport + } + + deps := make(map[*types.Package]bool) + + tc.Import = func(packages map[string]*types.Package, path string) (pkg *types.Package, err error) { + pkg, err = clientImport(packages, path) + if pkg != nil { + deps[pkg] = true + } + return + } + + info := &types.Info{ + Types: make(map[ast.Expr]types.TypeAndValue), + Defs: make(map[*ast.Ident]types.Object), + Uses: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + Scopes: make(map[ast.Node]*types.Scope), + Selections: make(map[*ast.SelectorExpr]*types.Selection), + } + if err := types.NewChecker(tc, fset, pkg, info).Files(files); err != nil { + return nil, nil, err + } + + // Create the SSA program and its packages. + prog := ssa.NewProgram(fset, mode) + for dep := range deps { + prog.CreatePackage(dep, nil, nil, true) + } + ssapkg := prog.CreatePackage(pkg, files, info, false) + ssapkg.Build() + return ssapkg, info, nil +} diff --git a/go/ssa/ssautil/load_test.go b/go/ssa/ssautil/load_test.go new file mode 100644 index 00000000000..3e6a5092fdd --- /dev/null +++ b/go/ssa/ssautil/load_test.go @@ -0,0 +1,60 @@ +// Copyright 2015 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. + +package ssautil_test + +import ( + "go/ast" + "go/parser" + "go/token" + "os" + "testing" + + "golang.org/x/tools/go/ssa/ssautil" + "golang.org/x/tools/go/types" +) + +const hello = `package main + +import "fmt" + +func main() { + fmt.Println("Hello, world") +} +` + +func TestLoadPackage(t *testing.T) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "hello.go", hello, 0) + if err != nil { + t.Fatal(err) + } + + pkg := types.NewPackage("hello", "") + ssapkg, _, err := ssautil.LoadPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) + if err != nil { + t.Fatal(err) + } + if pkg.Name() != "main" { + t.Errorf("pkg.Name() = %s, want main", pkg.Name()) + } + if ssapkg.Func("main") == nil { + ssapkg.WriteTo(os.Stderr) + t.Errorf("ssapkg has no main function") + } +} + +func TestLoadPackage_MissingImport(t *testing.T) { + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "bad.go", `package bad; import "missing"`, 0) + if err != nil { + t.Fatal(err) + } + + pkg := types.NewPackage("bad", "") + ssapkg, _, err := ssautil.LoadPackage(new(types.Config), fset, pkg, []*ast.File{f}, 0) + if err == nil || ssapkg != nil { + t.Fatal("LoadPackage succeeded unexpectedly") + } +}