1
0
mirror of https://github.com/golang/go synced 2024-11-18 23:14:43 -07:00

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 <gri@golang.org>
This commit is contained in:
Alan Donovan 2015-04-08 16:14:19 -04:00
parent 14c815ee28
commit 20186168d5
8 changed files with 271 additions and 74 deletions

View File

@ -257,6 +257,8 @@ type Config struct {
// //
// NB: there is a bug when loading multiple initial packages with // NB: there is a bug when loading multiple initial packages with
// this flag enabled: https://github.com/golang/go/issues/9955. // this flag enabled: https://github.com/golang/go/issues/9955.
//
// THIS FEATURE IS DEPRECATED and will be removed shortly (Apr 2015).
ImportFromBinary bool ImportFromBinary bool
// If Build is non-nil, it is used to locate source packages. // If Build is non-nil, it is used to locate source packages.
@ -321,6 +323,8 @@ type Config struct {
// the package scope, for example. // the package scope, for example.
// //
// It must be safe to call concurrently from multiple goroutines. // 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) PackageCreated func(*types.Package)
} }

View File

@ -2212,8 +2212,13 @@ func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) {
// BuildAll calls Package.Build() for each package in prog. // BuildAll calls Package.Build() for each package in prog.
// Building occurs in parallel unless the BuildSerially mode flag was set. // 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. // BuildAll is idempotent and thread-safe.
// //
// TODO(adonovan): rename to Build.
//
func (prog *Program) BuildAll() { func (prog *Program) BuildAll() {
var wg sync.WaitGroup var wg sync.WaitGroup
for _, p := range prog.packages { for _, p := range prog.packages {
@ -2245,7 +2250,7 @@ func (p *Package) Build() {
if p.info == nil { if p.info == nil {
return // synthetic package, e.g. "testmain" return // synthetic package, e.g. "testmain"
} }
if len(p.info.Files) == 0 { if p.files == nil {
p.info = nil p.info = nil
return // package loaded from export data return // package loaded from export data
} }
@ -2276,7 +2281,7 @@ func (p *Package) Build() {
emitStore(init, initguard, vTrue, token.NoPos) emitStore(init, initguard, vTrue, token.NoPos)
// Call the init() function of each package we import. // 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] prereq := p.Prog.packages[pkg]
if prereq == nil { if prereq == nil {
panic(fmt.Sprintf("Package(%q).Build(): unsatisfied import: Program.CreatePackage(%q) was not called", p.Object.Path(), pkg.Path())) 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 // Build all package-level functions, init functions
// and methods, including unreachable/blank ones. // and methods, including unreachable/blank ones.
// We build them in source order, but it's not significant. // 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 { for _, decl := range file.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok { if decl, ok := decl.(*ast.FuncDecl); ok {
b.buildFuncDecl(p, decl) b.buildFuncDecl(p, decl)

View File

@ -19,17 +19,13 @@ import (
"golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/go/types/typeutil"
) )
// Create returns a new SSA Program. An SSA Package is created for // NewProgram returns a new SSA Program.
// each transitively error-free package of iprog.
//
// Code for bodies of functions is not built until Build() is called
// on the result.
// //
// mode controls diagnostics and checking during SSA construction. // 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{ prog := &Program{
Fset: iprog.Fset, Fset: fset,
imported: make(map[string]*Package), imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package), packages: make(map[*types.Package]*Package),
thunks: make(map[selectionKey]*Function), thunks: make(map[selectionKey]*Function),
@ -41,11 +37,26 @@ func Create(iprog *loader.Program, mode BuilderMode) *Program {
prog.methodSets.SetHasher(h) prog.methodSets.SetHasher(h)
prog.canon.SetHasher(h) prog.canon.SetHasher(h)
for _, info := range iprog.AllPackages { return prog
// TODO(adonovan): relax this constraint if the }
// program contains only "soft" errors.
// 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 { 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 // CreatePackage constructs and returns an SSA Package from the
// error-free package described by info, and populates its Members // specified type-checked, error-free file ASTs, and populates its
// mapping. // 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 // The real work of building SSA form for each function is not done
// until a subsequent call to Package.Build(). // until a subsequent call to Package.Build().
// //
func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package { func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *types.Info, importable bool) *Package {
if p := prog.packages[info.Pkg]; p != nil {
return p // already loaded
}
p := &Package{ p := &Package{
Prog: prog, Prog: prog,
Members: make(map[string]Member), Members: make(map[string]Member),
values: make(map[types.Object]Value), values: make(map[types.Object]Value),
Object: info.Pkg, Object: pkg,
info: info, // transient (CREATE and BUILD phases) info: info, // transient (CREATE and BUILD phases)
files: files, // transient (CREATE and BUILD phases)
} }
// Add init() function. // Add init() function.
@ -194,9 +203,9 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package {
// CREATE phase. // CREATE phase.
// Allocate all package members: vars, funcs, consts and types. // Allocate all package members: vars, funcs, consts and types.
if len(info.Files) > 0 { if len(files) > 0 {
// Go source package. // Go source package.
for _, file := range info.Files { for _, file := range files {
for _, decl := range file.Decls { for _, decl := range file.Decls {
membersFromDecl(p, decl) membersFromDecl(p, decl)
} }
@ -238,8 +247,8 @@ func (prog *Program) CreatePackage(info *loader.PackageInfo) *Package {
printMu.Unlock() printMu.Unlock()
} }
if info.Importable { if importable {
prog.imported[info.Pkg.Path()] = p prog.imported[p.Object.Path()] = p
} }
prog.packages[p.Object] = p prog.packages[p.Object] = p

View File

@ -8,28 +8,17 @@ import (
"fmt" "fmt"
"os" "os"
"go/ast"
"go/parser"
"go/token"
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
"golang.org/x/tools/go/ssa" "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, const 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 = `
package main package main
import "fmt" import "fmt"
@ -40,49 +29,64 @@ func main() {
fmt.Println(message) fmt.Println(message)
} }
` `
var conf loader.Config
// Parse the input file. // This program demonstrates how to run the SSA builder on a single
file, err := conf.ParseFile("hello.go", hello) // 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 { if err != nil {
fmt.Print(err) // parse error fmt.Print(err) // parse error
return return
} }
files := []*ast.File{f}
// Create single-file main package. // Create the type-checker's package.
conf.CreateFromFiles("main", file) pkg := types.NewPackage("hello", "")
// Load the main package and its dependencies. // Type-check the package, load dependencies.
iprog, err := conf.Load() // Create and build the SSA program.
hello, _, err := ssautil.LoadPackage(
new(types.Config), fset, pkg, files, ssa.SanityCheckFunctions)
if err != nil { if err != nil {
fmt.Print(err) // type error in some package fmt.Print(err) // type error in some package
return return
} }
// Create SSA-form program representation.
prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
mainPkg := prog.Package(iprog.Created[0].Pkg)
// Print out the package. // Print out the package.
mainPkg.WriteTo(os.Stdout) hello.WriteTo(os.Stdout)
// Build SSA code for bodies of functions in mainPkg.
mainPkg.Build()
// Print out the package-level functions. // Print out the package-level functions.
mainPkg.Func("init").WriteTo(os.Stdout) hello.Func("init").WriteTo(os.Stdout)
mainPkg.Func("main").WriteTo(os.Stdout) hello.Func("main").WriteTo(os.Stdout)
// Output: // Output:
// //
// package main: // package hello:
// func init func() // func init func()
// var init$guard bool // var init$guard bool
// func main func() // func main func()
// const message message = "Hello, World!":untyped string // const message message = "Hello, World!":untyped string
// //
// # Name: main.init // # Name: hello.init
// # Package: main // # Package: hello
// # Synthetic: package initializer // # Synthetic: package initializer
// func init(): // func init():
// 0: entry P:0 S:2 // 0: entry P:0 S:2
@ -95,8 +99,8 @@ func main() {
// 2: init.done P:2 S:0 // 2: init.done P:2 S:0
// return // return
// //
// # Name: main.main // # Name: hello.main
// # Package: main // # Package: hello
// # Location: hello.go:8:6 // # Location: hello.go:8:6
// func main(): // func main():
// 0: entry P:0 S:0 // 0: entry P:0 S:0
@ -108,3 +112,27 @@ func main() {
// t4 = fmt.Println(t3...) (n int, err error) // t4 = fmt.Println(t3...) (n int, err error)
// return // 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:
}

View File

@ -140,7 +140,7 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
// - f was not built with debug information; or // - f was not built with debug information; or
// - e is a constant expression. (For efficiency, no debug // - e is a constant expression. (For efficiency, no debug
// information is stored for constants. Use // 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. // - e is a reference to nil or a built-in function.
// - the value was optimised away. // - the value was optimised away.
// //

View File

@ -14,7 +14,6 @@ import (
"sync" "sync"
"golang.org/x/tools/go/exact" "golang.org/x/tools/go/exact"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types" "golang.org/x/tools/go/types"
"golang.org/x/tools/go/types/typeutil" "golang.org/x/tools/go/types/typeutil"
) )
@ -54,9 +53,10 @@ type Package struct {
// The following fields are set transiently, then cleared // The following fields are set transiently, then cleared
// after building. // after building.
started int32 // atomically tested and set at start of build phase started int32 // atomically tested and set at start of build phase
ninit int32 // number of init functions ninit int32 // number of init functions
info *loader.PackageInfo // package ASTs and type information info *types.Info // package type information
files []*ast.File // package ASTs
} }
// A Member is a member of a Go package, implemented by *NamedConst, // A Member is a member of a Go package, implemented by *NamedConst,

91
go/ssa/ssautil/load.go Normal file
View File

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

View File

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