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/compiler/Maker/WebServer.zig b/lib/compiler/Maker/WebServer.zig index f77c367f6103724c7990a05c0e3be94a38ecf736..54527c805c35cfc4bdfbeadf27acee68fce8d691 100644 --- a/lib/compiler/Maker/WebServer.zig +++ b/lib/compiler/Maker/WebServer.zig @@ -163,7 +163,7 @@ pub fn updateConfiguration(ws: *WebServer, maker: *Maker) !void { assert(idx == step_names_trailing.len); } - const step_status_bits = try gpa.alloc(u8, std.math.divCeil(usize, all_steps.len, 4) catch unreachable); + const step_status_bits = try gpa.alloc(u8, @divCeil(all_steps.len, 4)); errdefer gpa.free(step_status_bits); @memset(step_status_bits, 0); diff --git a/lib/compiler_rt/limb64.zig b/lib/compiler_rt/limb64.zig index 56e4bfc817373e3ba1e90c3525f83ee6a99fd995..bfe4c441f0525b2e7f5e858e3fa98f81a2e9c865 100644 --- a/lib/compiler_rt/limb64.zig +++ b/lib/compiler_rt/limb64.zig @@ -3,7 +3,6 @@ const testing = std.testing; const assert = std.debug.assert; const maxInt = std.math.maxInt; const minInt = std.math.minInt; -const divCeil = std.math.divCeil; const builtin = @import("builtin"); const compiler_rt = @import("../compiler_rt.zig"); @@ -26,7 +25,7 @@ inline fn limbSet(limbs: []u64, i: usize, value: u64) void { } fn usedLimbCount(bits: u16) u16 { - return divCeil(u16, bits, 64) catch unreachable; + return @divCeil(bits, 64); } fn limbCount(bits: u16) u16 { diff --git a/lib/compiler_rt/udivmodei4.zig b/lib/compiler_rt/udivmodei4.zig index 5aa8421bdb920c33c7d0f3a10c32ee9db6c21da0..41ba32cdbef1532de8b77e0f91db4bbf4b39f29a 100644 --- a/lib/compiler_rt/udivmodei4.zig +++ b/lib/compiler_rt/udivmodei4.zig @@ -8,7 +8,7 @@ const shl = std.math.shl; const compiler_rt = @import("../compiler_rt.zig"); const symbol = @import("../compiler_rt.zig").symbol; -const max_limbs = std.math.divCeil(usize, 65535, 32) catch unreachable; // max supported type is u65535 +const max_limbs = @divCeil(65535, 32); // max supported type is u65535 comptime { symbol(&__udivei4, "__udivei4"); diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index acb23b66c58777bc2a6738081e23fcb52ee3c1d9..d4353db874490765967e47e90aaa923c871f2011 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -52,7 +52,7 @@ var fuzzer: Fuzzer = undefined; var current_test_name: ?[]const u8 = null; fn bitsetUsizes(elems: usize) usize { - return math.divCeil(usize, elems, @bitSizeOf(usize)) catch unreachable; + return @divCeil(elems, @bitSizeOf(usize)); } const Executable = struct { diff --git a/lib/std/Random.zig b/lib/std/Random.zig index 7593e9680f9f714d9853275c115acfdbe95d13fb..4ab514844eb2e4ecf485f33beb2085853732a4ff 100644 --- a/lib/std/Random.zig +++ b/lib/std/Random.zig @@ -126,7 +126,7 @@ pub fn enumValueWithIndex(r: Random, comptime EnumType: type, comptime Index: ty pub fn int(r: Random, comptime T: type) T { const bits = @typeInfo(T).int.bits; const UnsignedT = @Int(.unsigned, bits); - const ceil_bytes = comptime std.math.divCeil(u16, bits, 8) catch unreachable; + const ceil_bytes = @divCeil(bits, 8); const ByteAlignedT = @Int(.unsigned, ceil_bytes * 8); var rand_bytes: [ceil_bytes]u8 = undefined; diff --git a/lib/std/Target.zig b/lib/std/Target.zig index 387f96d04b75c647832e946bfe1d2cacc5c78e15..103d8842de5a2dde6b7e6c9bea0a1f6e47bd555b 100644 --- a/lib/std/Target.zig +++ b/lib/std/Target.zig @@ -1232,7 +1232,7 @@ pub const Cpu = struct { ints: [usize_count]usize, pub const needed_bit_count = 347; - pub const byte_count = (needed_bit_count + 7) / 8; + pub const byte_count = @divCeil(needed_bit_count, 8); pub const usize_count = (byte_count + (@sizeOf(usize) - 1)) / @sizeOf(usize); pub const Index = std.math.Log2Int(@Int(.unsigned, usize_count * @bitSizeOf(usize))); pub const ShiftInt = std.math.Log2Int(usize); diff --git a/lib/std/crypto/aes_gcm.zig b/lib/std/crypto/aes_gcm.zig index 2bc56b8c8ee05c91ef432894030a2c36a84292f9..d4e3e3168571ea55438b295c5be787a7f8e54e0b 100644 --- a/lib/std/crypto/aes_gcm.zig +++ b/lib/std/crypto/aes_gcm.zig @@ -39,7 +39,7 @@ fn AesGcm(comptime Aes: anytype) type { mem.writeInt(u32, j[nonce_length..][0..4], 1, .big); aes.encrypt(&t, &j); - const block_count = (math.divCeil(usize, ad.len, Ghash.block_length) catch unreachable) + (math.divCeil(usize, c.len, Ghash.block_length) catch unreachable) + 1; + const block_count = @divCeil(ad.len, Ghash.block_length) + @divCeil(c.len, Ghash.block_length) + 1; var mac = Ghash.initForBlockCount(&h, block_count); mac.update(ad); mac.pad(); @@ -81,7 +81,7 @@ fn AesGcm(comptime Aes: anytype) type { mem.writeInt(u32, j[nonce_length..][0..4], 1, .big); aes.encrypt(&t, &j); - const block_count = (math.divCeil(usize, ad.len, Ghash.block_length) catch unreachable) + (math.divCeil(usize, c.len, Ghash.block_length) catch unreachable) + 1; + const block_count = @divCeil(ad.len, Ghash.block_length) + @divCeil(c.len, Ghash.block_length) + 1; var mac = Ghash.initForBlockCount(&h, block_count); mac.update(ad); mac.pad(); diff --git a/lib/std/crypto/ascon.zig b/lib/std/crypto/ascon.zig index 3142bc0a8972c394be1e927361f8ad162780b040..9d2ac52e83dc500062c93aa4af56cd9b50f11f03 100644 --- a/lib/std/crypto/ascon.zig +++ b/lib/std/crypto/ascon.zig @@ -198,7 +198,7 @@ pub fn State(comptime endian: std.builtin.Endian) type { /// /// Note: Clears complete words that contain the specified byte range pub fn clear(self: *Self, from: usize, to: usize) void { - @memset(self.st[from / 8 .. (to + 7) / 8], 0); + @memset(self.st[from / 8 .. @divCeil(to, 8)], 0); } /// Clear the entire state, disabling compiler optimizations. diff --git a/lib/std/crypto/ff.zig b/lib/std/crypto/ff.zig index 796a11e435eed7e956311232b523bf49bf301eb0..bf9584dfa3ad033652130705937972171370669c 100644 --- a/lib/std/crypto/ff.zig +++ b/lib/std/crypto/ff.zig @@ -61,14 +61,14 @@ pub fn Uint(comptime max_bits: comptime_int) type { return struct { const Self = @This(); - const max_limbs_count = math.divCeil(usize, max_bits, t_bits) catch unreachable; + const max_limbs_count = @divCeil(max_bits, t_bits); limbs_buffer: [max_limbs_count]Limb, /// The number of active limbs. limbs_len: usize, /// Number of bytes required to serialize an integer. - pub const encoded_bytes = math.divCeil(usize, max_bits, 8) catch unreachable; + pub const encoded_bytes = @divCeil(max_bits, 8); /// Constant slice of active limbs. fn limbsConst(self: *const Self) []const Limb { @@ -847,7 +847,7 @@ pub fn Modulus(comptime max_bits: comptime_int) type { } var e_normalized = Fe{ .v = e.v.normalize() }; var buf_: [Fe.encoded_bytes]u8 = undefined; - var buf = buf_[0 .. math.divCeil(usize, e_normalized.v.limbs_len * t_bits, 8) catch unreachable]; + var buf = buf_[0..@divCeil(e_normalized.v.limbs_len * t_bits, 8)]; e_normalized.toBytes(buf, .little) catch unreachable; const leading = @clz(e_normalized.v.limbsConst()[e_normalized.v.limbs_len - carry_bits]); buf = buf[0 .. buf.len - leading / 8]; diff --git a/lib/std/crypto/pbkdf2.zig b/lib/std/crypto/pbkdf2.zig index 88b6cf690dc40107e615ac253bebf5bfa2b25bb4..5e7f45087f3c8fd703700cf4fba2fa7667c30787 100644 --- a/lib/std/crypto/pbkdf2.zig +++ b/lib/std/crypto/pbkdf2.zig @@ -74,7 +74,7 @@ pub fn pbkdf2(dk: []u8, password: []const u8, salt: []const u8, rounds: u32, com // block // - const blocks_count = @as(u32, @intCast(std.math.divCeil(usize, dk_len, h_len) catch unreachable)); + const blocks_count: u32 = @intCast(@divCeil(dk_len, h_len)); var r = dk_len % h_len; if (r == 0) { r = h_len; diff --git a/lib/std/crypto/sha2.zig b/lib/std/crypto/sha2.zig index 78e0bbd7858ff55dbfb62a9cb4bcdb3c58f1b2e8..3c5773af50fc827bc34cd10af4d90185d86f27d7 100644 --- a/lib/std/crypto/sha2.zig +++ b/lib/std/crypto/sha2.zig @@ -474,7 +474,7 @@ fn Sha2x64(comptime iv: Iv64, digest_bits: comptime_int) type { return struct { const Self = @This(); pub const block_length = 128; - pub const digest_length = std.math.divCeil(comptime_int, digest_bits, 8) catch unreachable; + pub const digest_length = @divCeil(digest_bits, 8); pub const Options = struct {}; s: Iv64, diff --git a/lib/std/crypto/sha3.zig b/lib/std/crypto/sha3.zig index d5e62904703c71ae685fb4b22b2062fe697179ce..6fe661981ab71fa49906f5998400aa5e1250d48c 100644 --- a/lib/std/crypto/sha3.zig +++ b/lib/std/crypto/sha3.zig @@ -58,7 +58,7 @@ pub fn Keccak(comptime f: u11, comptime output_bits: u11, comptime default_delim st: State, /// The output length, in bytes. - pub const digest_length = std.math.divCeil(comptime_int, output_bits, 8) catch unreachable; + pub const digest_length: comptime_int = @divCeil(output_bits, 8); /// The block length, or rate, in bytes. pub const block_length = State.rate; /// The delimiter can be overwritten in the options. @@ -464,7 +464,7 @@ pub const NistLengthEncoding = enum { /// Encode a length according to NIST SP 800-185. pub fn encode(comptime encoding: NistLengthEncoding, len: usize) Length { const len_bits = @bitSizeOf(@TypeOf(len)) - @clz(len) + 3; - const len_bytes = std.math.divCeil(usize, len_bits, 8) catch unreachable; + const len_bytes = @divCeil(len_bits, 8); var res = Length{ .len = len_bytes + 1 }; if (encoding == .right) { diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 819360ec28f296518e7eddc06d6f5f7a97c4c97a..db6060d7e8bc620c9c05b88bead578c2b09f548a 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -349,7 +349,7 @@ pub fn dumpHexFallible(t: Io.Terminal, bytes: []const u8) !void { var chunks = mem.window(u8, bytes, 16, 16); while (chunks.next()) |window| { // 1. Print the address. - const address = (@intFromPtr(bytes.ptr) + 0x10 * (std.math.divCeil(usize, chunks.index orelse bytes.len, 16) catch unreachable)) - 0x10; + const address = (@intFromPtr(bytes.ptr) + 0x10 * @divCeil(chunks.index orelse bytes.len, 16) - 0x10); try t.setColor(.dim); // We print the address in lowercase and the bytes in uppercase hexadecimal to distinguish them more. // Also, make sure all lines are aligned by padding the address. diff --git a/lib/std/enums.zig b/lib/std/enums.zig index 10b85ddeeb570510f3b6c93ada06f9bfdcdaa07f..47e0e4d0bb9266e5cd3d6a5ce5409b276e28527a 100644 --- a/lib/std/enums.zig +++ b/lib/std/enums.zig @@ -1391,7 +1391,7 @@ test "EnumIndexer non-exhaustive" { const max_index: comptime_int = std.math.maxInt(RangedType); const number_zero_tag_index: usize = switch (@typeInfo(BackingInt).int.signedness) { .unsigned => 0, - .signed => std.math.divCeil(comptime_int, max_index, 2) catch unreachable, + .signed => @divCeil(max_index, 2), }; try testing.expectEqual(E, Indexer.Key); diff --git a/lib/std/hash/auto_hash.zig b/lib/std/hash/auto_hash.zig index 2562c5157355aa312b3fdd5a89e652a131035d61..31f565ea93f9440316e11f2656a3d38e0f14475d 100644 --- a/lib/std/hash/auto_hash.zig +++ b/lib/std/hash/auto_hash.zig @@ -99,7 +99,7 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void { } else { // Take only the part containing the key value, the remaining // bytes are undefined and must not be hashed! - const byte_size = comptime std.math.divCeil(comptime_int, @bitSizeOf(Key), 8) catch unreachable; + const byte_size = @divCeil(@bitSizeOf(Key), 8); @call(.always_inline, Hasher.update, .{ hasher, std.mem.asBytes(&key)[0..byte_size] }); } }, diff --git a/lib/std/math.zig b/lib/std/math.zig index ec4f47edc1f0ff887ed728ffb636d363e66dfaad..90ff8a4abdede9f325b77ca143996840f14a3060 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -922,21 +922,10 @@ fn testDivFloor() !void { pub fn divCeil(comptime T: type, numerator: T, denominator: T) !T { @setRuntimeSafety(false); if (denominator == 0) return error.DivisionByZero; - const info = @typeInfo(T); - switch (info) { - .comptime_float, .float => return @ceil(numerator / denominator), - .comptime_int, .int => { - if (numerator < 0 and denominator < 0) { - if (info == .int and numerator == minInt(T) and denominator == -1) - return error.Overflow; - return @divFloor(numerator + 1, denominator) + 1; - } - if (numerator > 0 and denominator > 0) - return @divFloor(numerator - 1, denominator) + 1; - return @divTrunc(numerator, denominator); - }, - else => @compileError("divCeil unsupported on " ++ @typeName(T)), + if (@typeInfo(T) == .int and numerator == minInt(T) and denominator == -1) { + return error.Overflow; } + return @divCeil(numerator, denominator); } test divCeil { diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 75a1eb4c70552e0b7cc3eaf4ef324570df1a1387..5f33b91b52a95b0e699b5db8e247c9d1390083d1 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -122,7 +122,7 @@ pub fn calcNonZeroTwosCompLimbCount(bit_count: usize) usize { /// Special cases `bit_count == 0` to return 1. Zero-bit integers can only store the value zero /// and this big integer implementation stores zero using one limb. pub fn calcTwosCompLimbCount(bit_count: usize) usize { - return @max(std.math.divCeil(usize, bit_count, @bitSizeOf(Limb)) catch unreachable, 1); + return @max(@divCeil(bit_count, @bitSizeOf(Limb)), 1); } /// a + b * c + *carry, sets carry to the overflow bits @@ -1221,6 +1221,62 @@ 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 { + // Same sign. + // 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 + // + // This works for both positive and negative b because b keeps its sign. + q.addScalar(q.toConst(), 1); + r.sub(r.toConst(), y.toConst()); + } + } + /// q = a / b (rem r) /// /// a / b are truncated (rounded towards -inf). @@ -3314,6 +3370,25 @@ pub const Managed = struct { r.setMetadata(mr.positive, mr.len); } + /// q = a / b (rem r) + /// + /// a / b are ceiled (rounded towards positive infinity). + /// + /// Returns an error if memory could not be allocated. + pub fn divCeil(q: *Managed, r: *Managed, a: *const Managed, b: *const Managed) !void { + const q_alias = limbsAliasDistinct(q, a) or limbsAliasDistinct(q, b); + const r_alias = limbsAliasDistinct(r, a) or limbsAliasDistinct(r, b); + try q.ensureAliasAwareCapacity(a.len(), q_alias); + try r.ensureAliasAwareCapacity(b.len(), r_alias); + var mq = q.toMutable(); + var mr = r.toMutable(); + const limbs_buffer = try q.allocator.alloc(Limb, calcDivLimbsBufferLen(a.len(), b.len())); + defer q.allocator.free(limbs_buffer); + mq.divCeil(&mr, a.toConst(), b.toConst(), limbs_buffer); + q.setMetadata(mq.positive, mq.len); + r.setMetadata(mr.positive, mr.len); + } + /// q = a / b (rem r) /// /// a / b are truncated (rounded towards -inf). diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 0bc1acd87670ae03cd3d2d957dc6e696dde31a19..485ae4918cce7545119b5b014f7c8133656aaf0b 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -2127,6 +2127,44 @@ test "div floor positive close to zero" { try testing.expectEqual(10, try r.toInt(i32)); } +fn testDivCeil(comptime T: type, u: T, v: T, eq: T, er: T) !void { + var a = try Managed.initSet(testing.allocator, u); + defer a.deinit(); + var b = try Managed.initSet(testing.allocator, v); + defer b.deinit(); + + var q = try Managed.init(testing.allocator); + defer q.deinit(); + var r = try Managed.init(testing.allocator); + defer r.deinit(); + + try Managed.divCeil(&q, &r, &a, &b); + + try testing.expectEqual(eq, try q.toInt(T)); + try testing.expectEqual(er, try r.toInt(T)); +} + +test "div ceil small" { + try testDivCeil(i32, 5, 3, 2, -1); + try testDivCeil(i32, -5, 3, -1, -2); + try testDivCeil(i32, 5, -3, -1, 2); + try testDivCeil(i32, -5, -3, 2, 1); + try testDivCeil(i32, -0x80000000, 1, -0x80000000, 0); +} + +test "div ceil multi-limb" { + { + const a = (@as(i128, 1) << 100) + 3; + const b: i128 = 4; + try testDivCeil(i128, a, b, (1 << 98) + 1, -1); + } + { + const a = -((@as(i128, 1) << 100) + 3); + const b: i128 = 4; + try testDivCeil(i128, a, b, -(1 << 98), -3); + } +} + test "div multi-multi with rem" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 2e0f3cb0ccee44adabf55ff740bd247ad762a5a6..d2c98e4c58e11a803db66a3da86d46dfb7e40bee 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1937,7 +1937,7 @@ fn readPackedIntLittle(comptime T: type, bytes: []const u8, bit_offset: usize) T const bit_count = @as(usize, @bitSizeOf(T)); const bit_shift = @as(u3, @intCast(bit_offset % 8)); - const load_size = (bit_count + 7) / 8; + const load_size = @divCeil(bit_count, 8); const load_tail_bits = @as(u3, @intCast((load_size * 8) - bit_count)); const LoadInt = @Int(.unsigned, load_size * 8); @@ -1964,9 +1964,9 @@ fn readPackedIntBig(comptime T: type, bytes: []const u8, bit_offset: usize) T { const bit_count = @as(usize, @bitSizeOf(T)); const bit_shift = @as(u3, @intCast(bit_offset % 8)); - const byte_count = (@as(usize, bit_shift) + bit_count + 7) / 8; + const byte_count = @divCeil(@as(usize, bit_shift) + bit_count, 8); - const load_size = (bit_count + 7) / 8; + const load_size = @divCeil(bit_count, 8); const load_tail_bits = @as(u3, @intCast((load_size * 8) - bit_count)); const LoadInt = @Int(.unsigned, load_size * 8); diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 4adf7d0232804afe4738add9bf7a5307e2f1cdd3..46aa2fea1231792ad4c8ec64c6f167c9340a0fdd 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, @@ -4895,7 +4896,7 @@ fn structDeclInner( const field_default_body_lens = try scratch.addOptionalSlice(scan_result.any_field_values, scan_result.fields_len); const field_comptime_bits = try scratch.addOptionalSlice( scan_result.any_comptime_fields, - std.math.divCeil(u32, scan_result.fields_len, 32) catch unreachable, + @divCeil(scan_result.fields_len, 32), ); if (field_comptime_bits) |bits| @memset(bits.get(astgen), 0); @@ -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..6e14e5ee9096b1e6ef9f190d6cb5dfd7e23de5a0 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, @@ -5272,7 +5279,7 @@ pub fn getStructDecl(zir: *const Zir, struct_decl: Inst.Index) UnwrappedStructDe break :lens @ptrCast(lens); } else null; const field_comptime_bits: ?[]const u32 = if (small.any_comptime_fields) bits: { - const bits_len = std.math.divCeil(u32, fields_len, 32) catch unreachable; + const bits_len = @divCeil(fields_len, 32); const bits = zir.extra[extra_index..][0..bits_len]; extra_index += bits_len; break :bits bits; diff --git a/lib/zig.h b/lib/zig.h index db94ac20445b942586741451f4e02bc7c579aae3..fbc924ca334e99eb12d2f37e3ebffa970ced7b9d 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..cdae23d2f5cf1b4ff470ee5f4bb0584b07e26a52 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. @@ -1510,7 +1517,7 @@ pub const ShuffleTwoMask = enum(u32) { /// Trailing: /// 0. `Inst.Ref` for every outputs_len /// 1. `Inst.Ref` for every inputs_len -/// 2. A number of u32 elements follow according to the equation `(source_len + 3) / 4`. +/// 2. A number of u32 elements follow according to the equation `@divCeil(source_len, 4)`. /// Memory starting at this position is reinterpreted as the source bytes. /// 3. for every outputs_len /// - constraint: memory at this position is reinterpreted as a null @@ -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, @@ -2214,7 +2225,7 @@ pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch { } const pl_op = inst.data.pl_op; const extra = air.extraData(SwitchBr, pl_op.payload); - const hint_bag_count = std.math.divCeil(usize, extra.data.cases_len + 1, 10) catch unreachable; + const hint_bag_count = @divCeil(extra.data.cases_len + 1, 10); return .{ .air = air, .operand = pl_op.operand, @@ -2383,7 +2394,7 @@ pub const UnwrappedAsm = struct { const name = std.mem.sliceTo(constraint_name[constraint.len + 1 ..], 0); // This equation accounts for the fact that even if we have exactly 4 bytes // for the string, we still use the next u32 for the null terminator. - const next_offset = std.math.divCeil(usize, constraint.len + 1 + name.len + 1, @sizeOf(u32)) catch unreachable; + const next_offset = @divCeil(constraint.len + 1 + name.len + 1, @sizeOf(u32)); self.constraint_names = self.constraint_names[next_offset..]; return .{ diff --git a/src/Air/Legalize.zig b/src/Air/Legalize.zig index 65c4a6afe023dae9ee7a03a08c77d585e9e1bd2e..81df10c169d0f358adc769d0681ce27bf45e6484 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| { @@ -709,7 +746,7 @@ fn legalizeBody(l: *Legalize, body_start: usize, body_len: usize) Error!void { .switch_br, .loop_switch_br => { const pl_op = l.air_instructions.items(.data)[@intFromEnum(inst)].pl_op; const extra = l.extraData(Air.SwitchBr, pl_op.payload); - const hint_bag_count = std.math.divCeil(usize, extra.data.cases_len + 1, 10) catch unreachable; + const hint_bag_count = @divCeil(extra.data.cases_len + 1, 10); var extra_index = extra.end + hint_bag_count; for (0..extra.data.cases_len) |_| { const case_extra = l.extraData(Air.SwitchBr.Case, extra_index); @@ -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/InternPool.zig b/src/InternPool.zig index 792b7636a46f0cf40686efe14bc490ef7d68f1f9..eae7f32c7f17782b694a21d06c52bfe456049dcf 100644 --- a/src/InternPool.zig +++ b/src/InternPool.zig @@ -3581,11 +3581,11 @@ pub fn loadStructType(ip: *const InternPool, index: Index) LoadedStructType { .start = extra_index, .len = extra.data.fields_len, } else .empty; - extra_index += std.math.divCeil(u32, field_aligns.len, 4) catch unreachable; + extra_index += @divCeil(field_aligns.len, 4); const field_is_comptime_bits: LoadedStructType.ComptimeBits = if (extra.data.flags.any_comptime_fields) .{ .tid = unwrapped_index.tid, .start = extra_index, - .len = std.math.divCeil(u32, extra.data.fields_len, 32) catch unreachable, + .len = @divCeil(extra.data.fields_len, 32), } else .empty; extra_index += field_is_comptime_bits.len; const field_runtime_order: LoadedStructType.RuntimeOrder.Slice = if (extra.data.flags.layout == .auto) .{ @@ -3737,7 +3737,7 @@ pub fn loadUnionType(ip: *const InternPool, index: Index) LoadedUnionType { .start = extra_index, .len = extra.data.fields_len, } else .empty; - extra_index += std.math.divCeil(u32, field_aligns.len, 4) catch unreachable; + extra_index += @divCeil(field_aligns.len, 4); return .{ .zir_index = extra.data.zir_index, diff --git a/src/Sema.zig b/src/Sema.zig index 0e58f0c819bf00d512bd58aa7f346e634bdb0368..dfd37d70ec9787b7d8ded314ea5314b1e8513b64 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), @@ -10304,7 +10305,7 @@ fn finishSwitchBr( fn ensureUnusedCapacity(hints: *@This(), gpa_inner: Allocator, additional_count: u32) Allocator.Error!void { const unused_hints = hints.bags.capacity * hints_per_bag - hints.count; if (unused_hints >= additional_count) return; - const bags_required = std.math.divCeil(u32, hints.count + additional_count, hints_per_bag) catch unreachable; + const bags_required = @divCeil(hints.count + additional_count, hints_per_bag); return hints.bags.ensureUnusedCapacity(gpa_inner, bags_required); } fn appendAssumeCapacity(hints: *@This(), hint: std.lang.BranchHint) void { @@ -10320,7 +10321,7 @@ fn finishSwitchBr( } }; var branch_hints: BranchHints = hints: { - const num_bags = std.math.divCeil(u32, estimated_cases_len, BranchHints.hints_per_bag) catch unreachable; + const num_bags = @divCeil(estimated_cases_len, BranchHints.hints_per_bag); break :hints .{ .bags = try .initCapacity(gpa, num_bags), .count = 0 }; }; defer branch_hints.bags.deinit(gpa); @@ -12211,7 +12212,7 @@ fn analyzeSwitchPayloadCaptureTaggedUnion( { // All branch hints are `.none`, so just add zero elems. comptime assert(@intFromEnum(std.lang.BranchHint.none) == 0); - const need_elems = std.math.divCeil(usize, field_indices.len + 1, 10) catch unreachable; + const need_elems = @divCeil(field_indices.len + 1, 10); try cases_extra.appendNTimes(gpa, 0, need_elems); } @@ -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, }; } @@ -18632,7 +18698,7 @@ fn finishStructInit( return sema.addConstantMaybeRef(sema.resolveValue(final_val_ref).?, is_ref); }, .@"packed" => { - const buf = try sema.arena.alloc(u8, @intCast((struct_ty.bitSize(zcu) + 7) / 8)); + const buf = try sema.arena.alloc(u8, @intCast(@divCeil(struct_ty.bitSize(zcu), 8))); @memset(buf, 0); var bit_offset: u16 = 0; for (field_inits) |field_init| { @@ -29679,7 +29745,7 @@ pub fn bitCastVal( if (val.isUndef(zcu)) { return pt.undefValue(dest_ty); } else { - const buf = try sema.arena.alloc(u8, @intCast((bit_size + 7) / 8)); + const buf = try sema.arena.alloc(u8, @intCast(@divCeil(bit_size, 8))); @memset(buf, 0); val.writeToPackedMemory(zcu, buf, 0); return .readFromPackedMemory(dest_ty, pt, buf, 0); diff --git a/src/Sema/arith.zig b/src/Sema/arith.zig index cf1c699e4a5682c6cd1d3669fe17aebad97d4b89..570cd3e533c5ffedae974f4198aedeee1e2268d7 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)) { + 16 => .{ .f16 = @divCeil(lhs.toFloat(f16, zcu), rhs.toFloat(f16, zcu)) }, + 32 => .{ .f32 = @divCeil(lhs.toFloat(f32, zcu), rhs.toFloat(f32, zcu)) }, + 64 => .{ .f64 = @divCeil(lhs.toFloat(f64, zcu), rhs.toFloat(f64, zcu)) }, + 80 => .{ .f80 = @divCeil(lhs.toFloat(f80, zcu), rhs.toFloat(f80, zcu)) }, + 128 => .{ .f128 = @divCeil(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(); @@ -2238,7 +2304,7 @@ fn intValueAa(sema: *Sema, ty: Type) !Value { if (ty.toIntern() == .u0_type) return pt.intValue(ty, 0); const info = ty.intInfo(zcu); - const buf = try sema.arena.alloc(u8, (info.bits + 7) / 8); + const buf = try sema.arena.alloc(u8, @divCeil(info.bits, 8)); @memset(buf, 0xAA); const limbs = try sema.arena.alloc( diff --git a/src/Type.zig b/src/Type.zig index fc867194e3f3ebe2a3a772946a0f57d90d87dbe9..6372c1e157172f6a7b4ee97f3d441af76e9a500b 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -968,7 +968,7 @@ pub fn abiAlignment(ty: Type, zcu: *const Zcu) Alignment { if (vector_type.len > 256 and target.cpu.has(.x86, .avx512f)) return .@"64"; if (vector_type.len > 128 and target.cpu.has(.x86, .avx)) return .@"32"; if (vector_type.len > 64) return .@"16"; - const bytes = std.math.divCeil(u32, vector_type.len, 8) catch unreachable; + const bytes = @divCeil(vector_type.len, 8); return .fromByteUnits(std.math.ceilPowerOfTwoAssert(u32, bytes)); } const elem_bytes: u32 = @intCast(Type.fromInterned(vector_type.child).abiSize(zcu)); @@ -1111,10 +1111,10 @@ pub fn abiSize(ty: Type, zcu: *const Zcu) u64 { .vector_type => |vec| { const elem_ty: Type = .fromInterned(vec.child); const bytes = switch (zcu.comp.getZigBackend()) { - else => std.math.divCeil(u64, vec.len * elem_ty.bitSize(zcu), 8) catch unreachable, + else => @divCeil(vec.len * elem_ty.bitSize(zcu), 8), .stage2_c, .stage2_wasm => vec.len * elem_ty.abiSize(zcu), .stage2_x86_64 => switch (elem_ty.toIntern()) { - .bool_type => std.math.divCeil(u64, vec.len, 8) catch unreachable, + .bool_type => @divCeil(vec.len, 8), else => vec.len * elem_ty.abiSize(zcu), }, }; diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index ba8cde4e9c45bbdca010d1d87eb1e8a2b3ef2f52..b74c7fe9ec580071a887498cd8ab0ef04e287257 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, @@ -403,11 +405,11 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { .live_registers = undefined, .repeat_list = undefined, }); - try isel.dom.appendNTimes(gpa, 0, std.math.divCeil(usize, isel.dom_len, @bitSizeOf(DomInt)) catch unreachable); + try isel.dom.appendNTimes(gpa, 0, @divCeil(isel.dom_len, @bitSizeOf(DomInt))); try isel.analyze(air_body_block.body); for ( isel.dom.items[initial_dom_start..].ptr, - isel.dom.items[isel.dom_start..][0 .. std.math.divCeil(usize, initial_dom_len, @bitSizeOf(DomInt)) catch unreachable], + isel.dom.items[isel.dom_start..][0..@divCeil(initial_dom_len, @bitSizeOf(DomInt))], ) |*initial_dom, loop_dom| initial_dom.* |= loop_dom; isel.dom_start = initial_dom_start; isel.dom_len = initial_dom_len; @@ -589,7 +591,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { .live_registers = undefined, .repeat_list = undefined, }); - try isel.dom.appendNTimes(gpa, 0, std.math.divCeil(usize, isel.dom_len, @bitSizeOf(DomInt)) catch unreachable); + try isel.dom.appendNTimes(gpa, 0, @divCeil(isel.dom_len, @bitSizeOf(DomInt))); var cases_it = switch_br.iterateCases(); while (cases_it.next()) |case| try isel.analyze(case.body); @@ -597,7 +599,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { for ( isel.dom.items[initial_dom_start..].ptr, - isel.dom.items[isel.dom_start..][0 .. std.math.divCeil(usize, initial_dom_len, @bitSizeOf(DomInt)) catch unreachable], + isel.dom.items[isel.dom_start..][0..@divCeil(initial_dom_len, @bitSizeOf(DomInt))], ) |*initial_dom, loop_dom| initial_dom.* |= loop_dom; isel.dom_start = initial_dom_start; isel.dom_len = initial_dom_len; @@ -10194,7 +10196,7 @@ pub const Value = struct { 0 => unreachable, 1...64 => unreachable, 65...256 => |bits| if (offset == 0 and size == ty_size) { - const parts_len = std.math.divCeil(u16, bits, 64) catch unreachable; + const parts_len = @divCeil(bits, 64); vi.setParts(isel, @intCast(parts_len)); for (0..parts_len) |part_index| _ = vi.addPart(isel, 8 * part_index, 8); }, @@ -10238,7 +10240,7 @@ pub const Value = struct { const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0; const array_len = array_type.lenIncludingSentinel(); if (array_len > Value.max_parts and - (std.math.divCeil(u64, size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts) + (@divCeil(size, @as(u64, 1) << min_part_log2_stride)) > Value.max_parts) return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)}); const alignment = vi.alignment(isel); const Part = struct { offset: u64, size: u64 }; @@ -10288,7 +10290,7 @@ pub const Value = struct { .anyframe_type => unreachable, .error_union_type => |error_union_type| { const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0; - if ((std.math.divCeil(u64, size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts) + if ((@divCeil(size, @as(u64, 1) << min_part_log2_stride)) > Value.max_parts) return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)}); const alignment = vi.alignment(isel); const payload_ty: ZigType = .fromInterned(error_union_type.payload_type); @@ -10395,7 +10397,7 @@ pub const Value = struct { } const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0; if (loaded_struct.field_types.len > Value.max_parts and - (std.math.divCeil(u64, size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts) + (@divCeil(size, @as(u64, 1) << min_part_log2_stride)) > Value.max_parts) return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)}); const alignment = vi.alignment(isel); const Part = struct { offset: u64, size: u64, signedness: ?std.lang.Signedness, is_vector: bool }; @@ -10456,7 +10458,7 @@ pub const Value = struct { .tuple_type => |tuple_type| { const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0; if (tuple_type.types.len > Value.max_parts and - (std.math.divCeil(u64, size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts) + (@divCeil(size, @as(u64, 1) << min_part_log2_stride)) > Value.max_parts) return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)}); const alignment = vi.alignment(isel); const Part = struct { offset: u64, size: u64, is_vector: bool }; @@ -10511,7 +10513,7 @@ pub const Value = struct { } }, } const min_part_log2_stride: u5 = if (size > 16) 4 else if (size > 8) 3 else 0; - if ((std.math.divCeil(u64, size, @as(u64, 1) << min_part_log2_stride) catch unreachable) > Value.max_parts) + if ((@divCeil(size, @as(u64, 1) << min_part_log2_stride)) > Value.max_parts) return isel.fail("Value.FieldPartIterator.next({f})", .{isel.fmtType(ty)}); const union_layout = ZigType.getUnionLayout(loaded_union, zcu); const alignment = vi.alignment(isel); 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..602277a91188d72994ac83b025ecf5c580f5ce2b 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; @@ -6874,7 +6948,7 @@ const ParamTypeIterator = struct { switch (ip.indexToKey(ty.toIntern())) { .struct_type => { const size = ty.abiSize(zcu); - assert((std.math.divCeil(u64, size, 8) catch unreachable) == types_index); + assert(@divCeil(size, 8) == types_index); if (size % 8 > 0) { it.types_buffer[types_index - 1] = try it.object.builder.intType(@intCast(size % 8 * 8)); @@ -7119,7 +7193,7 @@ fn lowerSystemVFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.E switch (ip.indexToKey(ret_ty.toIntern())) { .struct_type => { const size = ret_ty.abiSize(zcu); - assert((std.math.divCeil(u64, size, 8) catch unreachable) == types_index); + assert(@divCeil(size, 8) == types_index); if (size % 8 > 0) { types_buffer[types_index - 1] = try o.builder.intType(@intCast(size % 8 * 8)); } diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig index 4bd30651254a7582d1d49a82562526a9c3e44735..b11b898556c0de6479e0aeaf2fe25d47d31e8445 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, @@ -2215,8 +2217,7 @@ fn airIntCast(func: *Func, inst: Air.Inst.Index) !void { }; const dst_mcv = if (dst_int_info.bits <= src_storage_bits and - math.divCeil(u16, dst_int_info.bits, 64) catch unreachable == - math.divCeil(u32, src_storage_bits, 64) catch unreachable and + @divCeil(dst_int_info.bits, 64) == @divCeil(src_storage_bits, 64) and func.reuseOperand(inst, ty_op.operand, 0, src_mcv)) src_mcv else dst: { const dst_mcv = try func.allocRegOrMem(dst_ty, inst, true); try func.genCopy(min_ty, dst_mcv, src_mcv); 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/spirv/Assembler.zig b/src/codegen/spirv/Assembler.zig index f18f9028212b4916ed70a89d080e28a3bad75f3f..e748f4789b0325f2a4d0309d0fcad4ea3b66771f 100644 --- a/src/codegen/spirv/Assembler.zig +++ b/src/codegen/spirv/Assembler.zig @@ -375,7 +375,7 @@ fn processGenericInstruction(ass: *Assembler) !?AsmValue { }, .string => |offset| { const text = std.mem.sliceTo(ass.inst.string_bytes.items[offset..], 0); - const size = std.math.divCeil(usize, text.len + 1, @sizeOf(Word)) catch unreachable; + const size = @divCeil(text.len + 1, @sizeOf(Word)); try section.ensureUnusedCapacity(cg.gpa, size); section.writeOperand(spec.LiteralString, text); }, diff --git a/src/codegen/spirv/Section.zig b/src/codegen/spirv/Section.zig index c3194a5456370a24f9b9e850e6f5753c1333f332..db338f77db57347d662a20d4c525f61f1b2c18ab 100644 --- a/src/codegen/spirv/Section.zig +++ b/src/codegen/spirv/Section.zig @@ -232,7 +232,7 @@ fn operandSize(comptime Operand: type, operand: Operand) usize { return switch (Operand) { spec.LiteralSpecConstantOpInteger => unreachable, spec.Id, spec.LiteralInteger, spec.LiteralExtInstInteger => 1, - spec.LiteralString => std.math.divCeil(usize, operand.len + 1, @sizeOf(Word)) catch unreachable, + spec.LiteralString => @divCeil(operand.len + 1, @sizeOf(Word)), spec.LiteralContextDependentNumber => switch (operand) { .int32, .uint32, .float32 => 1, .int64, .uint64, .float64 => 2, diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index ad4b9e4b6d308c6f645785c570dc7eec5c9fe986..6d42d8d1ff69c9f5270106b8c9c311de3f309586 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, @@ -3581,7 +3678,7 @@ fn intWrap(cg: *CodeGen, ty: IntType, operand: WValue) InnerError!WValue { const result = try cg.allocInt(ty); - const used_len = (math.divCeil(u16, ty.bits, 64) catch unreachable) * 8; + const used_len = @divCeil(ty.bits, 64) * 8; if (ty.bits % 64 != 0) { try cg.memcpy(result, operand, .{ .imm32 = used_len - 8 }); @@ -3647,7 +3744,7 @@ fn intMaxValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue { } else { const result = try cg.allocInt(int_ty); const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8); - const used_len = (math.divCeil(u16, int_ty.bits, 64) catch unreachable) * 8; + const used_len = @divCeil(int_ty.bits, 64) * 8; try cg.memset(Type.u8, result, .{ .imm32 = used_len - 8 }, .{ .imm32 = 0xFF }); @@ -3681,7 +3778,7 @@ fn intMinValue(cg: *CodeGen, int_ty: IntType) InnerError!WValue { } else { const result = try cg.allocInt(int_ty); const full_len = @divExact(cg.intBackingBits(int_ty.bits), 8); - const used_len = (math.divCeil(u16, int_ty.bits, 64) catch unreachable) * 8; + const used_len = @divCeil(int_ty.bits, 64) * 8; try cg.memset(Type.u8, result, .{ .imm32 = used_len - 8 }, .{ .imm32 = 0 }); try cg.store(result, .{ .imm64 = ~@as(u64, 0) << @intCast(int_ty.bits - (used_len - 8) * 8 - 1) }, Type.u64, used_len - 8); @@ -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..dbb942eaed207109e57a2f5a39bf7d5d4f21c0d7 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, @@ -174781,7 +174786,7 @@ fn genShiftBinOpMir( try self.spillEflagsIfOccupied(); if (abi_size > 16) { - const limbs_len = std.math.divCeil(u32, abi_size, 8) catch unreachable; + const limbs_len = @divCeil(abi_size, 8); assert(shift_abi_size >= 1 and shift_abi_size <= 2); const rcx_lock: ?RegisterLock = switch (rhs_mcv) { @@ -179593,7 +179598,7 @@ fn genSetReg( if (pack_alias != sign_alias) try cg.asmRegisterRegister(.{ ._dqa, .mov }, pack_alias, sign_alias); try cg.asmRegisterRegister(.{ .p_b, .ackssw }, pack_alias, pack_alias); } - mask_size = std.math.divCeil(u32, mask_size, 2) catch unreachable; + mask_size = @divCeil(mask_size, 2); break :pack_reg pack_reg; }, }; @@ -180157,7 +180162,7 @@ fn airBitCast(self: *CodeGen, inst: Air.Inst.Index) !void { const bit_size = dst_ty.bitSize(zcu); if (abi_size * 8 <= bit_size) break :result dst_mcv; - const dst_limbs_len = std.math.divCeil(u31, @intCast(bit_size), 64) catch unreachable; + const dst_limbs_len: u31 = @intCast(@divCeil(bit_size, 64)); const high_mcv: MCValue = switch (dst_mcv) { .register => |dst_reg| .{ .register = dst_reg }, .register_pair => |dst_regs| .{ .register = dst_regs[1] }, @@ -183550,7 +183555,7 @@ const Temp = struct { const part_ty: Type = if (src_regs.len == 1) src_ty else if (cg.intInfo(src_ty)) |int_info| part_ty: { - assert(src_regs.len == std.math.divCeil(u16, int_info.bits, 64) catch unreachable); + assert(src_regs.len == @divCeil(int_info.bits, 64)); break :part_ty .u64; } else part_ty: switch (ip.indexToKey(src_ty.toIntern())) { else => std.debug.panic("{s}: {f}\n", .{ @src().fn_name, src_ty.fmt(cg.pt) }), @@ -183560,7 +183565,7 @@ const Temp = struct { break :part_ty .usize; }, .array_type => { - assert(src_regs.len - part_index == std.math.divCeil(u32, src_size, 8) catch unreachable); + assert(src_regs.len - part_index == @divCeil(src_size, 8)); break :part_ty try cg.pt.intType(.unsigned, @as(u16, 8) * @min(src_size, 8)); }, .vector_type => |vector_type| switch (@divExact(vector_type.len, src_regs.len)) { @@ -183580,7 +183585,7 @@ const Temp = struct { }, }, .struct_type, .union_type => { - assert(src_regs.len - part_index == std.math.divCeil(u32, src_size, 8) catch unreachable); + assert(src_regs.len - part_index == @divCeil(src_size, 8)); break :part_ty switch (src_size) { 0, 3, 5...7 => unreachable, 1 => .u8, diff --git a/src/codegen/x86_64/Emit.zig b/src/codegen/x86_64/Emit.zig index 1e8e60ffb665fc70709a5291040a32a1bebcb5af..d5ec79ce22a8f650992ae77567b3d9f303dd2b82 100644 --- a/src/codegen/x86_64/Emit.zig +++ b/src/codegen/x86_64/Emit.zig @@ -772,7 +772,7 @@ fn encodeInst(emit: *Emit, lowered_inst: Instruction, reloc_info: []const RelocI const enc_length: u4 = if (is_mem) switch (lowered_inst.ops[op_index].mem.sib.base) { .rip_inst => 4, else => unreachable, - } else @intCast(std.math.divCeil(u7, @intCast(op.immBitSize()), 8) catch unreachable); + } else @intCast(@divCeil(op.immBitSize(), 8)); reloc_offset -= enc_length; if (op_index == reloc.op_index) break :reloc_offset_length .{ reloc_offset, enc_length }; assert(!is_mem); diff --git a/src/link/Dwarf.zig b/src/link/Dwarf.zig index d51c59ed23941283b2de8258db0ad865adf8aa33..b6ff6c754bc97b0efc78522b30dcf302d9ad072d 100644 --- a/src/link/Dwarf.zig +++ b/src/link/Dwarf.zig @@ -2137,7 +2137,7 @@ pub const WipNav = struct { .signed => DW.FORM.sdata, .unsigned => DW.FORM.udata, })); - try wip_nav.debug_info.ensureUnusedCapacity(std.math.divCeil(usize, bits, 7) catch unreachable); + try wip_nav.debug_info.ensureUnusedCapacity(@divCeil(bits, 7)); var bit: usize = 0; var carry: u1 = 1; while (bit < bits) { @@ -2158,7 +2158,7 @@ pub const WipNav = struct { } } else { try diw.writeUleb128(DW.FORM.block); - const bytes = @max(ty.abiSize(zcu), std.math.divCeil(usize, bits, 8) catch unreachable); + const bytes = @max(ty.abiSize(zcu), @divCeil(bits, 8)); try diw.writeUleb128(bytes); try wip_nav.debug_info.ensureUnusedCapacity(@intCast(bytes)); big_int.writeTwosComplement( @@ -4275,7 +4275,7 @@ fn updateConstInner(dwarf: *Dwarf, pt: Zcu.PerThread, debug_const_index: link.Co try wip_nav.abbrevCode(.aggregate_undefined_comptime_value); try wip_nav.refType(.fromInterned(error_union.ty)); var err_buf: [4]u8 = undefined; - const err_bytes = err_buf[0 .. std.math.divCeil(u17, zcu.errorSetBits(), 8) catch unreachable]; + const err_bytes = err_buf[0..@divCeil(zcu.errorSetBits(), 8)]; dwarf.writeInt(err_bytes, switch (error_union.val) { .err_name => |err_name| try pt.getErrorValue(err_name), .payload => 0, diff --git a/src/main.zig b/src/main.zig index 8f569d2a856e039466416c5bd3e810a1a0d15610..91d1a8cee30b57880bfb3092ca05576dd71abe30 100644 --- a/src/main.zig +++ b/src/main.zig @@ -162,8 +162,7 @@ const use_safe_allocator = build_options.debug_gpa or .ReleaseFast, .ReleaseSmall => false, }); -// TODO: The `align(@alignOf(std.heap.SafeAllocator))` can be removed the next time zig1.wasm is updated -var safe_allocator: std.heap.SafeAllocator align(@alignOf(std.heap.SafeAllocator)) = .init(std.heap.page_allocator, .{ +var safe_allocator: std.heap.SafeAllocator = .init(std.heap.page_allocator, .{ .stack_trace_frames = build_options.mem_leak_frames, }); 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/stage1/zig.h b/stage1/zig.h index db94ac20445b942586741451f4e02bc7c579aae3..fbc924ca334e99eb12d2f37e3ebffa970ced7b9d 100644 --- a/stage1/zig.h +++ b/stage1/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/stage1/zig1.wasm b/stage1/zig1.wasm index 1a985c43006c7abfd1e82ead3ac9b656a5b753ab..6a222c0c6138663c1a36630a48e164f351fd17b7 100644 Binary files a/stage1/zig1.wasm and b/stage1/zig1.wasm differ 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