diff --git a/importer/importer.go b/importer/importer.go index b25d4100..d7b09b4f 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -131,7 +131,11 @@ func (imp *Importer) LoadPackage(importPath string) (*PackageInfo, error) { if err != nil { return nil, err } - return imp.CreateSourcePackage(importPath, files) + info := imp.CreateSourcePackage(importPath, files) + if info.Err != nil { + return nil, info.Err + } + return info, nil } // CreateSourcePackage invokes the type-checker on files and returns a @@ -146,33 +150,20 @@ func (imp *Importer) LoadPackage(importPath string) (*PackageInfo, error) { // The ParseFiles utility may be helpful for parsing a set of Go // source files. // -func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) (*PackageInfo, error) { +// The result is always non-nil; the presence of errors is indicated +// by the PackageInfo.Err field. +// +func (imp *Importer) CreateSourcePackage(importPath string, files []*ast.File) *PackageInfo { pkgInfo := &PackageInfo{ - Files: files, - types: make(map[ast.Expr]types.Type), - idents: make(map[*ast.Ident]types.Object), - constants: make(map[ast.Expr]exact.Value), - typecases: make(map[*ast.CaseClause]*types.Var), + Files: files, + Info: types.Info{ + Types: make(map[ast.Expr]types.Type), + Values: make(map[ast.Expr]exact.Value), + Objects: make(map[*ast.Ident]types.Object), + Implicits: make(map[ast.Node]types.Object), + }, } - - info := &types.Info{ - Types: pkgInfo.types, - Values: pkgInfo.constants, - Objects: pkgInfo.idents, - Implicits: make(map[ast.Node]types.Object), - } - var firstErr error - pkgInfo.Pkg, firstErr = imp.context.TypeChecker.Check(importPath, imp.Fset, files, info) - if firstErr != nil { - return nil, firstErr - } - - for node, obj := range info.Implicits { - if cc, ok := node.(*ast.CaseClause); ok { - pkgInfo.typecases[cc] = obj.(*types.Var) - } - } - + pkgInfo.Pkg, pkgInfo.Err = imp.context.TypeChecker.Check(importPath, imp.Fset, files, &pkgInfo.Info) imp.Packages[importPath] = pkgInfo - return pkgInfo, nil + return pkgInfo } diff --git a/importer/pkginfo.go b/importer/pkginfo.go index a78ece5b..07cb71ff 100644 --- a/importer/pkginfo.go +++ b/importer/pkginfo.go @@ -3,11 +3,13 @@ package importer // TODO(gri): absorb this into go/types. import ( - "code.google.com/p/go.tools/go/exact" - "code.google.com/p/go.tools/go/types" + "fmt" "go/ast" "go/token" "strconv" + + "code.google.com/p/go.tools/go/exact" + "code.google.com/p/go.tools/go/types" ) // PackageInfo holds the ASTs and facts derived by the type-checker @@ -16,14 +18,14 @@ import ( // Not mutated once constructed. // type PackageInfo struct { - Pkg *types.Package - Files []*ast.File // abstract syntax for the package's files + Pkg *types.Package + Err error // non-nil if the package had static errors + Files []*ast.File // abstract syntax for the package's files + types.Info // type-checker deductions. +} - // Type-checker deductions. - types map[ast.Expr]types.Type // inferred types of expressions - constants map[ast.Expr]exact.Value // values of constant expressions - idents map[*ast.Ident]types.Object // resolved objects for named entities - typecases map[*ast.CaseClause]*types.Var // implicit vars for single-type typecases +func (info *PackageInfo) String() string { + return fmt.Sprintf("PackageInfo(%s)", info.Pkg.Path()) } // Imports returns the set of packages imported by this one, in source @@ -57,7 +59,7 @@ func (info *PackageInfo) Imports() []*types.Package { // Precondition: e belongs to the package's ASTs. // func (info *PackageInfo) TypeOf(e ast.Expr) types.Type { - if t, ok := info.types[e]; ok { + if t, ok := info.Types[e]; ok { return t } // Defining ast.Idents (id := expr) get only Ident callbacks @@ -73,20 +75,18 @@ func (info *PackageInfo) TypeOf(e ast.Expr) types.Type { // Precondition: e belongs to the package's ASTs. // func (info *PackageInfo) ValueOf(e ast.Expr) exact.Value { - return info.constants[e] + return info.Values[e] } // ObjectOf returns the typechecker object denoted by the specified id. // Precondition: id belongs to the package's ASTs. // func (info *PackageInfo) ObjectOf(id *ast.Ident) types.Object { - return info.idents[id] + return info.Objects[id] } // IsType returns true iff expression e denotes a type. // Precondition: e belongs to the package's ASTs. -// e must be a true expression, not a KeyValueExpr, or an Ident -// appearing in a SelectorExpr or declaration. // func (info *PackageInfo) IsType(e ast.Expr) bool { switch e := e.(type) { @@ -126,7 +126,10 @@ func (info *PackageInfo) IsPackageRef(sel *ast.SelectorExpr) types.Object { // case clause in a type switch, or nil if not found. // func (info *PackageInfo) TypeCaseVar(cc *ast.CaseClause) *types.Var { - return info.typecases[cc] + if v := info.Implicits[cc]; v != nil { + return v.(*types.Var) + } + return nil } var ( @@ -146,9 +149,6 @@ var ( // be moved there and made accessible via an additional types.Context // callback. // -// The returned Signature is degenerate and only intended for use by -// emitCallArgs. -// func (info *PackageInfo) BuiltinCallSignature(e *ast.CallExpr) *types.Signature { var params []*types.Var var isVariadic bool diff --git a/importer/source_test.go b/importer/source_test.go index 8493425d..821d1888 100644 --- a/importer/source_test.go +++ b/importer/source_test.go @@ -245,14 +245,16 @@ func TestEnclosingFunction(t *testing.T) { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue } - info, err := imp.CreateSourcePackage("main", []*ast.File{f}) - if err != nil { - t.Error(err.Error()) + info := imp.CreateSourcePackage("main", []*ast.File{f}) + if info.Err != nil { + t.Error(info.Err.Error()) continue } prog := ssa.NewProgram(imp.Fset, 0) - prog.CreatePackages(imp) + for _, info := range imp.Packages { + prog.CreatePackage(info) + } pkg := prog.Package(info.Pkg) pkg.Build() diff --git a/importer/util.go b/importer/util.go index 6ea57438..8a04e35e 100644 --- a/importer/util.go +++ b/importer/util.go @@ -38,7 +38,8 @@ func CreatePackageFromArgs(imp *Importer, args []string) (info *PackageInfo, res files, err = ParseFiles(imp.Fset, ".", args[:i]...) rest = args[i:] if err == nil { - info, err = imp.CreateSourcePackage("main", files) + info = imp.CreateSourcePackage("main", files) + err = info.Err } default: diff --git a/ssa/builder.go b/ssa/builder.go index c9ab2c10..8d5dd843 100644 --- a/ssa/builder.go +++ b/ssa/builder.go @@ -7,7 +7,7 @@ package ssa // definitions of all package members are created, method-sets are // computed, and wrapper methods are synthesized. The create phase // proceeds in topological order over the import dependency graph, -// initiated by client calls to CreatePackages. +// initiated by client calls to Program.CreatePackage. // // In the BUILD phase (builder.go), the builder traverses the AST of // each Go source function and generates SSA instructions for the @@ -447,7 +447,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { if v == nil { v = fn.lookup(obj, escaping) } - return address{addr: v, id: e, object: obj} + return &address{addr: v, id: e, object: obj} case *ast.CompositeLit: t := deref(fn.Pkg.typeOf(e)) @@ -458,7 +458,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { v = fn.addLocal(t, e.Lbrace) } b.compLit(fn, v, e, t) // initialize in place - return address{addr: v} + return &address{addr: v} case *ast.ParenExpr: return b.addr(fn, e.X, escaping) @@ -467,13 +467,13 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { // p.M where p is a package. if obj := fn.Pkg.info.IsPackageRef(e); obj != nil { if v := b.lookup(fn.Pkg, obj); v != nil { - return address{addr: v} + return &address{addr: v} } panic("undefined package-qualified name: " + obj.Name()) } // e.f where e is an expression. - return address{addr: b.selectField(fn, e, true, escaping)} + return &address{addr: b.selectField(fn, e, true, escaping)} case *ast.IndexExpr: var x Value @@ -502,10 +502,10 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { Index: emitConv(fn, b.expr(fn, e.Index), tInt), } v.setType(et) - return address{addr: fn.emit(v)} + return &address{addr: fn.emit(v)} case *ast.StarExpr: - return address{addr: b.expr(fn, e.X), starPos: e.Star} + return &address{addr: b.expr(fn, e.X), starPos: e.Star} } panic(fmt.Sprintf("unexpected address expression: %T", e)) @@ -519,7 +519,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { // in an addressable location. // func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) { - if _, ok := loc.(address); ok { + if _, ok := loc.(*address); ok { if e, ok := e.(*ast.CompositeLit); ok { typ := loc.typ() switch typ.Underlying().(type) { @@ -1047,7 +1047,7 @@ func (b *builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global continue } g.spec = nil - lval = address{addr: g} + lval = &address{addr: g} } else { // Mode B: initialize all globals. if !isBlankIdent(id) { @@ -1056,7 +1056,7 @@ func (b *builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global continue // already done } g2.spec = nil - lval = address{addr: g2} + lval = &address{addr: g2} } } if init.Prog.mode&LogSource != 0 { @@ -1241,7 +1241,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, typ typ } faddr.setType(types.NewPointer(sf.Type())) fn.emit(faddr) - b.exprInPlace(fn, address{addr: faddr}, e) + b.exprInPlace(fn, &address{addr: faddr}, e) } case *types.Array, *types.Slice: @@ -1274,7 +1274,7 @@ func (b *builder) compLit(fn *Function, addr Value, e *ast.CompositeLit, typ typ } iaddr.setType(types.NewPointer(at.Elem())) fn.emit(iaddr) - b.exprInPlace(fn, address{addr: iaddr}, e) + b.exprInPlace(fn, &address{addr: iaddr}, e) } if t != at { // slice s := &Slice{X: array} diff --git a/ssa/builder_test.go b/ssa/builder_test.go index 263012f6..98cd2f60 100644 --- a/ssa/builder_test.go +++ b/ssa/builder_test.go @@ -42,14 +42,16 @@ func main() { return } - info, err := imp.CreateSourcePackage("main", []*ast.File{f}) - if err != nil { - t.Error(err.Error()) + info := imp.CreateSourcePackage("main", []*ast.File{f}) + if info.Err != nil { + t.Error(info.Err.Error()) return } prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) - prog.CreatePackages(imp) + for _, info := range imp.Packages { + prog.CreatePackage(info) + } mainPkg := prog.Package(info.Pkg) mainPkg.Build() diff --git a/ssa/create.go b/ssa/create.go index 5203c6c9..ed66adc8 100644 --- a/ssa/create.go +++ b/ssa/create.go @@ -57,24 +57,6 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program { return prog } -// CreatePackages creates an SSA Package for each type-checker package -// held by imp. All such packages must be error-free. -// -// The created packages may be accessed via the Program.Packages field. -// -// A package in the 'created' state has its Members mapping populated, -// but a subsequent call to Package.Build() or Program.BuildAll() is -// required to build SSA code for the bodies of its functions. -// -func (prog *Program) CreatePackages(imp *importer.Importer) { - // TODO(adonovan): make this idempotent, so that a second call - // to CreatePackages creates only the packages that appeared - // in imp since the first. - for path, info := range imp.Packages { - createPackage(prog, path, info) - } -} - // memberFromObject populates package pkg with a member for the // typechecker object obj. // @@ -143,10 +125,6 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) { pkg.Prog.concreteMethods[method] = fn } - case *types.Method: - // TODO(adonovan): do something more sensible here? - memberFromObject(pkg, obj.Func, syntax) - default: // (incl. *types.Package) panic(fmt.Sprintf("unexpected Object type: %T", obj)) } @@ -202,13 +180,23 @@ func membersFromDecl(pkg *Package, decl ast.Decl) { } } -// createPackage constructs an SSA Package from an error-free -// package described by info, and populates its Members mapping. +// CreatePackage constructs and returns an SSA Package from an +// error-free package described by info, and populates its Members +// mapping. +// +// Repeated calls with the same info returns the same Package. // // The real work of building SSA form for each function is not done // until a subsequent call to Package.Build(). // -func createPackage(prog *Program, importPath string, info *importer.PackageInfo) { +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 + } + p := &Package{ Prog: prog, Members: make(map[string]Member), @@ -246,11 +234,11 @@ func createPackage(prog *Program, importPath string, info *importer.PackageInfo) if obj, ok := obj.(*types.TypeName); ok { mset := types.NewMethodSet(obj.Type()) for i, n := 0, mset.Len(); i < n; i++ { - memberFromObject(p, mset.At(i), nil) + memberFromObject(p, mset.At(i).Func, nil) } mset = types.NewMethodSet(types.NewPointer(obj.Type())) for i, n := 0, mset.Len(); i < n; i++ { - memberFromObject(p, mset.At(i), nil) + memberFromObject(p, mset.At(i).Func, nil) } } memberFromObject(p, obj, nil) @@ -269,10 +257,12 @@ func createPackage(prog *Program, importPath string, info *importer.PackageInfo) p.DumpTo(os.Stderr) } - prog.PackagesByPath[importPath] = p + prog.PackagesByPath[info.Pkg.Path()] = p prog.packages[p.Object] = p if prog.mode&SanityCheckFunctions != 0 { sanityCheckPackage(p) } + + return p } diff --git a/ssa/example_test.go b/ssa/example_test.go index a64db301..b6c96d99 100644 --- a/ssa/example_test.go +++ b/ssa/example_test.go @@ -47,16 +47,18 @@ func main() { } // Create a "main" package containing one file. - info, err := imp.CreateSourcePackage("main", []*ast.File{file}) - if err != nil { - fmt.Printf(err.Error()) // type error + info := imp.CreateSourcePackage("main", []*ast.File{file}) + if info.Err != nil { + fmt.Printf(info.Err.Error()) // type error return } // Create SSA-form program representation. var mode ssa.BuilderMode prog := ssa.NewProgram(imp.Fset, mode) - prog.CreatePackages(imp) + for _, info := range imp.Packages { + prog.CreatePackage(info) + } mainPkg := prog.Package(info.Pkg) // Print out the package. diff --git a/ssa/interp/interp_test.go b/ssa/interp/interp_test.go index 9f8b4bed..12d1cb6f 100644 --- a/ssa/interp/interp_test.go +++ b/ssa/interp/interp_test.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" "testing" + "time" "code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/ssa" @@ -79,22 +80,22 @@ var gorootTests = []string{ "ddd.go", "blank.go", // partly disabled; TODO(adonovan): skip blank fields in struct{_} equivalence. "map.go", - "bom.go", "closedchan.go", "divide.go", "rename.go", "const3.go", "nil.go", "recover.go", // partly disabled; TODO(adonovan): fix. - // Slow tests follow. - "cmplxdivide.go cmplxdivide1.go", - "append.go", - "crlf.go", // doesn't actually assert anything "typeswitch1.go", "floatcmp.go", - "gc1.go", + "crlf.go", // doesn't actually assert anything + // Slow tests follow. + "bom.go", // ~1.7s + "gc1.go", // ~1.7s + "cmplxdivide.go cmplxdivide1.go", // ~2.4s // Working, but not worth enabling: + // "append.go", // works, but slow (15s). // "gc2.go", // works, but slow, and cheats on the memory check. // "sigchld.go", // works, but only on POSIX. // "peano.go", // works only up to n=9, and slow even then. @@ -111,7 +112,6 @@ var gorootTests = []string{ // Typechecker failures: // "switch.go", // https://code.google.com/p/go/issues/detail?id=5505 - // "rune.go", // https://code.google.com/p/go/issues/detail?id=5895 // Broken. TODO(adonovan): fix. // copy.go // very slow; but with N=4 quickly crashes, slice index out of range. @@ -139,6 +139,8 @@ var testdataTests = []string{ func run(t *testing.T, dir, input string) bool { fmt.Printf("Input: %s\n", input) + start := time.Now() + var inputs []string for _, i := range strings.Split(input, " ") { inputs = append(inputs, dir+i) @@ -166,14 +168,16 @@ func run(t *testing.T, dir, input string) bool { }() hint = fmt.Sprintf("To dump SSA representation, run:\n%% go run src/code.google.com/p/go.tools/ssa/ssadump.go -build=CFP %s\n", input) - info, err := imp.CreateSourcePackage("main", files) - if err != nil { - t.Errorf("ssa.Builder.CreatePackage(%s) failed: %s", inputs, err.Error()) + info := imp.CreateSourcePackage("main", files) + if info.Err != nil { + t.Errorf("importer.CreateSourcePackage(%s) failed: %s", inputs, info.Err.Error()) return false } prog := ssa.NewProgram(imp.Fset, ssa.SanityCheckFunctions) - prog.CreatePackages(imp) + for _, info := range imp.Packages { + prog.CreatePackage(info) + } prog.BuildAll() hint = fmt.Sprintf("To trace execution, run:\n%% go run src/code.google.com/p/go.tools/ssa/ssadump.go -build=C -run --interp=T %s\n", input) @@ -183,6 +187,11 @@ func run(t *testing.T, dir, input string) bool { } hint = "" // call off the hounds + + if false { + fmt.Println(input, time.Since(start)) // test profiling + } + return true } diff --git a/ssa/lvalue.go b/ssa/lvalue.go index 16a44370..c2056661 100644 --- a/ssa/lvalue.go +++ b/ssa/lvalue.go @@ -29,13 +29,13 @@ type address struct { object types.Object // source var, if from *ast.Ident } -func (a address) load(fn *Function) Value { +func (a *address) load(fn *Function) Value { load := emitLoad(fn, a.addr) load.pos = a.starPos return load } -func (a address) store(fn *Function, v Value) { +func (a *address) store(fn *Function, v Value) { store := emitStore(fn, a.addr, v) store.pos = a.starPos if a.id != nil { @@ -44,7 +44,7 @@ func (a address) store(fn *Function, v Value) { } } -func (a address) address(fn *Function) Value { +func (a *address) address(fn *Function) Value { if a.id != nil { // NB: this kind of DebugRef yields the object's address. emitDebugRef(fn, a.id, a.addr) @@ -52,7 +52,7 @@ func (a address) address(fn *Function) Value { return a.addr } -func (a address) typ() types.Type { +func (a *address) typ() types.Type { return deref(a.addr.Type()) } diff --git a/ssa/source_test.go b/ssa/source_test.go index b76fe08c..9bc07662 100644 --- a/ssa/source_test.go +++ b/ssa/source_test.go @@ -40,14 +40,16 @@ func TestObjValueLookup(t *testing.T) { } } - info, err := imp.CreateSourcePackage("main", []*ast.File{f}) - if err != nil { - t.Error(err.Error()) + info := imp.CreateSourcePackage("main", []*ast.File{f}) + if info.Err != nil { + t.Error(info.Err.Error()) return } prog := ssa.NewProgram(imp.Fset, ssa.DebugInfo /*|ssa.LogFunctions*/) - prog.CreatePackages(imp) + for _, info := range imp.Packages { + prog.CreatePackage(info) + } pkg := prog.Package(info.Pkg) pkg.Build() diff --git a/ssa/ssadump.go b/ssa/ssadump.go index 0941dd57..9880e0d5 100644 --- a/ssa/ssadump.go +++ b/ssa/ssadump.go @@ -114,7 +114,9 @@ func main() { // Create and build SSA-form program representation. prog := ssa.NewProgram(imp.Fset, mode) - prog.CreatePackages(imp) + for _, info := range imp.Packages { + prog.CreatePackage(info) + } prog.BuildAll() // Run the interpreter.