From baceee4084a661319035265bc7f5d0637c1809ef Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Fri, 16 Feb 2024 20:40:01 +0100 Subject: [PATCH] go/ir: emit field and index lvals on demand This backports 1928cea0f0cc5f74e1840d00b5c809f7ed73402b from x/tools: go/ssa: emit field and index lvals on demand Adds a new lazyAddress construct. This is the same as an *address except it emits a FieldAddr selection, Field selection, or IndexAddr on demand. This fixes issues with ordering on assignment statements. For example, x.f = e panics on x being nil in phase 2 of assignment statements. This change delays the introduction of the FieldAddr for x.f until it is used instead of as a side effect of (*builder).addr. The nil deref panic is from FieldAddr is now after side-effects of evaluating x and e but before the assignment to x.f. --- go/ir/builder.go | 30 +++++++++++++++++++++--------- go/ir/lvalue.go | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 9 deletions(-) diff --git a/go/ir/builder.go b/go/ir/builder.go index 83ce63f0d..cb33e773b 100644 --- a/go/ir/builder.go +++ b/go/ir/builder.go @@ -353,11 +353,16 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) { } wantAddr := true v := b.receiver(fn, e.X, wantAddr, escaping, sel, e) - last := len(sel.Index()) - 1 - return &address{ - addr: emitFieldSelection(fn, v, sel.Index()[last], true, e.Sel), - expr: e.Sel, + index := sel.Index()[len(sel.Index())-1] + vut := typeutil.CoreType(deref(v.Type())).Underlying().(*types.Struct) + fld := vut.Field(index) + // Due to the two phases of resolving AssignStmt, a panic from x.f = p() + // when x is nil is required to come after the side-effects of + // evaluating x and p(). + emit := func(fn *Function) Value { + return emitFieldSelection(fn, v, index, true, e.Sel) } + return &lazyAddress{addr: emit, t: fld.Type(), expr: e.Sel} case *ast.IndexExpr: var x Value @@ -411,12 +416,19 @@ func (b *builder) addr(fn *Function, e ast.Expr, escaping bool) (RET lvalue) { panic("unexpected container type in IndexExpr: " + t.String()) } - v := &IndexAddr{ - X: x, - Index: b.expr(fn, e.Index), + // Due to the two phases of resolving AssignStmt, a panic from x[i] = p() + // when x is nil or i is out-of-bounds is required to come after the + // side-effects of evaluating x, i and p(). + index := b.expr(fn, e.Index) + emit := func(fn *Function) Value { + v := &IndexAddr{ + X: x, + Index: index, + } + v.setType(et) + return fn.emit(v, e) } - v.setType(et) - return &address{addr: fn.emit(v, e), expr: e} + return &lazyAddress{addr: emit, t: deref(et), expr: e} case *ast.StarExpr: return &address{addr: b.expr(fn, e.X), expr: e} diff --git a/go/ir/lvalue.go b/go/ir/lvalue.go index 119eed6c3..86eb4a5d1 100644 --- a/go/ir/lvalue.go +++ b/go/ir/lvalue.go @@ -114,6 +114,40 @@ func (e *element) typ() types.Type { return e.t } +// A lazyAddress is an lvalue whose address is the result of an instruction. +// These work like an *address except a new address.address() Value +// is created on each load, store and address call. +// A lazyAddress can be used to control when a side effect (nil pointer +// dereference, index out of bounds) of using a location happens. +type lazyAddress struct { + addr func(fn *Function) Value // emit to fn the computation of the address + t types.Type // type of the location + expr ast.Expr // source syntax of the value (not address) [debug mode] +} + +func (l *lazyAddress) load(fn *Function, source ast.Node) Value { + load := emitLoad(fn, l.addr(fn), source) + return load +} + +func (l *lazyAddress) store(fn *Function, v Value, source ast.Node) { + store := emitStore(fn, l.addr(fn), v, source) + if l.expr != nil { + // store.Val is v, converted for assignability. + emitDebugRef(fn, l.expr, store.Val, false) + } +} + +func (l *lazyAddress) address(fn *Function) Value { + addr := l.addr(fn) + if l.expr != nil { + emitDebugRef(fn, l.expr, addr, true) + } + return addr +} + +func (l *lazyAddress) typ() types.Type { return l.t } + // A blank is a dummy variable whose name is "_". // It is not reified: loads are illegal and stores are ignored. type blank struct{}