Skip to content

Commit 7fe1e63

Browse files
committed
compiler, runtime: implement recoverable divide-by-zero panic
This gets the math/bits 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.
1 parent adc0cae commit 7fe1e63

File tree

4 files changed

+37
-11
lines changed

4 files changed

+37
-11
lines changed

GNUmakefile

+3
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ TEST_PACKAGES_FAST = \
362362
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
363363
# image requires recover(), which is not yet supported on wasi
364364
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
365+
# math/bits: needs panic()/recover()
365366
# mime: fail on wasi; neds panic()/recover()
366367
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
367368
# mime/quotedprintable requires syscall.Faccessat
@@ -384,6 +385,7 @@ TEST_PACKAGES_LINUX := \
384385
debug/plan9obj \
385386
image \
386387
io/ioutil \
388+
math/bits \
387389
mime \
388390
mime/multipart \
389391
mime/quotedprintable \
@@ -403,6 +405,7 @@ TEST_PACKAGES_WINDOWS := \
403405
compress/flate \
404406
crypto/des \
405407
crypto/hmac \
408+
math/bits \
406409
strconv \
407410
text/template/parse \
408411
$(nil)

compiler/asserts.go

+16-10
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (b *builder) createLookupBoundsCheck(arrayLen, index llvm.Value) {
3131

3232
// Now do the bounds check: index >= arrayLen
3333
outOfBounds := b.CreateICmp(llvm.IntUGE, index, arrayLen, "")
34-
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic")
34+
b.createRuntimeAssert(outOfBounds, "lookup", "lookupPanic", false)
3535
}
3636

3737
// 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
7474
outOfBounds3 := b.CreateICmp(llvm.IntUGT, max, capacity, "slice.maxcap")
7575
outOfBounds := b.CreateOr(outOfBounds1, outOfBounds2, "slice.lowmax")
7676
outOfBounds = b.CreateOr(outOfBounds, outOfBounds3, "slice.lowcap")
77-
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic")
77+
b.createRuntimeAssert(outOfBounds, "slice", "slicePanic", false)
7878
}
7979

8080
// createSliceToArrayPointerCheck adds a check for slice-to-array pointer
@@ -86,7 +86,7 @@ func (b *builder) createSliceToArrayPointerCheck(sliceLen llvm.Value, arrayLen i
8686
// > run-time panic occurs.
8787
arrayLenValue := llvm.ConstInt(b.uintptrType, uint64(arrayLen), false)
8888
isLess := b.CreateICmp(llvm.IntULT, sliceLen, arrayLenValue, "")
89-
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic")
89+
b.createRuntimeAssert(isLess, "slicetoarray", "sliceToArrayPointerPanic", false)
9090
}
9191

9292
// createUnsafeSliceStringCheck inserts a runtime check used for unsafe.Slice
@@ -118,7 +118,7 @@ func (b *builder) createUnsafeSliceStringCheck(name string, ptr, len llvm.Value,
118118
lenIsNotZero := b.CreateICmp(llvm.IntNE, len, zero, "")
119119
assert := b.CreateAnd(ptrIsNil, lenIsNotZero, "")
120120
assert = b.CreateOr(assert, lenOutOfBounds, "")
121-
b.createRuntimeAssert(assert, name, "unsafeSlicePanic")
121+
b.createRuntimeAssert(assert, name, "unsafeSlicePanic", false)
122122
}
123123

124124
// createChanBoundsCheck creates a bounds check before creating a new channel to
@@ -155,7 +155,7 @@ func (b *builder) createChanBoundsCheck(elementSize uint64, bufSize llvm.Value,
155155

156156
// Do the check for a too large (or negative) buffer size.
157157
bufSizeTooBig := b.CreateICmp(llvm.IntUGE, bufSize, maxBufSize, "")
158-
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic")
158+
b.createRuntimeAssert(bufSizeTooBig, "chan", "chanMakePanic", false)
159159
}
160160

161161
// 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
199199
isnil := b.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
200200

201201
// Emit the nil check in IR.
202-
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic")
202+
b.createRuntimeAssert(isnil, blockPrefix, "nilPanic", false)
203203
}
204204

205205
// createNegativeShiftCheck creates an assertion that panics if the given shift value is negative.
@@ -212,7 +212,7 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) {
212212

213213
// isNegative = shift < 0
214214
isNegative := b.CreateICmp(llvm.IntSLT, shift, llvm.ConstInt(shift.Type(), 0, false), "")
215-
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic")
215+
b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic", false)
216216
}
217217

218218
// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic
@@ -225,12 +225,12 @@ func (b *builder) createDivideByZeroCheck(y llvm.Value) {
225225

226226
// isZero = y == 0
227227
isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "")
228-
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic")
228+
b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic", true)
229229
}
230230

231231
// createRuntimeAssert is a common function to create a new branch on an assert
232232
// bool, calling an assert func if the assert value is true (1).
233-
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string) {
233+
func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc string, invoke bool) {
234234
// Check whether we can resolve this check at compile time.
235235
if !assert.IsAConstantInt().IsNil() {
236236
val := assert.ZExtValue()
@@ -252,7 +252,13 @@ func (b *builder) createRuntimeAssert(assert llvm.Value, blockPrefix, assertFunc
252252

253253
// Fail: the assert triggered so panic.
254254
b.SetInsertPointAtEnd(faultBlock)
255-
b.createRuntimeCall(assertFunc, nil, "")
255+
if invoke {
256+
// This runtime panic is recoverable.
257+
b.createRuntimeInvoke(assertFunc, nil, "")
258+
} else {
259+
// This runtime panic is not recoverable.
260+
b.createRuntimeCall(assertFunc, nil, "")
261+
}
256262
b.CreateUnreachable()
257263

258264
// Ok: assert didn't trigger so continue normally.

src/runtime/error.go

+17
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,22 @@ package runtime
44
type Error interface {
55
error
66

7+
// Method to indicate this is indeed a runtime error.
78
RuntimeError()
89
}
10+
11+
type runtimeError struct {
12+
msg string
13+
}
14+
15+
func (r runtimeError) Error() string {
16+
return r.msg
17+
}
18+
19+
// Purely here to satisfy the Error interface.
20+
func (r runtimeError) RuntimeError() {}
21+
22+
var (
23+
divideError error = runtimeError{"runtime error: integer divide by zero"}
24+
overflowError error = runtimeError{"runtime error: integer overflow"}
25+
)

src/runtime/panic.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func negativeShiftPanic() {
220220

221221
// Panic when there is a divide by zero.
222222
func divideByZeroPanic() {
223-
runtimePanicAt(returnAddress(0), "divide by zero")
223+
_panic(divideError)
224224
}
225225

226226
func blockingPanic() {

0 commit comments

Comments
 (0)