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#}. +

    + +

    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