go.tools/importer: API rethink.
The Importer type has been replaced with Config and Program.
Clients populate a Config, directly or more usually via
convenience functions. They then call its Load() method to do
all of the typechecking and transitive-closure computation.
ssa.NewProgram and ssa.CreatePackages have been fused into
ssa.Create, which now cannot fail, since (*Config).Load()
reports all type errors.
Also:
- The addition of an ssa.GlobalDebug builder mode flag
eliminates a loop-over-packages repeated in many clients.
- PackageInfo.Err flag unexported. Clients never see bad infos now.
- cmd/ssadump: now only looks for func "main" in package "main".
- importsOf deleted, was dead code.
STILL TODO:
- ParseFile seems like API creep (though it's convenient)
and CreateFromFiles is dangerous (w.r.t. FileSet identity).
Need to think more...
- the need for clients to rely on elementwise correspondence
of Config.CreatePkgs and Program.Created is a little sad.
- The command-line interface has not changed.
That will happen in a follow-up.
r recommends using a repeated flag: -package p -package q ...
R=gri
CC=axwalk, frederik.zipp, golang-codereviews
https://golang.org/cl/49530047
diff --git a/ssa/builder_test.go b/ssa/builder_test.go
index 757a171..490e085 100644
--- a/ssa/builder_test.go
+++ b/ssa/builder_test.go
@@ -5,7 +5,6 @@
package ssa_test
import (
- "go/parser"
"reflect"
"sort"
"strings"
@@ -40,22 +39,24 @@
w.Write(nil) // interface invoke of external declared method
}
`
- imp := importer.New(&importer.Config{})
- f, err := parser.ParseFile(imp.Fset, "<input>", test, 0)
+ // Create a single-file main package.
+ var conf importer.Config
+ f, err := conf.ParseFile("<input>", test, 0)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ conf.CreateFromFiles(f)
+
+ iprog, err := conf.Load()
if err != nil {
t.Error(err)
return
}
- mainInfo := imp.CreatePackage("main", f)
-
- prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
- if err := prog.CreatePackages(imp); err != nil {
- t.Error(err)
- return
- }
- mainPkg := prog.Package(mainInfo.Pkg)
+ prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
+ mainPkg := prog.Package(iprog.Created[0].Pkg)
mainPkg.Build()
// The main package, its direct and indirect dependencies are loaded.
@@ -204,21 +205,22 @@
},
}
for i, test := range tests {
- imp := importer.New(&importer.Config{})
-
- f, err := parser.ParseFile(imp.Fset, "<input>", test.input, 0)
+ // Create a single-file main package.
+ var conf importer.Config
+ f, err := conf.ParseFile("<input>", test.input, 0)
if err != nil {
t.Errorf("test %d: %s", i, err)
continue
}
+ conf.CreateFromFiles(f)
- mainInfo := imp.CreatePackage("p", f)
- prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
- if err := prog.CreatePackages(imp); err != nil {
- t.Errorf("test %d: %s", i, err)
+ iprog, err := conf.Load()
+ if err != nil {
+ t.Errorf("test %d: Load: %s", i, err)
continue
}
- mainPkg := prog.Package(mainInfo.Pkg)
+ prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
+ mainPkg := prog.Package(iprog.Created[0].Pkg)
prog.BuildAll()
var typstrs []string
diff --git a/ssa/create.go b/ssa/create.go
index 951400f..60b2986 100644
--- a/ssa/create.go
+++ b/ssa/create.go
@@ -8,11 +8,9 @@
// See builder.go for explanation.
import (
- "fmt"
"go/ast"
"go/token"
"os"
- "strings"
"code.google.com/p/go.tools/go/types"
"code.google.com/p/go.tools/importer"
@@ -28,25 +26,32 @@
SanityCheckFunctions // Perform sanity checking of function bodies
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
)
-// NewProgram returns a new SSA Program initially containing no
-// packages.
+// Create returns a new SSA Program, creating all packages and all
+// their members.
//
-// fset specifies the mapping from token positions to source location
-// that will be used by all ASTs of this program.
+// Code for bodies of functions is not built until Build() is called
+// on the result.
//
// mode controls diagnostics and checking during SSA construction.
//
-func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
- return &Program{
- Fset: fset,
+func Create(iprog *importer.Program, mode BuilderMode) *Program {
+ prog := &Program{
+ Fset: iprog.Fset,
imported: make(map[string]*Package),
packages: make(map[*types.Package]*Package),
boundMethodWrappers: make(map[*types.Func]*Function),
ifaceMethodWrappers: make(map[*types.Func]*Function),
mode: mode,
}
+
+ for _, info := range iprog.AllPackages {
+ prog.CreatePackage(info)
+ }
+
+ return prog
}
// memberFromObject populates package pkg with a member for the
@@ -166,9 +171,6 @@
// until a subsequent call to Package.Build().
//
func (prog *Program) CreatePackage(info *importer.PackageInfo) *Package {
- if info.Err != nil {
- panic(fmt.Sprintf("package %s has errors: %s", info, info.Err))
- }
if p := prog.packages[info.Pkg]; p != nil {
return p // already loaded
}
@@ -225,6 +227,10 @@
}
p.Members[initguard.Name()] = initguard
+ if prog.mode&GlobalDebug != 0 {
+ p.SetDebugMode(true)
+ }
+
if prog.mode&LogPackages != 0 {
p.DumpTo(os.Stderr)
}
@@ -241,40 +247,6 @@
return p
}
-// CreatePackages creates SSA Packages for all error-free packages
-// loaded by the specified Importer.
-//
-// If all packages were error-free, it is safe to call
-// prog.BuildAll(), and nil is returned. Otherwise an error is
-// returned.
-//
-func (prog *Program) CreatePackages(imp *importer.Importer) error {
- var errpkgs []string
-
- // Create source packages and directly imported packages.
- for _, info := range imp.AllPackages() {
- if info.Err != nil {
- errpkgs = append(errpkgs, info.Pkg.Path())
- } else {
- prog.CreatePackage(info)
- }
- }
-
- // Create indirectly imported packages.
- for _, obj := range imp.Config.TypeChecker.Packages {
- prog.CreatePackage(&importer.PackageInfo{
- Pkg: obj,
- Importable: true,
- })
- }
-
- if errpkgs != nil {
- return fmt.Errorf("couldn't create these SSA packages due to type errors: %s",
- strings.Join(errpkgs, ", "))
- }
- return nil
-}
-
// AllPackages returns a new slice containing all packages in the
// program prog in unspecified order.
//
diff --git a/ssa/doc.go b/ssa/doc.go
index 878addb..8204787 100644
--- a/ssa/doc.go
+++ b/ssa/doc.go
@@ -23,17 +23,11 @@
// primitives in the future to facilitate constant-time dispatch of
// switch statements, for example.
//
-// Builder encapsulates the tasks of type-checking (using go/types)
-// abstract syntax trees (as defined by go/ast) for the source files
-// comprising a Go program, and the conversion of each function from
-// Go ASTs to the SSA representation.
-//
-// By supplying an instance of the SourceLocator function prototype,
-// clients may control how the builder locates, loads and parses Go
-// sources files for imported packages. This package provides
-// MakeGoBuildLoader, which creates a loader that uses go/build to
-// locate packages in the Go source distribution, and go/parser to
-// parse them.
+// To construct an SSA-form program, call ssa.Create on an
+// importer.Program, a set of type-checked packages created from
+// parsed Go source files. The resulting ssa.Program contains all the
+// packages and their members, but SSA code is not created for
+// function bodies until a subsequent call to (*Package).Build.
//
// The builder initially builds a naive SSA form in which all local
// variables are addresses of stack locations with explicit loads and
diff --git a/ssa/example_test.go b/ssa/example_test.go
index a9d98c9..20383ad 100644
--- a/ssa/example_test.go
+++ b/ssa/example_test.go
@@ -6,7 +6,6 @@
import (
"fmt"
- "go/parser"
"os"
"code.google.com/p/go.tools/importer"
@@ -41,27 +40,28 @@
fmt.Println(message)
}
`
- // Construct an importer.
- imp := importer.New(&importer.Config{})
+ var conf importer.Config
// Parse the input file.
- file, err := parser.ParseFile(imp.Fset, "hello.go", hello, 0)
+ file, err := conf.ParseFile("hello.go", hello, 0)
if err != nil {
fmt.Print(err) // parse error
return
}
- // Create single-file main package and import its dependencies.
- mainInfo := imp.CreatePackage("main", file)
+ // Create single-file main package.
+ conf.CreateFromFiles(file)
- // Create SSA-form program representation.
- var mode ssa.BuilderMode
- prog := ssa.NewProgram(imp.Fset, mode)
- if err := prog.CreatePackages(imp); err != nil {
+ // Load the main package and its dependencies.
+ iprog, err := conf.Load()
+ if err != nil {
fmt.Print(err) // type error in some package
return
}
- mainPkg := prog.Package(mainInfo.Pkg)
+
+ // Create SSA-form program representation.
+ prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
+ mainPkg := prog.Package(iprog.Created[0].Pkg)
// Print out the package.
mainPkg.DumpTo(os.Stdout)
diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go
index e712f99..515db61 100644
--- a/ssa/interp/interp_test.go
+++ b/ssa/interp/interp_test.go
@@ -168,16 +168,16 @@
inputs = append(inputs, dir+i)
}
- imp := importer.New(&importer.Config{SourceImports: true})
- // TODO(adonovan): use LoadInitialPackages, then un-export ParseFiles.
- // Then add the following packages' tests, which pass:
+ conf := importer.Config{SourceImports: true}
+ // TODO(adonovan): add the following packages' tests, which pass:
// "flag", "unicode", "unicode/utf8", "testing", "log", "path".
- files, err := importer.ParseFiles(imp.Fset, ".", inputs...)
- if err != nil {
- t.Errorf("ssa.ParseFiles(%s) failed: %s", inputs, err.Error())
+ if err := conf.CreateFromFilenames(inputs...); err != nil {
+ t.Errorf("CreateFromFilenames(%s) failed: %s", inputs, err)
return false
}
+ conf.Import("runtime")
+
// Print a helpful hint if we don't make it to the end.
var hint string
defer func() {
@@ -192,20 +192,17 @@
}()
hint = fmt.Sprintf("To dump SSA representation, run:\n%% go build code.google.com/p/go.tools/cmd/ssadump && ./ssadump -build=CFP %s\n", input)
- mainInfo := imp.CreatePackage(files[0].Name.Name, files...)
- if _, err := imp.ImportPackage("runtime"); err != nil {
- t.Errorf("ImportPackage(runtime) failed: %s", err)
- }
-
- prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
- if err := prog.CreatePackages(imp); err != nil {
- t.Errorf("CreatePackages failed: %s", err)
+ iprog, err := conf.Load()
+ if err != nil {
+ t.Errorf("conf.Load(%s) failed: %s", inputs, err)
return false
}
+
+ prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
prog.BuildAll()
- mainPkg := prog.Package(mainInfo.Pkg)
+ mainPkg := prog.Package(iprog.Created[0].Pkg)
if mainPkg.Func("main") == nil {
testmainPkg := prog.CreateTestMainPackage(mainPkg)
if testmainPkg == nil {
@@ -321,17 +318,16 @@
// CreateTestMainPackage should return nil if there were no tests.
func TestNullTestmainPackage(t *testing.T) {
- imp := importer.New(&importer.Config{})
- files, err := importer.ParseFiles(imp.Fset, ".", "testdata/b_test.go")
- if err != nil {
- t.Fatalf("ParseFiles failed: %s", err)
+ var conf importer.Config
+ if err := conf.CreateFromFilenames("testdata/b_test.go"); err != nil {
+ t.Fatalf("ParseFile failed: %s", err)
}
- mainInfo := imp.CreatePackage("b", files...)
- prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
- if err := prog.CreatePackages(imp); err != nil {
+ iprog, err := conf.Load()
+ if err != nil {
t.Fatalf("CreatePackages failed: %s", err)
}
- mainPkg := prog.Package(mainInfo.Pkg)
+ prog := ssa.Create(iprog, ssa.SanityCheckFunctions)
+ mainPkg := prog.Package(iprog.Created[0].Pkg)
if mainPkg.Func("main") != nil {
t.Fatalf("unexpected main function")
}
diff --git a/ssa/source_test.go b/ssa/source_test.go
index e3d94b8..17ba439 100644
--- a/ssa/source_test.go
+++ b/ssa/source_test.go
@@ -24,12 +24,13 @@
)
func TestObjValueLookup(t *testing.T) {
- imp := importer.New(&importer.Config{})
- f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.ParseComments)
+ var conf importer.Config
+ f, err := conf.ParseFile("testdata/objlookup.go", nil, parser.ParseComments)
if err != nil {
t.Error(err)
return
}
+ conf.CreateFromFiles(f)
// Maps each var Ident (represented "name:linenum") to the
// kind of ssa.Value we expect (represented "Constant", "&Alloc").
@@ -39,7 +40,7 @@
re := regexp.MustCompile(`(\b|&)?(\w*)::(\w*)\b`)
for _, c := range f.Comments {
text := c.Text()
- pos := imp.Fset.Position(c.Pos())
+ pos := conf.Fset.Position(c.Pos())
for _, m := range re.FindAllStringSubmatch(text, -1) {
key := fmt.Sprintf("%s:%d", m[2], pos.Line)
value := m[1] + m[3]
@@ -47,13 +48,14 @@
}
}
- mainInfo := imp.CreatePackage("main", f)
-
- prog := ssa.NewProgram(imp.Fset, 0 /*|ssa.LogFunctions*/)
- if err := prog.CreatePackages(imp); err != nil {
+ iprog, err := conf.Load()
+ if err != nil {
t.Error(err)
return
}
+
+ prog := ssa.Create(iprog, 0 /*|ssa.LogFunctions*/)
+ mainInfo := iprog.Created[0]
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
@@ -90,7 +92,7 @@
}
if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok {
ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
- pos := imp.Fset.Position(id.Pos())
+ pos := prog.Fset.Position(id.Pos())
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
if exp == "" {
t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
@@ -186,20 +188,23 @@
// Ensure that, in debug mode, we can determine the ssa.Value
// corresponding to every ast.Expr.
func TestValueForExpr(t *testing.T) {
- imp := importer.New(&importer.Config{})
- f, err := parser.ParseFile(imp.Fset, "testdata/valueforexpr.go", nil, parser.ParseComments)
+ var conf importer.Config
+ f, err := conf.ParseFile("testdata/valueforexpr.go", nil, parser.ParseComments)
+ if err != nil {
+ t.Error(err)
+ return
+ }
+ conf.CreateFromFiles(f)
+
+ iprog, err := conf.Load()
if err != nil {
t.Error(err)
return
}
- mainInfo := imp.CreatePackage("main", f)
+ mainInfo := iprog.Created[0]
- prog := ssa.NewProgram(imp.Fset, 0)
- if err := prog.CreatePackages(imp); err != nil {
- t.Error(err)
- return
- }
+ prog := ssa.Create(iprog, 0)
mainPkg := prog.Package(mainInfo.Pkg)
mainPkg.SetDebugMode(true)
mainPkg.Build()
@@ -232,7 +237,7 @@
}
text = text[1:]
pos := c.End() + 1
- position := imp.Fset.Position(pos)
+ position := prog.Fset.Position(pos)
var e ast.Expr
if target := parenExprByPos[pos]; target == nil {
t.Errorf("%s: annotation doesn't precede ParenExpr: %q", position, text)
diff --git a/ssa/ssa.go b/ssa/ssa.go
index 7c2c722..111af69 100644
--- a/ssa/ssa.go
+++ b/ssa/ssa.go
@@ -1170,7 +1170,8 @@
// For non-Ident expressions, Object() returns nil.
//
// DebugRefs are generated only for functions built with debugging
-// enabled; see Package.SetDebugMode().
+// enabled; see Package.SetDebugMode() and the GlobalDebug builder
+// mode flag.
//
// DebugRefs are not emitted for ast.Idents referring to constants or
// predeclared identifiers, since they are trivial and numerous.
@@ -1240,7 +1241,7 @@
// (b) a *MakeClosure, indicating an immediately applied
// function literal with free variables.
// (c) a *Builtin, indicating a statically dispatched call
-// to a built-in function. StaticCallee returns nil.
+// to a built-in function.
// (d) any other value, indicating a dynamically dispatched
// function call.
// StaticCallee returns the identity of the callee in cases
diff --git a/ssa/ssautil/switch_test.go b/ssa/ssautil/switch_test.go
index b7cce89..c41a3f2 100644
--- a/ssa/ssautil/switch_test.go
+++ b/ssa/ssautil/switch_test.go
@@ -15,21 +15,22 @@
)
func TestSwitches(t *testing.T) {
- imp := importer.New(&importer.Config{})
- f, err := parser.ParseFile(imp.Fset, "testdata/switches.go", nil, parser.ParseComments)
+ var conf importer.Config
+ f, err := conf.ParseFile("testdata/switches.go", nil, parser.ParseComments)
if err != nil {
t.Error(err)
return
}
- mainInfo := imp.CreatePackage("main", f)
-
- prog := ssa.NewProgram(imp.Fset, 0)
- if err := prog.CreatePackages(imp); err != nil {
+ conf.CreateFromFiles(f)
+ iprog, err := conf.Load()
+ if err != nil {
t.Error(err)
return
}
- mainPkg := prog.Package(mainInfo.Pkg)
+
+ prog := ssa.Create(iprog, 0)
+ mainPkg := prog.Package(iprog.Created[0].Pkg)
mainPkg.Build()
for _, mem := range mainPkg.Members {
diff --git a/ssa/stdlib_test.go b/ssa/stdlib_test.go
index ac5bd6f..f95e366 100644
--- a/ssa/stdlib_test.go
+++ b/ssa/stdlib_test.go
@@ -23,8 +23,6 @@
"code.google.com/p/go.tools/ssa/ssautil"
)
-const debugMode = true // cost: +30% space, +18% time for SSA building
-
func allPackages() []string {
var pkgs []string
root := filepath.Join(runtime.GOROOT(), "src/pkg") + string(os.PathSeparator)
@@ -54,13 +52,17 @@
// Load, parse and type-check the program.
t0 := time.Now()
- imp := importer.New(&importer.Config{})
-
- if _, _, err := imp.LoadInitialPackages(allPackages()); err != nil {
- t.Errorf("LoadInitialPackages failed: %s", err)
+ var conf importer.Config
+ if _, err := conf.FromArgs(allPackages()); err != nil {
+ t.Errorf("FromArgs failed: %s", err)
return
}
+ iprog, err := conf.Load()
+ if err != nil {
+ t.Errorf("Load failed: %s", err)
+ }
+
t1 := time.Now()
runtime.GC()
@@ -69,15 +71,11 @@
alloc := memstats.Alloc
// Create SSA packages.
- prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions)
- if err := prog.CreatePackages(imp); err != nil {
- t.Errorf("CreatePackages failed: %s", err)
- return
- }
- // Enable debug mode globally.
- for _, info := range imp.AllPackages() {
- prog.Package(info.Pkg).SetDebugMode(debugMode)
- }
+ var mode ssa.BuilderMode
+ // Comment out these lines during benchmarking. Approx SSA build costs are noted.
+ mode |= ssa.SanityCheckFunctions // + 2% space, + 4% time
+ mode |= ssa.GlobalDebug // +30% space, +18% time
+ prog := ssa.Create(iprog, mode)
t2 := time.Now()
@@ -105,7 +103,7 @@
// determine line count
var lineCount int
- imp.Fset.Iterate(func(f *token.File) bool {
+ prog.Fset.Iterate(func(f *token.File) bool {
lineCount += f.LineCount()
return true
})