mirror of
https://github.com/golang/go
synced 2024-11-18 16:04:44 -07:00
go.tools/ssa: fix a package-level var initialization order bug.
buildDecl was visiting all decls in source order, but the spec calls for visiting all vars and init() funcs in order, then all remaining functions. These two passes are now called buildInit(), buildFuncDecl(). + Test. Also: - Added workaround to gcimporter for Func with pkg==nil. - Prog.concreteMethods has been merged into Pkg.values. - Prog.concreteMethod() renamed declaredFunc(). - s/mfunc/obj/ (name cleanup from recent gri CL) R=gri CC=golang-dev https://golang.org/cl/12030044
This commit is contained in:
parent
64ea46e0bc
commit
fb0642f5fb
@ -611,6 +611,10 @@ func (p *gcParser) parseInterfaceType() Type {
|
||||
// TODO(gri) Ideally, we should use a named type here instead of
|
||||
// typ, for less verbose printing of interface method signatures.
|
||||
sig.recv = NewVar(token.NoPos, pkg, "", typ)
|
||||
// TODO(gri): fix: pkg may be nil!
|
||||
if pkg == nil {
|
||||
pkg = p.imports[p.id]
|
||||
}
|
||||
m := NewFunc(token.NoPos, pkg, name, sig)
|
||||
if alt := mset.insert(m); alt != nil {
|
||||
p.errorf("multiple methods named %s.%s", alt.Pkg().name, alt.Name())
|
||||
@ -906,6 +910,10 @@ func (p *gcParser) parseMethodDecl() {
|
||||
// and method exists already
|
||||
// TODO(gri) This is a quadratic algorithm - ok for now because method counts are small.
|
||||
if _, m := lookupMethod(base.methods, pkg, name); m == nil {
|
||||
// TODO(gri): fix: pkg may be nil.
|
||||
if pkg == nil {
|
||||
pkg = p.imports[p.id]
|
||||
}
|
||||
base.methods = append(base.methods, NewFunc(token.NoPos, pkg, name, sig))
|
||||
}
|
||||
}
|
||||
|
@ -646,10 +646,6 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
||||
c.setType(fn.Pkg.typeOf(e))
|
||||
return fn.emit(c)
|
||||
|
||||
// TODO(adonovan): add more tests for
|
||||
// interaction of bound interface method
|
||||
// closures and promotion.
|
||||
|
||||
case types.FieldVal:
|
||||
indices := sel.Index()
|
||||
last := len(indices) - 1
|
||||
@ -803,7 +799,7 @@ func (b *builder) setCallFunc(fn *Function, e *ast.CallExpr, c *CallCommon) {
|
||||
c.Method = obj
|
||||
} else {
|
||||
// "Call"-mode call.
|
||||
c.Value = fn.Prog.concreteMethod(obj)
|
||||
c.Value = fn.Prog.declaredFunc(obj)
|
||||
c.Args = append(c.Args, v)
|
||||
}
|
||||
return
|
||||
@ -2175,48 +2171,26 @@ func (b *builder) buildFunction(fn *Function) {
|
||||
fn.finishBody()
|
||||
}
|
||||
|
||||
// buildDecl builds SSA code for all globals, functions or methods
|
||||
// declared by decl in package pkg.
|
||||
// buildInit emits to init any initialization code needed for
|
||||
// declaration decl, causing SSA-building of any functions or methods
|
||||
// it references transitively.
|
||||
//
|
||||
func (b *builder) buildDecl(pkg *Package, decl ast.Decl) {
|
||||
func (b *builder) buildInit(init *Function, decl ast.Decl) {
|
||||
switch decl := decl.(type) {
|
||||
case *ast.GenDecl:
|
||||
switch decl.Tok {
|
||||
// Nothing to do for CONST, IMPORT.
|
||||
case token.VAR:
|
||||
if decl.Tok == token.VAR {
|
||||
for _, spec := range decl.Specs {
|
||||
b.globalValueSpec(pkg.init, spec.(*ast.ValueSpec), nil, nil)
|
||||
}
|
||||
case token.TYPE:
|
||||
for _, spec := range decl.Specs {
|
||||
id := spec.(*ast.TypeSpec).Name
|
||||
if isBlankIdent(id) {
|
||||
continue
|
||||
}
|
||||
nt := pkg.objectOf(id).Type().(*types.Named)
|
||||
for i, n := 0, nt.NumMethods(); i < n; i++ {
|
||||
b.buildFunction(pkg.Prog.concreteMethod(nt.Method(i)))
|
||||
}
|
||||
b.globalValueSpec(init, spec.(*ast.ValueSpec), nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.FuncDecl:
|
||||
id := decl.Name
|
||||
if decl.Recv != nil {
|
||||
return // method declaration
|
||||
}
|
||||
if isBlankIdent(id) {
|
||||
// no-op
|
||||
|
||||
// TODO(adonovan): test: can references within
|
||||
// the blank functions' body affect the program?
|
||||
|
||||
} else if id.Name == "init" {
|
||||
if decl.Recv == nil && decl.Name.Name == "init" {
|
||||
// init() block
|
||||
if pkg.Prog.mode&LogSource != 0 {
|
||||
fmt.Fprintln(os.Stderr, "build init block @", pkg.Prog.Fset.Position(decl.Pos()))
|
||||
if init.Prog.mode&LogSource != 0 {
|
||||
fmt.Fprintln(os.Stderr, "build init block @",
|
||||
init.Prog.Fset.Position(decl.Pos()))
|
||||
}
|
||||
init := pkg.init
|
||||
|
||||
// A return statement within an init block is
|
||||
// treated like a "goto" to the the next init
|
||||
@ -2234,13 +2208,24 @@ func (b *builder) buildDecl(pkg *Package, decl ast.Decl) {
|
||||
emitJump(init, next)
|
||||
init.targets = init.targets.tail
|
||||
init.currentBlock = next
|
||||
|
||||
} else {
|
||||
// Package-level function.
|
||||
b.buildFunction(pkg.values[pkg.objectOf(id)].(*Function))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildFuncDecl builds SSA code for the function or method declared
|
||||
// by decl in package pkg.
|
||||
//
|
||||
func (b *builder) buildFuncDecl(pkg *Package, decl *ast.FuncDecl) {
|
||||
id := decl.Name
|
||||
if isBlankIdent(id) {
|
||||
// TODO(gri): workaround for missing object,
|
||||
// see e.g. $GOROOT/test/blank.go:27.
|
||||
return
|
||||
}
|
||||
if decl.Recv == nil && id.Name == "init" {
|
||||
return // init() block: already done in pass 1
|
||||
}
|
||||
b.buildFunction(pkg.values[pkg.objectOf(id)].(*Function))
|
||||
}
|
||||
|
||||
// BuildAll calls Package.Build() for each package in prog.
|
||||
@ -2303,28 +2288,35 @@ func (p *Package) Build() {
|
||||
nTo1Vars: make(map[*ast.ValueSpec]bool),
|
||||
}
|
||||
|
||||
// Visit the package's var decls and init funcs in source
|
||||
// order. This causes init() code to be generated in
|
||||
// topological order. We visit them transitively through
|
||||
// functions of the same package, but we don't treat functions
|
||||
// as roots.
|
||||
//
|
||||
// We also ensure all functions and methods are built, even if
|
||||
// they are unreachable.
|
||||
// Pass 1: visit the package's var decls and init funcs in
|
||||
// source order, causing init() code to be generated in
|
||||
// topological order. We visit package-level vars
|
||||
// transitively through functions and methods, building them
|
||||
// as we go.
|
||||
for _, file := range p.info.Files {
|
||||
for _, decl := range file.Decls {
|
||||
b.buildDecl(p, decl)
|
||||
b.buildInit(init, decl)
|
||||
}
|
||||
}
|
||||
|
||||
p.info = nil // We no longer need ASTs or go/types deductions.
|
||||
|
||||
// Finish up.
|
||||
// Finish up init().
|
||||
emitJump(init, done)
|
||||
init.currentBlock = done
|
||||
init.emit(new(RunDefers))
|
||||
init.emit(new(Ret))
|
||||
init.finishBody()
|
||||
|
||||
// Pass 2: build all remaining package-level functions and
|
||||
// methods in source order, including unreachable/blank ones.
|
||||
for _, file := range p.info.Files {
|
||||
for _, decl := range file.Decls {
|
||||
if decl, ok := decl.(*ast.FuncDecl); ok {
|
||||
b.buildFuncDecl(p, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.info = nil // We no longer need ASTs or go/types deductions.
|
||||
}
|
||||
|
||||
// Only valid during p's create and build phases.
|
||||
|
@ -40,7 +40,6 @@ func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||
PackagesByPath: make(map[string]*Package),
|
||||
packages: make(map[*types.Package]*Package),
|
||||
builtins: make(map[types.Object]*Builtin),
|
||||
concreteMethods: make(map[*types.Func]*Function),
|
||||
boundMethodWrappers: make(map[*types.Func]*Function),
|
||||
ifaceMethodWrappers: make(map[*types.Func]*Function),
|
||||
mode: mode,
|
||||
@ -101,25 +100,19 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
|
||||
body: decl.Body,
|
||||
}
|
||||
}
|
||||
sig := obj.Type().(*types.Signature)
|
||||
fn := &Function{
|
||||
name: name,
|
||||
object: obj,
|
||||
Signature: sig,
|
||||
Signature: obj.Type().(*types.Signature),
|
||||
Synthetic: synthetic,
|
||||
pos: obj.Pos(), // (iff syntax)
|
||||
Pkg: pkg,
|
||||
Prog: pkg.Prog,
|
||||
syntax: fs,
|
||||
}
|
||||
if recv := sig.Recv(); recv == nil {
|
||||
// Function declaration.
|
||||
pkg.values[obj] = fn
|
||||
pkg.Members[name] = fn
|
||||
} else {
|
||||
// Concrete method.
|
||||
_ = deref(recv.Type()).(*types.Named) // assertion
|
||||
pkg.Prog.concreteMethods[obj] = fn
|
||||
pkg.values[obj] = fn
|
||||
if fn.Signature.Recv() == nil {
|
||||
pkg.Members[name] = fn // package-level function
|
||||
}
|
||||
|
||||
default: // (incl. *types.Package)
|
||||
|
@ -134,6 +134,7 @@ var testdataTests = []string{
|
||||
"fieldprom.go",
|
||||
"ifaceconv.go",
|
||||
"ifaceprom.go",
|
||||
"initorder.go",
|
||||
"methprom.go",
|
||||
"mrvchain.go",
|
||||
}
|
||||
|
2
ssa/interp/testdata/ifaceprom.go
vendored
2
ssa/interp/testdata/ifaceprom.go
vendored
@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
// Test of promotion of methods of an interface embedded within a
|
||||
// struct. In particular, this test excercises that the correct
|
||||
// struct. In particular, this test exercises that the correct
|
||||
// method is called.
|
||||
|
||||
type I interface {
|
||||
|
55
ssa/interp/testdata/initorder.go
vendored
Normal file
55
ssa/interp/testdata/initorder.go
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
// Test of initialization order of package-level vars.
|
||||
|
||||
type T int
|
||||
|
||||
var counter int
|
||||
|
||||
func next() int {
|
||||
c := counter
|
||||
counter++
|
||||
return c
|
||||
}
|
||||
|
||||
func (T) next() int {
|
||||
return next()
|
||||
}
|
||||
|
||||
var t T
|
||||
|
||||
func makeOrder1() [6]int {
|
||||
return [6]int{f1, b1, d1, e1, c1, a1}
|
||||
}
|
||||
|
||||
func makeOrder2() [6]int {
|
||||
return [6]int{f2, b2, d2, e2, c2, a2}
|
||||
}
|
||||
|
||||
var order1 = makeOrder1()
|
||||
|
||||
func main() {
|
||||
// order1 is a package-level variable:
|
||||
// [a-f]1 are initialized is reference order.
|
||||
if order1 != [6]int{0, 1, 2, 3, 4, 5} {
|
||||
panic(order1)
|
||||
}
|
||||
|
||||
// order2 is a local variable:
|
||||
// [a-f]2 are initialized in lexical order.
|
||||
var order2 = makeOrder2()
|
||||
if order2 != [6]int{11, 7, 9, 10, 8, 6} {
|
||||
panic(order2)
|
||||
}
|
||||
}
|
||||
|
||||
// The references traversal visits through calls to package-level
|
||||
// functions (next), method expressions (T.next) and methods (t.next).
|
||||
|
||||
var a1, b1 = next(), next()
|
||||
var c1, d1 = T.next(0), T.next(0)
|
||||
var e1, f1 = t.next(), t.next()
|
||||
|
||||
var a2, b2 = next(), next()
|
||||
var c2, d2 = T.next(0), T.next(0)
|
||||
var e2, f2 = t.next(), t.next()
|
@ -118,16 +118,14 @@ func (prog *Program) LookupMethod(meth *types.Selection) *Function {
|
||||
return prog.populateMethodSet(meth.Recv(), meth)[meth.Obj().Id()]
|
||||
}
|
||||
|
||||
// concreteMethod returns the concrete method denoted by obj.
|
||||
// Panic ensues if there is no such method (e.g. it's a standalone
|
||||
// function).
|
||||
// declaredFunc returns the concrete function/method denoted by obj.
|
||||
// Panic ensues if there is none.
|
||||
//
|
||||
func (prog *Program) concreteMethod(obj *types.Func) *Function {
|
||||
fn := prog.concreteMethods[obj]
|
||||
if fn == nil {
|
||||
panic("no concrete method: " + obj.String())
|
||||
func (prog *Program) declaredFunc(obj *types.Func) *Function {
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v.(*Function)
|
||||
}
|
||||
return fn
|
||||
panic("no concrete method: " + obj.String())
|
||||
}
|
||||
|
||||
// findMethod returns the concrete Function for the method meth,
|
||||
@ -137,18 +135,18 @@ func (prog *Program) concreteMethod(obj *types.Func) *Function {
|
||||
//
|
||||
func findMethod(prog *Program, meth *types.Selection) *Function {
|
||||
needsPromotion := len(meth.Index()) > 1
|
||||
mfunc := meth.Obj().(*types.Func)
|
||||
needsIndirection := !isPointer(recvType(mfunc)) && isPointer(meth.Recv())
|
||||
obj := meth.Obj().(*types.Func)
|
||||
needsIndirection := !isPointer(recvType(obj)) && isPointer(meth.Recv())
|
||||
|
||||
if needsPromotion || needsIndirection {
|
||||
return makeWrapper(prog, meth.Recv(), meth)
|
||||
}
|
||||
|
||||
if _, ok := meth.Recv().Underlying().(*types.Interface); ok {
|
||||
return interfaceMethodWrapper(prog, meth.Recv(), mfunc)
|
||||
return interfaceMethodWrapper(prog, meth.Recv(), obj)
|
||||
}
|
||||
|
||||
return prog.concreteMethod(mfunc)
|
||||
return prog.declaredFunc(obj)
|
||||
}
|
||||
|
||||
// makeWrapper returns a synthetic wrapper Function that optionally
|
||||
@ -173,21 +171,21 @@ func findMethod(prog *Program, meth *types.Selection) *Function {
|
||||
// EXCLUSIVE_LOCKS_REQUIRED(prog.methodsMu)
|
||||
//
|
||||
func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function {
|
||||
mfunc := meth.Obj().(*types.Func)
|
||||
old := mfunc.Type().(*types.Signature)
|
||||
obj := meth.Obj().(*types.Func)
|
||||
old := obj.Type().(*types.Signature)
|
||||
sig := types.NewSignature(nil, types.NewVar(token.NoPos, nil, "recv", typ), old.Params(), old.Results(), old.IsVariadic())
|
||||
|
||||
description := fmt.Sprintf("wrapper for %s", mfunc)
|
||||
description := fmt.Sprintf("wrapper for %s", obj)
|
||||
if prog.mode&LogSource != 0 {
|
||||
defer logStack("make %s to (%s)", description, typ)()
|
||||
}
|
||||
fn := &Function{
|
||||
name: mfunc.Name(),
|
||||
name: obj.Name(),
|
||||
method: meth,
|
||||
Signature: sig,
|
||||
Synthetic: description,
|
||||
Prog: prog,
|
||||
pos: mfunc.Pos(),
|
||||
pos: obj.Pos(),
|
||||
}
|
||||
fn.startBody()
|
||||
fn.addSpilledParam(sig.Recv())
|
||||
@ -220,10 +218,10 @@ func makeWrapper(prog *Program, typ types.Type, meth *types.Selection) *Function
|
||||
if !isPointer(old.Recv().Type()) {
|
||||
v = emitLoad(fn, v)
|
||||
}
|
||||
c.Call.Value = prog.concreteMethod(mfunc)
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
c.Call.Args = append(c.Call.Args, v)
|
||||
} else {
|
||||
c.Call.Method = mfunc
|
||||
c.Call.Method = obj
|
||||
c.Call.Value = emitLoad(fn, v)
|
||||
}
|
||||
for _, arg := range fn.Params[1:] {
|
||||
@ -357,7 +355,7 @@ func boundMethodWrapper(prog *Program, obj *types.Func) *Function {
|
||||
var c Call
|
||||
|
||||
if _, ok := recvType(obj).Underlying().(*types.Interface); !ok { // concrete
|
||||
c.Call.Value = prog.concreteMethod(obj)
|
||||
c.Call.Value = prog.declaredFunc(obj)
|
||||
c.Call.Args = []Value{cap}
|
||||
} else {
|
||||
c.Call.Value = cap
|
||||
|
@ -220,14 +220,10 @@ func (prog *Program) FuncValue(obj *types.Func) Value {
|
||||
if v, ok := prog.builtins[obj]; ok {
|
||||
return v
|
||||
}
|
||||
// Package-level function?
|
||||
// Package-level function or declared method?
|
||||
if v := prog.packageLevelValue(obj); v != nil {
|
||||
return v
|
||||
}
|
||||
// Concrete method?
|
||||
if v := prog.concreteMethods[obj]; v != nil {
|
||||
return v
|
||||
}
|
||||
// TODO(adonovan): interface method wrappers? other wrappers?
|
||||
return nil
|
||||
}
|
||||
|
13
ssa/ssa.go
13
ssa/ssa.go
@ -18,12 +18,11 @@ import (
|
||||
// A Program is a partial or complete Go program converted to SSA form.
|
||||
//
|
||||
type Program struct {
|
||||
Fset *token.FileSet // position information for the files of this Program
|
||||
PackagesByPath map[string]*Package // all loaded Packages, keyed by import path
|
||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
|
||||
concreteMethods map[*types.Func]*Function // maps declared concrete methods to their code
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
Fset *token.FileSet // position information for the files of this Program
|
||||
PackagesByPath map[string]*Package // all loaded Packages, keyed by import path
|
||||
packages map[*types.Package]*Package // all loaded Packages, keyed by object
|
||||
builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
|
||||
mode BuilderMode // set of mode bits for SSA construction
|
||||
|
||||
methodsMu sync.Mutex // guards the following maps:
|
||||
methodSets typemap.M // maps type to its concrete MethodSet
|
||||
@ -40,7 +39,7 @@ type Package struct {
|
||||
Prog *Program // the owning program
|
||||
Object *types.Package // the type checker's package object for this package
|
||||
Members map[string]Member // all package members keyed by name
|
||||
values map[types.Object]Value // package-level vars and funcs, keyed by object
|
||||
values map[types.Object]Value // package-level vars & funcs (incl. methods), keyed by object
|
||||
init *Function // Func("init"); the package's (concatenated) init function
|
||||
|
||||
// The following fields are set transiently, then cleared
|
||||
|
Loading…
Reference in New Issue
Block a user