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

View File

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

View File

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

View File

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

View File

@ -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.
//

View File

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

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