From 514bdfb1b7d1c8768d36e37ac730b86f2f5e3f45 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Thu, 17 Jul 2014 15:06:09 -0400 Subject: [PATCH] go.tools/go/ssa: improve generated code for addressable expressions. If an expression is addressable, we compute its address then load, rather than extracting the value of subelements. For aggregates this avoids large copies. Example: var x [2]struct{y [3]int} print(x[1].y[2]) Was: t0 = local [3]struct{x [5]int} (x) *[3]struct{x [5]int} t1 = *t0 [3]struct{x [5]int} t2 = t1[1:int] struct{x [5]int} t3 = t2.x [#0] [5]int t4 = t3[2:int] int Now: t1 = &t0[1:int] *struct{x [5]int} t2 = &t1.x [#0] *[5]int t3 = &t2[2:int] *int t4 = *t3 int Also: - make emitFieldSelections responsible for calling emitDebugRef, as one of its two calls was forgetting to do it. - relax the specification of (*Program).VarValue because not all subexpressions are materalized as values now. - fix up the objlookup.go test expectations to match. go/ssa/interp test runs 10% faster. Thanks to Peter Collingbourne for pointing this out. LGTM=pcc R=pcc, gri CC=golang-codereviews https://golang.org/cl/109710043 --- go/ssa/builder.go | 15 +++++++++++---- go/ssa/emit.go | 8 +++++--- go/ssa/source.go | 36 +++++++++++++++++------------------- go/ssa/testdata/objlookup.go | 8 ++++---- 4 files changed, 37 insertions(+), 30 deletions(-) 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.