diff --git a/doc/langref.html.in b/doc/langref.html.in
index d9312249b74737e7b985c38696ffcbee14661878..f24f03c981f5d78513468aa31c3983b06ae2670d 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1370,7 +1370,8 @@ a /= b{#endsyntax#}
Can cause {#link|Division by Zero#} for floats in {#link|FloatMode.optimized Mode|Floating Point Operations#}.
Signed integer operands must be comptime-known and positive. In other cases, use
{#link|@divTrunc#},
- {#link|@divFloor#}, or
+ {#link|@divFloor#},
+ {#link|@divCeil#}, or
{#link|@divExact#} instead.
Invokes {#link|Peer Type Resolution#} for the operands.
@@ -4735,7 +4736,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}@divExact(a, b) * b == a{#endsyntax#}
For a function that returns a possible error code, use {#syntax#}@import("std").math.divExact{#endsyntax#}.
- {#see_also|@divTrunc|@divFloor#}
+ {#see_also|@divTrunc|@divFloor|@divCeil#}
{#header_close#}
{#header_open|@divFloor#}
{#syntax#}@divFloor(numerator: T, denominator: T) T{#endsyntax#}
@@ -4749,7 +4750,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}(@divFloor(a, b) * b) + @mod(a, b) == a{#endsyntax#}
For a function that returns a possible error code, use {#syntax#}@import("std").math.divFloor{#endsyntax#}.
- {#see_also|@divTrunc|@divExact#}
+ {#see_also|@divTrunc|@divCeil|@divExact#}
{#header_close#}
{#header_open|@divTrunc#}
{#syntax#}@divTrunc(numerator: T, denominator: T) T{#endsyntax#}
@@ -4763,7 +4764,20 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}(@divTrunc(a, b) * b) + @rem(a, b) == a{#endsyntax#}
For a function that returns a possible error code, use {#syntax#}@import("std").math.divTrunc{#endsyntax#}.
- {#see_also|@divFloor|@divExact#}
+ {#see_also|@divFloor|@divCeil|@divExact#}
+ {#header_close#}
+ {#header_open|@divCeil#}
+ {#syntax#}@divCeil(numerator: T, denominator: T) T{#endsyntax#}
+
+ Ceiled division. Rounds toward positive infinity. Caller guarantees {#syntax#}denominator != 0{#endsyntax#} and
+ {#syntax#}!(@typeInfo(T) == .int and T.is_signed and numerator == std.math.minInt(T) and denominator == -1){#endsyntax#}.
+
+
+ - {#syntax#}@divCeil(5, 3) == 2{#endsyntax#}
+ - {#syntax#}@divCeil(-5, 3) == -1{#endsyntax#}
+
+ For a function that returns a possible error code, use {#syntax#}@import("std").math.divCeil{#endsyntax#}.
+ {#see_also|@divFloor|@divTrunc|@divExact#}
{#header_close#}
{#header_open|@embedFile#}
@@ -6095,6 +6109,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}/{#endsyntax#} (division)
{#link|@divTrunc#} (division)
{#link|@divFloor#} (division)
+ {#link|@divCeil#} (division)
{#link|@divExact#} (division)
Example with addition at compile-time:
@@ -6112,6 +6127,7 @@ fn cmpxchgWeakButNotAtomic(comptime T: type, ptr: *T, expected_value: T, new_val
{#syntax#}@import("std").math.mul{#endsyntax#}
{#syntax#}@import("std").math.divTrunc{#endsyntax#}
{#syntax#}@import("std").math.divFloor{#endsyntax#}
+ {#syntax#}@import("std").math.divCeil{#endsyntax#}
{#syntax#}@import("std").math.divExact{#endsyntax#}
{#syntax#}@import("std").math.shl{#endsyntax#}
diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig
index 75a1eb4c70552e0b7cc3eaf4ef324570df1a1387..24d4f36afd80b8db8873492e92e8ceea4d80040b 100644
--- a/lib/std/math/big/int.zig
+++ b/lib/std/math/big/int.zig
@@ -1221,6 +1221,72 @@ pub const Mutable = struct {
}
}
+ /// q = a / b (rem r)
+ ///
+ /// a / b are ceiled (rounded towards +inf).
+ /// q may alias with a or b.
+ ///
+ /// Asserts there is enough memory to store q and r.
+ /// The upper bound for r limb count is `b.limbs.len`.
+ /// The upper bound for q limb count is given by `a.limbs`.
+ ///
+ /// `limbs_buffer` is used for temporary storage. The amount required is given by `calcDivLimbsBufferLen`.
+ pub fn divCeil(
+ q: *Mutable,
+ r: *Mutable,
+ a: Const,
+ b: Const,
+ limbs_buffer: []Limb,
+ ) void {
+ const sep = a.limbs.len + 2;
+ var x = a.toMutable(limbs_buffer[0..sep]);
+ var y = b.toMutable(limbs_buffer[sep..]);
+
+ // div performs truncating division (@divTrunc) which rounds towards negative
+ // infinity if the result is positive and towards positive infinity if the result is
+ // negative.
+ div(q, r, &x, &y);
+
+ // @rem gives the remainder after @divTrunc, and is defined by:
+ // x * @divTrunc(x, y) + @rem(x, y) = x
+ // For all integers x, y with y != 0.
+ // In the following comments, a, b will be integers with a >= 0, b > 0, and we will take
+ // modCeil to be the remainder after @divCeil, defined by:
+ // x * @divCeil(x, y) + modCeil(x, y) = x
+ // For all integers x, y with y != 0.
+
+ if (a.positive != b.positive or r.eqlZero()) {
+ // In this case either the result is negative or the remainder is 0.
+ // If the result is negative then the default truncating division already rounds
+ // towards positive infinity, so no adjustment is needed.
+ // If the remainder is 0 then the division is exact and no adjustment is needed.
+ } else if (a.positive) {
+ // Both positive.
+ // We have:
+ // modCeil(a, b) != 0
+ // => @divCeil(a, b) = @divTrunc(a, b) + 1
+ // And:
+ // b * @divTrunc(a, b) + @rem(a, b) = a
+ // b * @divCeil(a, b) + modCeil(a, b) = a
+ // => b * @divTrunc(a, b) + b + modCeil(a, b) = a
+ // => modCeil(a, b) = @rem(a, b) - b
+ q.addScalar(q.toConst(), 1);
+ r.sub(r.toConst(), y.toConst());
+ } else {
+ // Both negative.
+ // We have:
+ // modCeil(-a, -b) != 0
+ // => @divCeil(-a, -b) = @divTrunc(-a, -b) + 1
+ // And:
+ // -b * @divTrunc(-a, -b) + @rem(-a, -b) = -a
+ // -b * @divCeil(-a, -b) + modCeil(-a, -b) = -a
+ // => -b * @divTrunc(-a, -b) - b + modCeil(-a, -b) = -a
+ // => modCeil(-a, -b) = @rem(-a, -b) + b
+ q.addScalar(q.toConst(), 1);
+ r.add(r.toConst(), y.toConst().abs());
+ }
+ }
+
/// q = a / b (rem r)
///
/// a / b are truncated (rounded towards -inf).
diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig
index 4adf7d0232804afe4738add9bf7a5307e2f1cdd3..037a9f766c1a66513173cdd7d0154500362856bd 100644
--- a/lib/std/zig/AstGen.zig
+++ b/lib/std/zig/AstGen.zig
@@ -2845,6 +2845,7 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
.bit_reverse,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
@@ -9392,6 +9393,7 @@ fn builtinCall(
.div_exact => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_exact),
.div_floor => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_floor),
+ .div_ceil => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_ceil),
.div_trunc => return divBuiltin(gz, scope, ri, node, params[0], params[1], .div_trunc),
.mod => return divBuiltin(gz, scope, ri, node, params[0], params[1], .mod),
.rem => return divBuiltin(gz, scope, ri, node, params[0], params[1], .rem),
diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig
index a9b680c39b2a9e01e967ab33f3b03fe43999392a..c42528e3ee2cf8d91509e86fd4a444994027be81 100644
--- a/lib/std/zig/AstRlAnnotate.zig
+++ b/lib/std/zig/AstRlAnnotate.zig
@@ -936,6 +936,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast.
},
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig
index 4464d1fa46c93a1170238627dc5fd0b03c32048e..7ff834487ce43f5700e96de0f58b5f12664343ac 100644
--- a/lib/std/zig/BuiltinFn.zig
+++ b/lib/std/zig/BuiltinFn.zig
@@ -31,6 +31,7 @@ pub const Tag = enum {
c_va_copy,
c_va_end,
c_va_start,
+ div_ceil,
div_exact,
div_floor,
div_trunc,
@@ -398,6 +399,13 @@ pub const list = list: {
.param_count = 2,
},
},
+ .{
+ "@divCeil",
+ .{
+ .tag = .div_ceil,
+ .param_count = 2,
+ },
+ },
.{
"@divTrunc",
.{
diff --git a/lib/std/zig/Zir.zig b/lib/std/zig/Zir.zig
index 49897a755f8a08cedc86d421250a887afe03a720..c5fc15d64fb50e09f5b5b809ec1cd6eba314bc70 100644
--- a/lib/std/zig/Zir.zig
+++ b/lib/std/zig/Zir.zig
@@ -200,6 +200,9 @@ pub const Inst = struct {
/// Implements the `@divFloor` builtin.
/// Uses the `pl_node` union field with payload `Bin`.
div_floor,
+ /// Implements the `@divCeil` builtin.
+ /// Uses the `pl_node` union field with payload `Bin`.
+ div_ceil,
/// Implements the `@divTrunc` builtin.
/// Uses the `pl_node` union field with payload `Bin`.
div_trunc,
@@ -1267,6 +1270,7 @@ pub const Inst = struct {
.bit_reverse,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
@@ -1547,6 +1551,7 @@ pub const Inst = struct {
.bit_reverse,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
@@ -1815,6 +1820,7 @@ pub const Inst = struct {
.div_exact = .pl_node,
.div_floor = .pl_node,
+ .div_ceil = .pl_node,
.div_trunc = .pl_node,
.mod = .pl_node,
.rem = .pl_node,
@@ -4115,6 +4121,7 @@ fn findTrackableInner(
.mul_sat,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
diff --git a/lib/zig.h b/lib/zig.h
index 34b56286a508b2707f4498f49612d9d625ee7c86..715bdabf9fcaedcb4de32fbeb014f7bd3c0a3af9 100644
--- a/lib/zig.h
+++ b/lib/zig.h
@@ -813,6 +813,15 @@ typedef ptrdiff_t intptr_t;
static inline int##w##_t zig_div_floor_i##w(int##w##_t lhs, int##w##_t rhs) { \
return lhs / rhs + (lhs % rhs != INT##w##_C(0) ? zig_shr_i##w(lhs ^ rhs, UINT8_C(w) - UINT8_C(1)) : INT##w##_C(0)); \
} \
+\
+ static inline uint##w##_t zig_div_ceil_u##w(uint##w##_t lhs, uint##w##_t rhs) { \
+ return lhs / rhs + (lhs % rhs != UINT##w##_C(0) ? UINT##w##_C(1) : UINT##w##_C(0)); \
+ } \
+\
+ static inline int##w##_t zig_div_ceil_i##w(int##w##_t lhs, int##w##_t rhs) { \
+ return lhs / rhs + (lhs % rhs != INT##w##_C(0) \
+ ? zig_shr_i##w(lhs ^ rhs, UINT8_C(w) - UINT8_C(1)) + INT##w##_C(1) : INT##w##_C(0)); \
+ } \
\
zig_basic_operator(uint##w##_t, mod_u##w, %) \
\
@@ -2058,6 +2067,21 @@ static inline zig_i128 zig_div_floor_i128(zig_i128 lhs, zig_i128 rhs) {
return zig_add_i128(zig_div_trunc_i128(lhs, rhs), zig_make_i128(mask, (uint64_t)mask));
}
+static inline zig_u128 zig_div_ceil_u128(zig_u128 lhs, zig_u128 rhs) {
+ zig_u128 rem = zig_rem_u128(lhs, rhs);
+ uint64_t mask = zig_or_u64(zig_hi_u128(rem), zig_lo_u128(rem)) != UINT64_C(0)
+ ? UINT64_C(1) : UINT64_C(0);
+ return zig_add_u128(zig_div_trunc_u128(lhs, rhs), zig_make_u128(UINT64_C(0), mask));
+}
+
+static inline zig_i128 zig_div_ceil_i128(zig_i128 lhs, zig_i128 rhs) {
+ zig_i128 rem = zig_rem_i128(lhs, rhs);
+ int64_t mask = zig_or_u64((uint64_t)zig_hi_i128(rem), zig_lo_i128(rem)) != UINT64_C(0)
+ ? zig_shr_i64(zig_xor_i64(zig_hi_i128(lhs), zig_hi_i128(rhs)), UINT8_C(63)) + INT64_C(1)
+ : INT64_C(0);
+ return zig_add_i128(zig_div_trunc_i128(lhs, rhs), zig_make_i128(INT64_C(0), (uint64_t)mask));
+}
+
#define zig_mod_u128 zig_rem_u128
static inline zig_i128 zig_mod_i128(zig_i128 lhs, zig_i128 rhs) {
@@ -3251,6 +3275,10 @@ static inline void zig_div_floor_big(void *res, const void *lhs, const void *rhs
zig_trap();
}
+static inline void zig_div_ceil_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) {
+ zig_trap();
+}
+
zig_extern void __umodei4(uint32_t *res, const uint32_t *lhs, const uint32_t *rhs, uintptr_t bits);
static inline void zig_rem_big(void *res, const void *lhs, const void *rhs, bool is_signed, uint16_t bits) {
if (!is_signed) {
@@ -4010,6 +4038,10 @@ zig_float_negate_builtin(128, zig_make_u128, (UINT64_C(1) << 63, UINT64_C(0)))
static inline zig_f##w zig_div_floor_f##w(zig_f##w lhs, zig_f##w rhs) { \
return zig_floor_f##w(zig_div_f##w(lhs, rhs)); \
} \
+\
+ static inline zig_f##w zig_div_ceil_f##w(zig_f##w lhs, zig_f##w rhs) { \
+ return zig_ceil_f##w(zig_div_f##w(lhs, rhs)); \
+ } \
\
static inline zig_f##w zig_mod_f##w(zig_f##w lhs, zig_f##w rhs) { \
return zig_sub_f##w(lhs, zig_mul_f##w(zig_div_floor_f##w(lhs, rhs), rhs)); \
diff --git a/src/Air.zig b/src/Air.zig
index 1c14a7c138cca6d445b8228ebb30c33100ded5aa..a03899740deb47f228cfddb27b47699427b81d95 100644
--- a/src/Air.zig
+++ b/src/Air.zig
@@ -143,6 +143,13 @@ pub const Inst = struct {
div_floor,
/// Same as `div_floor` with optimized float mode.
div_floor_optimized,
+ /// Ceiling integer or float division. For integers, wrapping is illegal behavior.
+ /// Both operands are guaranteed to be the same type, and the result type
+ /// is the same as both operands.
+ /// Uses the `bin_op` field.
+ div_ceil,
+ /// Same as `div_ceil` with optimized float mode.
+ div_ceil_optimized,
/// Integer or float division.
/// If a remainder would be produced, illegal behavior occurs.
/// For integers, overflow is illegal behavior.
@@ -1605,6 +1612,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
.rem,
.mod,
@@ -1624,6 +1632,7 @@ pub fn typeOfIndex(air: *const Air, inst: Air.Inst.Index, ip: *const InternPool)
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -1985,6 +1994,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool {
.div_trunc_optimized,
.div_floor,
.div_floor_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.div_exact,
.div_exact_optimized,
.rem,
diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig
index 65c4a6afe023dae9ee7a03a08c77d585e9e1bd2e..08081e5fee85ded748401f7257042fdcd5e71c02 100644
--- a/src/Air/Legalize.zig
+++ b/src/Air/Legalize.zig
@@ -54,6 +54,8 @@ pub const Feature = enum {
scalarize_div_trunc_optimized,
scalarize_div_floor,
scalarize_div_floor_optimized,
+ scalarize_div_ceil,
+ scalarize_div_ceil_optimized,
scalarize_div_exact,
scalarize_div_exact_optimized,
scalarize_rem,
@@ -173,6 +175,15 @@ pub const Feature = enum {
/// Not compatible with `scalarize_mul_safe`.
expand_mul_safe,
+ /// Replace `div_ceil` with truncating division followed by a remainder based adjustment for integers,
+ /// or division followed by ceil for floats.
+ /// Not compatible with `scalarize_div_ceil`.
+ expand_div_ceil,
+ /// Replace `div_ceil_optimized` with truncating division followed by a remainder based adjustment for integers,
+ /// or division followed by ceil for floats.
+ /// Not compatible with `scalarize_div_ceil_optimized`.
+ expand_div_ceil_optimized,
+
/// Replace `load` from a packed pointer with a non-packed `load`, `shr`, `truncate`.
/// Currently assumes little endian and a specific integer layout where the lsb of every integer is the lsb of the
/// first byte of memory until bit pointers know their backing type.
@@ -231,6 +242,8 @@ pub const Feature = enum {
.div_trunc_optimized => .scalarize_div_trunc_optimized,
.div_floor => .scalarize_div_floor,
.div_floor_optimized => .scalarize_div_floor_optimized,
+ .div_ceil => .scalarize_div_ceil,
+ .div_ceil_optimized => .scalarize_div_ceil_optimized,
.div_exact => .scalarize_div_exact,
.div_exact_optimized => .scalarize_div_exact_optimized,
.rem => .scalarize_rem,
@@ -382,7 +395,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(bin_op.lhs))) {
.none => {},
.scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .bin_op)),
- .soft_float => continue :inst l.replaceInst(inst, .block, try l.softFloatDivTruncFloorBlockPayload(
+ .soft_float => continue :inst l.replaceInst(inst, .block, try l.softFloatDivTruncFloorCeilBlockPayload(
inst,
bin_op.lhs,
bin_op.rhs,
@@ -596,6 +609,30 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void {
continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .ty_op));
}
},
+ inline .div_ceil, .div_ceil_optimized => |air_tag| {
+ const expand_feature: Feature = switch (air_tag) {
+ .div_ceil => .expand_div_ceil,
+ .div_ceil_optimized => .expand_div_ceil_optimized,
+ else => unreachable,
+ };
+
+ if (l.features.has(expand_feature)) {
+ assert(!l.features.has(.scalarize(air_tag))); // it doesn't make sense to do both
+ continue :inst l.replaceInst(inst, .block, try l.divCeilBlockPayload(inst, air_tag));
+ } else {
+ const bin_op = l.air_instructions.items(.data)[@intFromEnum(inst)].bin_op;
+ switch (l.wantScalarizeOrSoftFloat(air_tag, l.typeOf(bin_op.lhs))) {
+ .none => {},
+ .scalarize => continue :inst l.replaceInst(inst, .block, try l.scalarizeBlockPayload(inst, .bin_op)),
+ .soft_float => continue :inst l.replaceInst(inst, .block, try l.softFloatDivTruncFloorCeilBlockPayload(
+ inst,
+ bin_op.lhs,
+ bin_op.rhs,
+ air_tag,
+ )),
+ }
+ }
+ },
inline .int_from_float_safe,
.int_from_float_optimized_safe,
=> |air_tag| {
@@ -2419,6 +2456,159 @@ fn safeArithmeticBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index, overflow_
} };
}
+fn divCeilBlockPayload(
+ l: *Legalize,
+ orig_inst: Air.Inst.Index,
+ air_tag: Air.Inst.Tag,
+) Error!Air.Inst.Data {
+ const pt = l.pt;
+ const zcu = pt.zcu;
+ const gpa = zcu.gpa;
+
+ const bin_op = l.air_instructions.items(.data)[@intFromEnum(orig_inst)].bin_op;
+ const operand_ty = l.typeOf(bin_op.lhs);
+ assert(l.typeOf(bin_op.rhs).toIntern() == operand_ty.toIntern());
+
+ const scalar_ty = operand_ty.scalarType(zcu);
+ const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
+
+ switch (scalar_ty.zigTypeTag(zcu)) {
+ .float => {
+ // %result = ceil(lhs / rhs)
+
+ var inst_buf: [3]Air.Inst.Index = undefined;
+ try l.air_instructions.ensureUnusedCapacity(gpa, inst_buf.len);
+
+ var main_block: Block = .init(&inst_buf);
+
+ const div_tag: Air.Inst.Tag = switch (air_tag) {
+ .div_ceil => .div_float,
+ .div_ceil_optimized => .div_float_optimized,
+ else => unreachable,
+ };
+
+ const div_inst = main_block.add(l, .{
+ .tag = div_tag,
+ .data = .{ .bin_op = bin_op },
+ });
+
+ const ceil_inst = main_block.add(l, .{
+ .tag = .ceil,
+ .data = .{ .un_op = div_inst.toRef() },
+ });
+
+ main_block.addBr(l, orig_inst, ceil_inst.toRef());
+
+ _ = main_block.stealRemainingCapacity();
+ return .{ .ty_pl = .{
+ .ty = .fromType(operand_ty),
+ .payload = try l.addBlockBody(main_block.body()),
+ } };
+ },
+
+ .int => {
+ // Integer div_ceil:
+ //
+ // q = div_trunc(lhs, rhs)
+ // r = rem(lhs, rhs)
+ //
+ // unsigned:
+ // q + int(r != 0)
+ //
+ // signed:
+ // q + int(r != 0 and same_sign(lhs, rhs))
+ //
+ // same_sign is `(lhs ^ rhs) >= 0`.
+
+ var inst_buf: [10]Air.Inst.Index = undefined;
+ try l.air_instructions.ensureUnusedCapacity(gpa, inst_buf.len);
+
+ var main_block: Block = .init(&inst_buf);
+
+ const q_inst = main_block.add(l, .{
+ .tag = .div_trunc,
+ .data = .{ .bin_op = bin_op },
+ });
+
+ const r_inst = main_block.add(l, .{
+ .tag = .rem,
+ .data = .{ .bin_op = bin_op },
+ });
+
+ const zero_ref: Air.Inst.Ref = if (is_vector) zero: {
+ const zero_scalar = try pt.intValue(scalar_ty, 0);
+ const zero_vec = try pt.aggregateSplatValue(operand_ty, zero_scalar);
+ break :zero Air.internedToRef(zero_vec.toIntern());
+ } else Air.internedToRef((try pt.intValue(operand_ty, 0)).toIntern());
+
+ const r_nonzero_inst = try main_block.addCmp(
+ l,
+ .neq,
+ r_inst.toRef(),
+ zero_ref,
+ .{ .vector = is_vector },
+ );
+
+ const int_info = scalar_ty.intInfo(zcu);
+
+ const need_adjust_inst: Air.Inst.Index = if (int_info.signedness == .unsigned) r_nonzero_inst else inst: {
+ const sign_xor_inst = main_block.add(l, .{
+ .tag = .xor,
+ .data = .{ .bin_op = .{
+ .lhs = bin_op.lhs,
+ .rhs = bin_op.rhs,
+ } },
+ });
+
+ const signs_same_inst = try main_block.addCmp(
+ l,
+ .gte,
+ sign_xor_inst.toRef(),
+ zero_ref,
+ .{ .vector = is_vector },
+ );
+
+ break :inst main_block.add(l, .{
+ .tag = .bit_and,
+ .data = .{ .bin_op = .{
+ .lhs = r_nonzero_inst.toRef(),
+ .rhs = signs_same_inst.toRef(),
+ } },
+ });
+ };
+
+ const adjust_u1_ty = if (is_vector)
+ try pt.vectorType(.{
+ .len = operand_ty.vectorLen(zcu),
+ .child = Type.u1.toIntern(),
+ })
+ else
+ Type.u1;
+
+ const adjust_u1_ref = main_block.addBitCast(l, adjust_u1_ty, need_adjust_inst.toRef());
+ const adjust_inst = main_block.addTyOp(l, .int_cast, operand_ty, adjust_u1_ref);
+
+ const result_inst = main_block.add(l, .{
+ .tag = .add,
+ .data = .{ .bin_op = .{
+ .lhs = q_inst.toRef(),
+ .rhs = adjust_inst.toRef(),
+ } },
+ });
+
+ main_block.addBr(l, orig_inst, result_inst.toRef());
+
+ _ = main_block.stealRemainingCapacity();
+ return .{ .ty_pl = .{
+ .ty = .fromType(operand_ty),
+ .payload = try l.addBlockBody(main_block.body()),
+ } };
+ },
+
+ else => unreachable,
+ }
+}
+
fn packedLoadBlockPayload(l: *Legalize, orig_inst: Air.Inst.Index) Error!Air.Inst.Data {
const pt = l.pt;
const zcu = pt.zcu;
@@ -3426,7 +3616,7 @@ fn softFloatNegBlockPayload(
} };
}
-fn softFloatDivTruncFloorBlockPayload(
+fn softFloatDivTruncFloorCeilBlockPayload(
l: *Legalize,
orig_inst: Air.Inst.Index,
lhs: Air.Inst.Ref,
@@ -3441,6 +3631,7 @@ fn softFloatDivTruncFloorBlockPayload(
const floor_tag: Air.Inst.Tag = switch (air_tag) {
.div_trunc, .div_trunc_optimized => .trunc_float,
.div_floor, .div_floor_optimized => .floor,
+ .div_ceil, .div_ceil_optimized => .ceil,
else => unreachable,
};
diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig
index 0570962e5475c2d6281c84168654d01db0cda490..fbfd74a772c0ec3028b56b22201d33d8895d166b 100644
--- a/src/Air/Liveness.zig
+++ b/src/Air/Liveness.zig
@@ -417,6 +417,8 @@ fn analyzeInst(
.div_floor_optimized,
.div_exact,
.div_exact_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.rem,
.rem_optimized,
.mod,
diff --git a/src/Air/Liveness/Verify.zig b/src/Air/Liveness/Verify.zig
index 200110fbfbfd89248a6ef262e7dd74a3bd3ef5ad..24737ebedf6e14446e5d699af845e7c8648f1833 100644
--- a/src/Air/Liveness/Verify.zig
+++ b/src/Air/Liveness/Verify.zig
@@ -235,6 +235,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void {
.div_trunc_optimized,
.div_floor,
.div_floor_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.div_exact,
.div_exact_optimized,
.rem,
diff --git a/src/Air/Verify.zig b/src/Air/Verify.zig
index f9813732130d2f093cd47f66634d6ddde92e846f..b2812f8660d136ce40a4b088b5c30b3bb138e385 100644
--- a/src/Air/Verify.zig
+++ b/src/Air/Verify.zig
@@ -253,6 +253,8 @@ fn body(verify: *Verify, body_insts: []const Air.Inst.Index) Error!void {
.div_trunc_optimized,
.div_floor,
.div_floor_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.div_exact,
.div_exact_optimized,
.rem,
diff --git a/src/Air/print.zig b/src/Air/print.zig
index 20c50983ec5d0c565ec6c6724562bb365369380b..b263c20634f3ea5a0442e6fa14af24ef77b9b64e 100644
--- a/src/Air/print.zig
+++ b/src/Air/print.zig
@@ -132,6 +132,7 @@ const Writer = struct {
.div_float,
.div_trunc,
.div_floor,
+ .div_ceil,
.div_exact,
.rem,
.mod,
@@ -160,6 +161,7 @@ const Writer = struct {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
diff --git a/src/Sema.zig b/src/Sema.zig
index 0e58f0c819bf00d512bd58aa7f346e634bdb0368..923f43414695ef79ebb2d732c349238363259b69 100644
--- a/src/Sema.zig
+++ b/src/Sema.zig
@@ -1335,6 +1335,7 @@ fn analyzeBodyInner(
.div => try sema.zirDiv(block, inst),
.div_exact => try sema.zirDivExact(block, inst),
.div_floor => try sema.zirDivFloor(block, inst),
+ .div_ceil => try sema.zirDivCeil(block, inst),
.div_trunc => try sema.zirDivTrunc(block, inst),
.mod_rem => try sema.zirModRem(block, inst),
@@ -13850,7 +13851,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
return sema.fail(
block,
src,
- "division with '{f}' and '{f}': signed integers must use @divTrunc, @divFloor, or @divExact",
+ "division with '{f}' and '{f}': signed integers must use @divTrunc, @divFloor, @divCeil, or @divExact",
.{ lhs_ty.fmt(pt), rhs_ty.fmt(pt) },
);
}
@@ -14023,6 +14024,71 @@ fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai
return block.addBinOp(airTag(block, is_int, .div_floor, .div_floor_optimized), casted_lhs, casted_rhs);
}
+fn zirDivCeil(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
+ const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
+ const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
+ const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
+ const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
+ const lhs = sema.resolveInst(extra.lhs);
+ const rhs = sema.resolveInst(extra.rhs);
+ const lhs_ty = sema.typeOf(lhs);
+ const rhs_ty = sema.typeOf(rhs);
+ const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
+ const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
+ try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
+ try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
+
+ const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
+ .override = &.{ lhs_src, rhs_src },
+ });
+
+ const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
+ const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
+
+ const lhs_scalar_ty = lhs_ty.scalarType(zcu);
+ const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
+
+ const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
+
+ try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_ceil);
+
+ const maybe_lhs_val = sema.resolveValue(casted_lhs);
+ const maybe_rhs_val = sema.resolveValue(casted_rhs);
+
+ const allow_div_zero = !is_int and
+ resolved_type.toIntern() != .comptime_float_type and
+ block.float_mode == .strict;
+
+ if (maybe_lhs_val) |lhs_val| {
+ if (maybe_rhs_val) |rhs_val| {
+ const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div_ceil);
+ return Air.internedToRef(result.toIntern());
+ }
+ if (allow_div_zero) {
+ if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
+ }
+ } else if (maybe_rhs_val) |rhs_val| {
+ if (allow_div_zero) {
+ if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
+ } else {
+ try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
+ if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
+ }
+ }
+
+ if (block.wantSafety()) {
+ try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
+ try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
+ }
+
+ return block.addBinOp(airTag(block, is_int, .div_ceil, .div_ceil_optimized), casted_lhs, casted_rhs);
+}
+
fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
@@ -18051,7 +18117,7 @@ fn analyzeRet(
fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
// extend this swich as additional operators are implemented
return switch (tag) {
- .add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .mod, .rem, .mod_rem => true,
+ .add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .div_ceil, .mod, .rem, .mod_rem => true,
else => false,
};
}
diff --git a/src/Sema/arith.zig b/src/Sema/arith.zig
index cf1c699e4a5682c6cd1d3669fe17aebad97d4b89..3759b65971ea88381e5cd98c2d3398408caf553d 100644
--- a/src/Sema/arith.zig
+++ b/src/Sema/arith.zig
@@ -768,7 +768,7 @@ fn mulSatScalar(
}
}
-pub const DivOp = enum { div, div_trunc, div_floor, div_exact };
+pub const DivOp = enum { div, div_trunc, div_floor, div_ceil, div_exact };
/// Applies the `/` operator to comptime-known values.
/// `lhs_val` and `rhs_val` are fully-resolved values of type `ty`.
@@ -843,6 +843,11 @@ fn divScalar(
if (res.overflow) return sema.failWithIntegerOverflow(block, src, ty, res.val, vec_idx);
return res.val;
},
+ .div_ceil => {
+ const res = try intDivCeil(sema, lhs_val, rhs_val, ty);
+ if (res.overflow) return sema.failWithIntegerOverflow(block, src, ty, res.val, vec_idx);
+ return res.val;
+ },
.div_exact => switch (try intDivExact(sema, lhs_val, rhs_val, ty)) {
.remainder => return sema.fail(block, src, "exact division produced remainder", .{}),
.overflow => |val| return sema.failWithIntegerOverflow(block, src, ty, val, vec_idx),
@@ -851,7 +856,7 @@ fn divScalar(
}
} else {
const allow_div_zero = switch (op) {
- .div, .div_trunc, .div_floor => ty.toIntern() != .comptime_float_type and block.float_mode == .strict,
+ .div, .div_trunc, .div_floor, .div_ceil => ty.toIntern() != .comptime_float_type and block.float_mode == .strict,
.div_exact => false,
};
if (!allow_div_zero) {
@@ -871,6 +876,7 @@ fn divScalar(
.div => return floatDiv(sema, lhs_val, rhs_val, ty),
.div_trunc => return floatDivTrunc(sema, lhs_val, rhs_val, ty),
.div_floor => return floatDivFloor(sema, lhs_val, rhs_val, ty),
+ .div_ceil => return floatDivCeil(sema, lhs_val, rhs_val, ty),
.div_exact => {
if (!floatDivIsExact(sema, lhs_val, rhs_val, ty)) {
return sema.fail(block, src, "exact division produced remainder", .{});
@@ -1755,6 +1761,49 @@ fn intDivFloorInner(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !Value {
}
return pt.intValue_big(ty, result_q.toConst());
}
+fn intDivCeil(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !struct { overflow: bool, val: Value } {
+ const result = intDivCeilInner(sema, lhs, rhs, ty) catch |err| switch (err) {
+ error.Overflow => {
+ const result = intDivCeilInner(sema, lhs, rhs, .comptime_int) catch |err1| switch (err1) {
+ error.Overflow => unreachable,
+ else => |e| return e,
+ };
+ return .{ .overflow = true, .val = result };
+ },
+ else => |e| return e,
+ };
+ return .{ .overflow = false, .val = result };
+}
+fn intDivCeilInner(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !Value {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ var lhs_space: Value.BigIntSpace = undefined;
+ var rhs_space: Value.BigIntSpace = undefined;
+ const lhs_bigint = lhs.toBigInt(&lhs_space, zcu);
+ const rhs_bigint = rhs.toBigInt(&rhs_space, zcu);
+ const limbs_q = try sema.arena.alloc(
+ std.math.big.Limb,
+ lhs_bigint.limbs.len,
+ );
+ const limbs_r = try sema.arena.alloc(
+ std.math.big.Limb,
+ rhs_bigint.limbs.len,
+ );
+ const limbs_buf = try sema.arena.alloc(
+ std.math.big.Limb,
+ std.math.big.int.calcDivLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
+ );
+ var result_q: BigIntMutable = .{ .limbs = limbs_q, .positive = undefined, .len = undefined };
+ var result_r: BigIntMutable = .{ .limbs = limbs_r, .positive = undefined, .len = undefined };
+ result_q.divCeil(&result_r, lhs_bigint, rhs_bigint, limbs_buf);
+ if (ty.toIntern() != .comptime_int_type) {
+ const info = ty.intInfo(zcu);
+ if (!result_q.toConst().fitsInTwosComp(info.signedness, info.bits)) {
+ return error.Overflow;
+ }
+ }
+ return pt.intValue_big(ty, result_q.toConst());
+}
fn intMod(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !Value {
const pt = sema.pt;
const zcu = pt.zcu;
@@ -2140,6 +2189,23 @@ fn floatDivFloor(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !Value {
.storage = storage,
} }));
}
+fn floatDivCeil(sema: *Sema, lhs: Value, rhs: Value, ty: Type) !Value {
+ const pt = sema.pt;
+ const zcu = pt.zcu;
+ const target = zcu.getTarget();
+ const storage: InternPool.Key.Float.Storage = switch (ty.floatBits(target)) { // TODO
+ 16 => .{ .f16 = @ceil(lhs.toFloat(f16, zcu) / rhs.toFloat(f16, zcu)) },
+ 32 => .{ .f32 = @ceil(lhs.toFloat(f32, zcu) / rhs.toFloat(f32, zcu)) },
+ 64 => .{ .f64 = @ceil(lhs.toFloat(f64, zcu) / rhs.toFloat(f64, zcu)) },
+ 80 => .{ .f80 = @ceil(lhs.toFloat(f80, zcu) / rhs.toFloat(f80, zcu)) },
+ 128 => .{ .f128 = @ceil(lhs.toFloat(f128, zcu) / rhs.toFloat(f128, zcu)) },
+ else => unreachable,
+ };
+ return .fromInterned(try pt.intern(.{ .float = .{
+ .ty = ty.toIntern(),
+ .storage = storage,
+ } }));
+}
fn floatDivIsExact(sema: *Sema, lhs: Value, rhs: Value, ty: Type) bool {
const zcu = sema.pt.zcu;
const target = zcu.getTarget();
diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig
index ba8cde4e9c45bbdca010d1d87eb1e8a2b3ef2f52..13a3823d86b13c5e8ca56bac16ec961962dafbff 100644
--- a/src/codegen/aarch64/Select.zig
+++ b/src/codegen/aarch64/Select.zig
@@ -175,6 +175,8 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void {
.div_trunc_optimized,
.div_floor,
.div_floor_optimized,
+ .div_ceil,
+ .div_ceil_optimized,
.div_exact,
.div_exact_optimized,
.rem,
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index b814fe55c16b3c96ab7f6caee7b4e1119ae73257..3cf7d9c2fbc2921c3f7745c8f020520f464e380e 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -2675,6 +2675,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) Error!void {
try airBinBuiltinCall(f, inst, "fmod", .none);
},
.div_floor => try airBinBuiltinCall(f, inst, "div_floor", .none),
+ .div_ceil => try airBinBuiltinCall(f, inst, "div_ceil", .none),
.mod => try airBinBuiltinCall(f, inst, "mod", .none),
.abs => try airUnBuiltinCall(f, inst, air_datas[@intFromEnum(inst)].ty_op.operand, "abs", .none),
@@ -2856,6 +2857,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) Error!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
diff --git a/src/codegen/llvm/FuncGen.zig b/src/codegen/llvm/FuncGen.zig
index bad4b1461b1c01274d481c984a752bb6704d4615..cac82b6de31150a262e32b0b5c0e0c142270d416 100644
--- a/src/codegen/llvm/FuncGen.zig
+++ b/src/codegen/llvm/FuncGen.zig
@@ -383,6 +383,7 @@ fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.Cov
.div_float => try self.airDivFloat(inst, .normal),
.div_trunc => try self.airDivTrunc(inst, .normal),
.div_floor => try self.airDivFloor(inst, .normal),
+ .div_ceil => try self.airDivCeil(inst, .normal),
.div_exact => try self.airDivExact(inst, .normal),
.rem => try self.airRem(inst, .normal),
.mod => try self.airMod(inst, .normal),
@@ -400,6 +401,7 @@ fn genBody(self: *FuncGen, body: []const Air.Inst.Index, coverage_point: Air.Cov
.div_float_optimized => try self.airDivFloat(inst, .fast),
.div_trunc_optimized => try self.airDivTrunc(inst, .fast),
.div_floor_optimized => try self.airDivFloor(inst, .fast),
+ .div_ceil_optimized => try self.airDivCeil(inst, .fast),
.div_exact_optimized => try self.airDivExact(inst, .fast),
.rem_optimized => try self.airRem(inst, .fast),
.mod_optimized => try self.airMod(inst, .fast),
@@ -3578,6 +3580,78 @@ fn airDivFloor(self: *FuncGen, inst: Air.Inst.Index, fast: Builder.FastMathKind)
return self.wip.bin(.udiv, lhs, rhs, "");
}
+fn airDivCeil(self: *FuncGen, inst: Air.Inst.Index, fast: Builder.FastMathKind) Allocator.Error!Builder.Value {
+ const o = self.object;
+ const zcu = o.zcu;
+ const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
+ const lhs = try self.resolveInst(bin_op.lhs);
+ const rhs = try self.resolveInst(bin_op.rhs);
+ const inst_ty = self.typeOfIndex(inst);
+ const scalar_ty = inst_ty.scalarType(zcu);
+
+ if (scalar_ty.isRuntimeFloat()) {
+ const result = try self.buildFloatOp(.div, fast, inst_ty, 2, .{ lhs, rhs });
+ return self.buildFloatOp(.ceil, fast, inst_ty, 1, .{result});
+ }
+ if (scalar_ty.isSignedInt(zcu)) {
+ const scalar_llvm_ty = try o.lowerType(scalar_ty, .by_value);
+ const inst_llvm_ty = try o.lowerType(inst_ty, .by_value);
+
+ const ExpectedContents = [std.math.big.int.calcTwosCompLimbCount(256)]std.math.big.Limb;
+ var bfa_buf: ExpectedContents = undefined;
+ var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&bfa_buf), self.gpa);
+ const allocator = bfa.allocator();
+
+ const scalar_bits = scalar_ty.intInfo(zcu).bits;
+ var smin_big_int: std.math.big.int.Mutable = .{
+ .limbs = try allocator.alloc(
+ std.math.big.Limb,
+ std.math.big.int.calcTwosCompLimbCount(scalar_bits),
+ ),
+ .len = undefined,
+ .positive = undefined,
+ };
+ defer allocator.free(smin_big_int.limbs);
+ smin_big_int.setTwosCompIntLimit(.min, .signed, scalar_bits);
+ const smin = try o.builder.splatValue(inst_llvm_ty, try o.builder.bigIntConst(
+ scalar_llvm_ty,
+ smin_big_int.toConst(),
+ ));
+
+ const zero = try o.builder.splatValue(
+ inst_llvm_ty,
+ try o.builder.intConst(scalar_llvm_ty, 0),
+ );
+
+ const div = try self.wip.bin(.sdiv, lhs, rhs, "divCeil.div");
+ const rem = try self.wip.bin(.srem, lhs, rhs, "divCeil.rem");
+
+ const rhs_sign = try self.wip.bin(.@"and", rhs, smin, "divCeil.rhs_sign");
+ const rem_xor_rhs_sign = try self.wip.bin(.xor, rem, rhs_sign, "divCeil.rem_xor_rhs_sign");
+
+ const need_correction = try self.wip.icmp(.sgt, rem_xor_rhs_sign, zero, "divCeil.need_correction");
+
+ const correction = try self.wip.cast(.zext, need_correction, inst_llvm_ty, "divCeil.correction");
+ return self.wip.bin(.@"add nsw", div, correction, "divCeil");
+ } else {
+ const scalar_llvm_ty = try o.lowerType(scalar_ty, .by_value);
+ const inst_llvm_ty = try o.lowerType(inst_ty, .by_value);
+
+ const zero = try o.builder.splatValue(
+ inst_llvm_ty,
+ try o.builder.intConst(scalar_llvm_ty, 0),
+ );
+
+ const div = try self.wip.bin(.udiv, lhs, rhs, "divCeil.div");
+ const rem = try self.wip.bin(.urem, lhs, rhs, "divCeil.rem");
+
+ const rem_nonzero = try self.wip.icmp(.ne, rem, zero, "divCeil.rem_nonzero");
+ const correction = try self.wip.cast(.zext, rem_nonzero, inst_llvm_ty, "divCeil.correction");
+
+ return self.wip.bin(.@"add nuw", div, correction, "divCeil");
+ }
+}
+
fn airDivExact(self: *FuncGen, inst: Air.Inst.Index, fast: Builder.FastMathKind) Allocator.Error!Builder.Value {
const zcu = self.object.zcu;
const bin_op = self.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig
index 4bd30651254a7582d1d49a82562526a9c3e44735..986e326ec9f62b76aa29c62b99bd08247451b19b 100644
--- a/src/codegen/riscv64/CodeGen.zig
+++ b/src/codegen/riscv64/CodeGen.zig
@@ -1422,6 +1422,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.mod,
.div_float,
.div_floor,
+ .div_ceil,
=> return func.fail("TODO: {s}", .{@tagName(tag)}),
.sqrt,
@@ -1621,6 +1622,7 @@ fn genBody(func: *Func, body: []const Air.Inst.Index) InnerError!void {
.div_trunc_optimized,
.div_floor_optimized,
.div_exact_optimized,
+ .div_ceil_optimized,
.rem_optimized,
.mod_optimized,
.neg_optimized,
diff --git a/src/codegen/sparc64/CodeGen.zig b/src/codegen/sparc64/CodeGen.zig
index f0c2d95f9c424d6c1952266834a05e81e1c18377..2d67e7cc54576394bd88d2dcbacab8330b94b21f 100644
--- a/src/codegen/sparc64/CodeGen.zig
+++ b/src/codegen/sparc64/CodeGen.zig
@@ -523,7 +523,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.mul_with_overflow => try self.airMulWithOverflow(inst),
.shl_with_overflow => try self.airShlWithOverflow(inst),
- .div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
+ .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => try self.airDiv(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
@@ -678,6 +678,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig
index ad4b9e4b6d308c6f645785c570dc7eec5c9fe986..75e92651639207755382f03379d4bd90fec1206b 100644
--- a/src/codegen/wasm/CodeGen.zig
+++ b/src/codegen/wasm/CodeGen.zig
@@ -62,6 +62,8 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
.scalarize_div_trunc_optimized,
.scalarize_div_floor,
.scalarize_div_floor_optimized,
+ .scalarize_div_ceil,
+ .scalarize_div_ceil_optimized,
.scalarize_div_exact,
.scalarize_div_exact_optimized,
.scalarize_rem,
@@ -1340,6 +1342,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.div_exact,
.div_trunc,
.div_floor,
+ .div_ceil,
=> |tag| {
const bin_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].bin_op;
const lhs = try cg.resolveInst(bin_op.lhs);
@@ -1366,6 +1369,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.div_exact => try cg.floatDiv(float_ty, lhs, rhs),
.div_trunc => try cg.floatDivTrunc(float_ty, lhs, rhs),
.div_floor => try cg.floatDivFloor(float_ty, lhs, rhs),
+ .div_ceil => try cg.floatDivCeil(float_ty, lhs, rhs),
else => unreachable,
};
@@ -1384,6 +1388,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.div_exact => try cg.intDiv(int_ty, lhs, rhs),
.div_trunc => try cg.intDiv(int_ty, lhs, rhs),
.div_floor => try cg.intDivFloor(int_ty, lhs, rhs),
+ .div_ceil => try cg.intDivCeil(int_ty, lhs, rhs),
else => unreachable,
};
@@ -1881,6 +1886,7 @@ fn genInst(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void {
.div_float_optimized,
.div_trunc_optimized,
.div_floor_optimized,
+ .div_ceil_optimized,
.div_exact_optimized,
.rem_optimized,
.mod_optimized,
@@ -2799,6 +2805,97 @@ fn intDivFloor(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!W
}
}
+fn intDivCeil(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue {
+ switch (ty.bits) {
+ 0 => unreachable,
+ 1...32 => {
+ var q = try (try cg.intDiv(ty, lhs, rhs)).toLocal(cg, Type.i32);
+ defer q.free(cg);
+
+ const zero: WValue = .{ .imm32 = 0 };
+
+ const r = try cg.intRem(ty, lhs, rhs);
+ var r_nonzero = try (try cg.intCmp(ty, .neq, r, zero)).toLocal(cg, Type.i32);
+ defer r_nonzero.free(cg);
+
+ if (!ty.is_signed) {
+ try cg.emitWValue(q);
+ try cg.emitWValue(r_nonzero);
+ try cg.addTag(.i32_add);
+ return .stack;
+ }
+
+ const sign_xor = try cg.intXor(ty, lhs, rhs);
+ var same_sign = try (try cg.intCmp(ty, .gte, sign_xor, zero)).toLocal(cg, Type.i32);
+ defer same_sign.free(cg);
+
+ try cg.emitWValue(q);
+ const need_adjust = try cg.intAnd(.u32, r_nonzero, same_sign);
+ try cg.emitWValue(need_adjust);
+ try cg.addTag(.i32_add);
+ return .stack;
+ },
+ 33...64 => {
+ var q = try (try cg.intDiv(ty, lhs, rhs)).toLocal(cg, Type.i64);
+ defer q.free(cg);
+
+ const zero: WValue = .{ .imm64 = 0 };
+
+ const r = try cg.intRem(ty, lhs, rhs);
+ var r_nonzero = try (try cg.intCmp(ty, .neq, r, zero)).toLocal(cg, Type.i32);
+ defer r_nonzero.free(cg);
+
+ if (!ty.is_signed) {
+ try cg.emitWValue(q);
+ try cg.emitWValue(r_nonzero);
+ try cg.addTag(.i64_extend_i32_u);
+ try cg.addTag(.i64_add);
+ return .stack;
+ }
+
+ const sign_xor = try cg.intXor(ty, lhs, rhs);
+ var same_sign = try (try cg.intCmp(ty, .gte, sign_xor, zero)).toLocal(cg, Type.i32);
+ defer same_sign.free(cg);
+
+ try cg.emitWValue(q);
+ const need_adjust = try cg.intAnd(.u32, r_nonzero, same_sign);
+ try cg.emitWValue(need_adjust);
+ try cg.addTag(.i64_extend_i32_u);
+ try cg.addTag(.i64_add);
+ return .stack;
+ },
+ else => {
+ var q = try (try cg.intDiv(ty, lhs, rhs)).toLocal(cg, Type.usize);
+ defer q.free(cg);
+
+ const zero = try cg.intZeroValue(ty);
+
+ const r = try cg.intRem(ty, lhs, rhs);
+ var r_nonzero = try (try cg.intCmp(ty, .neq, r, zero)).toLocal(cg, Type.u32);
+ defer r_nonzero.free(cg);
+
+ if (!ty.is_signed) {
+ var adjust_bigint = try (try cg.intCast(ty, .u32, r_nonzero)).toLocal(cg, Type.usize);
+ defer adjust_bigint.free(cg);
+
+ return try cg.intAdd(ty, q, adjust_bigint);
+ }
+
+ const sign_xor = try cg.intXor(ty, lhs, rhs);
+ var same_sign = try (try cg.intCmp(ty, .gte, sign_xor, zero)).toLocal(cg, Type.u32);
+ defer same_sign.free(cg);
+
+ var adjust = try (try cg.intAnd(.u32, r_nonzero, same_sign)).toLocal(cg, Type.u32);
+ defer adjust.free(cg);
+
+ var adjust_bigint = try (try cg.intCast(ty, .u32, adjust)).toLocal(cg, Type.usize);
+ defer adjust_bigint.free(cg);
+
+ return try cg.intAdd(ty, q, adjust_bigint);
+ },
+ }
+}
+
fn intRem(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue {
switch (ty.bits) {
0 => unreachable,
@@ -4265,6 +4362,12 @@ fn floatDivFloor(cg: *CodeGen, ty: FloatType, lhs: WValue, rhs: WValue) InnerErr
return cg.floatFloor(ty, div_result);
}
+// div_ceil(a, b) = ceil(a / b)
+fn floatDivCeil(cg: *CodeGen, ty: FloatType, lhs: WValue, rhs: WValue) InnerError!WValue {
+ const div_result = try cg.floatDiv(ty, lhs, rhs);
+ return cg.floatCeil(ty, div_result);
+}
+
// mod(a, b) = fmod(fmod(a, b) + b, b)
fn floatMod(cg: *CodeGen, ty: FloatType, lhs: WValue, rhs: WValue) InnerError!WValue {
const r = try cg.floatRem(ty, lhs, rhs);
diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig
index 74fb9b4f87725574506a14178d989a66b5ae5731..9975fa4d87e0c248bd67a6bd15a8d9d9b0faed59 100644
--- a/src/codegen/x86_64/CodeGen.zig
+++ b/src/codegen/x86_64/CodeGen.zig
@@ -70,6 +70,9 @@ pub fn legalizeFeatures(_: *const std.Target) *const Air.Legalize.Features {
.expand_sub_safe,
.expand_mul_safe,
+ .expand_div_ceil,
+ .expand_div_ceil_optimized,
+
.expand_packed_load,
.expand_packed_store,
.expand_packed_agg_field_val,
@@ -173873,6 +173876,8 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void {
for (ops) |op| try op.die(cg);
},
+ .div_ceil, .div_ceil_optimized => unreachable,
+
// No soft-float `Legalize` features are enabled, so this instruction never appears.
.legalize_compiler_rt_call => unreachable,
diff --git a/src/print_zir.zig b/src/print_zir.zig
index 64703fbd60052209854fc733570fa008ec5b201e..74aaf0bda3e6406fefa28610ed092dce5bc583c8 100644
--- a/src/print_zir.zig
+++ b/src/print_zir.zig
@@ -392,6 +392,7 @@ const Writer = struct {
.truncate,
.div_exact,
.div_floor,
+ .div_ceil,
.div_trunc,
.mod,
.rem,
diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig
index 9687e3171497df8fb41e9c7fd3277829e81069e2..ce69d80522516c71cc586b84bb3784f0ba9c75e6 100644
--- a/test/behavior/int128.zig
+++ b/test/behavior/int128.zig
@@ -59,6 +59,7 @@ test "int128" {
const a: i128 = -170141183460469231731687303715884105728;
const b: i128 = -0x8000_0000_0000_0000_0000_0000_0000_0000;
try expect(@divFloor(b, 1_000_000) == -170141183460469231731687303715885);
+ try expect(@divCeil(b, 1_000_000) == -170141183460469231731687303715884);
try expect(a == b);
}
diff --git a/test/behavior/math.zig b/test/behavior/math.zig
index 705aae28dbcfe45cadf9421b4ae6268b0f6599a9..22b91be640579e1e8fe460f4652f998c5c1b9b6d 100644
--- a/test/behavior/math.zig
+++ b/test/behavior/math.zig
@@ -488,6 +488,36 @@ fn testIntDivision() !void {
try expect(divFloor(i64, -0x80000000, -2) == 0x40000000);
try expect(divFloor(i64, -0x40000001, 0x40000000) == -2);
+ try expect(divCeil(i32, 5, 3) == 2);
+ try expect(divCeil(i32, -5, 3) == -1);
+ try expect(divCeil(i32, -0x80000000, -2) == 0x40000000);
+ try expect(divCeil(i32, 0, -0x80000000) == 0);
+ try expect(divCeil(i32, -0x40000001, 0x40000000) == -1);
+ try expect(divCeil(i32, -0x80000000, 1) == -0x80000000);
+ try expect(divCeil(i32, 10, 12) == 1);
+ try expect(divCeil(i32, -14, 12) == -1);
+ try expect(divCeil(i32, -2, 12) == 0);
+
+ try expect(divCeil(u32, 5, 3) == 2);
+ try expect(divCeil(u32, 16, 4) == 4);
+ try expect(divCeil(u32, 0, 100) == 0);
+ try expect(divCeil(u32, maxInt(u32) - 1, 100) == 42949673);
+
+ try expect(divCeil(i64, 5, 3) == 2);
+ try expect(divCeil(i64, -5, 3) == -1);
+ try expect(divCeil(i64, -0x80000000, -2) == 0x40000000);
+ try expect(divCeil(i64, 0, -0x80000000) == 0);
+ try expect(divCeil(i64, -0x40000001, 0x40000000) == -1);
+ try expect(divCeil(i64, -0x80000000, 1) == -0x80000000);
+ try expect(divCeil(i64, 10, 12) == 1);
+ try expect(divCeil(i64, -14, 12) == -1);
+ try expect(divCeil(i64, -2, 12) == 0);
+
+ try expect(divCeil(u64, 5, 3) == 2);
+ try expect(divCeil(u64, 16, 4) == 4);
+ try expect(divCeil(u64, 0, 100) == 0);
+ try expect(divCeil(u64, maxInt(u64) - 1, 10000) == 1844674407370956);
+
try expect(divTrunc(i32, 5, 3) == 1);
try expect(divTrunc(i32, -5, 3) == -1);
try expect(divTrunc(i32, 9, -10) == 0);
@@ -531,6 +561,24 @@ fn testIntDivision() !void {
try expect(
1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2,
);
+ try expect(
+ @divFloor(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -3,
+ );
+ try expect(
+ @divFloor(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -3,
+ );
+ try expect(
+ @divFloor(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2,
+ );
+ try expect(
+ @divCeil(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
+ );
+ try expect(
+ @divCeil(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2,
+ );
+ try expect(
+ @divCeil(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 3,
+ );
try expect(
@divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2,
);
@@ -559,6 +607,13 @@ fn testFloatDivision() !void {
try expect(divFloor(f16, -43.0, 12.0) == -4.0);
try expect(divFloor(f64, -90.0, -9.0) == 10.0);
+ try expect(divCeil(f32, 5.0, 3.0) == 2.0);
+ try expect(divCeil(f32, -5.0, 3.0) == -1.0);
+ try expect(divCeil(f32, 56.0, 9.0) == 7.0);
+ try expect(divCeil(f32, 1053.0, -41.0) == -25.0);
+ try expect(divCeil(f16, -43.0, 12.0) == -3.0);
+ try expect(divCeil(f64, -90.0, -9.0) == 10.0);
+
try expect(divTrunc(f32, 5.0, 3.0) == 1.0);
try expect(divTrunc(f32, -5.0, 3.0) == -1.0);
try expect(divTrunc(f32, 9.0, -10.0) == 0.0);
@@ -607,6 +662,8 @@ fn testDivisionFP16() !void {
try expect(divFloor(f16, 5.0, 3.0) == 1.0);
try expect(divFloor(f16, -5.0, 3.0) == -2.0);
+ try expect(divCeil(f16, 5.0, 3.0) == 2.0);
+ try expect(divCeil(f16, -5.0, 3.0) == -1.0);
try expect(divTrunc(f16, 5.0, 3.0) == 1.0);
try expect(divTrunc(f16, -5.0, 3.0) == -1.0);
try expect(divTrunc(f16, 9.0, -10.0) == 0.0);
@@ -622,6 +679,9 @@ fn divExact(comptime T: type, a: T, b: T) T {
fn divFloor(comptime T: type, a: T, b: T) T {
return @divFloor(a, b);
}
+fn divCeil(comptime T: type, a: T, b: T) T {
+ return @divCeil(a, b);
+}
fn divTrunc(comptime T: type, a: T, b: T) T {
return @divTrunc(a, b);
}
@@ -1846,6 +1906,35 @@ test "@divFloor > 128 bits" {
try testDivFloor(i200, maxInt(i200), 2, (1 << 198) - 1);
}
+fn testDivCeil(comptime T: type, numerator: T, denominator: T, expected: T) !void {
+ try expect(@divCeil(numerator, denominator) == expected);
+}
+
+test "@divCeil > 128 bits" {
+ if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+ if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
+
+ try testDivCeil(u140, 0, maxInt(u140), 0);
+ try testDivCeil(u140, maxInt(u140), maxInt(u140), 1);
+ try testDivCeil(u140, maxInt(u140), 2, maxInt(u140) / 2 + 1);
+ try testDivCeil(u140, (1 << 139) + 5, 1 << 70, (1 << 69) + 1);
+ try testDivCeil(u140, (1 << 100) + (1 << 50) + 7, 1 << 50, (1 << 50) + 2);
+ try testDivCeil(u200, 123, 1 << 100, 1);
+ try testDivCeil(u200, 1 << 120, 1 << 60, 1 << 60);
+ try testDivCeil(u200, maxInt(u200), 1 << 100, 1 << 100);
+
+ try testDivCeil(i140, 0, maxInt(i140), 0);
+ try testDivCeil(i140, maxInt(i140), maxInt(i140), 1);
+ try testDivCeil(i140, -((1 << 100) + 1), 1 << 50, -(1 << 50));
+ try testDivCeil(i140, (1 << 100) + 1, -(1 << 50), -(1 << 50));
+ try testDivCeil(i140, -((1 << 100) + 1), -(1 << 50), (1 << 50) + 1);
+ try testDivCeil(i200, -3, 2, -1);
+ try testDivCeil(i200, minInt(i200), 1, minInt(i200));
+ try testDivCeil(i200, minInt(i200), -2, 1 << 198);
+ try testDivCeil(i200, maxInt(i200), 2, 1 << 198);
+}
+
fn testDivTrunc(comptime T: type, numerator: T, denominator: T, expected: T) !void {
try expect(@divTrunc(numerator, denominator) == expected);
}
diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig
index 06d5677fe7e392c5c8c12b50a17adcd30950c675..7d63b136fda14fca1358123c1546820a42f0dcf9 100644
--- a/test/behavior/vector.zig
+++ b/test/behavior/vector.zig
@@ -510,8 +510,37 @@ test "vector division operators" {
inline for (@as([4]T, d2), 0..) |v, i| {
try expect(@divFloor(x[i], y[i]) == v);
}
- const d3 = @divTrunc(x, y);
+ const d3 = @divCeil(x, y);
inline for (@as([4]T, d3), 0..) |v, i| {
+ try expect(@divCeil(x[i], y[i]) == v);
+ }
+ const d4 = @divTrunc(x, y);
+ inline for (@as([4]T, d4), 0..) |v, i| {
+ try expect(@divTrunc(x[i], y[i]) == v);
+ }
+ }
+
+ fn doTheTestDivNoExact(comptime T: type, x: @Vector(4, T), y: @Vector(4, T)) !void {
+ const is_signed_int = switch (@typeInfo(T)) {
+ .int => |info| info.signedness == .signed,
+ else => false,
+ };
+ if (!is_signed_int) {
+ const d0 = x / y;
+ inline for (@as([4]T, d0), 0..) |v, i| {
+ try expect(x[i] / y[i] == v);
+ }
+ }
+ const d2 = @divFloor(x, y);
+ inline for (@as([4]T, d2), 0..) |v, i| {
+ try expect(@divFloor(x[i], y[i]) == v);
+ }
+ const d3 = @divCeil(x, y);
+ inline for (@as([4]T, d3), 0..) |v, i| {
+ try expect(@divCeil(x[i], y[i]) == v);
+ }
+ const d4 = @divTrunc(x, y);
+ inline for (@as([4]T, d4), 0..) |v, i| {
try expect(@divTrunc(x[i], y[i]) == v);
}
}
@@ -566,6 +595,9 @@ test "vector division operators" {
try doTheTestMod(u16, [4]u16{ 1, 2, 4, 8 }, [4]u16{ 1, 1, 2, 4 });
try doTheTestMod(u32, [4]u32{ 1, 2, 4, 8 }, [4]u32{ 1, 1, 2, 4 });
try doTheTestMod(u64, [4]u64{ 1, 2, 4, 8 }, [4]u64{ 1, 1, 2, 4 });
+
+ try doTheTestDivNoExact(u64, [4]u64{ 4, 5, 6, 7 }, [4]u64{ 4, 4, 4, 4 });
+ try doTheTestDivNoExact(i64, [4]i64{ 4, -4, 4, -4 }, [4]i64{ 3, 3, -3, -3 });
}
};
@@ -1318,11 +1350,13 @@ test "zero divisor" {
const v2 = @divExact(zeros, ones);
const v3 = @divTrunc(zeros, ones);
const v4 = @divFloor(zeros, ones);
+ const v5 = @divCeil(zeros, ones);
_ = v1[0];
_ = v2[0];
_ = v3[0];
_ = v4[0];
+ _ = v5[0];
}
test "zero multiplicand" {
diff --git a/test/behavior/x86_64/binary.zig b/test/behavior/x86_64/binary.zig
index 45965adffb800aa0d84bcbc95b579a8e0df2a757..a1e827cbb58411013cd95873ff4f8abf713c4322 100644
--- a/test/behavior/x86_64/binary.zig
+++ b/test/behavior/x86_64/binary.zig
@@ -5279,6 +5279,27 @@ test divFloorOptimized {
try test_div_floor_optimized.testFloatVectors();
}
+inline fn divCeilUnoptimized(comptime Type: type, lhs: Type, rhs: Type) Type {
+ return @divCeil(lhs, rhs);
+}
+test divCeilUnoptimized {
+ const test_div_ceil_unoptimized = binary(divCeilUnoptimized, .{ .compare = .approx_int });
+ try test_div_ceil_unoptimized.testInts();
+ try test_div_ceil_unoptimized.testIntVectors();
+ try test_div_ceil_unoptimized.testFloats();
+ try test_div_ceil_unoptimized.testFloatVectors();
+}
+
+inline fn divCeilOptimized(comptime Type: type, lhs: Type, rhs: Type) Type {
+ @setFloatMode(.optimized);
+ return @divCeil(lhs, select(@abs(rhs) > splat(Type, 0.0), rhs, splat(Type, 1.0)));
+}
+test divCeilOptimized {
+ const test_div_ceil_optimized = binary(divCeilOptimized, .{ .compare = .approx_int });
+ try test_div_ceil_optimized.testFloats();
+ try test_div_ceil_optimized.testFloatVectors();
+}
+
inline fn rem(comptime Type: type, lhs: Type, rhs: Type) Type {
return @rem(lhs, rhs);
}
diff --git a/test/cases/compile_errors/signed_integer_division.zig b/test/cases/compile_errors/signed_integer_division.zig
index 9e55835adfd2a67fdab96114c6c5423902fe5f69..02c386632bab31c2233fb7a4a6e85a7798b0d90b 100644
--- a/test/cases/compile_errors/signed_integer_division.zig
+++ b/test/cases/compile_errors/signed_integer_division.zig
@@ -4,4 +4,4 @@ export fn foo(a: i32, b: i32) i32 {
// error
//
-// :2:14: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, or @divExact
+// :2:14: error: division with 'i32' and 'i32': signed integers must use @divTrunc, @divFloor, @divCeil, or @divExact