mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-18 19:44:00 +00:00
compiler, runtime: implement recoverable divide-by-zero panic
This gets the math/bits and go/constant package tests to pass. Unfortunately, this also has a binary size impact of around 150-200 bytes in many cases. I'm a bit on the edge on whether this is worth it, since it's mostly used for getting package tests to work. But at the same time, having working package tests is very valuable.
This commit is contained in:
@@ -362,6 +362,7 @@ TEST_PACKAGES_FAST = \
|
|||||||
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
|
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
|
||||||
# image requires recover(), which is not yet supported on wasi
|
# image requires recover(), which is not yet supported on wasi
|
||||||
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
|
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
|
||||||
|
# math/bits: needs panic()/recover()
|
||||||
# mime: fail on wasi; neds panic()/recover()
|
# mime: fail on wasi; neds panic()/recover()
|
||||||
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
|
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
|
||||||
# mime/quotedprintable requires syscall.Faccessat
|
# mime/quotedprintable requires syscall.Faccessat
|
||||||
@@ -382,8 +383,10 @@ TEST_PACKAGES_LINUX := \
|
|||||||
crypto/hmac \
|
crypto/hmac \
|
||||||
debug/dwarf \
|
debug/dwarf \
|
||||||
debug/plan9obj \
|
debug/plan9obj \
|
||||||
|
go/constant \
|
||||||
image \
|
image \
|
||||||
io/ioutil \
|
io/ioutil \
|
||||||
|
math/bits \
|
||||||
mime \
|
mime \
|
||||||
mime/multipart \
|
mime/multipart \
|
||||||
mime/quotedprintable \
|
mime/quotedprintable \
|
||||||
@@ -403,6 +406,8 @@ TEST_PACKAGES_WINDOWS := \
|
|||||||
compress/flate \
|
compress/flate \
|
||||||
crypto/des \
|
crypto/des \
|
||||||
crypto/hmac \
|
crypto/hmac \
|
||||||
|
go/constant \
|
||||||
|
math/bits \
|
||||||
strconv \
|
strconv \
|
||||||
text/template/parse \
|
text/template/parse \
|
||||||
$(nil)
|
$(nil)
|
||||||
|
|||||||
+16
-10
@@ -31,7 +31,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
|
|||||||
|
|
||||||
// Now do the bounds check: index >= arrayLen
|
// Now do the bounds check: index >= arrayLen
|
||||||
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
|
||||||
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
|
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSliceBoundsCheck emits a bounds check before a slicing operation to make
|
// createSliceBoundsCheck emits a bounds check before a slicing operation to make
|
||||||
@@ -74,7 +74,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
|
|||||||
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
|
||||||
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
|
||||||
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
|
||||||
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
|
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
|
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
|
||||||
@@ -86,7 +86,7 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i
|
|||||||
// > run-time panic occurs.
|
// > run-time panic occurs.
|
||||||
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
|
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
|
||||||
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
|
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
|
||||||
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
|
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice
|
// createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice
|
||||||
@@ -118,7 +118,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value,
|
|||||||
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
|
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
|
||||||
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
|
||||||
assert = b.CreateOr(assert, lenOutOfBounds, "")
|
assert = b.CreateOr(assert, lenOutOfBounds, "")
|
||||||
b.createRuntimeAssert(assert, name, "unsafeSlicePanic")
|
b.createRuntimeAssert(assert, name, "unsafeSlicePanic", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
// createChanBoundsCheck creates a bounds check before creating a new channel to
|
||||||
@@ -155,7 +155,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
|
|||||||
|
|
||||||
// Do the check for a too large (or negative) buffer size.
|
// Do the check for a too large (or negative) buffer size.
|
||||||
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
|
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
|
||||||
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
|
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createNilCheck checks whether the given pointer is nil, and panics if it is.
|
// createNilCheck checks whether the given pointer is nil, and panics if it is.
|
||||||
@@ -199,7 +199,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
|
|||||||
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
|
||||||
|
|
||||||
// Emit the nil check in IR.
|
// Emit the nil check in IR.
|
||||||
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
|
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
|
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
|
||||||
@@ -212,7 +212,7 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
|
|||||||
|
|
||||||
// isNegative = shift < 0
|
// isNegative = shift < 0
|
||||||
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
|
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
|
||||||
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
|
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
|
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
|
||||||
@@ -225,12 +225,12 @@ func (b *builder) createDivideByZeroCheck(y llvm.Value) {
|
|||||||
|
|
||||||
// isZero = y == 0
|
// isZero = y == 0
|
||||||
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
|
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
|
||||||
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
|
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createRuntimeAssert is a common function to create a new branch on an assert
|
// createRuntimeAssert is a common function to create a new branch on an assert
|
||||||
// bool, calling an assert func if the assert value is true (1).
|
// bool, calling an assert func if the assert value is true (1).
|
||||||
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
|
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string, invoke bool) {
|
||||||
// Check whether we can resolve this check at compile time.
|
// Check whether we can resolve this check at compile time.
|
||||||
if !assert.IsAConstantInt().IsNil() {
|
if !assert.IsAConstantInt().IsNil() {
|
||||||
val := assert.ZExtValue()
|
val := assert.ZExtValue()
|
||||||
@@ -252,7 +252,13 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
|
|||||||
|
|
||||||
// Fail: the assert triggered so panic.
|
// Fail: the assert triggered so panic.
|
||||||
b.SetInsertPointAtEnd(faultBlock)
|
b.SetInsertPointAtEnd(faultBlock)
|
||||||
b.createRuntimeCall(assertFunc, nil, "")
|
if invoke {
|
||||||
|
// This runtime panic is recoverable.
|
||||||
|
b.createRuntimeInvoke(assertFunc, nil, "")
|
||||||
|
} else {
|
||||||
|
// This runtime panic is not recoverable.
|
||||||
|
b.createRuntimeCall(assertFunc, nil, "")
|
||||||
|
}
|
||||||
b.CreateUnreachable()
|
b.CreateUnreachable()
|
||||||
|
|
||||||
// Ok: assert didn't trigger so continue normally.
|
// Ok: assert didn't trigger so continue normally.
|
||||||
|
|||||||
@@ -4,5 +4,22 @@ package runtime
|
|||||||
type Error interface {
|
type Error interface {
|
||||||
error
|
error
|
||||||
|
|
||||||
|
// Method to indicate this is indeed a runtime error.
|
||||||
RuntimeError()
|
RuntimeError()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type runtimeError struct {
|
||||||
|
msg string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r runtimeError) Error() string {
|
||||||
|
return r.msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Purely here to satisfy the Error interface.
|
||||||
|
func (r runtimeError) RuntimeError() {}
|
||||||
|
|
||||||
|
var (
|
||||||
|
divideError error = runtimeError{"runtime error: integer divide by zero"}
|
||||||
|
overflowError error = runtimeError{"runtime error: integer overflow"}
|
||||||
|
)
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ func negativeShiftPanic() {
|
|||||||
|
|
||||||
// Panic when there is a divide by zero.
|
// Panic when there is a divide by zero.
|
||||||
func divideByZeroPanic() {
|
func divideByZeroPanic() {
|
||||||
runtimePanicAt(returnAddress(0), "divide by zero")
|
_panic(divideError)
|
||||||
}
|
}
|
||||||
|
|
||||||
func blockingPanic() {
|
func blockingPanic() {
|
||||||
|
|||||||
Vendored
+13
@@ -30,6 +30,9 @@ func main() {
|
|||||||
println("\n# defer panic")
|
println("\n# defer panic")
|
||||||
deferPanic()
|
deferPanic()
|
||||||
|
|
||||||
|
println("\n# runtime panics")
|
||||||
|
runtimePanicDivByZero(1, 0)
|
||||||
|
|
||||||
println("\n# runtime.Goexit")
|
println("\n# runtime.Goexit")
|
||||||
runtimeGoexit()
|
runtimeGoexit()
|
||||||
}
|
}
|
||||||
@@ -114,6 +117,16 @@ func deferPanic() {
|
|||||||
println("defer panic")
|
println("defer panic")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runtimePanicDivByZero(a, b int) int {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
println("recovered:", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return a / b
|
||||||
|
}
|
||||||
|
|
||||||
func runtimeGoexit() {
|
func runtimeGoexit() {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
Reference in New Issue
Block a user