go.tools/ssa: small changes accumulated during gri's vacation. :)
Method sets:
- Simplify CallCommon.
Avoid the implicit copy when calling a T method on a *T
receiver. This simplifies clients. Instead we generate
"indirection wrapper" functions that do this (like gc does).
New invariant:
m's receiver type is exactly T for all m in MethodSet(T)
- MakeInterface no longer holds the concrete type's MethodSet.
We can defer its computation this way.
- ssa.Type now just wraps a types.TypeName object.
MethodSets are computed as needed, not eagerly.
Position info:
- new CanonicalPos utility maps ast.Expr to canonical
token.Pos, as returned by {Instruction,Value}.Pos() methods.
- Don't set posn for implicit operations (e.g. varargs array alloc)
- Set position info for ChangeInterface and Slice instructions.
Cosmetic:
- add Member.Token() method
- simplify isPointer
- Omit words "interface", "slice" when printing MakeInterface,
MakeSlice; the type is enough.
- Comments on PathEnclosingInterval.
- Remove Function.FullName() where implicit String() suffices.
Also:
- Exposed NewLiteral to clients.
- Added ssa.Instruction.Parent() *Function
Added ssa.BasicBlock.Parent() *Function.
Added Sanity checks for above.
R=golang-dev, gri
CC=golang-dev
https://golang.org/cl/10166045
This commit is contained in:
parent
9ce6fcb502
commit
341a07a3aa
|
|
@ -62,8 +62,8 @@ var (
|
||||||
// SSA Value constants.
|
// SSA Value constants.
|
||||||
vZero = intLiteral(0)
|
vZero = intLiteral(0)
|
||||||
vOne = intLiteral(1)
|
vOne = intLiteral(1)
|
||||||
vTrue = newLiteral(exact.MakeBool(true), tBool)
|
vTrue = NewLiteral(exact.MakeBool(true), tBool)
|
||||||
vFalse = newLiteral(exact.MakeBool(false), tBool)
|
vFalse = NewLiteral(exact.MakeBool(false), tBool)
|
||||||
)
|
)
|
||||||
|
|
||||||
// builder holds state associated with the package currently being built.
|
// builder holds state associated with the package currently being built.
|
||||||
|
|
@ -230,7 +230,7 @@ func (b *builder) exprN(fn *Function, e ast.Expr) Value {
|
||||||
tuple = fn.emit(lookup)
|
tuple = fn.emit(lookup)
|
||||||
|
|
||||||
case *ast.TypeAssertExpr:
|
case *ast.TypeAssertExpr:
|
||||||
return emitTypeTest(fn, b.expr(fn, e.X), fn.Pkg.typeOf(e))
|
return emitTypeTest(fn, b.expr(fn, e.X), fn.Pkg.typeOf(e), e.Lparen)
|
||||||
|
|
||||||
case *ast.UnaryExpr: // must be receive <-
|
case *ast.UnaryExpr: // must be receive <-
|
||||||
typ = fn.Pkg.typeOf(e.X).Underlying().(*types.Chan).Elem()
|
typ = fn.Pkg.typeOf(e.X).Underlying().(*types.Chan).Elem()
|
||||||
|
|
@ -345,7 +345,7 @@ func (b *builder) selector(fn *Function, e *ast.SelectorExpr, wantAddr, escaping
|
||||||
if !wantAddr {
|
if !wantAddr {
|
||||||
if m, recv := b.findMethod(fn, e.X, id); m != nil {
|
if m, recv := b.findMethod(fn, e.X, id); m != nil {
|
||||||
c := &MakeClosure{
|
c := &MakeClosure{
|
||||||
Fn: makeBoundMethodThunk(fn.Prog, m, recv),
|
Fn: makeBoundMethodThunk(fn.Prog, m, recv.Type()),
|
||||||
Bindings: []Value{recv},
|
Bindings: []Value{recv},
|
||||||
}
|
}
|
||||||
c.setPos(e.Sel.Pos())
|
c.setPos(e.Sel.Pos())
|
||||||
|
|
@ -579,7 +579,7 @@ func (b *builder) exprInPlace(fn *Function, loc lvalue, e ast.Expr) {
|
||||||
//
|
//
|
||||||
func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
||||||
if v := fn.Pkg.info.ValueOf(e); v != nil {
|
if v := fn.Pkg.info.ValueOf(e); v != nil {
|
||||||
return newLiteral(v, fn.Pkg.typeOf(e))
|
return NewLiteral(v, fn.Pkg.typeOf(e))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch e := e.(type) {
|
switch e := e.(type) {
|
||||||
|
|
@ -618,7 +618,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
||||||
return b.expr(fn, e.X)
|
return b.expr(fn, e.X)
|
||||||
|
|
||||||
case *ast.TypeAssertExpr: // single-result form only
|
case *ast.TypeAssertExpr: // single-result form only
|
||||||
return emitTypeAssert(fn, b.expr(fn, e.X), fn.Pkg.typeOf(e))
|
return emitTypeAssert(fn, b.expr(fn, e.X), fn.Pkg.typeOf(e), e.Lparen)
|
||||||
|
|
||||||
case *ast.CallExpr:
|
case *ast.CallExpr:
|
||||||
typ := fn.Pkg.typeOf(e)
|
typ := fn.Pkg.typeOf(e)
|
||||||
|
|
@ -709,6 +709,7 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value {
|
||||||
Low: low,
|
Low: low,
|
||||||
High: high,
|
High: high,
|
||||||
}
|
}
|
||||||
|
v.setPos(e.Lbrack)
|
||||||
v.setType(fn.Pkg.typeOf(e))
|
v.setType(fn.Pkg.typeOf(e))
|
||||||
return fn.emit(v)
|
return fn.emit(v)
|
||||||
|
|
||||||
|
|
@ -969,7 +970,8 @@ func (b *builder) emitCallArgs(fn *Function, sig *types.Signature, e *ast.CallEx
|
||||||
} else {
|
} else {
|
||||||
// Replace a suffix of args with a slice containing it.
|
// Replace a suffix of args with a slice containing it.
|
||||||
at := types.NewArray(vt, int64(len(varargs)))
|
at := types.NewArray(vt, int64(len(varargs)))
|
||||||
a := emitNew(fn, at, e.Lparen)
|
// Don't set pos (e.g. to e.Lparen) for implicit Allocs.
|
||||||
|
a := emitNew(fn, at, token.NoPos)
|
||||||
for i, arg := range varargs {
|
for i, arg := range varargs {
|
||||||
iaddr := &IndexAddr{
|
iaddr := &IndexAddr{
|
||||||
X: a,
|
X: a,
|
||||||
|
|
@ -1501,7 +1503,7 @@ func (b *builder) typeSwitchStmt(fn *Function, s *ast.TypeSwitchStmt, label *lbl
|
||||||
if casetype == tUntypedNil {
|
if casetype == tUntypedNil {
|
||||||
condv = emitCompare(fn, token.EQL, x, nilLiteral(x.Type()), token.NoPos)
|
condv = emitCompare(fn, token.EQL, x, nilLiteral(x.Type()), token.NoPos)
|
||||||
} else {
|
} else {
|
||||||
yok := emitTypeTest(fn, x, casetype)
|
yok := emitTypeTest(fn, x, casetype, token.NoPos)
|
||||||
ti = emitExtract(fn, yok, 0, casetype)
|
ti = emitExtract(fn, yok, 0, casetype)
|
||||||
condv = emitExtract(fn, yok, 1, tBool)
|
condv = emitExtract(fn, yok, 1, tBool)
|
||||||
}
|
}
|
||||||
|
|
@ -1643,7 +1645,7 @@ func (b *builder) selectStmt(fn *Function, s *ast.SelectStmt, label *lblock) {
|
||||||
switch comm := clause.Comm.(type) {
|
switch comm := clause.Comm.(type) {
|
||||||
case *ast.AssignStmt: // x := <-states[state].Chan
|
case *ast.AssignStmt: // x := <-states[state].Chan
|
||||||
xdecl := fn.addNamedLocal(fn.Pkg.objectOf(comm.Lhs[0].(*ast.Ident)))
|
xdecl := fn.addNamedLocal(fn.Pkg.objectOf(comm.Lhs[0].(*ast.Ident)))
|
||||||
recv := emitTypeAssert(fn, emitExtract(fn, triple, 1, tEface), xdecl.Type().Deref())
|
recv := emitTypeAssert(fn, emitExtract(fn, triple, 1, tEface), xdecl.Type().Deref(), token.NoPos)
|
||||||
emitStore(fn, xdecl, recv)
|
emitStore(fn, xdecl, recv)
|
||||||
|
|
||||||
if len(comm.Lhs) == 2 { // x, ok := ...
|
if len(comm.Lhs) == 2 { // x, ok := ...
|
||||||
|
|
|
||||||
|
|
@ -35,13 +35,14 @@ const (
|
||||||
//
|
//
|
||||||
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
func NewProgram(fset *token.FileSet, mode BuilderMode) *Program {
|
||||||
prog := &Program{
|
prog := &Program{
|
||||||
Files: fset,
|
Files: fset,
|
||||||
Packages: make(map[string]*Package),
|
Packages: make(map[string]*Package),
|
||||||
packages: make(map[*types.Package]*Package),
|
packages: make(map[*types.Package]*Package),
|
||||||
Builtins: make(map[types.Object]*Builtin),
|
Builtins: make(map[types.Object]*Builtin),
|
||||||
methodSets: make(map[types.Type]MethodSet),
|
methodSets: make(map[types.Type]MethodSet),
|
||||||
concreteMethods: make(map[*types.Func]*Function),
|
concreteMethods: make(map[*types.Func]*Function),
|
||||||
mode: mode,
|
indirectionWrappers: make(map[*Function]*Function),
|
||||||
|
mode: mode,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Values for built-in functions.
|
// Create Values for built-in functions.
|
||||||
|
|
@ -67,25 +68,9 @@ func (prog *Program) CreatePackages(imp *importer.Importer) {
|
||||||
// TODO(adonovan): make this idempotent, so that a second call
|
// TODO(adonovan): make this idempotent, so that a second call
|
||||||
// to CreatePackages creates only the packages that appeared
|
// to CreatePackages creates only the packages that appeared
|
||||||
// in imp since the first.
|
// in imp since the first.
|
||||||
//
|
|
||||||
// TODO(adonovan): make it create packages in topological
|
|
||||||
// order (using info.Imports()) so we can compute method sets
|
|
||||||
// in postorder (or within createPackage) rather than as a
|
|
||||||
// second pass.
|
|
||||||
|
|
||||||
for path, info := range imp.Packages {
|
for path, info := range imp.Packages {
|
||||||
createPackage(prog, path, info)
|
createPackage(prog, path, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the method sets, now that we have all packages' methods.
|
|
||||||
for _, pkg := range prog.Packages {
|
|
||||||
for _, mem := range pkg.Members {
|
|
||||||
if t, ok := mem.(*Type); ok {
|
|
||||||
t.Methods = prog.MethodSet(t.NamedType)
|
|
||||||
t.PtrMethods = prog.MethodSet(pointer(t.NamedType))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// memberFromObject populates package pkg with a member for the
|
// memberFromObject populates package pkg with a member for the
|
||||||
|
|
@ -99,12 +84,12 @@ func memberFromObject(pkg *Package, obj types.Object, syntax ast.Node) {
|
||||||
name := obj.Name()
|
name := obj.Name()
|
||||||
switch obj := obj.(type) {
|
switch obj := obj.(type) {
|
||||||
case *types.TypeName:
|
case *types.TypeName:
|
||||||
pkg.Members[name] = &Type{NamedType: obj.Type().(*types.Named)}
|
pkg.Members[name] = &Type{Object: obj}
|
||||||
|
|
||||||
case *types.Const:
|
case *types.Const:
|
||||||
pkg.Members[name] = &Constant{
|
pkg.Members[name] = &Constant{
|
||||||
name: name,
|
name: name,
|
||||||
Value: newLiteral(obj.Val(), obj.Type()),
|
Value: NewLiteral(obj.Val(), obj.Type()),
|
||||||
pos: obj.Pos(),
|
pos: obj.Pos(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
16
ssa/emit.go
16
ssa/emit.go
|
|
@ -160,7 +160,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||||
|
|
||||||
// Assignment from one interface type to another?
|
// Assignment from one interface type to another?
|
||||||
if _, ok := ut_src.(*types.Interface); ok {
|
if _, ok := ut_src.(*types.Interface); ok {
|
||||||
return emitTypeAssert(f, val, typ)
|
return emitTypeAssert(f, val, typ, token.NoPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Untyped nil literal? Return interface-typed nil literal.
|
// Untyped nil literal? Return interface-typed nil literal.
|
||||||
|
|
@ -174,10 +174,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||||
val = emitConv(f, val, DefaultType(ut_src))
|
val = emitConv(f, val, DefaultType(ut_src))
|
||||||
}
|
}
|
||||||
|
|
||||||
mi := &MakeInterface{
|
mi := &MakeInterface{X: val}
|
||||||
X: val,
|
|
||||||
Methods: f.Prog.MethodSet(t_src),
|
|
||||||
}
|
|
||||||
mi.setType(typ)
|
mi.setType(typ)
|
||||||
return f.emit(mi)
|
return f.emit(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +185,7 @@ func emitConv(f *Function, val Value, typ types.Type) Value {
|
||||||
// change yet; this defers the point at which the number of
|
// change yet; this defers the point at which the number of
|
||||||
// possible representations explodes.
|
// possible representations explodes.
|
||||||
if l, ok := val.(*Literal); ok {
|
if l, ok := val.(*Literal); ok {
|
||||||
return newLiteral(l.Value, typ)
|
return NewLiteral(l.Value, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
// A representation-changing conversion.
|
// A representation-changing conversion.
|
||||||
|
|
@ -246,7 +243,7 @@ func emitExtract(f *Function, tuple Value, index int, typ types.Type) Value {
|
||||||
// emitTypeAssert emits to f a type assertion value := x.(t) and
|
// emitTypeAssert emits to f a type assertion value := x.(t) and
|
||||||
// returns the value. x.Type() must be an interface.
|
// returns the value. x.Type() must be an interface.
|
||||||
//
|
//
|
||||||
func emitTypeAssert(f *Function, x Value, t types.Type) Value {
|
func emitTypeAssert(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||||
// Simplify infallible assertions.
|
// Simplify infallible assertions.
|
||||||
txi := x.Type().Underlying().(*types.Interface)
|
txi := x.Type().Underlying().(*types.Interface)
|
||||||
if ti, ok := t.Underlying().(*types.Interface); ok {
|
if ti, ok := t.Underlying().(*types.Interface); ok {
|
||||||
|
|
@ -255,12 +252,14 @@ func emitTypeAssert(f *Function, x Value, t types.Type) Value {
|
||||||
}
|
}
|
||||||
if isSuperinterface(ti, txi) {
|
if isSuperinterface(ti, txi) {
|
||||||
c := &ChangeInterface{X: x}
|
c := &ChangeInterface{X: x}
|
||||||
|
c.setPos(pos)
|
||||||
c.setType(t)
|
c.setType(t)
|
||||||
return f.emit(c)
|
return f.emit(c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a := &TypeAssert{X: x, AssertedType: t}
|
a := &TypeAssert{X: x, AssertedType: t}
|
||||||
|
a.setPos(pos)
|
||||||
a.setType(t)
|
a.setType(t)
|
||||||
return f.emit(a)
|
return f.emit(a)
|
||||||
}
|
}
|
||||||
|
|
@ -268,7 +267,7 @@ func emitTypeAssert(f *Function, x Value, t types.Type) Value {
|
||||||
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
|
// emitTypeTest emits to f a type test value,ok := x.(t) and returns
|
||||||
// a (value, ok) tuple. x.Type() must be an interface.
|
// a (value, ok) tuple. x.Type() must be an interface.
|
||||||
//
|
//
|
||||||
func emitTypeTest(f *Function, x Value, t types.Type) Value {
|
func emitTypeTest(f *Function, x Value, t types.Type, pos token.Pos) Value {
|
||||||
// TODO(adonovan): opt: simplify infallible tests as per
|
// TODO(adonovan): opt: simplify infallible tests as per
|
||||||
// emitTypeAssert, and return (x, vTrue).
|
// emitTypeAssert, and return (x, vTrue).
|
||||||
// (Requires that exprN returns a slice of extracted values,
|
// (Requires that exprN returns a slice of extracted values,
|
||||||
|
|
@ -278,6 +277,7 @@ func emitTypeTest(f *Function, x Value, t types.Type) Value {
|
||||||
AssertedType: t,
|
AssertedType: t,
|
||||||
CommaOk: true,
|
CommaOk: true,
|
||||||
}
|
}
|
||||||
|
a.setPos(pos)
|
||||||
a.setType(types.NewTuple(
|
a.setType(types.NewTuple(
|
||||||
types.NewVar(token.NoPos, nil, "value", t),
|
types.NewVar(token.NoPos, nil, "value", t),
|
||||||
varOk,
|
varOk,
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@ func main() {
|
||||||
// .0.entry: P:0 S:0
|
// .0.entry: P:0 S:0
|
||||||
// a0 = new [1]interface{} *[1]interface{}
|
// a0 = new [1]interface{} *[1]interface{}
|
||||||
// t0 = &a0[0:untyped integer] *interface{}
|
// t0 = &a0[0:untyped integer] *interface{}
|
||||||
// t1 = make interface interface{} <- string ("Hello, World!":string) interface{}
|
// t1 = make interface{} <- string ("Hello, World!":string) interface{}
|
||||||
// *t0 = t1
|
// *t0 = t1
|
||||||
// t2 = slice a0[:] []interface{}
|
// t2 = slice a0[:] []interface{}
|
||||||
// t3 = fmt.Println(t2) (n int, err error)
|
// t3 = fmt.Println(t2) (n int, err error)
|
||||||
|
|
|
||||||
21
ssa/func.go
21
ssa/func.go
|
|
@ -19,6 +19,9 @@ func addEdge(from, to *BasicBlock) {
|
||||||
to.Preds = append(to.Preds, from)
|
to.Preds = append(to.Preds, from)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parent returns the function that contains block b.
|
||||||
|
func (b *BasicBlock) Parent() *Function { return b.parent }
|
||||||
|
|
||||||
// String returns a human-readable label of this block.
|
// String returns a human-readable label of this block.
|
||||||
// It is not guaranteed unique within the function.
|
// It is not guaranteed unique within the function.
|
||||||
//
|
//
|
||||||
|
|
@ -168,9 +171,10 @@ func (f *Function) labelledBlock(label *ast.Ident) *lblock {
|
||||||
//
|
//
|
||||||
func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter {
|
func (f *Function) addParam(name string, typ types.Type, pos token.Pos) *Parameter {
|
||||||
v := &Parameter{
|
v := &Parameter{
|
||||||
name: name,
|
name: name,
|
||||||
typ: typ,
|
typ: typ,
|
||||||
pos: pos,
|
pos: pos,
|
||||||
|
parent: f,
|
||||||
}
|
}
|
||||||
f.Params = append(f.Params, v)
|
f.Params = append(f.Params, v)
|
||||||
return v
|
return v
|
||||||
|
|
@ -412,10 +416,11 @@ func (f *Function) lookup(obj types.Object, escaping bool) Value {
|
||||||
}
|
}
|
||||||
outer := f.Enclosing.lookup(obj, true) // escaping
|
outer := f.Enclosing.lookup(obj, true) // escaping
|
||||||
v := &Capture{
|
v := &Capture{
|
||||||
name: outer.Name(),
|
name: outer.Name(),
|
||||||
typ: outer.Type(),
|
typ: outer.Type(),
|
||||||
pos: outer.Pos(),
|
pos: outer.Pos(),
|
||||||
outer: outer,
|
outer: outer,
|
||||||
|
parent: f,
|
||||||
}
|
}
|
||||||
f.objects[obj] = v
|
f.objects[obj] = v
|
||||||
f.FreeVars = append(f.FreeVars, v)
|
f.FreeVars = append(f.FreeVars, v)
|
||||||
|
|
@ -612,7 +617,7 @@ func (f *Function) newBasicBlock(comment string) *BasicBlock {
|
||||||
b := &BasicBlock{
|
b := &BasicBlock{
|
||||||
Index: len(f.Blocks),
|
Index: len(f.Blocks),
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
Func: f,
|
parent: f,
|
||||||
}
|
}
|
||||||
b.Succs = b.succs2[:0]
|
b.Succs = b.succs2[:0]
|
||||||
f.Blocks = append(f.Blocks, b)
|
f.Blocks = append(f.Blocks, b)
|
||||||
|
|
|
||||||
|
|
@ -369,7 +369,7 @@ func visitInstr(fr *frame, instr ssa.Instruction) continuation {
|
||||||
}
|
}
|
||||||
|
|
||||||
// prepareCall determines the function value and argument values for a
|
// prepareCall determines the function value and argument values for a
|
||||||
// function call in a Call, Go or Defer instruction, peforming
|
// function call in a Call, Go or Defer instruction, performing
|
||||||
// interface method lookup if needed.
|
// interface method lookup if needed.
|
||||||
//
|
//
|
||||||
func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
|
func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
|
||||||
|
|
@ -383,23 +383,12 @@ func prepareCall(fr *frame, call *ssa.CallCommon) (fn value, args []value) {
|
||||||
panic("method invoked on nil interface")
|
panic("method invoked on nil interface")
|
||||||
}
|
}
|
||||||
id := call.MethodId()
|
id := call.MethodId()
|
||||||
m := findMethodSet(fr.i, recv.t)[id]
|
fn = findMethodSet(fr.i, recv.t)[id]
|
||||||
if m == nil {
|
if fn == nil {
|
||||||
// Unreachable in well-typed programs.
|
// Unreachable in well-typed programs.
|
||||||
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, id))
|
panic(fmt.Sprintf("method set for dynamic type %v does not contain %s", recv.t, id))
|
||||||
}
|
}
|
||||||
_, aptr := recv.v.(*value) // actual pointerness
|
args = append(args, copyVal(recv.v))
|
||||||
_, fptr := m.Signature.Recv().Type().(*types.Pointer) // formal pointerness
|
|
||||||
switch {
|
|
||||||
case aptr == fptr:
|
|
||||||
args = append(args, copyVal(recv.v))
|
|
||||||
case aptr:
|
|
||||||
// Calling func(T) with a *T receiver: make a copy.
|
|
||||||
args = append(args, copyVal(*recv.v.(*value)))
|
|
||||||
case fptr:
|
|
||||||
panic("illegal call of *T method with T receiver")
|
|
||||||
}
|
|
||||||
fn = m
|
|
||||||
}
|
}
|
||||||
for _, arg := range call.Args {
|
for _, arg := range call.Args {
|
||||||
args = append(args, fr.get(arg))
|
args = append(args, fr.get(arg))
|
||||||
|
|
|
||||||
|
|
@ -349,7 +349,7 @@ func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
|
||||||
fmt.Fprintln(os.Stderr, "liftAlloc: lifting ", alloc, alloc.Name())
|
fmt.Fprintln(os.Stderr, "liftAlloc: lifting ", alloc, alloc.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn := alloc.Block().Func
|
fn := alloc.Parent()
|
||||||
|
|
||||||
// Φ-insertion.
|
// Φ-insertion.
|
||||||
//
|
//
|
||||||
|
|
|
||||||
|
|
@ -11,25 +11,23 @@ import (
|
||||||
"code.google.com/p/go.tools/go/types"
|
"code.google.com/p/go.tools/go/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newLiteral returns a new literal of the specified value and type.
|
// NewLiteral returns a new literal of the specified value and type.
|
||||||
// val must be valid according to the specification of Literal.Value.
|
// val must be valid according to the specification of Literal.Value.
|
||||||
//
|
//
|
||||||
func newLiteral(val exact.Value, typ types.Type) *Literal {
|
func NewLiteral(val exact.Value, typ types.Type) *Literal {
|
||||||
// This constructor exists to provide a single place to
|
|
||||||
// insert logging/assertions during debugging.
|
|
||||||
return &Literal{typ, val}
|
return &Literal{typ, val}
|
||||||
}
|
}
|
||||||
|
|
||||||
// intLiteral returns an untyped integer literal that evaluates to i.
|
// intLiteral returns an untyped integer literal that evaluates to i.
|
||||||
func intLiteral(i int64) *Literal {
|
func intLiteral(i int64) *Literal {
|
||||||
return newLiteral(exact.MakeInt64(i), types.Typ[types.UntypedInt])
|
return NewLiteral(exact.MakeInt64(i), types.Typ[types.UntypedInt])
|
||||||
}
|
}
|
||||||
|
|
||||||
// nilLiteral returns a nil literal of the specified type, which may
|
// nilLiteral returns a nil literal of the specified type, which may
|
||||||
// be any reference type, including interfaces.
|
// be any reference type, including interfaces.
|
||||||
//
|
//
|
||||||
func nilLiteral(typ types.Type) *Literal {
|
func nilLiteral(typ types.Type) *Literal {
|
||||||
return newLiteral(exact.MakeNil(), typ)
|
return NewLiteral(exact.MakeNil(), typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
// zeroLiteral returns a new "zero" literal of the specified type,
|
// zeroLiteral returns a new "zero" literal of the specified type,
|
||||||
|
|
@ -41,11 +39,11 @@ func zeroLiteral(t types.Type) *Literal {
|
||||||
case *types.Basic:
|
case *types.Basic:
|
||||||
switch {
|
switch {
|
||||||
case t.Info()&types.IsBoolean != 0:
|
case t.Info()&types.IsBoolean != 0:
|
||||||
return newLiteral(exact.MakeBool(false), t)
|
return NewLiteral(exact.MakeBool(false), t)
|
||||||
case t.Info()&types.IsNumeric != 0:
|
case t.Info()&types.IsNumeric != 0:
|
||||||
return newLiteral(exact.MakeInt64(0), t)
|
return NewLiteral(exact.MakeInt64(0), t)
|
||||||
case t.Info()&types.IsString != 0:
|
case t.Info()&types.IsString != 0:
|
||||||
return newLiteral(exact.MakeString(""), t)
|
return NewLiteral(exact.MakeString(""), t)
|
||||||
case t.Kind() == types.UnsafePointer:
|
case t.Kind() == types.UnsafePointer:
|
||||||
fallthrough
|
fallthrough
|
||||||
case t.Kind() == types.UntypedNil:
|
case t.Kind() == types.UntypedNil:
|
||||||
|
|
@ -56,7 +54,7 @@ func zeroLiteral(t types.Type) *Literal {
|
||||||
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
|
case *types.Pointer, *types.Slice, *types.Interface, *types.Chan, *types.Map, *types.Signature:
|
||||||
return nilLiteral(t)
|
return nilLiteral(t)
|
||||||
case *types.Named:
|
case *types.Named:
|
||||||
return newLiteral(zeroLiteral(t.Underlying()).Value, t)
|
return NewLiteral(zeroLiteral(t.Underlying()).Value, t)
|
||||||
case *types.Array, *types.Struct:
|
case *types.Array, *types.Struct:
|
||||||
panic(fmt.Sprint("zeroLiteral applied to aggregate:", t))
|
panic(fmt.Sprint("zeroLiteral applied to aggregate:", t))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
ssa/print.go
23
ssa/print.go
|
|
@ -28,14 +28,14 @@ func (id Id) String() string {
|
||||||
func relName(v Value, i Instruction) string {
|
func relName(v Value, i Instruction) string {
|
||||||
switch v := v.(type) {
|
switch v := v.(type) {
|
||||||
case *Global:
|
case *Global:
|
||||||
if i != nil && v.Pkg == i.Block().Func.Pkg {
|
if i != nil && v.Pkg == i.Parent().Pkg {
|
||||||
return v.Name()
|
return v.Name()
|
||||||
}
|
}
|
||||||
return v.FullName()
|
return v.FullName()
|
||||||
case *Function:
|
case *Function:
|
||||||
var pkg *Package
|
var pkg *Package
|
||||||
if i != nil {
|
if i != nil {
|
||||||
pkg = i.Block().Func.Pkg
|
pkg = i.Parent().Pkg
|
||||||
}
|
}
|
||||||
return v.fullName(pkg)
|
return v.fullName(pkg)
|
||||||
}
|
}
|
||||||
|
|
@ -166,7 +166,7 @@ func (v *ChangeInterface) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *MakeInterface) String() string {
|
func (v *MakeInterface) String() string {
|
||||||
return fmt.Sprintf("make interface %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
|
return fmt.Sprintf("make %s <- %s (%s)", v.Type(), v.X.Type(), relName(v.X, v))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *MakeClosure) String() string {
|
func (v *MakeClosure) String() string {
|
||||||
|
|
@ -187,7 +187,7 @@ func (v *MakeClosure) String() string {
|
||||||
|
|
||||||
func (v *MakeSlice) String() string {
|
func (v *MakeSlice) String() string {
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
b.WriteString("make slice ")
|
b.WriteString("make ")
|
||||||
b.WriteString(v.Type().String())
|
b.WriteString(v.Type().String())
|
||||||
b.WriteString(" ")
|
b.WriteString(" ")
|
||||||
b.WriteString(relName(v.Len, v))
|
b.WriteString(relName(v.Len, v))
|
||||||
|
|
@ -381,19 +381,22 @@ func (p *Package) DumpTo(w io.Writer) {
|
||||||
fmt.Fprintf(w, " func %-*s %s\n", maxname, name, mem.Type())
|
fmt.Fprintf(w, " func %-*s %s\n", maxname, name, mem.Type())
|
||||||
|
|
||||||
case *Type:
|
case *Type:
|
||||||
fmt.Fprintf(w, " type %-*s %s\n", maxname, name, mem.NamedType.Underlying())
|
fmt.Fprintf(w, " type %-*s %s\n", maxname, name, mem.Type().Underlying())
|
||||||
// We display only PtrMethods since its keys
|
// We display only mset(*T) since its keys
|
||||||
// are a superset of Methods' keys, though the
|
// are a superset of mset(T)'s keys, though the
|
||||||
// methods themselves may differ,
|
// methods themselves may differ,
|
||||||
// e.g. different bridge methods.
|
// e.g. different bridge methods.
|
||||||
// TODO(adonovan): show pointerness of receivers.
|
// NB: if mem.Type() is a pointer, mset is empty.
|
||||||
|
mset := p.Prog.MethodSet(pointer(mem.Type()))
|
||||||
var keys ids
|
var keys ids
|
||||||
for id := range mem.PtrMethods {
|
for id := range mset {
|
||||||
keys = append(keys, id)
|
keys = append(keys, id)
|
||||||
}
|
}
|
||||||
sort.Sort(keys)
|
sort.Sort(keys)
|
||||||
for _, id := range keys {
|
for _, id := range keys {
|
||||||
method := mem.PtrMethods[id]
|
method := mset[id]
|
||||||
|
// TODO(adonovan): show pointerness of receiver of declared method, not the index
|
||||||
|
|
||||||
fmt.Fprintf(w, " method %s %s\n", id, method.Signature)
|
fmt.Fprintf(w, " method %s %s\n", id, method.Signature)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,8 @@ func (c candidate) ptrRecv() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MethodSet returns the method set for type typ,
|
// MethodSet returns the method set for type typ,
|
||||||
// building bridge methods as needed for promoted methods.
|
// building bridge methods as needed for promoted methods
|
||||||
|
// and indirection wrappers for *T receiver types.
|
||||||
// A nil result indicates an empty set.
|
// A nil result indicates an empty set.
|
||||||
//
|
//
|
||||||
// Thread-safe.
|
// Thread-safe.
|
||||||
|
|
@ -104,7 +105,7 @@ func (p *Program) MethodSet(typ types.Type) MethodSet {
|
||||||
defer p.methodSetsMu.Unlock()
|
defer p.methodSetsMu.Unlock()
|
||||||
|
|
||||||
// TODO(adonovan): Using Types as map keys doesn't properly
|
// TODO(adonovan): Using Types as map keys doesn't properly
|
||||||
// de-dup. e.g. *NamedType are canonical but *Struct and
|
// de-dup. e.g. *Named are canonical but *Struct and
|
||||||
// others are not. Need to de-dup using typemap.T.
|
// others are not. Need to de-dup using typemap.T.
|
||||||
mset := p.methodSets[typ]
|
mset := p.methodSets[typ]
|
||||||
if mset == nil {
|
if mset == nil {
|
||||||
|
|
@ -151,7 +152,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
||||||
m := nt.Method(i)
|
m := nt.Method(i)
|
||||||
concrete := prog.concreteMethods[m]
|
concrete := prog.concreteMethods[m]
|
||||||
if concrete == nil {
|
if concrete == nil {
|
||||||
panic(fmt.Sprintf("no ssa.Function for mset(%s)[%s]", t, m.Name()))
|
panic(fmt.Sprintf("no ssa.Function for methodset(%s)[%s]", t, m.Name()))
|
||||||
}
|
}
|
||||||
addCandidate(nextcands, MakeId(m.Name(), m.Pkg()), m, concrete, node)
|
addCandidate(nextcands, MakeId(m.Name(), m.Pkg()), m, concrete, node)
|
||||||
}
|
}
|
||||||
|
|
@ -205,7 +206,7 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
||||||
if cand == nil {
|
if cand == nil {
|
||||||
continue // blocked; ignore
|
continue // blocked; ignore
|
||||||
}
|
}
|
||||||
if cand.ptrRecv() && !(isPointer(typ) || cand.path.isIndirect()) {
|
if cand.ptrRecv() && !isPointer(typ) && !cand.path.isIndirect() {
|
||||||
// A candidate concrete method f with receiver
|
// A candidate concrete method f with receiver
|
||||||
// *C is promoted into the method set of
|
// *C is promoted into the method set of
|
||||||
// (non-pointer) E iff the implicit path selection
|
// (non-pointer) E iff the implicit path selection
|
||||||
|
|
@ -214,8 +215,13 @@ func buildMethodSet(prog *Program, typ types.Type) MethodSet {
|
||||||
}
|
}
|
||||||
var method *Function
|
var method *Function
|
||||||
if cand.path == nil {
|
if cand.path == nil {
|
||||||
// Trivial member of method-set; no bridge needed.
|
// Trivial member of method-set; no promotion needed.
|
||||||
method = cand.concrete
|
method = cand.concrete
|
||||||
|
|
||||||
|
if !cand.ptrRecv() && isPointer(typ) {
|
||||||
|
// Call to method on T from receiver of type *T.
|
||||||
|
method = indirectionWrapper(method)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
method = makeBridgeMethod(prog, typ, cand)
|
method = makeBridgeMethod(prog, typ, cand)
|
||||||
}
|
}
|
||||||
|
|
@ -332,7 +338,9 @@ func makeBridgeMethod(prog *Program, typ types.Type, cand *candidate) *Function
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
// createParams creates parameters for bridge method fn based on its Signature.
|
// createParams creates parameters for bridge method fn based on its
|
||||||
|
// Signature.Params, which do not include the receiver.
|
||||||
|
//
|
||||||
func createParams(fn *Function) {
|
func createParams(fn *Function) {
|
||||||
var last *Parameter
|
var last *Parameter
|
||||||
tparams := fn.Signature.Params()
|
tparams := fn.Signature.Params()
|
||||||
|
|
@ -412,7 +420,7 @@ func makeImethodThunk(prog *Program, typ types.Type, id Id) *Function {
|
||||||
//
|
//
|
||||||
// TODO(adonovan): memoize creation of these functions in the Program.
|
// TODO(adonovan): memoize creation of these functions in the Program.
|
||||||
//
|
//
|
||||||
func makeBoundMethodThunk(prog *Program, meth *Function, recv Value) *Function {
|
func makeBoundMethodThunk(prog *Program, meth *Function, recvType types.Type) *Function {
|
||||||
if prog.mode&LogSource != 0 {
|
if prog.mode&LogSource != 0 {
|
||||||
defer logStack("makeBoundMethodThunk %s", meth)()
|
defer logStack("makeBoundMethodThunk %s", meth)()
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +431,7 @@ func makeBoundMethodThunk(prog *Program, meth *Function, recv Value) *Function {
|
||||||
Prog: prog,
|
Prog: prog,
|
||||||
}
|
}
|
||||||
|
|
||||||
cap := &Capture{name: "recv", typ: recv.Type()}
|
cap := &Capture{name: "recv", typ: recvType, parent: fn}
|
||||||
fn.FreeVars = []*Capture{cap}
|
fn.FreeVars = []*Capture{cap}
|
||||||
fn.startBody()
|
fn.startBody()
|
||||||
createParams(fn)
|
createParams(fn)
|
||||||
|
|
@ -438,6 +446,53 @@ func makeBoundMethodThunk(prog *Program, meth *Function, recv Value) *Function {
|
||||||
return fn
|
return fn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Receiver indirection wrapper ------------------------------------
|
||||||
|
|
||||||
|
// indirectionWrapper returns a synthetic method with *T receiver
|
||||||
|
// that delegates to meth, which has a T receiver.
|
||||||
|
//
|
||||||
|
// func (recv *T) f(...) ... {
|
||||||
|
// return (*recv).f(...)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodSetsMu)
|
||||||
|
//
|
||||||
|
func indirectionWrapper(meth *Function) *Function {
|
||||||
|
prog := meth.Prog
|
||||||
|
fn, ok := prog.indirectionWrappers[meth]
|
||||||
|
if !ok {
|
||||||
|
if prog.mode&LogSource != 0 {
|
||||||
|
defer logStack("makeIndirectionWrapper %s", meth)()
|
||||||
|
}
|
||||||
|
|
||||||
|
s := meth.Signature
|
||||||
|
recv := types.NewVar(token.NoPos, meth.Pkg.Types, "recv",
|
||||||
|
types.NewPointer(s.Recv().Type()))
|
||||||
|
fn = &Function{
|
||||||
|
name: meth.Name(),
|
||||||
|
Signature: types.NewSignature(recv, s.Params(), s.Results(), s.IsVariadic()),
|
||||||
|
Prog: prog,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn.startBody()
|
||||||
|
fn.addParamObj(recv)
|
||||||
|
createParams(fn)
|
||||||
|
// TODO(adonovan): consider emitting a nil-pointer check here
|
||||||
|
// with a nice error message, like gc does.
|
||||||
|
var c Call
|
||||||
|
c.Call.Func = meth
|
||||||
|
c.Call.Args = append(c.Call.Args, emitLoad(fn, fn.Params[0]))
|
||||||
|
for _, arg := range fn.Params[1:] {
|
||||||
|
c.Call.Args = append(c.Call.Args, arg)
|
||||||
|
}
|
||||||
|
emitTailCall(fn, &c)
|
||||||
|
fn.finishBody()
|
||||||
|
|
||||||
|
prog.indirectionWrappers[meth] = fn
|
||||||
|
}
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
// Implicit field promotion ----------------------------------------
|
// Implicit field promotion ----------------------------------------
|
||||||
|
|
||||||
// For a given struct type and (promoted) field Id, findEmbeddedField
|
// For a given struct type and (promoted) field Id, findEmbeddedField
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ func MustSanityCheck(fn *Function, reporter io.Writer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sanity) diagnostic(prefix, format string, args ...interface{}) {
|
func (s *sanity) diagnostic(prefix, format string, args ...interface{}) {
|
||||||
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn.FullName())
|
fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn)
|
||||||
if s.block != nil {
|
if s.block != nil {
|
||||||
fmt.Fprintf(s.reporter, ", block %s", s.block)
|
fmt.Fprintf(s.reporter, ", block %s", s.block)
|
||||||
}
|
}
|
||||||
|
|
@ -197,8 +197,8 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) {
|
||||||
if b.Index != index {
|
if b.Index != index {
|
||||||
s.errorf("block has incorrect Index %d", b.Index)
|
s.errorf("block has incorrect Index %d", b.Index)
|
||||||
}
|
}
|
||||||
if b.Func != s.fn {
|
if b.parent != s.fn {
|
||||||
s.errorf("block has incorrect Func %s", b.Func.FullName())
|
s.errorf("block has incorrect parent %s", b.parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check all blocks are reachable.
|
// Check all blocks are reachable.
|
||||||
|
|
@ -226,8 +226,8 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) {
|
||||||
if !found {
|
if !found {
|
||||||
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
|
s.errorf("expected successor edge in predecessor %s; found only: %s", a, a.Succs)
|
||||||
}
|
}
|
||||||
if a.Func != s.fn {
|
if a.parent != s.fn {
|
||||||
s.errorf("predecessor %s belongs to different function %s", a, a.Func.FullName())
|
s.errorf("predecessor %s belongs to different function %s", a, a.parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, c := range b.Succs {
|
for _, c := range b.Succs {
|
||||||
|
|
@ -241,8 +241,8 @@ func (s *sanity) checkBlock(b *BasicBlock, index int) {
|
||||||
if !found {
|
if !found {
|
||||||
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
|
s.errorf("expected predecessor edge in successor %s; found only: %s", c, c.Preds)
|
||||||
}
|
}
|
||||||
if c.Func != s.fn {
|
if c.parent != s.fn {
|
||||||
s.errorf("successor %s belongs to different function %s", c, c.Func.FullName())
|
s.errorf("successor %s belongs to different function %s", c, c.parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,10 +286,24 @@ func (s *sanity) checkFunction(fn *Function) bool {
|
||||||
s.errorf("nil Prog")
|
s.errorf("nil Prog")
|
||||||
}
|
}
|
||||||
for i, l := range fn.Locals {
|
for i, l := range fn.Locals {
|
||||||
|
if l.Parent() != fn {
|
||||||
|
s.errorf("Local %s at index %d has wrong parent", l.Name(), i)
|
||||||
|
}
|
||||||
if l.Heap {
|
if l.Heap {
|
||||||
s.errorf("Local %s at index %d has Heap flag set", l.Name(), i)
|
s.errorf("Local %s at index %d has Heap flag set", l.Name(), i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for i, p := range fn.Params {
|
||||||
|
if p.Parent() != fn {
|
||||||
|
s.errorf("Param %s at index %d has wrong parent", p.Name(), i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i, fv := range fn.FreeVars {
|
||||||
|
if fv.Parent() != fn {
|
||||||
|
s.errorf("FreeVar %s at index %d has wrong parent", fv.Name(), i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if fn.Blocks != nil && len(fn.Blocks) == 0 {
|
if fn.Blocks != nil && len(fn.Blocks) == 0 {
|
||||||
// Function _had_ blocks (so it's not external) but
|
// Function _had_ blocks (so it's not external) but
|
||||||
// they were "optimized" away, even the entry block.
|
// they were "optimized" away, even the entry block.
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,19 @@ func tokenFileContainsPos(f *token.File, pos token.Pos) bool {
|
||||||
//
|
//
|
||||||
// imp provides ASTs for the program's packages.
|
// imp provides ASTs for the program's packages.
|
||||||
//
|
//
|
||||||
|
// pkg may be nil if no SSA package has yet been created for the found
|
||||||
|
// package. Call prog.CreatePackages(imp) to avoid this.
|
||||||
|
//
|
||||||
// The result is (nil, nil, false) if not found.
|
// The result is (nil, nil, false) if not found.
|
||||||
//
|
//
|
||||||
func (prog *Program) PathEnclosingInterval(imp *importer.Importer, start, end token.Pos) (pkg *Package, path []ast.Node, exact bool) {
|
func (prog *Program) PathEnclosingInterval(imp *importer.Importer, start, end token.Pos) (pkg *Package, path []ast.Node, exact bool) {
|
||||||
for path, info := range imp.Packages {
|
for importPath, info := range imp.Packages {
|
||||||
pkg := prog.Packages[path]
|
|
||||||
for _, f := range info.Files {
|
for _, f := range info.Files {
|
||||||
if !tokenFileContainsPos(prog.Files.File(f.Package), start) {
|
if !tokenFileContainsPos(prog.Files.File(f.Package), start) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if path, exact := PathEnclosingInterval(f, start, end); path != nil {
|
if path, exact := PathEnclosingInterval(f, start, end); path != nil {
|
||||||
return pkg, path, exact
|
return prog.Packages[importPath], path, exact
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +136,12 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
|
||||||
return mem
|
return mem
|
||||||
}
|
}
|
||||||
case *Type:
|
case *Type:
|
||||||
for _, meth := range mem.PtrMethods {
|
for _, meth := range pkg.Prog.MethodSet(mem.Type()) {
|
||||||
|
if meth.Pos() == pos {
|
||||||
|
return meth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, meth := range pkg.Prog.MethodSet(pointer(mem.Type())) {
|
||||||
if meth.Pos() == pos {
|
if meth.Pos() == pos {
|
||||||
return meth
|
return meth
|
||||||
}
|
}
|
||||||
|
|
@ -143,3 +150,77 @@ func findNamedFunc(pkg *Package, pos token.Pos) *Function {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanonicalPos returns the canonical position of the AST node n,
|
||||||
|
//
|
||||||
|
// For each Node kind that may generate an SSA Value or Instruction,
|
||||||
|
// exactly one token within it is designated as "canonical". The
|
||||||
|
// position of that token is returned by {Value,Instruction}.Pos().
|
||||||
|
// The specifications of those methods determine the implementation of
|
||||||
|
// this function.
|
||||||
|
//
|
||||||
|
// TODO(adonovan): test coverage.
|
||||||
|
//
|
||||||
|
func CanonicalPos(n ast.Node) token.Pos {
|
||||||
|
// Comments show the Value/Instruction kinds v that may be
|
||||||
|
// created by n such that CanonicalPos(n) == v.Pos().
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *ast.ParenExpr:
|
||||||
|
return CanonicalPos(n.X)
|
||||||
|
|
||||||
|
case *ast.CallExpr:
|
||||||
|
// f(x): *Call, *Go, *Defer.
|
||||||
|
// T(x): *ChangeType, *Convert, *MakeInterface, *ChangeInterface.
|
||||||
|
// make(): *MakeMap, *MakeChan, *MakeSlice.
|
||||||
|
// new(): *Alloc.
|
||||||
|
// panic(): *Panic.
|
||||||
|
return n.Lparen
|
||||||
|
|
||||||
|
case *ast.Ident:
|
||||||
|
return n.NamePos // *Parameter, *Alloc, *Capture
|
||||||
|
|
||||||
|
case *ast.TypeAssertExpr:
|
||||||
|
return n.Lparen // *ChangeInterface or *TypeAssertExpr
|
||||||
|
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
return n.Sel.NamePos // *MakeClosure, *Field or *FieldAddr
|
||||||
|
|
||||||
|
case *ast.FuncLit:
|
||||||
|
return n.Type.Func // *Function or *MakeClosure
|
||||||
|
|
||||||
|
case *ast.CompositeLit:
|
||||||
|
return n.Lbrace // *Alloc or *Slice
|
||||||
|
|
||||||
|
case *ast.BinaryExpr:
|
||||||
|
return n.OpPos // *Phi or *BinOp
|
||||||
|
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
return n.OpPos // *Phi or *UnOp
|
||||||
|
|
||||||
|
case *ast.IndexExpr:
|
||||||
|
return n.Lbrack // *Index or *IndexAddr
|
||||||
|
|
||||||
|
case *ast.SliceExpr:
|
||||||
|
return n.Lbrack // *Slice
|
||||||
|
|
||||||
|
case *ast.SelectStmt:
|
||||||
|
return n.Select // *Select
|
||||||
|
|
||||||
|
case *ast.RangeStmt:
|
||||||
|
return n.For // *Range
|
||||||
|
|
||||||
|
case *ast.ReturnStmt:
|
||||||
|
return n.Return // *Ret
|
||||||
|
|
||||||
|
case *ast.SendStmt:
|
||||||
|
return n.Arrow // *Send
|
||||||
|
|
||||||
|
case *ast.StarExpr:
|
||||||
|
return n.Star // *Store
|
||||||
|
|
||||||
|
case *ast.KeyValueExpr:
|
||||||
|
return n.Colon // *MapUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
return token.NoPos
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import (
|
||||||
// additional whitespace abutting a node to be enclosed by it.
|
// additional whitespace abutting a node to be enclosed by it.
|
||||||
// In this example:
|
// In this example:
|
||||||
//
|
//
|
||||||
// z := x + y // add them
|
// z := x + y // add them
|
||||||
// <-A->
|
// <-A->
|
||||||
// <----B----->
|
// <----B----->
|
||||||
//
|
//
|
||||||
|
|
@ -41,7 +41,7 @@ import (
|
||||||
// interior whitespace of path[0].
|
// interior whitespace of path[0].
|
||||||
// In this example:
|
// In this example:
|
||||||
//
|
//
|
||||||
// z := x + y // add them
|
// z := x + y // add them
|
||||||
// <--C--> <---E-->
|
// <--C--> <---E-->
|
||||||
// ^
|
// ^
|
||||||
// D
|
// D
|
||||||
|
|
@ -430,7 +430,10 @@ func childrenOf(n ast.Node) []ast.Node {
|
||||||
children = append(children, tok(n.Switch, len("switch")))
|
children = append(children, tok(n.Switch, len("switch")))
|
||||||
|
|
||||||
case *ast.TypeAssertExpr:
|
case *ast.TypeAssertExpr:
|
||||||
// nop
|
children = append(children,
|
||||||
|
tok(n.Lparen-1, len(".")),
|
||||||
|
tok(n.Lparen, len("(")),
|
||||||
|
tok(n.Rparen, len(")")))
|
||||||
|
|
||||||
case *ast.TypeSpec:
|
case *ast.TypeSpec:
|
||||||
// TODO(adonovan): TypeSpec.{Doc,Comment}?
|
// TODO(adonovan): TypeSpec.{Doc,Comment}?
|
||||||
|
|
|
||||||
123
ssa/ssa.go
123
ssa/ssa.go
|
|
@ -17,15 +17,16 @@ import (
|
||||||
// A Program is a partial or complete Go program converted to SSA form.
|
// A Program is a partial or complete Go program converted to SSA form.
|
||||||
//
|
//
|
||||||
type Program struct {
|
type Program struct {
|
||||||
Files *token.FileSet // position information for the files of this Program [TODO: rename Fset]
|
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[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]
|
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.
|
Builtins map[types.Object]*Builtin // all built-in functions, keyed by typechecker objects.
|
||||||
|
concreteMethods map[*types.Func]*Function // maps named concrete methods to their code
|
||||||
|
mode BuilderMode // set of mode bits for SSA construction
|
||||||
|
|
||||||
methodSets map[types.Type]MethodSet // concrete method sets for all needed types [TODO(adonovan): de-dup]
|
methodSetsMu sync.Mutex // guards methodSets, indirectionWrappers
|
||||||
methodSetsMu sync.Mutex // serializes all accesses to methodSets
|
methodSets map[types.Type]MethodSet // concrete method sets for all needed types [TODO(adonovan): de-dup]
|
||||||
concreteMethods map[*types.Func]*Function // maps named concrete methods to their code
|
indirectionWrappers map[*Function]*Function // func(*T) wrappers for T-methods
|
||||||
mode BuilderMode // set of mode bits for SSA construction
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Package is a single analyzed Go package containing Members for
|
// A Package is a single analyzed Go package containing Members for
|
||||||
|
|
@ -51,10 +52,11 @@ type Package struct {
|
||||||
// const, var, func and type declarations respectively.
|
// const, var, func and type declarations respectively.
|
||||||
//
|
//
|
||||||
type Member interface {
|
type Member interface {
|
||||||
Name() string // the declared name of the package member
|
Name() string // the declared name of the package member
|
||||||
String() string // human-readable information about the value
|
String() string // human-readable information about the value
|
||||||
Pos() token.Pos // position of member's declaration, if known
|
Pos() token.Pos // position of member's declaration, if known
|
||||||
Type() types.Type // the type of the package member
|
Type() types.Type // the type of the package member
|
||||||
|
Token() token.Token // token.{VAR,FUNC,CONST,TYPE}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Id identifies the name of a field of a struct type, or the name
|
// An Id identifies the name of a field of a struct type, or the name
|
||||||
|
|
@ -77,20 +79,21 @@ type Id struct {
|
||||||
Name string
|
Name string
|
||||||
}
|
}
|
||||||
|
|
||||||
// A MethodSet contains all the methods for a particular type.
|
// A MethodSet contains all the methods for a particular type T.
|
||||||
// The method sets for T and *T are distinct entities.
|
// The method sets for T and *T are distinct entities.
|
||||||
// The methods for a non-pointer type T all have receiver type T, but
|
//
|
||||||
// the methods for pointer type *T may have receiver type *T or T.
|
// All methods in the method set for T have a receiver type of exactly
|
||||||
|
// T. The method set of *T may contain synthetic indirection methods
|
||||||
|
// that wrap methods whose receiver type is T.
|
||||||
//
|
//
|
||||||
type MethodSet map[Id]*Function
|
type MethodSet map[Id]*Function
|
||||||
|
|
||||||
// A Type is a Member of a Package representing the name, underlying
|
// A Type is a Member of a Package representing a package-level named type.
|
||||||
// type and method set of a named type declared at package scope.
|
//
|
||||||
|
// Type() returns a *types.Named.
|
||||||
//
|
//
|
||||||
type Type struct {
|
type Type struct {
|
||||||
NamedType *types.Named
|
Object *types.TypeName
|
||||||
Methods MethodSet // concrete method set of N
|
|
||||||
PtrMethods MethodSet // concrete method set of (*N)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// A Constant is a Member of Package representing a package-level
|
// A Constant is a Member of Package representing a package-level
|
||||||
|
|
@ -190,6 +193,10 @@ type Instruction interface {
|
||||||
// memory 'local int' and a definition of a pointer y.)
|
// memory 'local int' and a definition of a pointer y.)
|
||||||
String() string
|
String() string
|
||||||
|
|
||||||
|
// Parent returns the function to which this instruction
|
||||||
|
// belongs.
|
||||||
|
Parent() *Function
|
||||||
|
|
||||||
// Block returns the basic block to which this instruction
|
// Block returns the basic block to which this instruction
|
||||||
// belongs.
|
// belongs.
|
||||||
Block() *BasicBlock
|
Block() *BasicBlock
|
||||||
|
|
@ -299,7 +306,7 @@ type Function struct {
|
||||||
type BasicBlock struct {
|
type BasicBlock struct {
|
||||||
Index int // index of this block within Func.Blocks
|
Index int // index of this block within Func.Blocks
|
||||||
Comment string // optional label; no semantic significance
|
Comment string // optional label; no semantic significance
|
||||||
Func *Function // containing function
|
parent *Function // parent function
|
||||||
Instrs []Instruction // instructions in order
|
Instrs []Instruction // instructions in order
|
||||||
Preds, Succs []*BasicBlock // predecessors and successors
|
Preds, Succs []*BasicBlock // predecessors and successors
|
||||||
succs2 [2]*BasicBlock // initial space for Succs.
|
succs2 [2]*BasicBlock // initial space for Succs.
|
||||||
|
|
@ -327,10 +334,10 @@ type BasicBlock struct {
|
||||||
// belongs to an enclosing function.
|
// belongs to an enclosing function.
|
||||||
//
|
//
|
||||||
type Capture struct {
|
type Capture struct {
|
||||||
name string
|
name string
|
||||||
typ types.Type
|
typ types.Type
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
|
parent *Function
|
||||||
referrers []Instruction
|
referrers []Instruction
|
||||||
|
|
||||||
// Transiently needed during building.
|
// Transiently needed during building.
|
||||||
|
|
@ -340,10 +347,10 @@ type Capture struct {
|
||||||
// A Parameter represents an input parameter of a function.
|
// A Parameter represents an input parameter of a function.
|
||||||
//
|
//
|
||||||
type Parameter struct {
|
type Parameter struct {
|
||||||
name string
|
name string
|
||||||
typ types.Type
|
typ types.Type
|
||||||
pos token.Pos
|
pos token.Pos
|
||||||
|
parent *Function
|
||||||
referrers []Instruction
|
referrers []Instruction
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -584,8 +591,12 @@ type Convert struct {
|
||||||
// This operation cannot fail. Use TypeAssert for interface
|
// This operation cannot fail. Use TypeAssert for interface
|
||||||
// conversions that may fail dynamically.
|
// conversions that may fail dynamically.
|
||||||
//
|
//
|
||||||
// Pos() returns the ast.CallExpr.Lparen, if the instruction arose
|
// Pos() returns the ast.CallExpr.Lparen if the instruction arose from
|
||||||
// from an explicit conversion in the source.
|
// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the
|
||||||
|
// instruction arose from an explicit e.(T) operation; or token.NoPos
|
||||||
|
// otherwise.
|
||||||
|
//
|
||||||
|
// TODO(adonovan) what about the nil case of e = e.(E)? Test.
|
||||||
//
|
//
|
||||||
// Example printed form:
|
// Example printed form:
|
||||||
// t1 = change interface interface{} <- I (t0)
|
// t1 = change interface interface{} <- I (t0)
|
||||||
|
|
@ -596,7 +607,9 @@ type ChangeInterface struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeInterface constructs an instance of an interface type from a
|
// MakeInterface constructs an instance of an interface type from a
|
||||||
// value and its method-set.
|
// value of a concrete type.
|
||||||
|
//
|
||||||
|
// Use Program.MethodSet(X.Type()) to find the method-set of X.
|
||||||
//
|
//
|
||||||
// To construct the zero value of an interface type T, use:
|
// To construct the zero value of an interface type T, use:
|
||||||
// &Literal{exact.MakeNil(), T}
|
// &Literal{exact.MakeNil(), T}
|
||||||
|
|
@ -605,12 +618,12 @@ type ChangeInterface struct {
|
||||||
// from an explicit conversion in the source.
|
// from an explicit conversion in the source.
|
||||||
//
|
//
|
||||||
// Example printed form:
|
// Example printed form:
|
||||||
// t1 = make interface interface{} <- int (42:int)
|
// t1 = make interface{} <- int (42:int)
|
||||||
|
// t2 = make Stringer <- t0
|
||||||
//
|
//
|
||||||
type MakeInterface struct {
|
type MakeInterface struct {
|
||||||
Register
|
Register
|
||||||
X Value
|
X Value
|
||||||
Methods MethodSet // method set of (non-interface) X
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The MakeClosure instruction yields a closure value whose code is
|
// The MakeClosure instruction yields a closure value whose code is
|
||||||
|
|
@ -641,6 +654,7 @@ type MakeClosure struct {
|
||||||
//
|
//
|
||||||
// Example printed form:
|
// Example printed form:
|
||||||
// t1 = make map[string]int t0
|
// t1 = make map[string]int t0
|
||||||
|
// t1 = make StringIntMap t0
|
||||||
//
|
//
|
||||||
type MakeMap struct {
|
type MakeMap struct {
|
||||||
Register
|
Register
|
||||||
|
|
@ -657,6 +671,7 @@ type MakeMap struct {
|
||||||
//
|
//
|
||||||
// Example printed form:
|
// Example printed form:
|
||||||
// t0 = make chan int 0
|
// t0 = make chan int 0
|
||||||
|
// t0 = make IntChan 0
|
||||||
//
|
//
|
||||||
type MakeChan struct {
|
type MakeChan struct {
|
||||||
Register
|
Register
|
||||||
|
|
@ -677,7 +692,8 @@ type MakeChan struct {
|
||||||
// created it.
|
// created it.
|
||||||
//
|
//
|
||||||
// Example printed form:
|
// Example printed form:
|
||||||
// t1 = make slice []string 1:int t0
|
// t1 = make []string 1:int t0
|
||||||
|
// t1 = make StringSlice 1:int t0
|
||||||
//
|
//
|
||||||
type MakeSlice struct {
|
type MakeSlice struct {
|
||||||
Register
|
Register
|
||||||
|
|
@ -905,6 +921,11 @@ type Next struct {
|
||||||
// Type() reflects the actual type of the result, possibly a
|
// Type() reflects the actual type of the result, possibly a
|
||||||
// 2-types.Tuple; AssertedType is the asserted type.
|
// 2-types.Tuple; AssertedType is the asserted type.
|
||||||
//
|
//
|
||||||
|
// Pos() returns the ast.CallExpr.Lparen if the instruction arose from
|
||||||
|
// an explicit T(e) conversion; the ast.TypeAssertExpr.Lparen if the
|
||||||
|
// instruction arose from an explicit e.(T) operation; or token.NoPos
|
||||||
|
// otherwise.
|
||||||
|
//
|
||||||
// Example printed form:
|
// Example printed form:
|
||||||
// t1 = typeassert t0.(int)
|
// t1 = typeassert t0.(int)
|
||||||
// t3 = typeassert,ok t2.(T)
|
// t3 = typeassert,ok t2.(T)
|
||||||
|
|
@ -1168,10 +1189,6 @@ type anInstruction struct {
|
||||||
// receiver but the first true argument. Func is not used in this
|
// receiver but the first true argument. Func is not used in this
|
||||||
// mode.
|
// mode.
|
||||||
//
|
//
|
||||||
// If the called method's receiver has non-pointer type T, but the
|
|
||||||
// receiver supplied by the interface value has type *T, an implicit
|
|
||||||
// load (copy) operation is performed.
|
|
||||||
//
|
|
||||||
// Example printed form:
|
// Example printed form:
|
||||||
// t1 = invoke t0.String()
|
// t1 = invoke t0.String()
|
||||||
// go invoke t3.Run(t2)
|
// go invoke t3.Run(t2)
|
||||||
|
|
@ -1244,21 +1261,25 @@ func (v *Capture) Type() types.Type { return v.typ }
|
||||||
func (v *Capture) Name() string { return v.name }
|
func (v *Capture) Name() string { return v.name }
|
||||||
func (v *Capture) Referrers() *[]Instruction { return &v.referrers }
|
func (v *Capture) Referrers() *[]Instruction { return &v.referrers }
|
||||||
func (v *Capture) Pos() token.Pos { return v.pos }
|
func (v *Capture) Pos() token.Pos { return v.pos }
|
||||||
|
func (v *Capture) Parent() *Function { return v.parent }
|
||||||
|
|
||||||
func (v *Global) Type() types.Type { return v.typ }
|
func (v *Global) Type() types.Type { return v.typ }
|
||||||
func (v *Global) Name() string { return v.name }
|
func (v *Global) Name() string { return v.name }
|
||||||
func (v *Global) Pos() token.Pos { return v.pos }
|
func (v *Global) Pos() token.Pos { return v.pos }
|
||||||
func (*Global) Referrers() *[]Instruction { return nil }
|
func (*Global) Referrers() *[]Instruction { return nil }
|
||||||
|
func (v *Global) Token() token.Token { return token.VAR }
|
||||||
|
|
||||||
func (v *Function) Name() string { return v.name }
|
func (v *Function) Name() string { return v.name }
|
||||||
func (v *Function) Type() types.Type { return v.Signature }
|
func (v *Function) Type() types.Type { return v.Signature }
|
||||||
func (v *Function) Pos() token.Pos { return v.pos }
|
func (v *Function) Pos() token.Pos { return v.pos }
|
||||||
func (*Function) Referrers() *[]Instruction { return nil }
|
func (*Function) Referrers() *[]Instruction { return nil }
|
||||||
|
func (v *Function) Token() token.Token { return token.FUNC }
|
||||||
|
|
||||||
func (v *Parameter) Type() types.Type { return v.typ }
|
func (v *Parameter) Type() types.Type { return v.typ }
|
||||||
func (v *Parameter) Name() string { return v.name }
|
func (v *Parameter) Name() string { return v.name }
|
||||||
func (v *Parameter) Referrers() *[]Instruction { return &v.referrers }
|
func (v *Parameter) Referrers() *[]Instruction { return &v.referrers }
|
||||||
func (v *Parameter) Pos() token.Pos { return v.pos }
|
func (v *Parameter) Pos() token.Pos { return v.pos }
|
||||||
|
func (v *Parameter) Parent() *Function { return v.parent }
|
||||||
|
|
||||||
func (v *Alloc) Type() types.Type { return v.typ }
|
func (v *Alloc) Type() types.Type { return v.typ }
|
||||||
func (v *Alloc) Name() string { return v.name }
|
func (v *Alloc) Name() string { return v.name }
|
||||||
|
|
@ -1274,18 +1295,21 @@ func (v *Register) asRegister() *Register { return v }
|
||||||
func (v *Register) Pos() token.Pos { return v.pos }
|
func (v *Register) Pos() token.Pos { return v.pos }
|
||||||
func (v *Register) setPos(pos token.Pos) { v.pos = pos }
|
func (v *Register) setPos(pos token.Pos) { v.pos = pos }
|
||||||
|
|
||||||
|
func (v *anInstruction) Parent() *Function { return v.block.parent }
|
||||||
func (v *anInstruction) Block() *BasicBlock { return v.block }
|
func (v *anInstruction) Block() *BasicBlock { return v.block }
|
||||||
func (v *anInstruction) SetBlock(block *BasicBlock) { v.block = block }
|
func (v *anInstruction) SetBlock(block *BasicBlock) { v.block = block }
|
||||||
|
|
||||||
func (t *Type) Name() string { return t.NamedType.Obj().Name() }
|
func (t *Type) Name() string { return t.Object.Name() }
|
||||||
func (t *Type) Pos() token.Pos { return t.NamedType.Obj().Pos() }
|
func (t *Type) Pos() token.Pos { return t.Object.Pos() }
|
||||||
func (t *Type) String() string { return t.Name() }
|
func (t *Type) String() string { return t.Name() }
|
||||||
func (t *Type) Type() types.Type { return t.NamedType }
|
func (t *Type) Type() types.Type { return t.Object.Type() }
|
||||||
|
func (t *Type) Token() token.Token { return token.TYPE }
|
||||||
|
|
||||||
func (c *Constant) Name() string { return c.name }
|
func (c *Constant) Name() string { return c.name }
|
||||||
func (c *Constant) Pos() token.Pos { return c.pos }
|
func (c *Constant) Pos() token.Pos { return c.pos }
|
||||||
func (c *Constant) String() string { return c.Name() }
|
func (c *Constant) String() string { return c.Name() }
|
||||||
func (c *Constant) Type() types.Type { return c.Value.Type() }
|
func (c *Constant) Type() types.Type { return c.Value.Type() }
|
||||||
|
func (c *Constant) Token() token.Token { return token.CONST }
|
||||||
|
|
||||||
// Func returns the package-level function of the specified name,
|
// Func returns the package-level function of the specified name,
|
||||||
// or nil if not found.
|
// or nil if not found.
|
||||||
|
|
@ -1335,7 +1359,8 @@ func (prog *Program) Value(obj types.Object) Value {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package returns the SSA package corresponding to the specified
|
// Package returns the SSA package corresponding to the specified
|
||||||
// type-checker package object. It returns nil if not found.
|
// type-checker package object.
|
||||||
|
// It returns nil if no such SSA package has been created.
|
||||||
//
|
//
|
||||||
func (prog *Program) Package(pkg *types.Package) *Package {
|
func (prog *Program) Package(pkg *types.Package) *Package {
|
||||||
return prog.packages[pkg]
|
return prog.packages[pkg]
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,7 @@ func isBlankIdent(e ast.Expr) bool {
|
||||||
|
|
||||||
// isPointer returns true for types whose underlying type is a pointer.
|
// isPointer returns true for types whose underlying type is a pointer.
|
||||||
func isPointer(typ types.Type) bool {
|
func isPointer(typ types.Type) bool {
|
||||||
if nt, ok := typ.(*types.Named); ok {
|
_, ok := typ.Underlying().(*types.Pointer)
|
||||||
typ = nt.Underlying()
|
|
||||||
}
|
|
||||||
_, ok := typ.(*types.Pointer)
|
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue