github.com/aykevl/tinygo@v0.5.0/compiler/asserts.go (about) 1 package compiler 2 3 // This file implements functions that do certain safety checks that are 4 // required by the Go programming language. 5 6 import ( 7 "go/types" 8 9 "tinygo.org/x/go-llvm" 10 ) 11 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 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. 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 } 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(), "") 33 } 34 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 38 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) 50 } 51 52 // emitSliceBoundsCheck emits a bounds check before a slicing operation to make 53 // sure it is within bounds. 54 // 55 // This function is both used for slicing a slice (low and high have their 56 // normal meaning) and for creating a new slice, where 'capacity' means the 57 // biggest possible slice capacity, 'low' means len and 'high' means cap. The 58 // logic is the same in both cases. 59 func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) { 60 if frame.fn.IsNoBounds() { 61 // The //go:nobounds pragma was added to the function to avoid bounds 62 // checking. 63 return 64 } 65 66 // Extend the capacity integer to be at least as wide as low and high. 67 capacityType := capacity.Type() 68 if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() { 69 capacityType = low.Type() 70 } 71 if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() { 72 capacityType = high.Type() 73 } 74 if capacityType != capacity.Type() { 75 capacity = c.builder.CreateZExt(capacity, capacityType, "") 76 } 77 78 // Extend low and high to be the same size as capacity. 79 if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() { 80 if lowType.Info()&types.IsUnsigned != 0 { 81 low = c.builder.CreateZExt(low, capacityType, "") 82 } else { 83 low = c.builder.CreateSExt(low, capacityType, "") 84 } 85 } 86 if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() { 87 if highType.Info()&types.IsUnsigned != 0 { 88 high = c.builder.CreateZExt(high, capacityType, "") 89 } else { 90 high = c.builder.CreateSExt(high, capacityType, "") 91 } 92 } 93 94 faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds") 95 nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next") 96 frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes 97 98 // Now do the bounds check: low > high || high > capacity 99 outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh") 100 outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap") 101 outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds") 102 c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock) 103 104 // Fail: this is a nil pointer, exit with a panic. 105 c.builder.SetInsertPointAtEnd(faultBlock) 106 c.createRuntimeCall("slicepanic", nil, "") 107 c.builder.CreateUnreachable() 108 109 // Ok: this is a valid pointer. 110 c.builder.SetInsertPointAtEnd(nextBlock) 111 } 112 113 // emitNilCheck checks whether the given pointer is nil, and panics if it is. It 114 // has no effect in well-behaved programs, but makes sure no uncaught nil 115 // pointer dereferences exist in valid Go code. 116 func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) { 117 // Check whether this is a nil pointer. 118 faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil") 119 nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next") 120 frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes 121 122 // Compare against nil. 123 var isnil llvm.Value 124 if ptr.Type().PointerAddressSpace() == 0 { 125 // Do the nil check using the isnil builtin, which marks the parameter 126 // as nocapture. 127 // The reason it has to go through a builtin, is that a regular icmp 128 // instruction may capture the pointer in LLVM semantics, see 129 // https://reviews.llvm.org/D60047 for details. Pointer capturing 130 // unfortunately breaks escape analysis, so we use this trick to let the 131 // functionattr pass know that this pointer doesn't really escape. 132 ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "") 133 isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "") 134 } else { 135 // Do the nil check using a regular icmp. This can happen with function 136 // pointers on AVR, which don't benefit from escape analysis anyway. 137 nilptr := llvm.ConstPointerNull(ptr.Type()) 138 isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "") 139 } 140 c.builder.CreateCondBr(isnil, faultBlock, nextBlock) 141 142 // Fail: this is a nil pointer, exit with a panic. 143 c.builder.SetInsertPointAtEnd(faultBlock) 144 c.createRuntimeCall("nilpanic", nil, "") 145 c.builder.CreateUnreachable() 146 147 // Ok: this is a valid pointer. 148 c.builder.SetInsertPointAtEnd(nextBlock) 149 }