go.tools/ssa: refactoring to make Builder stateless.

A Builder is now just a Program and a Context.

Details of this CL:
- Builder.imp field removed.
- Builder.globals split up into Package.values and Prog.Builtins.
- Builder.packages  moved to Prog.packages.
- Builder.PackageFor moved to Program.Package(types.Object)
- Program.Lookup() func replaces Builder.globals map.
- also: keep Package.info field around until end of BuildPackage.

Planned follow-ups to eliminate Builder from API:
- split NewBuilder up into NewProgram and Program.CreatePackages(...)
- move Builder.BuildAllPackages -> Program.BuildAll(Context)
- move Builder.BuildPackage -> Package.Build(Context)

R=gri, iant
CC=golang-dev
https://golang.org/cl/9966044
This commit is contained in:
Alan Donovan 2013-06-03 14:15:19 -04:00
parent 1f2812fe9b
commit 4d628a0312
5 changed files with 103 additions and 93 deletions

View File

@ -95,10 +95,6 @@ const (
type Builder struct {
Prog *Program // the program being built
Context *Context // the client context
imp *importer.Importer // ASTs and types for loaded packages
packages map[*types.Package]*Package // SSA packages by types.Package
globals map[types.Object]Value // all package-level funcs and vars, and universal built-ins
}
// NewBuilder creates and returns a new SSA builder with options
@ -116,45 +112,46 @@ type Builder struct {
// analysis may then begin.
//
func NewBuilder(context *Context, imp *importer.Importer) *Builder {
b := &Builder{
Prog: &Program{
Files: imp.Fset,
Packages: make(map[string]*Package),
Builtins: make(map[types.Object]*Builtin),
methodSets: make(map[types.Type]MethodSet),
concreteMethods: make(map[*types.Func]*Function),
mode: context.Mode,
},
Context: context,
imp: imp,
globals: make(map[types.Object]Value),
packages: make(map[*types.Package]*Package),
prog := &Program{
Files: imp.Fset,
Packages: make(map[string]*Package),
packages: make(map[*types.Package]*Package),
Builtins: make(map[types.Object]*Builtin),
methodSets: make(map[types.Type]MethodSet),
concreteMethods: make(map[*types.Func]*Function),
mode: context.Mode,
}
// Create Values for built-in functions.
for i, n := 0, types.Universe.NumEntries(); i < n; i++ {
switch obj := types.Universe.At(i).(type) {
case *types.Func:
v := &Builtin{obj}
b.globals[obj] = v
b.Prog.Builtins[obj] = v
if obj, ok := types.Universe.At(i).(*types.Func); ok {
prog.Builtins[obj] = &Builtin{obj}
}
}
b := &Builder{
Prog: prog,
Context: context,
}
// TODO(adonovan): split the rest off as a separate method of Prog.
// Create ssa.Package for each types.Package.
// TODO(adonovan): rethink the API to avoid phase ordering issues.
// (What if the Importer was to load more packages later?)
for path, info := range imp.Packages {
p := b.createPackage(info)
b.Prog.Packages[path] = p
b.packages[p.Types] = p
prog.Packages[path] = p
prog.packages[p.Types] = p
}
// Compute the method sets, now that we have all packages' methods.
for _, pkg := range b.Prog.Packages {
for _, pkg := range prog.Packages {
for _, mem := range pkg.Members {
switch t := mem.(type) {
case *Type:
t.Methods = b.Prog.MethodSet(t.NamedType)
t.PtrMethods = b.Prog.MethodSet(pointer(t.NamedType))
t.Methods = prog.MethodSet(t.NamedType)
t.PtrMethods = prog.MethodSet(pointer(t.NamedType))
}
}
}
@ -162,40 +159,27 @@ func NewBuilder(context *Context, imp *importer.Importer) *Builder {
return b
}
// PackageFor returns the ssa.Package corresponding to the specified
// types.Package, or nil if this builder has not created such a
// package.
//
// TODO(adonovan): better name?
// TODO(adonovan): can we make this a method of Program?
//
func (b *Builder) PackageFor(p *types.Package) *Package {
return b.packages[p]
}
// lookup returns the package-level *Function or *Global (or universal
// *Builtin) for the named object obj.
// lookup returns the package-level *Function or *Global for the named
// object obj, building it if necessary.
//
// Intra-package references are edges in the initialization dependency
// graph. If the result v is a Function or Global belonging to
// 'from', the package on whose behalf this lookup occurs, then lookup
// emits initialization code into from.Init if not already done.
//
func (b *Builder) lookup(from *Package, obj types.Object) (v Value, ok bool) {
v, ok = b.globals[obj]
if ok {
switch v := v.(type) {
case *Function:
if from == v.Pkg {
b.buildFunction(v)
}
case *Global:
if from == v.Pkg {
b.buildGlobal(v, obj)
}
func (b *Builder) lookup(from *Package, obj types.Object) Value {
v := b.Prog.Value(obj)
switch v := v.(type) {
case *Function:
if from == v.Pkg {
b.buildFunction(v)
}
case *Global:
if from == v.Pkg {
b.buildGlobal(v, obj)
}
}
return
return v
}
// cond emits to fn code to evaluate boolean condition e and jump
@ -578,8 +562,8 @@ func (b *Builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
switch e := e.(type) {
case *ast.Ident:
obj := fn.Pkg.objectOf(e)
v, ok := b.lookup(fn.Pkg, obj) // var (address)
if !ok {
v := b.lookup(fn.Pkg, obj) // var (address)
if v == nil {
v = fn.lookup(obj, escaping)
}
return address{addr: v}
@ -601,7 +585,7 @@ func (b *Builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue {
case *ast.SelectorExpr:
// p.M where p is a package.
if obj := fn.Pkg.info.IsPackageRef(e); obj != nil {
if v, ok := b.lookup(fn.Pkg, obj); ok {
if v := b.lookup(fn.Pkg, obj); v != nil {
return address{addr: v}
}
panic("undefined package-qualified name: " + obj.Name())
@ -817,8 +801,12 @@ func (b *Builder) expr(fn *Function, e ast.Expr) Value {
case *ast.Ident:
obj := fn.Pkg.objectOf(e)
// Global or universal?
if v, ok := b.lookup(fn.Pkg, obj); ok {
// Universal built-in?
if obj.Pkg() == nil {
return b.Prog.Builtins[obj]
}
// Package-level func or var?
if v := b.lookup(fn.Pkg, obj); v != nil {
if _, ok := obj.(*types.Var); ok {
return emitLoad(fn, v) // var (address)
}
@ -958,7 +946,7 @@ func (b *Builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
// Case 1: call of form x.F() where x is a package name.
if obj := fn.Pkg.info.IsPackageRef(sel); obj != nil {
// This is a specialization of expr(ast.Ident(obj)).
if v, ok := b.lookup(fn.Pkg, obj); ok {
if v := b.lookup(fn.Pkg, obj); v != nil {
if _, ok := v.(*Function); !ok {
v = emitLoad(fn, v) // var (address)
}
@ -1111,7 +1099,7 @@ func (b *Builder) assignOp(fn *Function, loc lvalue, incr Value, op token.Token)
// buildGlobal emits code to the g.Pkg.Init function for the variable
// definition(s) of g. Effects occur out of lexical order; see
// explanation at globalValueSpec.
// Precondition: g == b.globals[obj]
// Precondition: g == b.Prog.Value(obj)
//
func (b *Builder) buildGlobal(g *Global, obj types.Object) {
spec := g.spec
@ -1134,7 +1122,7 @@ func (b *Builder) buildGlobal(g *Global, obj types.Object) {
// B) with g and obj nil, to initialize all globals in the same ValueSpec.
// This occurs during the left-to-right traversal over the ast.File.
//
// Precondition: g == b.globals[obj]
// Precondition: g == b.Prog.Value(obj)
//
// Package-level var initialization order is quite subtle.
// The side effects of:
@ -1181,7 +1169,7 @@ func (b *Builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global
} else {
// Mode B: initialize all globals.
if !isBlankIdent(id) {
g2 := b.globals[init.Pkg.objectOf(id)].(*Global)
g2 := b.Prog.Value(init.Pkg.objectOf(id)).(*Global)
if g2.spec == nil {
continue // already done
}
@ -1215,7 +1203,7 @@ func (b *Builder) globalValueSpec(init *Function, spec *ast.ValueSpec, g *Global
result := tuple.Type().(*types.Tuple)
for i, id := range spec.Names {
if !isBlankIdent(id) {
g := b.globals[init.Pkg.objectOf(id)].(*Global)
g := b.Prog.Value(init.Pkg.objectOf(id)).(*Global)
g.spec = nil // just an optimization
emitStore(init, g, emitExtract(init, tuple, i, result.At(i).Type()))
}
@ -1853,7 +1841,7 @@ func (b *Builder) rangeIndexed(fn *Function, x Value, tv types.Type) (k, v Value
} else {
// length = len(x).
var c Call
c.Call.Func = b.globals[types.Universe.Lookup(nil, "len")]
c.Call.Func = b.Prog.Builtins[types.Universe.Lookup(nil, "len")]
c.Call.Args = []Value{x}
c.setType(tInt)
length = fn.emit(&c)
@ -2328,7 +2316,7 @@ func (b *Builder) buildFunction(fn *Function) {
}
return
}
if fn.Prog.mode&LogSource != 0 {
if b.Context.Mode&LogSource != 0 {
defer logStack("build function %s @ %s",
fn.FullName(), fn.Prog.Files.Position(fn.pos))()
}
@ -2375,7 +2363,7 @@ func (b *Builder) memberFromObject(pkg *Package, obj types.Object, syntax ast.No
pos: obj.Pos(),
spec: spec,
}
b.globals[obj] = g
pkg.values[obj] = g
pkg.Members[name] = g
case *types.Func:
@ -2399,7 +2387,7 @@ func (b *Builder) memberFromObject(pkg *Package, obj types.Object, syntax ast.No
}
if sig.Recv() == nil {
// Function declaration.
b.globals[obj] = fn
pkg.values[obj] = fn
pkg.Members[name] = fn
} else {
// Method declaration.
@ -2476,6 +2464,7 @@ func (b *Builder) createPackage(info *importer.PackageInfo) *Package {
p := &Package{
Prog: b.Prog,
Members: make(map[string]Member),
values: make(map[types.Object]Value),
Types: info.Pkg,
info: info, // transient (CREATE and BUILD phases)
}
@ -2519,8 +2508,6 @@ func (b *Builder) createPackage(info *importer.PackageInfo) *Package {
p.DumpTo(os.Stderr)
}
p.info = nil
return p
}
@ -2551,10 +2538,13 @@ func (b *Builder) buildDecl(pkg *Package, decl ast.Decl) {
case *ast.FuncDecl:
id := decl.Name
if decl.Recv != nil {
return // method declaration
}
if isBlankIdent(id) {
// no-op
} else if decl.Recv == nil && id.Name == "init" {
} else if id.Name == "init" {
// init() block
if b.Context.Mode&LogSource != 0 {
fmt.Fprintln(os.Stderr, "build init block @", b.Prog.Files.Position(decl.Pos()))
@ -2578,9 +2568,9 @@ func (b *Builder) buildDecl(pkg *Package, decl ast.Decl) {
init.targets = init.targets.tail
init.currentBlock = next
} else if m, ok := b.globals[pkg.objectOf(id)]; ok {
} else {
// Package-level function.
b.buildFunction(m.(*Function))
b.buildFunction(b.Prog.Value(pkg.objectOf(id)).(*Function))
}
}
@ -2616,11 +2606,8 @@ func (b *Builder) BuildPackage(p *Package) {
if !atomic.CompareAndSwapInt32(&p.started, 0, 1) {
return // already started
}
info := b.imp.Packages[p.Types.Path()]
if info == nil {
panic("no PackageInfo for " + p.Types.Path())
}
if info.Files == nil {
if p.info.Files == nil {
p.info = nil
return // nothing to do
}
@ -2628,7 +2615,6 @@ func (b *Builder) BuildPackage(p *Package) {
// package-specific part (needn't be exported) containing
// info, nto1Vars which actually traverses the AST, plus the
// shared portion (Builder).
p.info = info
p.nTo1Vars = make(map[*ast.ValueSpec]bool)
if b.Context.Mode&LogSource != 0 {
@ -2651,7 +2637,7 @@ func (b *Builder) BuildPackage(p *Package) {
// transitive closure of dependencies,
// e.g. when using GcImporter.
seen := make(map[*types.Package]bool)
for _, file := range info.Files {
for _, file := range p.info.Files {
for _, imp := range file.Imports {
path, _ := strconv.Unquote(imp.Path.Value)
if path == "unsafe" {
@ -2663,7 +2649,7 @@ func (b *Builder) BuildPackage(p *Package) {
}
seen[typkg] = true
p2 := b.packages[typkg]
p2 := b.Prog.packages[typkg]
if p2 == nil {
panic("Building " + p.String() + ": CreatePackage has not been called for package " + path)
}
@ -2684,7 +2670,7 @@ func (b *Builder) BuildPackage(p *Package) {
//
// We also ensure all functions and methods are built, even if
// they are unreachable.
for _, file := range info.Files {
for _, file := range p.info.Files {
for _, decl := range file.Decls {
b.buildDecl(p, decl)
}

View File

@ -55,7 +55,7 @@ func main() {
// Construct an SSA builder.
builder := ssa.NewBuilder(&ssa.Context{}, imp)
mainPkg := builder.PackageFor(info.Pkg)
mainPkg := builder.Prog.Package(info.Pkg)
// Print out the package.
mainPkg.DumpTo(os.Stdout)

View File

@ -172,7 +172,7 @@ func run(t *testing.T, dir, input string) bool {
}
b := ssa.NewBuilder(&ssa.Context{Mode: ssa.SanityCheckFunctions}, imp)
mainpkg := b.PackageFor(info.Pkg)
mainpkg := b.Prog.Package(info.Pkg)
b.BuildAllPackages()
b = nil // discard Builder

View File

@ -253,7 +253,7 @@ func TestEnclosingFunction(t *testing.T) {
}
b := ssa.NewBuilder(new(ssa.Context), imp)
pkg := b.PackageFor(info.Pkg)
pkg := b.Prog.Package(info.Pkg)
b.BuildPackage(pkg)
name := "(none)"

View File

@ -19,9 +19,10 @@ import (
// lifetime.
//
type Program struct {
Files *token.FileSet // position information for the files of this Program [TODO: rename Fset]
Packages map[string]*Package // all loaded Packages, keyed by import path
Builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
Files *token.FileSet // position information for the files of this Program [TODO: rename Fset]
Packages map[string]*Package // all loaded Packages, keyed by import path [TODO rename packagesByPath]
packages map[*types.Package]*Package // all loaded Packages, keyed by object [TODO rename Packages]
Builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
methodSets map[types.Type]MethodSet // concrete method sets for all needed types [TODO(adonovan): de-dup]
methodSetsMu sync.Mutex // serializes all accesses to methodSets
@ -35,13 +36,14 @@ type Program struct {
// type-specific accessor methods Func, Type, Var and Const.
//
type Package struct {
Prog *Program // the owning program
Types *types.Package // the type checker's package object for this package
Members map[string]Member // all exported and unexported members of the package
Init *Function // the package's (concatenated) init function
Prog *Program // the owning program
Types *types.Package // the type checker's package object for this package [TODO rename Object]
Members map[string]Member // all package members keyed by name
values map[types.Object]Value // package-level vars and funcs, keyed by object
Init *Function // the package's (concatenated) init function
// The following fields are set transiently during building,
// then cleared.
// The following fields are set transiently, then cleared
// after building.
started int32 // atomically tested and set at start of build phase
info *importer.PackageInfo // package ASTs and type information
nTo1Vars map[*ast.ValueSpec]bool // set of n:1 ValueSpecs already built
@ -1320,6 +1322,28 @@ func (p *Package) Type(name string) (t *Type) {
return
}
// Value returns the program-level value corresponding to the
// specified named object, which may be a universal built-in
// (*Builtin) or a package-level var (*Global) or func (*Function) of
// some package in prog. It returns nil if the object is not found.
//
func (prog *Program) Value(obj types.Object) Value {
if p := obj.Pkg(); p != nil {
if pkg, ok := prog.packages[p]; ok {
return pkg.values[obj]
}
return nil
}
return prog.Builtins[obj]
}
// Package returns the SSA package corresponding to the specified
// type-checker package object. It returns nil if not found.
//
func (prog *Program) Package(pkg *types.Package) *Package {
return prog.packages[pkg]
}
func (v *Call) Pos() token.Pos { return v.Call.pos }
func (s *Defer) Pos() token.Pos { return s.Call.pos }
func (s *Go) Pos() token.Pos { return s.Call.pos }