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).
Этот коммит содержится в:
Ayke van Laethem 2020-03-25 19:23:13 +01:00 коммит произвёл Ron Evans
родитель 85854cd58b
коммит 19f8874764
3 изменённых файлов: 17 добавлений и 8 удалений

Просмотреть файл

@ -8,6 +8,7 @@ import (
"go/token" "go/token"
"go/types" "go/types"
"golang.org/x/tools/go/ssa"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@ -151,12 +152,20 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
// createNilCheck checks whether the given pointer is nil, and panics if it is. // createNilCheck checks whether the given pointer is nil, and panics if it is.
// It has no effect in well-behaved programs, but makes sure no uncaught nil // It has no effect in well-behaved programs, but makes sure no uncaught nil
// pointer dereferences exist in valid Go code. // pointer dereferences exist in valid Go code.
func (b *builder) createNilCheck(ptr llvm.Value, blockPrefix string) { func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix string) {
// Check whether we need to emit this check at all. // Check whether we need to emit this check at all.
if !ptr.IsAGlobalValue().IsNil() { if !ptr.IsAGlobalValue().IsNil() {
return return
} }
switch inst.(type) {
case *ssa.IndexAddr:
// This pointer is the result of an index operation into a slice or
// array. Such slices/arrays are already bounds checked so the pointer
// must be a valid (non-nil) pointer. No nil checking is necessary.
return
}
// Compare against nil. // Compare against nil.
var isnil llvm.Value var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 { if ptr.Type().PointerAddressSpace() == 0 {

Просмотреть файл

@ -1154,7 +1154,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.Store: case *ssa.Store:
llvmAddr := b.getValue(instr.Addr) llvmAddr := b.getValue(instr.Addr)
llvmVal := b.getValue(instr.Val) llvmVal := b.getValue(instr.Val)
b.createNilCheck(llvmAddr, "store") b.createNilCheck(instr.Addr, llvmAddr, "store")
if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 { if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
// nothing to store // nothing to store
return return
@ -1402,7 +1402,7 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error)
// This is a func value, which cannot be called directly. We have to // This is a func value, which cannot be called directly. We have to
// extract the function pointer and context first from the func value. // extract the function pointer and context first from the func value.
callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature)) callee, context = b.decodeFuncValue(value, instr.Value.Type().Underlying().(*types.Signature))
b.createNilCheck(callee, "fpcall") b.createNilCheck(instr.Value, callee, "fpcall")
} }
var params []llvm.Value var params []llvm.Value
@ -1548,7 +1548,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// > For an operand x of type T, the address operation &x generates a // > For an operand x of type T, the address operation &x generates a
// > pointer of type *T to x. [...] If the evaluation of x would cause a // > pointer of type *T to x. [...] If the evaluation of x would cause a
// > run-time panic, then the evaluation of &x does too. // > run-time panic, then the evaluation of &x does too.
b.createNilCheck(val, "gep") b.createNilCheck(expr.X, val, "gep")
// Do a GEP on the pointer to get the field address. // Do a GEP on the pointer to get the field address.
indices := []llvm.Value{ indices := []llvm.Value{
llvm.ConstInt(b.ctx.Int32Type(), 0, false), llvm.ConstInt(b.ctx.Int32Type(), 0, false),
@ -1596,7 +1596,7 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
// > generates a pointer of type *T to x. [...] If the // > generates a pointer of type *T to x. [...] If the
// > evaluation of x would cause a run-time panic, then the // > evaluation of x would cause a run-time panic, then the
// > evaluation of &x does too. // > evaluation of &x does too.
b.createNilCheck(bufptr, "gep") b.createNilCheck(expr.X, bufptr, "gep")
default: default:
return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+typ.String()) return llvm.Value{}, b.makeError(expr.Pos(), "todo: indexaddr: "+typ.String())
} }
@ -2588,7 +2588,7 @@ func (b *builder) createUnOp(unop *ssa.UnOp) (llvm.Value, error) {
} }
return b.CreateBitCast(fn, b.i8ptrType, ""), nil return b.CreateBitCast(fn, b.i8ptrType, ""), nil
} else { } else {
b.createNilCheck(x, "deref") b.createNilCheck(unop.X, x, "deref")
load := b.CreateLoad(x, "") load := b.CreateLoad(x, "")
return load, nil return load, nil
} }

Просмотреть файл

@ -12,7 +12,7 @@ import (
// runtime/volatile.LoadT(). // runtime/volatile.LoadT().
func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) { func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0]) addr := b.getValue(instr.Args[0])
b.createNilCheck(addr, "deref") b.createNilCheck(instr.Args[0], addr, "deref")
val := b.CreateLoad(addr, "") val := b.CreateLoad(addr, "")
val.SetVolatile(true) val.SetVolatile(true)
return val, nil return val, nil
@ -23,7 +23,7 @@ func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error)
func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) { func (b *builder) createVolatileStore(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0]) addr := b.getValue(instr.Args[0])
val := b.getValue(instr.Args[1]) val := b.getValue(instr.Args[1])
b.createNilCheck(addr, "deref") b.createNilCheck(instr.Args[0], addr, "deref")
store := b.CreateStore(val, addr) store := b.CreateStore(val, addr)
store.SetVolatile(true) store.SetVolatile(true)
return llvm.Value{}, nil return llvm.Value{}, nil