Skip to content

Commit 19f8874

Browse files
aykevldeadprogram
authored andcommitted
compiler: do not perform nil checking when indexing slices
The x/tools/go/ssa package splits slice loads/stores into two operations. So for code like this: x = p[3] It has two instructions: x_ptr = &p[3] x = *x_ptr This makes the IR simpler, but also means we're accidentally inserting more nil checks than necessary: the slice index operation has effectively already checked for nil by performing a bounds check. Therefore, omit nil pointer checks for pointers created by *ssa.IndexAddr. This change is necessary to make sure a future removal of runtime.isnil will not cause the escape analysis pass to regress. Apart from that, it reduces code size slightly in many smoke tests (with no increases in code size).
1 parent 85854cd commit 19f8874

File tree

3 files changed

+17
-8
lines changed

3 files changed

+17
-8
lines changed

compiler/asserts.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"go/token"
99
"go/types"
1010

11+
"golang.org/x/tools/go/ssa"
1112
"tinygo.org/x/go-llvm"
1213
)
1314

@@ -151,12 +152,20 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
151152
// createNilCheck checks whether the given pointer is nil, and panics if it is.
152153
// It has no effect in well-behaved programs, but makes sure no uncaught nil
153154
// pointer dereferences exist in valid Go code.
154-
func (b *builder) createNilCheck(ptr llvm.Value, blockPrefix string) {
155+
func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
155156
// Check whether we need to emit this check at all.
156157
if !ptr.IsAGlobalValue().IsNil() {
157158
return
158159
}
159160

161+
switch inst.(type) {
162+
case *ssa.IndexAddr:
163+
// This pointer is the result of an index operation into a slice or
164+
// array. Such slices/arrays are already bounds checked so the pointer
165+
// must be a valid (non-nil) pointer. No nil checking is necessary.
166+
return
167+
}
168+
160169
// Compare against nil.
161170
var isnil llvm.Value
162171
if ptr.Type().PointerAddressSpace() == 0 {

compiler/compiler.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,7 +1154,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
11541154
case *ssa.Store:
11551155
llvmAddr := b.getValue(instr.Addr)
11561156
llvmVal := b.getValue(instr.Val)
1157-
b.createNilCheck(llvmAddr, "store")
1157+
b.createNilCheck(instr.Addr, llvmAddr, "store")
11581158
if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
11591159
// nothing to store
11601160
return
@@ -1402,7 +1402,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
14021402
// This is a func value, which cannot be called directly. We have to
14031403
// extract the function pointer and context first from the func value.
14041404
callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
1405-
b.createNilCheck(callee, "fpcall")
1405+
b.createNilCheck(instr.Value, callee, "fpcall")
14061406
}
14071407

14081408
var params []llvm.Value
@@ -1548,7 +1548,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
15481548
// > For an operand x of type T, the address operation &x generates a
15491549
// > pointer of type *T to x. [...] If the evaluation of x would cause a
15501550
// > run-time panic, then the evaluation of &x does too.
1551-
b.createNilCheck(val, "gep")
1551+
b.createNilCheck(expr.X, val, "gep")
15521552
// Do a GEP on the pointer to get the field address.
15531553
indices := []llvm.Value{
15541554
llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@@ -1596,7 +1596,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
15961596
// > generates a pointer of type *T to x. [...] If the
15971597
// > evaluation of x would cause a run-time panic, then the
15981598
// > evaluation of &x does too.
1599-
b.createNilCheck(bufptr, "gep")
1599+
b.createNilCheck(expr.X, bufptr, "gep")
16001600
default:
16011601
return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+typ.String())
16021602
}
@@ -2588,7 +2588,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
25882588
}
25892589
return b.CreateBitCast(fn, b.i8ptrType, ""), nil
25902590
} else {
2591-
b.createNilCheck(x, "deref")
2591+
b.createNilCheck(unop.X, x, "deref")
25922592
load := b.CreateLoad(x, "")
25932593
return load, nil
25942594
}

compiler/volatile.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
// runtime/volatile.LoadT().
1313
func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) {
1414
addr := b.getValue(instr.Args[0])
15-
b.createNilCheck(addr, "deref")
15+
b.createNilCheck(instr.Args[0], addr, "deref")
1616
val := b.CreateLoad(addr, "")
1717
val.SetVolatile(true)
1818
return val, nil
@@ -23,7 +23,7 @@ func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error)
2323
func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) {
2424
addr := b.getValue(instr.Args[0])
2525
val := b.getValue(instr.Args[1])
26-
b.createNilCheck(addr, "deref")
26+
b.createNilCheck(instr.Args[0], addr, "deref")
2727
store := b.CreateStore(val, addr)
2828
store.SetVolatile(true)
2929
return llvm.Value{}, nil

0 commit comments

Comments
 (0)