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:
parent
14c815ee28
commit
20186168d5
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
}
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -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
91
go/ssa/ssautil/load.go
Normal 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
|
||||
}
|
60
go/ssa/ssautil/load_test.go
Normal file
60
go/ssa/ssautil/load_test.go
Normal 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")
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user