diff --git a/compiler/asserts.go b/compiler/asserts.go index bcfafb99..92083bc6 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -253,6 +253,19 @@ func (b *builder) createNegativeShiftCheck(shift llvm.Value) { b.createRuntimeAssert(isNegative, "shift", "negativeShiftPanic") } +// createDivideByZeroCheck asserts that y is not zero. If it is, a runtime panic +// will be emitted. This follows the Go specification which says that a divide +// by zero must cause a run time panic. +func (b *builder) createDivideByZeroCheck(y llvm.Value) { + if b.info.nobounds { + return + } + + // isZero = y == 0 + isZero := b.CreateICmp(llvm.IntEQ, y, llvm.ConstInt(y.Type(), 0, false), "") + b.createRuntimeAssert(isZero, "divbyzero", "divideByZeroPanic") +} + // 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) { diff --git a/compiler/compiler.go b/compiler/compiler.go index a93a9afb..b079235c 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -23,7 +23,7 @@ import ( // Version of the compiler pacakge. Must be incremented each time the compiler // package changes in a way that affects the generated LLVM module. // This version is independent of the TinyGo version number. -const Version = 21 // last change: add nounwind attribute +const Version = 22 // last change: check for divide by zero func init() { llvm.InitializeAllTargets() @@ -2005,17 +2005,58 @@ func (b *builder) createBinOp(op token.Token, typ, ytyp types.Type, x, y llvm.Va return b.CreateSub(x, y, ""), nil case token.MUL: // * return b.CreateMul(x, y, ""), nil - case token.QUO: // / + case token.QUO, token.REM: // /, % + // Check for a divide by zero. If y is zero, the Go + // specification says that a runtime error must be triggered. + b.createDivideByZeroCheck(y) + if signed { - return b.CreateSDiv(x, y, ""), nil + // Deal with signed division overflow. + // The LLVM LangRef says: + // + // Overflow also leads to undefined behavior; this is a + // rare case, but can occur, for example, by doing a + // 32-bit division of -2147483648 by -1. + // + // The Go specification however says this about division: + // + // The one exception to this rule is that if the dividend + // x is the most negative value for the int type of x, the + // quotient q = x / -1 is equal to x (and r = 0) due to + // two's-complement integer overflow. + // + // In other words, in the special case that the lowest + // possible signed integer is divided by -1, the result of + // the division is the same as x (the dividend). + // This is implemented by checking for this condition and + // changing y to 1 if it occurs, for example for 32-bit + // ints: + // + // if x == -2147483648 && y == -1 { + // y = 1 + // } + // + // Dividing x by 1 obviously returns x, therefore satisfying + // the Go specification without a branch. + llvmType := x.Type() + minusOne := llvm.ConstSub(llvm.ConstInt(llvmType, 0, false), llvm.ConstInt(llvmType, 1, false)) + lowestInteger := llvm.ConstInt(x.Type(), 1<<(llvmType.IntTypeWidth()-1), false) + yIsMinusOne := b.CreateICmp(llvm.IntEQ, y, minusOne, "") + xIsLowestInteger := b.CreateICmp(llvm.IntEQ, x, lowestInteger, "") + hasOverflow := b.CreateAnd(yIsMinusOne, xIsLowestInteger, "") + y = b.CreateSelect(hasOverflow, llvm.ConstInt(llvmType, 1, true), y, "") + + if op == token.QUO { + return b.CreateSDiv(x, y, ""), nil + } else { + return b.CreateSRem(x, y, ""), nil + } } else { - return b.CreateUDiv(x, y, ""), nil - } - case token.REM: // % - if signed { - return b.CreateSRem(x, y, ""), nil - } else { - return b.CreateURem(x, y, ""), nil + if op == token.QUO { + return b.CreateUDiv(x, y, ""), nil + } else { + return b.CreateURem(x, y, ""), nil + } } case token.AND: // & return b.CreateAnd(x, y, ""), nil diff --git a/compiler/testdata/basic.go b/compiler/testdata/basic.go index ab8b5986..e9fcbad3 100644 --- a/compiler/testdata/basic.go +++ b/compiler/testdata/basic.go @@ -10,6 +10,22 @@ func equalInt(x, y int) bool { return x == y } +func divInt(x, y int) int { + return x / y +} + +func divUint(x, y uint) uint { + return x / y +} + +func remInt(x, y int) int { + return x % y +} + +func remUint(x, y uint) uint { + return x % y +} + func floatEQ(x, y float32) bool { return x == y } diff --git a/compiler/testdata/basic.ll b/compiler/testdata/basic.ll index 910c3ed5..2ac982c9 100644 --- a/compiler/testdata/basic.ll +++ b/compiler/testdata/basic.ll @@ -28,6 +28,76 @@ entry: ret i1 %0 } +; Function Attrs: nounwind +define hidden i32 @main.divInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 { +entry: + %0 = icmp eq i32 %y, 0 + br i1 %0, label %divbyzero.throw, label %divbyzero.next + +divbyzero.throw: ; preds = %entry + call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0 + unreachable + +divbyzero.next: ; preds = %entry + %1 = icmp eq i32 %y, -1 + %2 = icmp eq i32 %x, -2147483648 + %3 = and i1 %1, %2 + %4 = select i1 %3, i32 1, i32 %y + %5 = sdiv i32 %x, %4 + ret i32 %5 +} + +declare void @runtime.divideByZeroPanic(i8*, i8*) + +; Function Attrs: nounwind +define hidden i32 @main.divUint(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 { +entry: + %0 = icmp eq i32 %y, 0 + br i1 %0, label %divbyzero.throw, label %divbyzero.next + +divbyzero.throw: ; preds = %entry + call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0 + unreachable + +divbyzero.next: ; preds = %entry + %1 = udiv i32 %x, %y + ret i32 %1 +} + +; Function Attrs: nounwind +define hidden i32 @main.remInt(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 { +entry: + %0 = icmp eq i32 %y, 0 + br i1 %0, label %divbyzero.throw, label %divbyzero.next + +divbyzero.throw: ; preds = %entry + call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0 + unreachable + +divbyzero.next: ; preds = %entry + %1 = icmp eq i32 %y, -1 + %2 = icmp eq i32 %x, -2147483648 + %3 = and i1 %1, %2 + %4 = select i1 %3, i32 1, i32 %y + %5 = srem i32 %x, %4 + ret i32 %5 +} + +; Function Attrs: nounwind +define hidden i32 @main.remUint(i32 %x, i32 %y, i8* %context, i8* %parentHandle) unnamed_addr #0 { +entry: + %0 = icmp eq i32 %y, 0 + br i1 %0, label %divbyzero.throw, label %divbyzero.next + +divbyzero.throw: ; preds = %entry + call void @runtime.divideByZeroPanic(i8* undef, i8* null) #0 + unreachable + +divbyzero.next: ; preds = %entry + %1 = urem i32 %x, %y + ret i32 %1 +} + ; Function Attrs: nounwind define hidden i1 @main.floatEQ(float %x, float %y, i8* %context, i8* %parentHandle) unnamed_addr #0 { entry: diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 37b7c259..51778d06 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -64,6 +64,11 @@ func negativeShiftPanic() { runtimePanic("negative shift") } +// Panic when there is a divide by zero. +func divideByZeroPanic() { + runtimePanic("divide by zero") +} + func blockingPanic() { runtimePanic("trying to do blocking operation in exported function") } diff --git a/testdata/binop.go b/testdata/binop.go index e8f0e641..8b6fa0f7 100644 --- a/testdata/binop.go +++ b/testdata/binop.go @@ -75,6 +75,10 @@ func main() { println("constant number") x := uint32(5) println(uint32(x) / (20e0 / 1)) + + // check for signed integer overflow + println("-2147483648 / -1:", sdiv32(-2147483648, -1)) + println("-2147483648 % -1:", srem32(-2147483648, -1)) } var x = true @@ -114,6 +118,14 @@ func ashr(x int, y uint) int { return x >> y } +func sdiv32(x, y int32) int32 { + return x / y +} + +func srem32(x, y int32) int32 { + return x % y +} + var shlSimple = shl(2, 1) var shlOverflow = shl(2, 1000) var shrSimple = shr(2, 1) diff --git a/testdata/binop.txt b/testdata/binop.txt index 590b14d6..269731ab 100644 --- a/testdata/binop.txt +++ b/testdata/binop.txt @@ -64,3 +64,5 @@ true true constant number 0 +-2147483648 / -1: -2147483648 +-2147483648 % -1: 0