From f67b677795c474e89d6ac01378710dc6805d2ab9 Mon Sep 17 00:00:00 2001 From: dvdvgt <40773635+dvdvgt@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:18:27 +0100 Subject: [PATCH 1/5] add Euclidean-based divmod --- examples/stdlib/mod.check | 10 ++++++++++ examples/stdlib/mod.effekt | 15 +++++++++++++++ libraries/common/effekt.effekt | 35 +++++++++++++++++++++++++++++----- 3 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 examples/stdlib/mod.check create mode 100644 examples/stdlib/mod.effekt diff --git a/examples/stdlib/mod.check b/examples/stdlib/mod.check new file mode 100644 index 000000000..a85d6996e --- /dev/null +++ b/examples/stdlib/mod.check @@ -0,0 +1,10 @@ +8 `divmod` 3 = 2, 2 +8 `divmod` -3 = -2, 2 +-8 `divmod` 3 = -3, 1 +-8 `divmod` -3 = 3, 1 + +8 `quotrem` 3 = 2, 2 +8 `quotrem` -3 = -3, -1 +-8 `quotrem` 3 = -3, 1 +-8 `quotrem` -3 = 2, -2 + diff --git a/examples/stdlib/mod.effekt b/examples/stdlib/mod.effekt new file mode 100644 index 000000000..268659f39 --- /dev/null +++ b/examples/stdlib/mod.effekt @@ -0,0 +1,15 @@ +def main() = { + val ops = [ + (box { (x: Int, y: Int) => (x.div(y), x.mod(y), x == y * x.div(y) + x.mod(y)) }, "divmod"), + (box { (x: Int, y: Int) => (x.quot(y), x.rem(y), x == y * x.quot(y) + x.rem(y)) }, "quotrem"), + ] + ops.foreach { case (op, name) => + [8, 8.neg].foreach { dividend => + [3, 3.neg].foreach { divisor => + val (q, r, assertion) = op(dividend, divisor) + println(s"${dividend.show} `${name}` ${divisor.show} = ${q.show}, ${r.show}") + } + } + println("") + } +} diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 05a2883d7..b49d49b54 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -343,11 +343,36 @@ extern def infixSub(x: Int, y: Int) at {}: Int = llvm "%z = sub %Int ${x}, ${y} ret %Int %z" vm "effekt::infixSub(Int, Int)" -extern def mod(x: Int, y: Int) at {}: Int = - js "(${x} % ${y})" - chez "(modulo ${x} ${y})" - llvm "%z = srem %Int ${x}, ${y} ret %Int %z" - vm "effekt::mod(Int, Int)" +// Computes the quotient based on floored-division +def quot(x: Int, y: Int): Int = + infixDiv(x, y) + +/// Computes the remainder based on floored-division +def rem(x: Int, y: Int): Int = + x - y * (x / y) + +/// Computes the result of Euclidean division +def div(x: Int, y: Int): Int = { + val q = x / y + val r = x.rem(y) + if (r < 0) { + if (y > 0) { q - 1 } + else { q + 1 } + } else { + q + } +} + +/// Computes the result of Euclidean modulo +def mod(x: Int, y: Int): Int = { + val r = x.rem(y) + if (r < 0) { + if (y > 0) { r + y } + else { r - y } + } else { + r + } +} extern def infixAdd(x: Double, y: Double) at {}: Double = js "(${x} + ${y})" From 5aba77326e5aa46d67d33ab5a4a66490bd8ccd3b Mon Sep 17 00:00:00 2001 From: dvdvgt <40773635+dvdvgt@users.noreply.github.com> Date: Wed, 17 Dec 2025 14:56:51 +0100 Subject: [PATCH 2/5] llvm did not use floor division before --- libraries/common/effekt.effekt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index b49d49b54..64b6572c2 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -334,7 +334,22 @@ extern def infixMul(x: Int, y: Int) at {}: Int = extern def infixDiv(x: Int, y: Int) at {}: Int = js "Math.floor(${x} / ${y})" chez "(floor (/ ${x} ${y}))" - llvm "%z = sdiv %Int ${x}, ${y} ret %Int %z" + llvm """ + %quot = sdiv %Int ${x}, ${y} + %rem = srem %Int ${x}, ${y} + ; Check if remainder is non-zero + %has_rem = icmp ne %Int %rem, 0 + ; Check if signs differ (XOR of sign bits) + %a_neg = icmp slt %Int ${x}, 0 + %b_neg = icmp slt %Int ${y}, 0 + %signs_differ = xor i1 %a_neg, %b_neg + ; Need adjustment if there's a remainder AND signs differ + %needs_adjust = and i1 %has_rem, %signs_differ + ; Adjust quotient: subtract 1 if needed + %adjust = select i1 %needs_adjust, %Int -1, %Int 0 + %result = add %Int %quot, %adjust + ret %Int %result + """ vm "effekt::infixDiv(Int, Int)" extern def infixSub(x: Int, y: Int) at {}: Int = From 17284d1eaee2b502de0c6921b7e8c7613d6d8ee6 Mon Sep 17 00:00:00 2001 From: dvdvgt <40773635+dvdvgt@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:21:05 +0100 Subject: [PATCH 3/5] use tuncated integer division for _all_ backends --- examples/stdlib/mod.check | 4 ++-- libraries/common/effekt.effekt | 21 +++------------------ 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/examples/stdlib/mod.check b/examples/stdlib/mod.check index a85d6996e..16c3c158f 100644 --- a/examples/stdlib/mod.check +++ b/examples/stdlib/mod.check @@ -4,7 +4,7 @@ -8 `divmod` -3 = 3, 1 8 `quotrem` 3 = 2, 2 -8 `quotrem` -3 = -3, -1 --8 `quotrem` 3 = -3, 1 +8 `quotrem` -3 = -2, 2 +-8 `quotrem` 3 = -2, -2 -8 `quotrem` -3 = 2, -2 diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 64b6572c2..ca46249c5 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -332,24 +332,9 @@ extern def infixMul(x: Int, y: Int) at {}: Int = vm "effekt::infixMul(Int, Int)" extern def infixDiv(x: Int, y: Int) at {}: Int = - js "Math.floor(${x} / ${y})" - chez "(floor (/ ${x} ${y}))" - llvm """ - %quot = sdiv %Int ${x}, ${y} - %rem = srem %Int ${x}, ${y} - ; Check if remainder is non-zero - %has_rem = icmp ne %Int %rem, 0 - ; Check if signs differ (XOR of sign bits) - %a_neg = icmp slt %Int ${x}, 0 - %b_neg = icmp slt %Int ${y}, 0 - %signs_differ = xor i1 %a_neg, %b_neg - ; Need adjustment if there's a remainder AND signs differ - %needs_adjust = and i1 %has_rem, %signs_differ - ; Adjust quotient: subtract 1 if needed - %adjust = select i1 %needs_adjust, %Int -1, %Int 0 - %result = add %Int %quot, %adjust - ret %Int %result - """ + js "Math.trunc(${x} / ${y})" + chez "(truncate (/ ${x} ${y}))" + llvm "%z = sdiv %Int ${x}, ${y} ret %Int %z" vm "effekt::infixDiv(Int, Int)" extern def infixSub(x: Int, y: Int) at {}: Int = From d9f6e609db161169d940dfae9cb3d934c9758a28 Mon Sep 17 00:00:00 2001 From: dvdvgt <40773635+dvdvgt@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:29:29 +0100 Subject: [PATCH 4/5] try to reduce branching through bitwise op magic --- libraries/common/effekt.effekt | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index ca46249c5..09fe8dae4 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -343,35 +343,26 @@ extern def infixSub(x: Int, y: Int) at {}: Int = llvm "%z = sub %Int ${x}, ${y} ret %Int %z" vm "effekt::infixSub(Int, Int)" -// Computes the quotient based on floored-division +// Computes the quotient based on truncated-division def quot(x: Int, y: Int): Int = infixDiv(x, y) -/// Computes the remainder based on floored-division -def rem(x: Int, y: Int): Int = - x - y * (x / y) +/// Computes the remainder based on truncated-division + extern def rem(x: Int, y: Int) at {}: Int = + llvm "%z = srem %Int ${x}, ${y} ret %Int %z" + default { x - y * (x / y) } /// Computes the result of Euclidean division def div(x: Int, y: Int): Int = { val q = x / y val r = x.rem(y) - if (r < 0) { - if (y > 0) { q - 1 } - else { q + 1 } - } else { - q - } + q - (r.bitwiseShr(31).bitwiseAnd(y.bitwiseShr(31).bitwiseOr(1))) } /// Computes the result of Euclidean modulo def mod(x: Int, y: Int): Int = { val r = x.rem(y) - if (r < 0) { - if (y > 0) { r + y } - else { r - y } - } else { - r - } + r + y.abs.bitwiseAnd(r.bitwiseShr(31)) } extern def infixAdd(x: Double, y: Double) at {}: Double = From eff8ed9ab8cdfe9d0f31b69aadf7cbe2a47240e7 Mon Sep 17 00:00:00 2001 From: dvdvgt <40773635+dvdvgt@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:12:13 +0100 Subject: [PATCH 5/5] try to reconcile impl with different int representations --- .../main/scala/effekt/core/vm/Builtin.scala | 27 ++++++++++++++++++- libraries/common/effekt.effekt | 21 ++++++++++++--- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala b/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala index 70ccfcf5e..4356dc342 100644 --- a/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala +++ b/effekt/shared/src/main/scala/effekt/core/vm/Builtin.scala @@ -129,8 +129,30 @@ lazy val integers: Builtins = Map( builtin("effekt::infixMul(Int, Int)") { case As.Int(x) :: As.Int(y) :: Nil => Value.Int(x * y) }, + builtin("effekt::rem(Int, Int)") { + case As.Int(x) :: As.Int(y) :: Nil => Value.Int(x - y * (x / y)) + }, + builtin("effekt::quot(Int, Int)") { + case As.Int(x) :: As.Int(y) :: Nil => Value.Int(x / y) + }, + builtin("effekt::div(Int, Int)") { + case As.Int(x) :: As.Int(y) :: Nil => + val q = x / y + val r = x - y * (x / y) + if (r < 0) { + Value.Int(q - y.sign) + } else { + Value.Int(q) + } + }, builtin("effekt::mod(Int, Int)") { - case As.Int(x) :: As.Int(y) :: Nil => Value.Int(x % y) + case As.Int(x) :: As.Int(y) :: Nil => + val r = x - y * (x / y) + if (r < 0) { + Value.Int(r + y.abs) + } else { + Value.Int(r) + } }, builtin("effekt::infixDiv(Int, Int)") { case As.Int(x) :: As.Int(y) :: Nil => Value.Int(x / y) @@ -150,6 +172,9 @@ lazy val integers: Builtins = Map( builtin("effekt::bitwiseXor(Int, Int)") { case As.Int(x) :: As.Int(y) :: Nil => Value.Int(x ^ y) }, + builtin("effekt::bitwiseMsb(Int)") { + case As.Int(x) :: Nil => Value.Int(x >> 63) + }, // Comparison // ---------- diff --git a/libraries/common/effekt.effekt b/libraries/common/effekt.effekt index 09fe8dae4..a47fe2f9b 100644 --- a/libraries/common/effekt.effekt +++ b/libraries/common/effekt.effekt @@ -319,6 +319,12 @@ extern def infixNeq(x: Bool, y: Bool) at {}: Bool = // Math ops // ======== + +// Numeric representations in the backends: +// LLVM: int64_t (64-bit integer) +// JS: 64-bit floating-point (64-bit IEEE 754) +// Chez: 64-bit integer + extern def infixAdd(x: Int, y: Int) at {}: Int = js "(${x} + ${y})" chez "(+ ${x} ${y})" @@ -343,7 +349,7 @@ extern def infixSub(x: Int, y: Int) at {}: Int = llvm "%z = sub %Int ${x}, ${y} ret %Int %z" vm "effekt::infixSub(Int, Int)" -// Computes the quotient based on truncated-division +/// Computes the quotient based on truncated-division def quot(x: Int, y: Int): Int = infixDiv(x, y) @@ -356,13 +362,17 @@ def quot(x: Int, y: Int): Int = def div(x: Int, y: Int): Int = { val q = x / y val r = x.rem(y) - q - (r.bitwiseShr(31).bitwiseAnd(y.bitwiseShr(31).bitwiseOr(1))) + val rNeg = r.bitwiseMsb + val ySign = y.bitwiseMsb.bitwiseOr(1) + q - rNeg.bitwiseAnd(ySign) } /// Computes the result of Euclidean modulo def mod(x: Int, y: Int): Int = { val r = x.rem(y) - r + y.abs.bitwiseAnd(r.bitwiseShr(31)) + val rNeg = r.bitwiseMsb + val absY = y.bitwiseXor(y.bitwiseMsb) - y.bitwiseMsb + r + rNeg.bitwiseAnd(absY) } extern def infixAdd(x: Double, y: Double) at {}: Double = @@ -693,6 +703,11 @@ extern def bitwiseXor(x: Int, y: Int) at {}: Int = llvm "%z = xor %Int ${x}, ${y} ret %Int %z" vm "effekt::bitwiseXor(Int, Int)" +extern def bitwiseMsb(x: Int) at {}: Int = + js "${x} >> 31" + chez "(bitwise-arithmetic-shift-right ${x} 63)" + llvm "%z = ashr %Int ${x}, 63 ret %Int %z" + vm "effekt::bitwiseMsb(Int)" // Byte operations // ===============