Skip to content

Commit

Permalink
go/ir: emit field and index lvals on demand
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dominikh committed Feb 16, 2024
1 parent d52656b commit baceee4
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 9 deletions.
30 changes: 21 additions & 9 deletions go/ir/builder.go
Expand Up @@ -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
Expand Down Expand Up @@ -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}
Expand Down
34 changes: 34 additions & 0 deletions go/ir/lvalue.go
Expand Up @@ -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{}
Expand Down

0 comments on commit baceee4

Please sign in to comment.