mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
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:
parent
1f2812fe9b
commit
4d628a0312
148
ssa/builder.go
148
ssa/builder.go
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)"
|
||||
|
42
ssa/ssa.go
42
ssa/ssa.go
@ -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 }
|
||||
|
Loading…
x
Reference in New Issue
Block a user