mirror of https://github.com/tinygo-org/tinygo.git
wasmstm32webassemblymicrocontrollerarmavrspiwasiadafruitarduinocircuitplayground-expressgpioi2cllvmmicrobitnrf51nrf52nrf52840samd21tinygo
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
4.8 KiB
129 lines
4.8 KiB
package compiler
|
|
|
|
// This file implements functions that do certain safety checks that are
|
|
// required by the Go programming language.
|
|
|
|
import (
|
|
"go/types"
|
|
|
|
"tinygo.org/x/go-llvm"
|
|
)
|
|
|
|
// emitLookupBoundsCheck emits a bounds check before doing a lookup into a
|
|
// slice. This is required by the Go language spec: an index out of bounds must
|
|
// cause a panic.
|
|
func (c *Compiler) emitLookupBoundsCheck(frame *Frame, arrayLen, index llvm.Value, indexType types.Type) {
|
|
if frame.fn.IsNoBounds() {
|
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
|
// checking.
|
|
return
|
|
}
|
|
|
|
if index.Type().IntTypeWidth() < arrayLen.Type().IntTypeWidth() {
|
|
// Sometimes, the index can be e.g. an uint8 or int8, and we have to
|
|
// correctly extend that type.
|
|
if indexType.(*types.Basic).Info()&types.IsUnsigned == 0 {
|
|
index = c.builder.CreateZExt(index, arrayLen.Type(), "")
|
|
} else {
|
|
index = c.builder.CreateSExt(index, arrayLen.Type(), "")
|
|
}
|
|
} else if index.Type().IntTypeWidth() > arrayLen.Type().IntTypeWidth() {
|
|
// The index is bigger than the array length type, so extend it.
|
|
arrayLen = c.builder.CreateZExt(arrayLen, index.Type(), "")
|
|
}
|
|
|
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.outofbounds")
|
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "lookup.next")
|
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
|
|
|
// Now do the bounds check: index >= arrayLen
|
|
outOfBounds := c.builder.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
|
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
|
|
|
// Fail: this is a nil pointer, exit with a panic.
|
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
|
c.createRuntimeCall("lookuppanic", nil, "")
|
|
c.builder.CreateUnreachable()
|
|
|
|
// Ok: this is a valid pointer.
|
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
|
}
|
|
|
|
// emitSliceBoundsCheck emits a bounds check before a slicing operation to make
|
|
// sure it is within bounds.
|
|
func (c *Compiler) emitSliceBoundsCheck(frame *Frame, capacity, low, high llvm.Value, lowType, highType *types.Basic) {
|
|
if frame.fn.IsNoBounds() {
|
|
// The //go:nobounds pragma was added to the function to avoid bounds
|
|
// checking.
|
|
return
|
|
}
|
|
|
|
// Extend the capacity integer to be at least as wide as low and high.
|
|
capacityType := capacity.Type()
|
|
if low.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
|
capacityType = low.Type()
|
|
}
|
|
if high.Type().IntTypeWidth() > capacityType.IntTypeWidth() {
|
|
capacityType = high.Type()
|
|
}
|
|
if capacityType != capacity.Type() {
|
|
capacity = c.builder.CreateZExt(capacity, capacityType, "")
|
|
}
|
|
|
|
// Extend low and high to be the same size as capacity.
|
|
if low.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
|
if lowType.Info()&types.IsUnsigned != 0 {
|
|
low = c.builder.CreateZExt(low, capacityType, "")
|
|
} else {
|
|
low = c.builder.CreateSExt(low, capacityType, "")
|
|
}
|
|
}
|
|
if high.Type().IntTypeWidth() < capacityType.IntTypeWidth() {
|
|
if highType.Info()&types.IsUnsigned != 0 {
|
|
high = c.builder.CreateZExt(high, capacityType, "")
|
|
} else {
|
|
high = c.builder.CreateSExt(high, capacityType, "")
|
|
}
|
|
}
|
|
|
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.outofbounds")
|
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, "slice.next")
|
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
|
|
|
// Now do the bounds check: low > high || high > capacity
|
|
outOfBounds1 := c.builder.CreateICmp(llvm.IntUGT, low, high, "slice.lowhigh")
|
|
outOfBounds2 := c.builder.CreateICmp(llvm.IntUGT, high, capacity, "slice.highcap")
|
|
outOfBounds := c.builder.CreateOr(outOfBounds1, outOfBounds2, "slice.outofbounds")
|
|
c.builder.CreateCondBr(outOfBounds, faultBlock, nextBlock)
|
|
|
|
// Fail: this is a nil pointer, exit with a panic.
|
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
|
c.createRuntimeCall("slicepanic", nil, "")
|
|
c.builder.CreateUnreachable()
|
|
|
|
// Ok: this is a valid pointer.
|
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
|
}
|
|
|
|
// emitNilCheck checks whether the given pointer is nil, and panics if it is. It
|
|
// has no effect in well-behaved programs, but makes sure no uncaught nil
|
|
// pointer dereferences exist in valid Go code.
|
|
func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string) {
|
|
// Check whether this is a nil pointer.
|
|
faultBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".nil")
|
|
nextBlock := c.ctx.AddBasicBlock(frame.fn.LLVMFn, blockPrefix+".next")
|
|
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
|
|
|
|
// Compare against nil.
|
|
nilptr := llvm.ConstPointerNull(ptr.Type())
|
|
isnil := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
|
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
|
|
|
|
// Fail: this is a nil pointer, exit with a panic.
|
|
c.builder.SetInsertPointAtEnd(faultBlock)
|
|
c.createRuntimeCall("nilpanic", nil, "")
|
|
c.builder.CreateUnreachable()
|
|
|
|
// Ok: this is a valid pointer.
|
|
c.builder.SetInsertPointAtEnd(nextBlock)
|
|
}
|
|
|