diff --git a/compiler/asserts.go b/compiler/asserts.go index b29126359a..2b01e6968a 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -19,33 +19,34 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu return } - // Sometimes, the index can be e.g. an uint8 or int8, and we have to - // correctly extend that type. if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() { + // Sometimes, the index can be e.g. an uint8 or int8, and we have to + // correctly extend that type. if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 { index = c.builder.CreateZExt(index, arrayLen.Type(), "") } else { index = c.builder.CreateSExt(index, arrayLen.Type(), "") } + } else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() { + // The index is bigger than the array length type, so extend it. + arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "") } - // Optimize away trivial cases. - // LLVM would do this anyway with interprocedural optimizations, but it - // helps to see cases where bounds check elimination would really help. - if index.IsConstant() && arrayLen.IsConstant() && !arrayLen.IsUndef() { - index := index.SExtValue() - arrayLen := arrayLen.SExtValue() - if index >= 0 && index < arrayLen { - return - } - } + faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds") + nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next") + frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes - if index.Type().IntTypeWidth() > c.intType.IntTypeWidth() { - // Index is too big for the regular bounds check. Use the one for int64. - c.createRuntimeCall("lookupBoundsCheckLong", []llvm.Value{arrayLen, index}, "") - } else { - c.createRuntimeCall("lookupBoundsCheck", []llvm.Value{arrayLen, index}, "") - } + // Now do the bounds check: index >= arrayLen + outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "") + c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock) + + // Fail: this is a nil pointer, exit with a panic. + c.builder.SetInsertPointAtEnd(faultBlock) + c.createRuntimeCall("lookuppanic", nil, "") + c.builder.CreateUnreachable() + + // Ok: this is a valid pointer. + c.builder.SetInsertPointAtEnd(nextBlock) } // emitSliceBoundsCheck emits a bounds check before a slicing operation to make @@ -57,27 +58,51 @@ func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.V return } - uintptrWidth := c.uintptrType.IntTypeWidth() - if low.Type().IntTypeWidth() > uintptrWidth || high.Type().IntTypeWidth() > uintptrWidth { - if low.Type().IntTypeWidth() < 64 { - if lowType.Info()&types.IsUnsigned != 0 { - low = c.builder.CreateZExt(low, c.ctx.Int64Type(), "") - } else { - low = c.builder.CreateSExt(low, c.ctx.Int64Type(), "") - } + // Extend the capacity integer to be at least as wide as low and high. + capacityType := capacity.Type() + if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() { + capacityType = low.Type() + } + if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() { + capacityType = high.Type() + } + if capacityType != capacity.Type() { + capacity = c.builder.CreateZExt(capacity, capacityType, "") + } + + // Extend low and high to be the same size as capacity. + if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() { + if lowType.Info()&types.IsUnsigned != 0 { + low = c.builder.CreateZExt(low, capacityType, "") + } else { + low = c.builder.CreateSExt(low, capacityType, "") } - if high.Type().IntTypeWidth() < 64 { - if highType.Info()&types.IsUnsigned != 0 { - high = c.builder.CreateZExt(high, c.ctx.Int64Type(), "") - } else { - high = c.builder.CreateSExt(high, c.ctx.Int64Type(), "") - } + } + if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() { + if highType.Info()&types.IsUnsigned != 0 { + high = c.builder.CreateZExt(high, capacityType, "") + } else { + high = c.builder.CreateSExt(high, capacityType, "") } - // TODO: 32-bit or even 16-bit slice bounds checks for 8-bit platforms - c.createRuntimeCall("sliceBoundsCheck64", []llvm.Value{capacity, low, high}, "") - } else { - c.createRuntimeCall("sliceBoundsCheck", []llvm.Value{capacity, low, high}, "") } + + faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds") + nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next") + frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes + + // Now do the bounds check: low > high || high > capacity + outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh") + outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap") + outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds") + c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock) + + // Fail: this is a nil pointer, exit with a panic. + c.builder.SetInsertPointAtEnd(faultBlock) + c.createRuntimeCall("slicepanic", nil, "") + c.builder.CreateUnreachable() + + // Ok: this is a valid pointer. + c.builder.SetInsertPointAtEnd(nextBlock) } // emitNilCheck checks whether the given pointer is nil, and panics if it is. It diff --git a/compiler/compiler.go b/compiler/compiler.go index 555e252e6f..ff592798b4 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1603,7 +1603,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } // Bounds check. - // LLVM optimizes this away in most cases. c.emitLookupBoundsCheck(frame, buflen, index, expr.Index.Type()) switch expr.X.Type().Underlying().(type) { @@ -1635,7 +1634,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } // Bounds check. - // LLVM optimizes this away in most cases. length := c.builder.CreateExtractValue(value, 1, "len") c.emitLookupBoundsCheck(frame, length, index, expr.Index.Type()) @@ -1883,7 +1881,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { low, } - // This check is optimized away in most cases. c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType) if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) { diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 721d80bf84..ba8c1018a5 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -32,33 +32,14 @@ func nilpanic() { runtimePanic("nil pointer dereference") } -// Check for bounds in *ssa.Index, *ssa.IndexAddr and *ssa.Lookup. -func lookupBoundsCheck(length uintptr, index int) { - if index < 0 || index >= int(length) { - runtimePanic("index out of range") - } -} - -// Check for bounds in *ssa.Index, *ssa.IndexAddr and *ssa.Lookup. -// Supports 64-bit indexes. -func lookupBoundsCheckLong(length uintptr, index int64) { - if index < 0 || index >= int64(length) { - runtimePanic("index out of range") - } +// Panic when trying to acces an array or slice out of bounds. +func lookuppanic() { + runtimePanic("index out of range") } -// Check for bounds in *ssa.Slice. -func sliceBoundsCheck(capacity, low, high uintptr) { - if !(0 <= low && low <= high && high <= capacity) { - runtimePanic("slice out of range") - } -} - -// Check for bounds in *ssa.Slice. Supports 64-bit indexes. -func sliceBoundsCheck64(capacity uintptr, low, high uint64) { - if !(0 <= low && low <= high && high <= uint64(capacity)) { - runtimePanic("slice out of range") - } +// Panic when trying to slice a slice out of bounds. +func slicepanic() { + runtimePanic("slice out of range") } // Check for bounds in *ssa.MakeSlice.