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:
Ayke van Laethem
2025-02-25 13:07:21 +01:00
parent adc0cae960
commit 88d273da97
5 changed files with 52 additions and 11 deletions
+16 -10
View File
@@ -31,7 +31,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
// Now do the bounds check: 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
@@ -74,7 +74,7 @@ func (b *builder) createSliceBoundsCheck(capacity, low, high, max llvm.Value, lo
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
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
@@ -86,7 +86,7 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i
// > run-time panic occurs.
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
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
@@ -118,7 +118,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value,
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
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
@@ -155,7 +155,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
// Do the check for a too large (or negative) buffer size.
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.
@@ -199,7 +199,7 @@ func (b *builder) createNilCheck(inst ssa.Value, ptr llvm.Value, blockPrefix str
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
// 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.
@@ -212,7 +212,7 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
// isNegative = shift < 0
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
@@ -225,12 +225,12 @@ func (b *builder) createDivideByZeroCheck(y llvm.Value) {
// isZero = y == 0
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
// 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.
if !assert.IsAConstantInt().IsNil() {
val := assert.ZExtValue()
@@ -252,7 +252,13 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
// Fail: the assert triggered so panic.
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()
// Ok: assert didn't trigger so continue normally.