Skip to content

Commit 051ad07

Browse files
aykevldeadprogram
authored andcommitted
compiler: refactor slice related asserts
Move these asserts into compiler/asserts.go, to keep them together. The make([]T) asserts aren't moved yet because that code is (still!) quite ugly and in need of some clean up.
1 parent 09e85b7 commit 051ad07

File tree

2 files changed

+76
-69
lines changed

2 files changed

+76
-69
lines changed

compiler/asserts.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,82 @@ package compiler
44
// required by the Go programming language.
55

66
import (
7+
"go/types"
8+
79
"tinygo.org/x/go-llvm"
810
)
911

12+
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
13+
// slice. This is required by the Go language spec: an index out of bounds must
14+
// cause a panic.
15+
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
16+
if frame.fn.IsNoBounds() {
17+
// The //go:nobounds pragma was added to the function to avoid bounds
18+
// checking.
19+
return
20+
}
21+
22+
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
23+
// correctly extend that type.
24+
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
25+
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
26+
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
27+
} else {
28+
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
29+
}
30+
}
31+
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+
}
42+
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+
}
49+
}
50+
51+
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
52+
// sure it is within bounds.
53+
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
54+
if frame.fn.IsNoBounds() {
55+
// The //go:nobounds pragma was added to the function to avoid bounds
56+
// checking.
57+
return
58+
}
59+
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+
}
68+
}
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+
}
75+
}
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}, "")
80+
}
81+
}
82+
1083
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
1184
// has no effect in well-behaved programs, but makes sure no uncaught nil
1285
// pointer dereferences exist in valid Go code.

compiler/compiler.go

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,72 +1413,6 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon) (llvm.Value, e
14131413
}
14141414
}
14151415

1416-
func (c *Compiler) emitBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
1417-
if frame.fn.IsNoBounds() {
1418-
// The //go:nobounds pragma was added to the function to avoid bounds
1419-
// checking.
1420-
return
1421-
}
1422-
1423-
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
1424-
// correctly extend that type.
1425-
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
1426-
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
1427-
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
1428-
} else {
1429-
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
1430-
}
1431-
}
1432-
1433-
// Optimize away trivial cases.
1434-
// LLVM would do this anyway with interprocedural optimizations, but it
1435-
// helps to see cases where bounds check elimination would really help.
1436-
if index.IsConstant() && arrayLen.IsConstant() && !arrayLen.IsUndef() {
1437-
index := index.SExtValue()
1438-
arrayLen := arrayLen.SExtValue()
1439-
if index >= 0 && index < arrayLen {
1440-
return
1441-
}
1442-
}
1443-
1444-
if index.Type().IntTypeWidth() > c.intType.IntTypeWidth() {
1445-
// Index is too big for the regular bounds check. Use the one for int64.
1446-
c.createRuntimeCall("lookupBoundsCheckLong", []llvm.Value{arrayLen, index}, "")
1447-
} else {
1448-
c.createRuntimeCall("lookupBoundsCheck", []llvm.Value{arrayLen, index}, "")
1449-
}
1450-
}
1451-
1452-
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
1453-
if frame.fn.IsNoBounds() {
1454-
// The //go:nobounds pragma was added to the function to avoid bounds
1455-
// checking.
1456-
return
1457-
}
1458-
1459-
uintptrWidth := c.uintptrType.IntTypeWidth()
1460-
if low.Type().IntTypeWidth() > uintptrWidth || high.Type().IntTypeWidth() > uintptrWidth {
1461-
if low.Type().IntTypeWidth() < 64 {
1462-
if lowType.Info()&types.IsUnsigned != 0 {
1463-
low = c.builder.CreateZExt(low, c.ctx.Int64Type(), "")
1464-
} else {
1465-
low = c.builder.CreateSExt(low, c.ctx.Int64Type(), "")
1466-
}
1467-
}
1468-
if high.Type().IntTypeWidth() < 64 {
1469-
if highType.Info()&types.IsUnsigned != 0 {
1470-
high = c.builder.CreateZExt(high, c.ctx.Int64Type(), "")
1471-
} else {
1472-
high = c.builder.CreateSExt(high, c.ctx.Int64Type(), "")
1473-
}
1474-
}
1475-
// TODO: 32-bit or even 16-bit slice bounds checks for 8-bit platforms
1476-
c.createRuntimeCall("sliceBoundsCheck64", []llvm.Value{capacity, low, high}, "")
1477-
} else {
1478-
c.createRuntimeCall("sliceBoundsCheck", []llvm.Value{capacity, low, high}, "")
1479-
}
1480-
}
1481-
14821416
func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
14831417
if value, ok := frame.locals[expr]; ok {
14841418
// Value is a local variable that has already been computed.
@@ -1623,7 +1557,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
16231557
// Check bounds.
16241558
arrayLen := expr.X.Type().(*types.Array).Len()
16251559
arrayLenLLVM := llvm.ConstInt(c.uintptrType, uint64(arrayLen), false)
1626-
c.emitBoundsCheck(frame, arrayLenLLVM, index, expr.Index.Type())
1560+
c.emitLookupBoundsCheck(frame, arrayLenLLVM, index, expr.Index.Type())
16271561

16281562
// Can't load directly from array (as index is non-constant), so have to
16291563
// do it using an alloca+gep+load.
@@ -1670,7 +1604,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
16701604

16711605
// Bounds check.
16721606
// LLVM optimizes this away in most cases.
1673-
c.emitBoundsCheck(frame, buflen, index, expr.Index.Type())
1607+
c.emitLookupBoundsCheck(frame, buflen, index, expr.Index.Type())
16741608

16751609
switch expr.X.Type().Underlying().(type) {
16761610
case *types.Pointer:
@@ -1703,7 +1637,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
17031637
// Bounds check.
17041638
// LLVM optimizes this away in most cases.
17051639
length := c.builder.CreateExtractValue(value, 1, "len")
1706-
c.emitBoundsCheck(frame, length, index, expr.Index.Type())
1640+
c.emitLookupBoundsCheck(frame, length, index, expr.Index.Type())
17071641

17081642
// Lookup byte
17091643
buf := c.builder.CreateExtractValue(value, 0, "")

0 commit comments

Comments
 (0)