compiler: calculate max number of entries in slice at compile time

This avoids difficult multiply-with-overflow code and avoids a multiply
at runtime.
Этот коммит содержится в:
Ayke van Laethem 2019-02-07 17:37:22 +01:00 коммит произвёл Ron Evans
родитель 26e7e93478
коммит b837c94366
3 изменённых файлов: 39 добавлений и 28 удалений

Просмотреть файл

@ -1736,9 +1736,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
elemSize := c.targetData.TypeAllocSize(llvmElemType) elemSize := c.targetData.TypeAllocSize(llvmElemType)
elemSizeValue := llvm.ConstInt(c.uintptrType, elemSize, false) elemSizeValue := llvm.ConstInt(c.uintptrType, elemSize, false)
// Calculate ^uintptr(0) // Calculate (^uintptr(0)) >> 1, which is the max value that fits in
maxSize := llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)).ZExtValue() // uintptr if uintptr were signed.
if elemSize > maxSize { maxSize := llvm.ConstLShr(llvm.ConstNot(llvm.ConstInt(c.uintptrType, 0, false)), llvm.ConstInt(c.uintptrType, 1, false))
if elemSize > maxSize.ZExtValue() {
// This seems to be checked by the typechecker already, but let's // This seems to be checked by the typechecker already, but let's
// check it again just to be sure. // check it again just to be sure.
return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize)) return llvm.Value{}, c.makeError(expr.Pos(), fmt.Sprintf("slice element type is too big (%v bytes)", elemSize))
@ -1770,10 +1771,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
sliceCap = c.builder.CreateSExt(sliceCap, biggestInt, "") sliceCap = c.builder.CreateSExt(sliceCap, biggestInt, "")
} }
} }
// Note: the max element size needs to be doubled to make sure it maxSliceSize := maxSize
// fits in an int for, for example, len(). if elemSize != 0 { // avoid divide by zero
elemSizeDoubled := c.builder.CreateMul(elemSizeValue, llvm.ConstInt(c.uintptrType, 2, false), "") maxSliceSize = llvm.ConstSDiv(maxSize, llvm.ConstInt(c.uintptrType, elemSize, false))
c.createRuntimeCall(checkFunc, []llvm.Value{sliceLen, sliceCap, elemSizeDoubled}, "") }
c.createRuntimeCall(checkFunc, []llvm.Value{sliceLen, sliceCap, maxSliceSize}, "")
} }
// Allocate the backing array. // Allocate the backing array.

Просмотреть файл

@ -57,21 +57,15 @@ func sliceBoundsCheck64(capacity uintptr, low, high uint64) {
} }
// Check for bounds in *ssa.MakeSlice. // Check for bounds in *ssa.MakeSlice.
func sliceBoundsCheckMake(length, capacity uintptr, elementSizeDoubled uintptr) { func sliceBoundsCheckMake(length, capacity uintptr, max uintptr) {
overflow := uint64(capacity*elementSizeDoubled) != uint64(capacity)*uint64(elementSizeDoubled) if length > capacity || capacity > max {
if length > capacity || overflow {
runtimePanic("slice size out of range") runtimePanic("slice size out of range")
} }
} }
// Check for bounds in *ssa.MakeSlice. Supports 64-bit indexes. // Check for bounds in *ssa.MakeSlice. Supports 64-bit indexes.
func sliceBoundsCheckMake64(length, capacity uint64, elementSizeDoubled uintptr) { func sliceBoundsCheckMake64(length, capacity uint64, max uintptr) {
// This function is only ever called on systems where uintptr is smaller if length > capacity || capacity > uint64(max) {
// than uint64 (thus must be 32-bit or less). So multiplying as uint64 will
// never overflow if we know that capacity fits in uintptr.
// That elementSizeDoubled fits in uintptr is checked by the compiler.
overflow := capacity != uint64(uintptr(capacity)) || capacity != uint64(uintptr(capacity*uint64(elementSizeDoubled)))
if length > capacity || overflow {
runtimePanic("slice size out of range") runtimePanic("slice size out of range")
} }
} }

37
testdata/slice.go предоставленный
Просмотреть файл

@ -13,17 +13,17 @@ func main() {
println("sum foo:", sum(foo)) println("sum foo:", sum(foo))
// creating a slice with uncommon len, cap types // creating a slice with uncommon len, cap types
assert(len(make([]int, int(2), int(3))) == 2) assert(len(make([]int, makeInt(2), makeInt(3))) == 2)
assert(len(make([]int, int8(2), int8(3))) == 2) assert(len(make([]int, makeInt8(2), makeInt8(3))) == 2)
assert(len(make([]int, int16(2), int16(3))) == 2) assert(len(make([]int, makeInt16(2), makeInt16(3))) == 2)
assert(len(make([]int, int32(2), int32(3))) == 2) assert(len(make([]int, makeInt32(2), makeInt32(3))) == 2)
assert(len(make([]int, int64(2), int64(3))) == 2) assert(len(make([]int, makeInt64(2), makeInt64(3))) == 2)
assert(len(make([]int, uint(2), uint(3))) == 2) assert(len(make([]int, makeUint(2), makeUint(3))) == 2)
assert(len(make([]int, uint8(2), uint8(3))) == 2) assert(len(make([]int, makeUint8(2), makeUint8(3))) == 2)
assert(len(make([]int, uint16(2), uint16(3))) == 2) assert(len(make([]int, makeUint16(2), makeUint16(3))) == 2)
assert(len(make([]int, uint32(2), uint32(3))) == 2) assert(len(make([]int, makeUint32(2), makeUint32(3))) == 2)
assert(len(make([]int, uint64(2), uint64(3))) == 2) assert(len(make([]int, makeUint64(2), makeUint64(3))) == 2)
assert(len(make([]int, uintptr(2), uintptr(3))) == 2) assert(len(make([]int, makeUintptr(2), makeUintptr(3))) == 2)
// indexing into a slice with uncommon index types // indexing into a slice with uncommon index types
assert(foo[int(2)] == 4) assert(foo[int(2)] == 4)
@ -120,3 +120,18 @@ func assert(ok bool) {
panic("assert failed") panic("assert failed")
} }
} }
// Helper functions used to hide const values from the compiler during IR
// construction.
func makeInt(x int) int { return x }
func makeInt8(x int8) int8 { return x }
func makeInt16(x int16) int16 { return x }
func makeInt32(x int32) int32 { return x }
func makeInt64(x int64) int64 { return x }
func makeUint(x uint) uint { return x }
func makeUint8(x uint8) uint8 { return x }
func makeUint16(x uint16) uint16 { return x }
func makeUint32(x uint32) uint32 { return x }
func makeUint64(x uint64) uint64 { return x }
func makeUintptr(x uintptr) uintptr { return x }