compiler: inline slice bounds checking
This improves code size in all tests by about 1% and up to 5% in some cases, likely because LLVM can better reason about inline bounds checks.
Этот коммит содержится в:
родитель
051ad07755
коммит
bd6a7b69ce
3 изменённых файлов: 69 добавлений и 66 удалений
|
@ -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(), "")
|
||||
}
|
||||
}
|
||||
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(), "")
|
||||
}
|
||||
}
|
||||
// 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}, "")
|
||||
// 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() < capacityType.IntTypeWidth() {
|
||||
if highType.Info()&types.IsUnsigned != 0 {
|
||||
high = c.builder.CreateZExt(high, capacityType, "")
|
||||
} else {
|
||||
high = c.builder.CreateSExt(high, capacityType, "")
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
// Panic when trying to acces an array or slice out of bounds.
|
||||
func lookuppanic() {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче