diff --git a/go/ssa/builder.go b/go/ssa/builder.go index a0ccaf0c..f1b6d171 100644 --- a/go/ssa/builder.go +++ b/go/ssa/builder.go @@ -363,7 +363,7 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) lvalue { v := b.receiver(fn, e.X, wantAddr, escaping, sel) last := len(sel.Index()) - 1 return &address{ - addr: emitFieldSelection(fn, v, sel.Index()[last], true, e.Sel.Pos()), + addr: emitFieldSelection(fn, v, sel.Index()[last], true, e.Sel), expr: e.Sel, } @@ -455,7 +455,15 @@ func (b *builder) expr(fn *Function, e ast.Expr) Value { return NewConst(tv.Value, tv.Type) } - v := b.expr0(fn, e, tv) + var v Value + if tv.Addressable() { + // Prefer pointer arithmetic ({Index,Field}Addr) followed + // by Load over subelement extraction (e.g. Index, Field), + // to avoid large copies. + v = b.addr(fn, e, false).load(fn) + } else { + v = b.expr0(fn, e, tv) + } if fn.debugInfo() { emitDebugRef(fn, e, v, false) } @@ -656,8 +664,7 @@ func (b *builder) expr0(fn *Function, e ast.Expr, tv types.TypeAndValue) Value { last := len(indices) - 1 v := b.expr(fn, e.X) v = emitImplicitSelections(fn, v, indices[:last]) - v = emitFieldSelection(fn, v, indices[last], false, e.Sel.Pos()) - emitDebugRef(fn, e.Sel, v, false) + v = emitFieldSelection(fn, v, indices[last], false, e.Sel) return v } diff --git a/go/ssa/emit.go b/go/ssa/emit.go index c5d23fda..0963d4fb 100644 --- a/go/ssa/emit.go +++ b/go/ssa/emit.go @@ -388,15 +388,16 @@ func emitImplicitSelections(f *Function, v Value, indices []int) Value { // If wantAddr, the input must be a pointer-to-struct and the result // will be the field's address; otherwise the result will be the // field's value. +// Ident id is used for position and debug info. // -func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, pos token.Pos) Value { +func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, id *ast.Ident) Value { fld := deref(v.Type()).Underlying().(*types.Struct).Field(index) if isPointer(v.Type()) { instr := &FieldAddr{ X: v, Field: index, } - instr.setPos(pos) + instr.setPos(id.Pos()) instr.setType(types.NewPointer(fld.Type())) v = f.emit(instr) // Load the field's value iff we don't want its address. @@ -408,10 +409,11 @@ func emitFieldSelection(f *Function, v Value, index int, wantAddr bool, pos toke X: v, Field: index, } - instr.setPos(pos) + instr.setPos(id.Pos()) instr.setType(fld.Type()) v = f.emit(instr) } + emitDebugRef(f, id, v, wantAddr) return v } diff --git a/go/ssa/source.go b/go/ssa/source.go index 0acfe8d5..8f0d0f35 100644 --- a/go/ssa/source.go +++ b/go/ssa/source.go @@ -235,28 +235,26 @@ func (prog *Program) ConstValue(obj *types.Const) *Const { // pkg is the package enclosing the reference. (A reference to a var // always occurs within a function, so we need to know where to find it.) // -// The Value of a defining (as opposed to referring) identifier is the -// value assigned to it in its definition. Similarly, the Value of an -// identifier that is the LHS of an assignment is the value assigned -// to it in that statement. In all these examples, VarValue(x) returns -// the value of x and isAddr==false. +// If the identifier is a field selector and its base expression is +// non-addressable, then VarValue returns the value of that field. +// For example: +// func f() struct {x int} +// f().x // VarValue(x) returns a *Field instruction of type int // -// var x X -// var x = X{} -// x := X{} -// x = X{} +// All other identifiers denote addressable locations (variables). +// For them, VarValue may return either the variable's address or its +// value, even when the expression is evaluated only for its value; the +// situation is reported by isAddr, the second component of the result. // -// When an identifier appears in an lvalue context other than as the -// LHS of an assignment, the resulting Value is the var's address, not -// its value. This situation is reported by isAddr, the second -// component of the result. In these examples, VarValue(x) returns -// the address of x and isAddr==true. +// If !isAddr, the returned value is the one associated with the +// specific identifier. For example, +// var x int // VarValue(x) returns Const 0 here +// x = 1 // VarValue(x) returns Const 1 here // -// x.y = 0 -// x[0] = 0 -// _ = x[:] (where x is an array) -// _ = &x -// x.method() (iff method is on &x) +// It is not specified whether the value or the address is returned in +// any particular case, as it may depend upon optimizations performed +// during SSA code generation, such as registerization, constant +// folding, avoidance of materialization of subexpressions, etc. // func (prog *Program) VarValue(obj *types.Var, pkg *Package, ref []ast.Node) (value Value, isAddr bool) { // All references to a var are local to some function, possibly init. diff --git a/go/ssa/testdata/objlookup.go b/go/ssa/testdata/objlookup.go index 3468a4a5..bd266e45 100644 --- a/go/ssa/testdata/objlookup.go +++ b/go/ssa/testdata/objlookup.go @@ -78,13 +78,13 @@ func main() { print(v6) // v6::Const var v7 S // &v7::Alloc - v7.x = 1 // &v7::Alloc x::Const - print(v7.x) // v7::UnOp x::Field + v7.x = 1 // &v7::Alloc &x::FieldAddr + print(v7.x) // &v7::Alloc &x::FieldAddr var v8 [1]int // &v8::Alloc v8[0] = 0 // &v8::Alloc print(v8[:]) // &v8::Alloc - _ = v8[0] // v8::UnOp (load from Alloc) + _ = v8[0] // &v8::Alloc _ = v8[:][0] // &v8::Alloc v8ptr := &v8 // v8ptr::Alloc &v8::Alloc _ = v8ptr[0] // v8ptr::Alloc @@ -145,7 +145,7 @@ func main() { // .Op is an inter-package FieldVal-selection. var err os.PathError // &err::Alloc - _ = err.Op // err::UnOp Op::Field + _ = err.Op // &err::Alloc &Op::FieldAddr _ = &err.Op // &err::Alloc &Op::FieldAddr // Exercise corner-cases of lvalues vs rvalues.