Optimize/eliminate bounds checking
TODO: do better at it by tracking min/max values of integers. The following straightforward code doesn't have its bounds checks removed: for _, n := range slice { println(n) }
Этот коммит содержится в:
родитель
42cddd3260
коммит
88b6b2e7f5
7 изменённых файлов: 48 добавлений и 19 удалений
36
compiler.go
36
compiler.go
|
@ -1610,6 +1610,26 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Compiler) emitBoundsCheck(frame *Frame, arrayLen, index llvm.Value) {
|
||||||
|
if frame.fn.nobounds {
|
||||||
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
||||||
|
// checking.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Optimize away trivial cases.
|
||||||
|
// LLVM would do this anyway with interprocedural optimizations, but it
|
||||||
|
// helps to see cases where bounds checking would really help.
|
||||||
|
if index.IsConstant() && arrayLen.IsConstant() {
|
||||||
|
index := index.SExtValue()
|
||||||
|
arrayLen := arrayLen.SExtValue()
|
||||||
|
if index >= 0 && index < arrayLen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck")
|
||||||
|
c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{arrayLen, index}, "")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
if value, ok := frame.locals[expr]; ok {
|
if value, ok := frame.locals[expr]; ok {
|
||||||
// Value is a local variable that has already been computed.
|
// Value is a local variable that has already been computed.
|
||||||
|
@ -1730,8 +1750,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
// Check bounds.
|
// Check bounds.
|
||||||
arrayLen := expr.X.Type().(*types.Array).Len()
|
arrayLen := expr.X.Type().(*types.Array).Len()
|
||||||
arrayLenLLVM := llvm.ConstInt(llvm.Int32Type(), uint64(arrayLen), false)
|
arrayLenLLVM := llvm.ConstInt(llvm.Int32Type(), uint64(arrayLen), false)
|
||||||
lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck")
|
c.emitBoundsCheck(frame, arrayLenLLVM, index)
|
||||||
c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{arrayLenLLVM, index}, "")
|
|
||||||
|
|
||||||
// Can't load directly from array (as index is non-constant), so have to
|
// Can't load directly from array (as index is non-constant), so have to
|
||||||
// do it using an alloca+gep+load.
|
// do it using an alloca+gep+load.
|
||||||
|
@ -1771,12 +1790,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
|
|
||||||
// Bounds check.
|
// Bounds check.
|
||||||
// LLVM optimizes this away in most cases.
|
// LLVM optimizes this away in most cases.
|
||||||
// TODO: runtime.lookupBoundsCheck is undefined in packages imported by
|
c.emitBoundsCheck(frame, buflen, index)
|
||||||
// package runtime, so we have to remove it. This should be fixed.
|
|
||||||
lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck")
|
|
||||||
if !lookupBoundsCheck.IsNil() && frame.fn.llvmFn.Name() != "runtime.interfaceMethod" {
|
|
||||||
c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{buflen, index}, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch expr.X.Type().(type) {
|
switch expr.X.Type().(type) {
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
|
@ -1811,13 +1825,11 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
|
|
||||||
// Bounds check.
|
// Bounds check.
|
||||||
// LLVM optimizes this away in most cases.
|
// LLVM optimizes this away in most cases.
|
||||||
if frame.fn.llvmFn.Name() != "runtime.lookupBoundsCheck" {
|
|
||||||
length, err := c.parseBuiltin(frame, []ssa.Value{expr.X}, "len")
|
length, err := c.parseBuiltin(frame, []ssa.Value{expr.X}, "len")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return llvm.Value{}, err // shouldn't happen
|
return llvm.Value{}, err // shouldn't happen
|
||||||
}
|
}
|
||||||
c.builder.CreateCall(c.mod.NamedFunction("runtime.lookupBoundsCheck"), []llvm.Value{length, index}, "")
|
c.emitBoundsCheck(frame, length, index)
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup byte
|
// Lookup byte
|
||||||
buf := c.builder.CreateExtractValue(value, 1, "")
|
buf := c.builder.CreateExtractValue(value, 1, "")
|
||||||
|
@ -1932,8 +1944,10 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This check is optimized away in most cases.
|
// This check is optimized away in most cases.
|
||||||
|
if !frame.fn.nobounds {
|
||||||
sliceBoundsCheck := c.mod.NamedFunction("runtime.sliceBoundsCheck")
|
sliceBoundsCheck := c.mod.NamedFunction("runtime.sliceBoundsCheck")
|
||||||
c.builder.CreateCall(sliceBoundsCheck, []llvm.Value{llvmLen, low, high}, "")
|
c.builder.CreateCall(sliceBoundsCheck, []llvm.Value{llvmLen, low, high}, "")
|
||||||
|
}
|
||||||
|
|
||||||
slice := llvm.ConstNamedStruct(sliceTyp, []llvm.Value{
|
slice := llvm.ConstNamedStruct(sliceTyp, []llvm.Value{
|
||||||
llvm.Undef(slicePtr.Type()),
|
llvm.Undef(slicePtr.Type()),
|
||||||
|
|
13
ir.go
13
ir.go
|
@ -35,8 +35,9 @@ type Program struct {
|
||||||
type Function struct {
|
type Function struct {
|
||||||
fn *ssa.Function
|
fn *ssa.Function
|
||||||
llvmFn llvm.Value
|
llvmFn llvm.Value
|
||||||
linkName string
|
linkName string // go:linkname pragma
|
||||||
blocking bool
|
nobounds bool // go:nobounds pragma
|
||||||
|
blocking bool // calculated by AnalyseBlockingRecursive
|
||||||
flag bool // used by dead code elimination
|
flag bool // used by dead code elimination
|
||||||
addressTaken bool // used as function pointer, calculated by AnalyseFunctionPointers
|
addressTaken bool // used as function pointer, calculated by AnalyseFunctionPointers
|
||||||
parents []*Function // calculated by AnalyseCallgraph
|
parents []*Function // calculated by AnalyseCallgraph
|
||||||
|
@ -169,6 +170,14 @@ func (f *Function) parsePragmas() {
|
||||||
if hasUnsafeImport(f.fn.Pkg.Pkg) {
|
if hasUnsafeImport(f.fn.Pkg.Pkg) {
|
||||||
f.linkName = parts[2]
|
f.linkName = parts[2]
|
||||||
}
|
}
|
||||||
|
case "//go:nobounds":
|
||||||
|
// Skip bounds checking in this function. Useful for some
|
||||||
|
// runtime functions.
|
||||||
|
// This is somewhat dangerous and thus only imported in packages
|
||||||
|
// that import unsafe.
|
||||||
|
if hasUnsafeImport(f.fn.Pkg.Pkg) {
|
||||||
|
f.nobounds = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,6 +66,7 @@ func hashmapMake(keySize, valueSize uint8) *hashmap {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set a specified key to a given value. Grow the map if necessary.
|
// Set a specified key to a given value. Grow the map if necessary.
|
||||||
|
//go:nobounds
|
||||||
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
||||||
numBuckets := uintptr(1) << m.bucketBits
|
numBuckets := uintptr(1) << m.bucketBits
|
||||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||||
|
@ -114,6 +115,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the value of a specified key, or zero the value if not found.
|
// Get the value of a specified key, or zero the value if not found.
|
||||||
|
//go:nobounds
|
||||||
func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
func hashmapGet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
|
||||||
numBuckets := uintptr(1) << m.bucketBits
|
numBuckets := uintptr(1) << m.bucketBits
|
||||||
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
bucketNumber := (uintptr(hash) & (numBuckets - 1))
|
||||||
|
|
|
@ -44,6 +44,7 @@ var (
|
||||||
|
|
||||||
// Get the function pointer for the method on the interface.
|
// Get the function pointer for the method on the interface.
|
||||||
// This is a compiler intrinsic.
|
// This is a compiler intrinsic.
|
||||||
|
//go:nobounds
|
||||||
func interfaceMethod(itf _interface, method uint16) *uint8 {
|
func interfaceMethod(itf _interface, method uint16) *uint8 {
|
||||||
// This function doesn't do bounds checking as the supplied method must be
|
// This function doesn't do bounds checking as the supplied method must be
|
||||||
// in the list of signatures. The compiler will only emit
|
// in the list of signatures. The compiler will only emit
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:nobounds
|
||||||
func printstring(s string) {
|
func printstring(s string) {
|
||||||
for i := 0; i < len(s); i++ {
|
for i := 0; i < len(s); i++ {
|
||||||
putchar(s[i])
|
putchar(s[i])
|
||||||
|
@ -42,6 +43,7 @@ func printint16(n uint16) {
|
||||||
printint32(int32(n))
|
printint32(int32(n))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//go:nobounds
|
||||||
func printuint32(n uint32) {
|
func printuint32(n uint32) {
|
||||||
digits := [10]byte{} // enough to hold (2^32)-1
|
digits := [10]byte{} // enough to hold (2^32)-1
|
||||||
// Fill in all 10 digits.
|
// Fill in all 10 digits.
|
||||||
|
|
|
@ -56,7 +56,7 @@ func _panic(message interface{}) {
|
||||||
abort()
|
abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for bounds in *ssa.IndexAddr and *ssa.Lookup.
|
// Check for bounds in *ssa.Index, *ssa.IndexAddr and *ssa.Lookup.
|
||||||
func lookupBoundsCheck(length, index int) {
|
func lookupBoundsCheck(length, index int) {
|
||||||
if index < 0 || index >= length {
|
if index < 0 || index >= length {
|
||||||
// printstring() here is safe as this function is excluded from bounds
|
// printstring() here is safe as this function is excluded from bounds
|
||||||
|
|
|
@ -13,6 +13,7 @@ type _string struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return true iff the strings match.
|
// Return true iff the strings match.
|
||||||
|
//go:nobounds
|
||||||
func stringEqual(x, y string) bool {
|
func stringEqual(x, y string) bool {
|
||||||
if len(x) != len(y) {
|
if len(x) != len(y) {
|
||||||
return false
|
return false
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче