aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2020-03-25 19:23:13 +0100
committerRon Evans <[email protected]>2020-03-27 07:38:16 +0100
commit19f8874764cdf5263b493c0acb33bb0465691405 (patch)
treebba61b2ddec02ace3d64a2bbf78e1a8c24df5a82
parent85854cd58b09ee026cecaa590a23013d43ba74e5 (diff)
downloadtinygo-19f8874764cdf5263b493c0acb33bb0465691405.tar.gz
tinygo-19f8874764cdf5263b493c0acb33bb0465691405.zip
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).
-rw-r--r--compiler/asserts.go11
-rw-r--r--compiler/compiler.go10
-rw-r--r--compiler/volatile.go4
3 files changed, 17 insertions, 8 deletions
diff --git a/compiler/asserts.go b/compiler/asserts.go
index 6b2547817..7aeb0fe06 100644
--- a/compiler/asserts.go
+++ b/compiler/asserts.go
@@ -8,6 +8,7 @@ import (
"go/token"
"go/types"
+ "golang.org/x/tools/go/ssa"
"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.
// It has no effect in well-behaved programs, but makes sure no uncaught nil
// 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.
if !ptr.IsAGlobalValue().IsNil() {
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.
var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 {
diff --git a/compiler/compiler.go b/compiler/compiler.go
index 6a17da79f..46dc70d89 100644
--- a/compiler/compiler.go
+++ b/compiler/compiler.go
@@ -1154,7 +1154,7 @@ func (b *builder) createInstruction(instr ssa.Instruction) {
case *ssa.Store:
llvmAddr := b.getValue(instr.Addr)
llvmVal := b.getValue(instr.Val)
- b.createNilCheck(llvmAddr, "store")
+ b.createNilCheck(instr.Addr, llvmAddr, "store")
if b.targetData.TypeAllocSize(llvmVal.Type()) == 0 {
// nothing to store
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
// extract the function pointer and context first from the func value.
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
@@ -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
// > pointer of type *T to x. [...] If the evaluation of x would cause a
// > 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.
indices := []llvm.Value{
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
// > evaluation of x would cause a run-time panic, then the
// > evaluation of &x does too.
- b.createNilCheck(bufptr, "gep")
+ b.createNilCheck(expr.X, bufptr, "gep")
default:
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
} else {
- b.createNilCheck(x, "deref")
+ b.createNilCheck(unop.X, x, "deref")
load := b.CreateLoad(x, "")
return load, nil
}
diff --git a/compiler/volatile.go b/compiler/volatile.go
index d2e51c84d..0c9458745 100644
--- a/compiler/volatile.go
+++ b/compiler/volatile.go
@@ -12,7 +12,7 @@ import (
// runtime/volatile.LoadT().
func (b *builder) createVolatileLoad(instr *ssa.CallCommon) (llvm.Value, error) {
addr := b.getValue(instr.Args[0])
- b.createNilCheck(addr, "deref")
+ b.createNilCheck(instr.Args[0], addr, "deref")
val := b.CreateLoad(addr, "")
val.SetVolatile(true)
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) {
addr := b.getValue(instr.Args[0])
val := b.getValue(instr.Args[1])
- b.createNilCheck(addr, "deref")
+ b.createNilCheck(instr.Args[0], addr, "deref")
store := b.CreateStore(val, addr)
store.SetVolatile(true)
return llvm.Value{}, nil