Skip to content

Commit 5a5a6a0

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 873c664 commit 5a5a6a0

File tree

4 files changed

+33
-11
lines changed

4 files changed

+33
-11
lines changed

GNUmakefile

+3
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ endif
370370
# debug/plan9obj requires os.ReadAt, which is not yet supported on windows
371371
# image requires recover(), which is not yet supported on wasi
372372
# io/ioutil requires os.ReadDir, which is not yet supported on windows or wasi
373+
# math/bits: needs panic()/recover()
373374
# mime: fail on wasi; neds panic()/recover()
374375
# mime/multipart: needs wasip1 syscall.FDFLAG_NONBLOCK
375376
# mime/quotedprintable requires syscall.Faccessat
@@ -391,6 +392,7 @@ TEST_PACKAGES_LINUX := \
391392
debug/plan9obj \
392393
image \
393394
io/ioutil \
395+
math/bits \
394396
mime \
395397
mime/multipart \
396398
mime/quotedprintable \
@@ -408,6 +410,7 @@ TEST_PACKAGES_DARWIN := $(TEST_PACKAGES_LINUX)
408410
TEST_PACKAGES_WINDOWS := \
409411
compress/flate \
410412
crypto/hmac \
413+
math/bits \
411414
os/user \
412415
strconv \
413416
text/template/parse \

compiler/asserts.go

+14-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,11 @@ 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+
b.createRuntimeInvoke(assertFunc, nil, "")
257+
} else {
258+
b.createRuntimeCall(assertFunc, nil, "")
259+
}
256260
b.CreateUnreachable()
257261

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

src/runtime/error.go

+15
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,18 @@ type Error interface {
66

77
RuntimeError()
88
}
9+
10+
type runtimeError struct {
11+
msg string
12+
}
13+
14+
func (r runtimeError) Error() string {
15+
return r.msg
16+
}
17+
18+
func (r runtimeError) RuntimeError() {}
19+
20+
var (
21+
divideError error = runtimeError{"runtime error: integer divide by zero"}
22+
overflowError error = runtimeError{"runtime error: integer overflow"}
23+
)

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)