Skip to content

Commit bd6a7b6

Browse files
aykevldeadprogram
authored andcommitted
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.
1 parent 051ad07 commit bd6a7b6

File tree

3 files changed

+67
-64
lines changed

3 files changed

+67
-64
lines changed

compiler/asserts.go

Lines changed: 61 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,34 @@ func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Valu
1919
return
2020
}
2121

22-
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
23-
// correctly extend that type.
2422
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
23+
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
24+
// correctly extend that type.
2525
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
2626
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
2727
} else {
2828
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
2929
}
30+
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
31+
// The index is bigger than the array length type, so extend it.
32+
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
3033
}
3134

32-
// Optimize away trivial cases.
33-
// LLVM would do this anyway with interprocedural optimizations, but it
34-
// helps to see cases where bounds check elimination would really help.
35-
if index.IsConstant() && arrayLen.IsConstant() && !arrayLen.IsUndef() {
36-
index := index.SExtValue()
37-
arrayLen := arrayLen.SExtValue()
38-
if index >= 0 && index < arrayLen {
39-
return
40-
}
41-
}
35+
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
36+
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
37+
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
4238

43-
if index.Type().IntTypeWidth() > c.intType.IntTypeWidth() {
44-
// Index is too big for the regular bounds check. Use the one for int64.
45-
c.createRuntimeCall("lookupBoundsCheckLong", []llvm.Value{arrayLen, index}, "")
46-
} else {
47-
c.createRuntimeCall("lookupBoundsCheck", []llvm.Value{arrayLen, index}, "")
48-
}
39+
// Now do the bounds check: index >= arrayLen
40+
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
41+
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
42+
43+
// Fail: this is a nil pointer, exit with a panic.
44+
c.builder.SetInsertPointAtEnd(faultBlock)
45+
c.createRuntimeCall("lookuppanic", nil, "")
46+
c.builder.CreateUnreachable()
47+
48+
// Ok: this is a valid pointer.
49+
c.builder.SetInsertPointAtEnd(nextBlock)
4950
}
5051

5152
// 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
5758
return
5859
}
5960

60-
uintptrWidth := c.uintptrType.IntTypeWidth()
61-
if low.Type().IntTypeWidth() > uintptrWidth || high.Type().IntTypeWidth() > uintptrWidth {
62-
if low.Type().IntTypeWidth() < 64 {
63-
if lowType.Info()&types.IsUnsigned != 0 {
64-
low = c.builder.CreateZExt(low, c.ctx.Int64Type(), "")
65-
} else {
66-
low = c.builder.CreateSExt(low, c.ctx.Int64Type(), "")
67-
}
61+
// Extend the capacity integer to be at least as wide as low and high.
62+
capacityType := capacity.Type()
63+
if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
64+
capacityType = low.Type()
65+
}
66+
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
67+
capacityType = high.Type()
68+
}
69+
if capacityType != capacity.Type() {
70+
capacity = c.builder.CreateZExt(capacity, capacityType, "")
71+
}
72+
73+
// Extend low and high to be the same size as capacity.
74+
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
75+
if lowType.Info()&types.IsUnsigned != 0 {
76+
low = c.builder.CreateZExt(low, capacityType, "")
77+
} else {
78+
low = c.builder.CreateSExt(low, capacityType, "")
6879
}
69-
if high.Type().IntTypeWidth() < 64 {
70-
if highType.Info()&types.IsUnsigned != 0 {
71-
high = c.builder.CreateZExt(high, c.ctx.Int64Type(), "")
72-
} else {
73-
high = c.builder.CreateSExt(high, c.ctx.Int64Type(), "")
74-
}
80+
}
81+
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
82+
if highType.Info()&types.IsUnsigned != 0 {
83+
high = c.builder.CreateZExt(high, capacityType, "")
84+
} else {
85+
high = c.builder.CreateSExt(high, capacityType, "")
7586
}
76-
// TODO: 32-bit or even 16-bit slice bounds checks for 8-bit platforms
77-
c.createRuntimeCall("sliceBoundsCheck64", []llvm.Value{capacity, low, high}, "")
78-
} else {
79-
c.createRuntimeCall("sliceBoundsCheck", []llvm.Value{capacity, low, high}, "")
8087
}
88+
89+
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
90+
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
91+
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
92+
93+
// Now do the bounds check: low > high || high > capacity
94+
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
95+
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
96+
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
97+
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
98+
99+
// Fail: this is a nil pointer, exit with a panic.
100+
c.builder.SetInsertPointAtEnd(faultBlock)
101+
c.createRuntimeCall("slicepanic", nil, "")
102+
c.builder.CreateUnreachable()
103+
104+
// Ok: this is a valid pointer.
105+
c.builder.SetInsertPointAtEnd(nextBlock)
81106
}
82107

83108
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It

compiler/compiler.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1603,7 +1603,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
16031603
}
16041604

16051605
// Bounds check.
1606-
// LLVM optimizes this away in most cases.
16071606
c.emitLookupBoundsCheck(frame, buflen, index, expr.Index.Type())
16081607

16091608
switch expr.X.Type().Underlying().(type) {
@@ -1635,7 +1634,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
16351634
}
16361635

16371636
// Bounds check.
1638-
// LLVM optimizes this away in most cases.
16391637
length := c.builder.CreateExtractValue(value, 1, "len")
16401638
c.emitLookupBoundsCheck(frame, length, index, expr.Index.Type())
16411639

@@ -1883,7 +1881,6 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
18831881
low,
18841882
}
18851883

1886-
// This check is optimized away in most cases.
18871884
c.emitSliceBoundsCheck(frame, llvmLen, low, high, lowType, highType)
18881885

18891886
if c.targetData.TypeAllocSize(high.Type()) > c.targetData.TypeAllocSize(c.uintptrType) {

src/runtime/panic.go

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -32,33 +32,14 @@ func nilpanic() {
3232
runtimePanic("nil pointer dereference")
3333
}
3434

35-
// Check for bounds in *ssa.Index, *ssa.IndexAddr and *ssa.Lookup.
36-
func lookupBoundsCheck(length uintptr, index int) {
37-
if index < 0 || index >= int(length) {
38-
runtimePanic("index out of range")
39-
}
40-
}
41-
42-
// Check for bounds in *ssa.Index, *ssa.IndexAddr and *ssa.Lookup.
43-
// Supports 64-bit indexes.
44-
func lookupBoundsCheckLong(length uintptr, index int64) {
45-
if index < 0 || index >= int64(length) {
46-
runtimePanic("index out of range")
47-
}
35+
// Panic when trying to acces an array or slice out of bounds.
36+
func lookuppanic() {
37+
runtimePanic("index out of range")
4838
}
4939

50-
// Check for bounds in *ssa.Slice.
51-
func sliceBoundsCheck(capacity, low, high uintptr) {
52-
if !(0 <= low && low <= high && high <= capacity) {
53-
runtimePanic("slice out of range")
54-
}
55-
}
56-
57-
// Check for bounds in *ssa.Slice. Supports 64-bit indexes.
58-
func sliceBoundsCheck64(capacity uintptr, low, high uint64) {
59-
if !(0 <= low && low <= high && high <= uint64(capacity)) {
60-
runtimePanic("slice out of range")
61-
}
40+
// Panic when trying to slice a slice out of bounds.
41+
func slicepanic() {
42+
runtimePanic("slice out of range")
6243
}
6344

6445
// Check for bounds in *ssa.MakeSlice.

0 commit comments

Comments
 (0)