From 4f4e5d764282db3b1dfd6aa4041d3f5e16e08caa Mon Sep 17 00:00:00 2001 From: riverbl <94326797+riverbl@users.noreply.github.com> Date: Mon, 7 Aug 2023 18:23:29 +0100 Subject: [PATCH 1/5] Add `@divCeil` builtin Includes implementation for comptime evaluation and lowering code for LLVM and C backends. --- lib/std/math.zig | 1 + lib/std/math/big/int.zig | 66 ++++++++++ lib/std/zig/BuiltinFn.zig | 8 ++ lib/zig.h | 33 +++++ src/Air.zig | 11 ++ src/AstGen.zig | 2 + src/Autodoc.zig | 1 + src/Liveness.zig | 4 + src/Liveness/Verify.zig | 2 + src/Sema.zig | 116 +++++++++++++++++- src/Zir.zig | 6 + src/arch/aarch64/CodeGen.zig | 20 +++ src/arch/arm/CodeGen.zig | 20 +++ src/arch/riscv64/CodeGen.zig | 3 +- src/arch/sparc64/CodeGen.zig | 3 +- src/arch/wasm/CodeGen.zig | 7 ++ src/arch/x86_64/CodeGen.zig | 30 +++-- src/codegen/c.zig | 2 + src/codegen/llvm.zig | 52 ++++++++ src/print_air.zig | 2 + src/print_zir.zig | 1 + src/value.zig | 87 +++++++++++++ test/behavior/int128.zig | 1 + test/behavior/int_div.zig | 31 +++++ test/behavior/math.zig | 5 + test/behavior/vector.zig | 8 +- .../signed_integer_division.zig | 2 +- 27 files changed, 508 insertions(+), 16 deletions(-) diff --git a/lib/std/math.zig b/lib/std/math.zig index d223b7948432..9fe80d80354d 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -916,6 +916,7 @@ fn testDivFloor() !void { /// infinity. Returns an error on overflow or when denominator is /// zero. pub fn divCeil(comptime T: type, numerator: T, denominator: T) !T { + // TODO: Change implementation to use @divCeil. @setRuntimeSafety(false); if (denominator == 0) return error.DivisionByZero; const info = @typeInfo(T); diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 20041c5650ea..e7319f04256f 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -1062,6 +1062,72 @@ pub const Mutable = struct { } } + /// q = a / b (rem r) + /// + /// Returns the result of a / b rounded towards positive infinity. + /// q may alias with a or b. + /// + /// Asserts there is enough memory to store q and r. + /// The upper bound for r limb count is `b.limbs.len`. + /// The upper bound for q limb count is given by `a.limbs`. + /// + /// `limbs_buffer` is used for temporary storage. The amount required is given by `calcDivLimbsBufferLen`. + pub fn divCeil( + q: *Mutable, + r: *Mutable, + a: Const, + b: Const, + limbs_buffer: []Limb, + ) void { + const sep = a.limbs.len + 2; + var x = a.toMutable(limbs_buffer[0..sep]); + var y = b.toMutable(limbs_buffer[sep..]); + + // div performs truncating division (@divTrunc) which rounds towards negative + // infinity if the result is positive and towards positive infinity if the result is + // negative. + div(q, r, &x, &y); + + // @rem gives the remainder after @divTrunc, and is defined by: + // x * @divTrunc(x, y) + @rem(x, y) = x + // For all integers x, y with y != 0. + // In the following comments, a, b will be integers with a >= 0, b > 0, and we will take + // modCeil to be the remainder after @divCeil, defined by: + // x * @divCeil(x, y) + modCeil(x, y) = x + // For all integers x, y with y != 0. + + if (a.positive != b.positive or r.eqlZero()) { + // In this case either the result is negative or the remainder is 0. + // If the result is negative then the default truncating division already rounds + // towards positive infinity, so no adjustment is needed. + // If the remainder is 0 then the division is exact and no adjustment is needed. + } else if (a.positive) { + // Both positive. + // We have: + // modCeil(a, b) != 0 + // => @divCeil(a, b) = @divTrunc(a, b) + 1 + // And: + // b * @divTrunc(a, b) + @rem(a, b) = a + // b * @divCeil(a, b) + modCeil(a, b) = a + // => b * @divTrunc(a, b) + b + modCeil(a, b) = a + // => modCeil(a, b) = @rem(a, b) - b + q.addScalar(q.toConst(), 1); + r.sub(r.toConst(), y.toConst()); + } else { + // Both negative. + // We have: + // modCeil(-a, -b) != 0 + // => @divCeil(-a, -b) = @divTrunc(-a, -b) + 1 + // And: + // -b * @divTrunc(-a, -b) + @rem(-a, -b) = -a + // -b * @divCeil(-a, -b) + modCeil(-a, -b) = -a + // => -b * @divTrunc(-a, -b) - b + modCeil(-a, -b) = -a + // => modCeil(-a, -b) = @rem(-a, -b) + b + q.addScalar(q.toConst(), 1); + r.add(r.toConst(), y.toConst().abs()); + } + } + /// q = a / b (rem r) /// /// a / b are truncated (rounded towards -inf). diff --git a/lib/std/zig/BuiltinFn.zig b/lib/std/zig/BuiltinFn.zig index decb3cf7fd8e..a212993d247f 100644 --- a/lib/std/zig/BuiltinFn.zig +++ b/lib/std/zig/BuiltinFn.zig @@ -37,6 +37,7 @@ pub const Tag = enum { c_va_start, div_exact, div_floor, + div_ceil, div_trunc, embed_file, int_from_enum, @@ -419,6 +420,13 @@ pub const list = list: { .param_count = 2, }, }, + .{ + "@divCeil", + .{ + .tag = .div_ceil, + .param_count = 2, + }, + }, .{ "@divTrunc", .{ diff --git a/lib/zig.h b/lib/zig.h index 7a1c69575a24..4be8317a1f08 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -537,6 +537,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, %) \ \ @@ -1480,6 +1489,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) { @@ -2655,6 +2679,11 @@ 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) { + // TODO: Does this need to be implemented? + 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) { @@ -3402,6 +3431,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_float_fn_f##w##_floor(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_libc_name_f##w(ceil)(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 bc3e31dff435..568a46fab6da 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 undefined 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, undefined behavior occurs. /// For integers, overflow is undefined behavior. @@ -1292,6 +1299,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, @@ -1313,6 +1321,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, @@ -1662,6 +1671,8 @@ pub fn mustLower(air: Air, inst: Air.Inst.Index, ip: *const InternPool) bool { .div_trunc_optimized, .div_floor, .div_floor_optimized, + .div_ceil, + .div_ceil_optimized, .div_exact, .div_exact_optimized, .rem, diff --git a/src/AstGen.zig b/src/AstGen.zig index f709751fde3c..f5e0dae146c7 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2719,6 +2719,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, @@ -9277,6 +9278,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/src/Autodoc.zig b/src/Autodoc.zig index a03ed351789d..69bce295a192 100644 --- a/src/Autodoc.zig +++ b/src/Autodoc.zig @@ -1878,6 +1878,7 @@ fn walkInstruction( .has_field, .div_exact, .div_floor, + .div_ceil, .div_trunc, .mod, .rem, diff --git a/src/Liveness.zig b/src/Liveness.zig index 56b70be317bf..9da8f16d32c4 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -249,6 +249,7 @@ pub fn categorizeOperand( .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, .rem, .mod, @@ -276,6 +277,7 @@ pub fn categorizeOperand( .div_float_optimized, .div_trunc_optimized, .div_floor_optimized, + .div_ceil_optimized, .div_exact_optimized, .rem_optimized, .mod_optimized, @@ -905,6 +907,8 @@ fn analyzeInst( .div_trunc_optimized, .div_floor, .div_floor_optimized, + .div_ceil, + .div_ceil_optimized, .div_exact, .div_exact_optimized, .rem, diff --git a/src/Liveness/Verify.zig b/src/Liveness/Verify.zig index 0493c9322b13..1d952162331e 100644 --- a/src/Liveness/Verify.zig +++ b/src/Liveness/Verify.zig @@ -217,6 +217,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/Sema.zig b/src/Sema.zig index 44a04e7d256c..fe5f6b50eea3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -1194,6 +1194,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), @@ -14853,7 +14854,7 @@ fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins return sema.fail( block, src, - "division with '{}' and '{}': signed integers must use @divTrunc, @divFloor, or @divExact", + "division with '{}' and '{}': signed integers must use @divTrunc, @divFloor, @divCeil, or @divExact", .{ lhs_ty.fmt(mod), rhs_ty.fmt(mod) }, ); } @@ -15142,6 +15143,117 @@ 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 mod = sema.mod; + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; + sema.src = src; + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(mod); + const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(mod); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + try sema.checkInvalidPtrArithmetic(block, src, lhs_ty); + + const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; + const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ + .override = &[_]?LazySrcLoc{ 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(mod); + const rhs_scalar_ty = rhs_ty.scalarType(mod); + const scalar_tag = resolved_type.scalarType(mod).zigTypeTag(mod); + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + + try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_ceil); + + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(casted_rhs); + + const runtime_src = rs: { + // For integers: + // If the lhs is zero, then zero is returned regardless of rhs. + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined: + // * if lhs type is signed: + // * if rhs is comptime-known and not -1, result is undefined + // * if rhs is -1 or runtime-known, compile error because there is a + // possible value (-min_int / -1) for which division would be + // illegal behavior. + // * if lhs type is unsigned, undef is returned regardless of rhs. + // TODO: emit runtime safety for division by zero + // + // For floats: + // If the rhs is zero, compile error for division by zero. + // If the rhs is undefined, compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // If the lhs is undefined, result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef(mod)) { + if (try lhs_val.compareAllWithZeroAdvanced(.eq, sema)) { + const scalar_zero = switch (scalar_tag) { + .ComptimeFloat, .Float => try mod.floatValue(resolved_type.scalarType(mod), 0.0), + .ComptimeInt, .Int => try mod.intValue(resolved_type.scalarType(mod), 0), + else => unreachable, + }; + const zero_val = try sema.splat(resolved_type, scalar_zero); + return Air.internedToRef(zero_val.toIntern()); + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef(mod)) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (!(try rhs_val.compareAllWithZeroAdvanced(.neq, sema))) { + return sema.failWithDivideByZero(block, rhs_src); + } + // TODO: if the RHS is one, return the LHS directly + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef(mod)) { + if (lhs_scalar_ty.isSignedInt(mod) and rhs_scalar_ty.isSignedInt(mod)) { + if (maybe_rhs_val) |rhs_val| { + if (try sema.compareAll(rhs_val, .neq, try mod.intValue(resolved_type, -1), resolved_type)) { + return mod.undefRef(resolved_type); + } + } + return sema.failWithUseOfUndef(block, rhs_src); + } + return mod.undefRef(resolved_type); + } + + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return Air.internedToRef((try lhs_val.intDivCeil(rhs_val, resolved_type, sema.arena, mod)).toIntern()); + } else { + return Air.internedToRef((try lhs_val.floatDivCeil(rhs_val, resolved_type, sema.arena, mod)).toIntern()); + } + } else break :rs rhs_src; + } else break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, src, runtime_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 mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node; @@ -19359,7 +19471,7 @@ fn analyzeRet( fn floatOpAllowed(tag: Zir.Inst.Tag) bool { // extend this swich as additional operators are implemented return switch (tag) { - .add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .mod, .rem, .mod_rem => true, + .add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .div_ceil, .mod, .rem, .mod_rem => true, else => false, }; } diff --git a/src/Zir.zig b/src/Zir.zig index 3737467e47be..1ef3c5d7c7b7 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -195,6 +195,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, @@ -1239,6 +1242,7 @@ pub const Inst = struct { .bit_reverse, .div_exact, .div_floor, + .div_ceil, .div_trunc, .mod, .rem, @@ -1534,6 +1538,7 @@ pub const Inst = struct { .bit_reverse, .div_exact, .div_floor, + .div_ceil, .div_trunc, .mod, .rem, @@ -1804,6 +1809,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, diff --git a/src/arch/aarch64/CodeGen.zig b/src/arch/aarch64/CodeGen.zig index ee5e58ae05e9..c49f20fc6e89 100644 --- a/src/arch/aarch64/CodeGen.zig +++ b/src/arch/aarch64/CodeGen.zig @@ -687,6 +687,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .div_float => try self.airBinOp(inst, .div_float), .div_trunc => try self.airBinOp(inst, .div_trunc), .div_floor => try self.airBinOp(inst, .div_floor), + .div_ceil => try self.airBinOp(inst, .div_ceil), .div_exact => try self.airBinOp(inst, .div_exact), .rem => try self.airBinOp(inst, .rem), .mod => try self.airBinOp(inst, .mod), @@ -871,6 +872,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, @@ -2072,6 +2074,22 @@ fn divFloor( } } +fn divCeil( + self: *Self, + lhs_bind: ReadArg.Bind, + rhs_bind: ReadArg.Bind, + lhs_ty: Type, + rhs_ty: Type, + maybe_inst: ?Air.Inst.Index, +) InnerError!MCValue { + _ = maybe_inst; + _ = rhs_ty; + _ = lhs_ty; + _ = rhs_bind; + _ = lhs_bind; + return self.fail("TODO: implement `@divCeil` for {}", .{self.target.cpu.arch}); +} + fn divExact( self: *Self, lhs_bind: ReadArg.Bind, @@ -2449,6 +2467,8 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { .div_floor => try self.divFloor(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), + .div_ceil => try self.divCeil(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), + .div_exact => try self.divExact(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), .rem => try self.rem(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), diff --git a/src/arch/arm/CodeGen.zig b/src/arch/arm/CodeGen.zig index 7227347e1b9e..d25a03e02101 100644 --- a/src/arch/arm/CodeGen.zig +++ b/src/arch/arm/CodeGen.zig @@ -673,6 +673,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void { .div_float => try self.airBinOp(inst, .div_float), .div_trunc => try self.airBinOp(inst, .div_trunc), .div_floor => try self.airBinOp(inst, .div_floor), + .div_ceil => try self.airBinOp(inst, .div_ceil), .div_exact => try self.airBinOp(inst, .div_exact), .rem => try self.airBinOp(inst, .rem), .mod => try self.airBinOp(inst, .mod), @@ -857,6 +858,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, @@ -1519,6 +1521,8 @@ fn airBinOp(self: *Self, inst: Air.Inst.Index, tag: Air.Inst.Tag) !void { .div_floor => try self.divFloor(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), + .div_ceil => try self.divCeil(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), + .div_exact => try self.divExact(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), .rem => try self.rem(lhs_bind, rhs_bind, lhs_ty, rhs_ty, inst), @@ -3578,6 +3582,22 @@ fn divFloor( } } +fn divCeil( + self: *Self, + lhs_bind: ReadArg.Bind, + rhs_bind: ReadArg.Bind, + lhs_ty: Type, + rhs_ty: Type, + maybe_inst: ?Air.Inst.Index, +) InnerError!MCValue { + _ = maybe_inst; + _ = rhs_ty; + _ = lhs_ty; + _ = rhs_bind; + _ = lhs_bind; + return self.fail("TODO: implement `@divCeil` for {}", .{self.target.cpu.arch}); +} + fn divExact( self: *Self, lhs_bind: ReadArg.Bind, diff --git a/src/arch/riscv64/CodeGen.zig b/src/arch/riscv64/CodeGen.zig index 7d3b0cd1a0ac..5f2ffb732924 100644 --- a/src/arch/riscv64/CodeGen.zig +++ b/src/arch/riscv64/CodeGen.zig @@ -534,7 +534,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), @@ -690,6 +690,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/arch/sparc64/CodeGen.zig b/src/arch/sparc64/CodeGen.zig index e7db3be8b020..055340a2c7fa 100644 --- a/src/arch/sparc64/CodeGen.zig +++ b/src/arch/sparc64/CodeGen.zig @@ -555,7 +555,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), @@ -703,6 +703,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/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index da33ae521d49..54649f3215e6 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -1847,6 +1847,7 @@ fn genInst(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { .div_float, .div_exact => func.airDiv(inst), .div_trunc => func.airDivTrunc(inst), .div_floor => func.airDivFloor(inst), + .div_ceil => func.airDivCeil(inst), .bit_and => func.airBinOp(inst, .@"and"), .bit_or => func.airBinOp(inst, .@"or"), .bool_and => func.airBinOp(inst, .@"and"), @@ -2049,6 +2050,7 @@ fn genInst(func: *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, @@ -6810,6 +6812,11 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); } +fn airDivCeil(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { + _ = inst; + return func.fail("TODO: implement `@divCeil` for {}", .{func.target.cpu.arch}); +} + fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WValue { const mod = func.bin_file.base.comp.module.?; const int_bits = ty.intInfo(mod).bits; diff --git a/src/arch/x86_64/CodeGen.zig b/src/arch/x86_64/CodeGen.zig index 55e241cbd4c3..c4b9980514df 100644 --- a/src/arch/x86_64/CodeGen.zig +++ b/src/arch/x86_64/CodeGen.zig @@ -1977,7 +1977,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.airMulDivBinOp(inst), + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => try self.airMulDivBinOp(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -2124,6 +2124,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, @@ -3243,7 +3244,7 @@ fn airMulDivBinOp(self: *Self, inst: Air.Inst.Index) !void { self.activeIntBits(bin_op.rhs), dst_info.bits / 2, ), - .div_trunc, .div_floor, .div_exact, .rem, .mod => dst_info.bits, + .div_trunc, .div_floor, .div_ceil, .div_exact, .rem, .mod => dst_info.bits, }); const src_abi_size: u32 = @intCast(src_ty.abiSize(mod)); @@ -8138,7 +8139,7 @@ fn genMulDivBinOp( if (switch (tag) { else => unreachable, .mul, .mul_wrap => dst_abi_size != src_abi_size and dst_abi_size != src_abi_size * 2, - .div_trunc, .div_floor, .div_exact, .rem, .mod => dst_abi_size != src_abi_size, + .div_trunc, .div_floor, .div_ceil, .div_exact, .rem, .mod => dst_abi_size != src_abi_size, } or src_abi_size > 8) return self.fail( "TODO implement genMulDivBinOp for {s} from {} to {}", .{ @tagName(tag), src_ty.fmt(mod), dst_ty.fmt(mod) }, @@ -8290,6 +8291,8 @@ fn genMulDivBinOp( } }, + .div_ceil => return self.fail("TODO: implement `@divCeil` for {}", .{self.target.cpu.arch}), + else => unreachable, } } @@ -8895,7 +8898,7 @@ fn genBinOp( .add => .{ .v_ss, .add }, .sub => .{ .v_ss, .sub }, .mul => .{ .v_ss, .mul }, - .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ss, .div }, + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ss, .div }, .max => .{ .v_ss, .max }, .min => .{ .v_ss, .max }, else => unreachable, @@ -8936,6 +8939,7 @@ fn genBinOp( .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, => if (self.hasFeature(.avx)) .{ .v_ss, .div } else .{ ._ss, .div }, .max => if (self.hasFeature(.avx)) .{ .v_ss, .max } else .{ ._ss, .max }, @@ -8949,6 +8953,7 @@ fn genBinOp( .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, => if (self.hasFeature(.avx)) .{ .v_sd, .div } else .{ ._sd, .div }, .max => if (self.hasFeature(.avx)) .{ .v_sd, .max } else .{ ._sd, .max }, @@ -9341,7 +9346,7 @@ fn genBinOp( .add => .{ .v_ss, .add }, .sub => .{ .v_ss, .sub }, .mul => .{ .v_ss, .mul }, - .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ss, .div }, + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ss, .div }, .max => .{ .v_ss, .max }, .min => .{ .v_ss, .max }, else => unreachable, @@ -9392,7 +9397,7 @@ fn genBinOp( .add => .{ .v_ps, .add }, .sub => .{ .v_ps, .sub }, .mul => .{ .v_ps, .mul }, - .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div }, + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div }, .max => .{ .v_ps, .max }, .min => .{ .v_ps, .max }, else => unreachable, @@ -9435,7 +9440,7 @@ fn genBinOp( .add => .{ .v_ps, .add }, .sub => .{ .v_ps, .sub }, .mul => .{ .v_ps, .mul }, - .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div }, + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div }, .max => .{ .v_ps, .max }, .min => .{ .v_ps, .max }, else => unreachable, @@ -9478,7 +9483,7 @@ fn genBinOp( .add => .{ .v_ps, .add }, .sub => .{ .v_ps, .sub }, .mul => .{ .v_ps, .mul }, - .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div }, + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div }, .max => .{ .v_ps, .max }, .min => .{ .v_ps, .max }, else => unreachable, @@ -9506,6 +9511,7 @@ fn genBinOp( .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, => if (self.hasFeature(.avx)) .{ .v_ss, .div } else .{ ._ss, .div }, .max => if (self.hasFeature(.avx)) .{ .v_ss, .max } else .{ ._ss, .max }, @@ -9526,6 +9532,7 @@ fn genBinOp( .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, => if (self.hasFeature(.avx)) .{ .v_ps, .div } else .{ ._ps, .div }, .max => if (self.hasFeature(.avx)) .{ .v_ps, .max } else .{ ._ps, .max }, @@ -9543,7 +9550,7 @@ fn genBinOp( .add => .{ .v_ps, .add }, .sub => .{ .v_ps, .sub }, .mul => .{ .v_ps, .mul }, - .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_ps, .div }, + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_ps, .div }, .max => .{ .v_ps, .max }, .min => .{ .v_ps, .min }, .cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq => .{ .v_ps, .cmp }, @@ -9559,6 +9566,7 @@ fn genBinOp( .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, => if (self.hasFeature(.avx)) .{ .v_sd, .div } else .{ ._sd, .div }, .max => if (self.hasFeature(.avx)) .{ .v_sd, .max } else .{ ._sd, .max }, @@ -9579,6 +9587,7 @@ fn genBinOp( .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, => if (self.hasFeature(.avx)) .{ .v_pd, .div } else .{ ._pd, .div }, .max => if (self.hasFeature(.avx)) .{ .v_pd, .max } else .{ ._pd, .max }, @@ -9596,7 +9605,7 @@ fn genBinOp( .add => .{ .v_pd, .add }, .sub => .{ .v_pd, .sub }, .mul => .{ .v_pd, .mul }, - .div_float, .div_trunc, .div_floor, .div_exact => .{ .v_pd, .div }, + .div_float, .div_trunc, .div_floor, .div_ceil, .div_exact => .{ .v_pd, .div }, .max => .{ .v_pd, .max }, .cmp_lt, .cmp_lte, .cmp_eq, .cmp_gte, .cmp_gt, .cmp_neq => .{ .v_pd, .cmp }, .min => .{ .v_pd, .min }, @@ -9721,6 +9730,7 @@ fn genBinOp( }, .precision = .inexact, }), + .div_ceil => return self.fail("TODO: implement `@divCeil` for {}", .{self.target.cpu.arch}), .bit_and, .bit_or, .xor => {}, .max, .min => if (maybe_mask_reg) |mask_reg| if (self.hasFeature(.avx)) { const rhs_copy_reg = registerAlias(src_mcv.getReg().?, abi_size); diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7fd367bb4993..4cd62281d5b2 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3137,6 +3137,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, try airBinFloatOp(f, inst, "fmod"); }, .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 airAbs(f, inst), @@ -3332,6 +3333,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, .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.zig b/src/codegen/llvm.zig index 10323b688c2e..013cc7524a4f 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -4978,6 +4978,7 @@ pub const FuncGen = struct { .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), @@ -4995,6 +4996,7 @@ pub const FuncGen = struct { .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), @@ -7925,6 +7927,56 @@ pub const FuncGen = struct { return self.wip.bin(.udiv, lhs, rhs, ""); } + fn airDivCeil(self: *FuncGen, inst: Air.Inst.Index, fast: Builder.FastMathKind) !Builder.Value { + const o = self.dg.object; + const mod = o.module; + const bin_op = self.air.instructions.items(.data)[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(mod); + + 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}); + } else if (scalar_ty.isSignedInt(mod)) { + const inst_llvm_ty = try o.lowerType(inst_ty); + const bit_size_minus_one = try o.builder.splatValue(inst_llvm_ty, try o.builder.intConst( + inst_llvm_ty.scalarType(&o.builder), + inst_llvm_ty.scalarBits(&o.builder) - 1, + )); + const zero = try o.builder.zeroInitValue(inst_llvm_ty); + const one = try o.builder.splatValue(inst_llvm_ty, try o.builder.intConst(inst_llvm_ty.scalarType(&o.builder), 1)); + + const quotient = try self.wip.bin(.sdiv, lhs, rhs, ""); + const rem = try self.wip.bin(.srem, lhs, rhs, ""); + + const quotient_sign = try self.wip.bin(.xor, lhs, rhs, ""); + // quotient_sign_mask has all bits set to 1 if the result is negative, or 0 + // otherwise. + const quotient_sign_mask = try self.wip.bin(.ashr, quotient_sign, bit_size_minus_one, ""); + // correction_if_inexact is 0 if the result is negative, 1 otherwise. + const correction_if_inexact = try self.wip.bin(.add, quotient_sign_mask, one, ""); + const is_rem_nonzero = try self.wip.icmp(.ne, rem, zero, ""); + const correction = try self.wip.select(fast, is_rem_nonzero, correction_if_inexact, zero, ""); + + return self.wip.bin(.@"add nsw", quotient, correction, ""); + } else if (scalar_ty.isUnsignedInt(mod)) { + const inst_llvm_ty = try o.lowerType(inst_ty); + const zero = try o.builder.zeroInitValue(inst_llvm_ty); + const one = try o.builder.splatValue(inst_llvm_ty, try o.builder.intConst(inst_llvm_ty.scalarType(&o.builder), 1)); + + const quotient = try self.wip.bin(.udiv, lhs, rhs, ""); + const rem = try self.wip.bin(.urem, lhs, rhs, ""); + const is_non_zero = try self.wip.icmp(.ne, rem, zero, ""); + const correction = try self.wip.select(fast, is_non_zero, one, zero, ""); + + return try self.wip.bin(.@"add nuw", quotient, correction, ""); + } else { + @panic("Unexpected scalar type"); + } + } + fn airDivExact(self: *FuncGen, inst: Air.Inst.Index, fast: Builder.FastMathKind) !Builder.Value { const o = self.dg.object; const mod = o.module; diff --git a/src/print_air.zig b/src/print_air.zig index 7cc09e9f992c..f979a7161853 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -120,6 +120,7 @@ const Writer = struct { .div_float, .div_trunc, .div_floor, + .div_ceil, .div_exact, .rem, .mod, @@ -150,6 +151,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/print_zir.zig b/src/print_zir.zig index a6e3ca91a89d..5074c9dd6a2a 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -406,6 +406,7 @@ const Writer = struct { .truncate, .div_exact, .div_floor, + .div_ceil, .div_trunc, .mod, .rem, diff --git a/src/value.zig b/src/value.zig index 1de0f66717cf..dedaab9dd1e9 100644 --- a/src/value.zig +++ b/src/value.zig @@ -2605,6 +2605,48 @@ pub const Value = struct { return mod.intValue_big(ty, result_q.toConst()); } + pub fn intDivCeil(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value { + if (ty.zigTypeTag(mod) == .Vector) { + const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod)); + const scalar_ty = ty.scalarType(mod); + for (result_data, 0..) |*scalar, i| { + const lhs_elem = try lhs.elemValue(mod, i); + const rhs_elem = try rhs.elemValue(mod, i); + scalar.* = try (try intDivCeilScalar(lhs_elem, rhs_elem, scalar_ty, allocator, mod)).intern(scalar_ty, mod); + } + return (try mod.intern(.{ .aggregate = .{ + .ty = ty.toIntern(), + .storage = .{ .elems = result_data }, + } })).toValue(); + } + return intDivCeilScalar(lhs, rhs, ty, allocator, mod); + } + + pub fn intDivCeilScalar(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space, mod); + const rhs_bigint = rhs.toBigInt(&rhs_space, mod); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + rhs_bigint.limbs.len, + ); + const limbs_buffer = try allocator.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_buffer); + return mod.intValue_big(ty, result_q.toConst()); + } + pub fn intMod(lhs: Value, rhs: Value, ty: Type, allocator: Allocator, mod: *Module) !Value { if (ty.zigTypeTag(mod) == .Vector) { const result_data = try allocator.alloc(InternPool.Index, ty.vectorLen(mod)); @@ -3336,6 +3378,51 @@ pub const Value = struct { } }))); } + pub fn floatDivCeil( + lhs: Value, + rhs: Value, + float_type: Type, + arena: Allocator, + mod: *Module, + ) !Value { + if (float_type.zigTypeTag(mod) == .Vector) { + const result_data = try arena.alloc(InternPool.Index, float_type.vectorLen(mod)); + const scalar_ty = float_type.scalarType(mod); + for (result_data, 0..) |*scalar, i| { + const lhs_elem = try lhs.elemValue(mod, i); + const rhs_elem = try rhs.elemValue(mod, i); + scalar.* = try (try floatDivCeilScalar(lhs_elem, rhs_elem, scalar_ty, mod)).intern(scalar_ty, mod); + } + return (try mod.intern(.{ .aggregate = .{ + .ty = float_type.toIntern(), + .storage = .{ .elems = result_data }, + } })).toValue(); + } + return floatDivCeilScalar(lhs, rhs, float_type, mod); + } + + pub fn floatDivCeilScalar( + lhs: Value, + rhs: Value, + float_type: Type, + mod: *Module, + ) !Value { + const target = mod.getTarget(); + // TODO: Replace @ceil(x / y) with @divCeil(x, y). + const storage: InternPool.Key.Float.Storage = switch (float_type.floatBits(target)) { + 16 => .{ .f16 = @ceil(lhs.toFloat(f16, mod) / rhs.toFloat(f16, mod)) }, + 32 => .{ .f32 = @ceil(lhs.toFloat(f32, mod) / rhs.toFloat(f32, mod)) }, + 64 => .{ .f64 = @ceil(lhs.toFloat(f64, mod) / rhs.toFloat(f64, mod)) }, + 80 => .{ .f80 = @ceil(lhs.toFloat(f80, mod) / rhs.toFloat(f80, mod)) }, + 128 => .{ .f128 = @ceil(lhs.toFloat(f128, mod) / rhs.toFloat(f128, mod)) }, + else => unreachable, + }; + return (try mod.intern(.{ .float = .{ + .ty = float_type.toIntern(), + .storage = storage, + } })).toValue(); + } + pub fn floatDivTrunc( lhs: Value, rhs: Value, diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index 6d7b54ea314d..0c58e2189132 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -61,6 +61,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/int_div.zig b/test/behavior/int_div.zig index bc570434cec9..ea381f6798b0 100644 --- a/test/behavior/int_div.zig +++ b/test/behavior/int_div.zig @@ -30,6 +30,16 @@ fn testDivision() !void { try expect(divFloor(i32, -14, 12) == -2); try expect(divFloor(i32, -2, 12) == -1); + try expect(divCeil(i8, 5, 3) == 2); + try expect(divCeil(i16, -5, 3) == -1); + try expect(divCeil(i64, -0x80000000, -2) == 0x40000000); + try expect(divCeil(i32, 0, -0x80000000) == 0); + try expect(divCeil(i64, -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(divTrunc(i32, 5, 3) == 1); try expect(divTrunc(i32, -5, 3) == -1); try expect(divTrunc(i32, 9, -10) == 0); @@ -58,6 +68,24 @@ fn testDivision() !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, ); @@ -81,6 +109,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); } diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 5b5d62df96bb..51a2f02cebd7 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -535,6 +535,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); @@ -550,6 +552,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); } diff --git a/test/behavior/vector.zig b/test/behavior/vector.zig index 6a38891494c5..e00d727a9bb3 100644 --- a/test/behavior/vector.zig +++ b/test/behavior/vector.zig @@ -559,8 +559,12 @@ test "vector division operators" { 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); for (@as([4]T, d3), 0..) |v, i| { + try expect(@divCeil(x[i], y[i]) == v); + } + const d4 = @divTrunc(x, y); + for (@as([4]T, d4), 0..) |v, i| { try expect(@divTrunc(x[i], y[i]) == v); } } @@ -1271,11 +1275,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/cases/compile_errors/signed_integer_division.zig b/test/cases/compile_errors/signed_integer_division.zig index 7e968ac77eb1..691476623931 100644 --- a/test/cases/compile_errors/signed_integer_division.zig +++ b/test/cases/compile_errors/signed_integer_division.zig @@ -6,4 +6,4 @@ export fn foo(a: i32, b: i32) i32 { // backend=stage2 // target=native // -// :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 From 19e7f33bff76c2faa507d24c617e158c1315c880 Mon Sep 17 00:00:00 2001 From: riverbl <94326797+riverbl@users.noreply.github.com> Date: Sun, 20 Aug 2023 08:39:38 +0100 Subject: [PATCH 2/5] Implement @divCeil lowering code for wasm --- src/arch/wasm/CodeGen.zig | 116 +++++++++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 54649f3215e6..89f9d70a572a 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -6813,8 +6813,120 @@ fn airDivFloor(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { } fn airDivCeil(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { - _ = inst; - return func.fail("TODO: implement `@divCeil` for {}", .{func.target.cpu.arch}); + const bin_op = func.air.instructions.items(.data)[inst].bin_op; + + const mod = func.bin_file.base.options.module.?; + const ty = func.typeOfIndex(inst); + const lhs = try func.resolveInst(bin_op.lhs); + const rhs = try func.resolveInst(bin_op.rhs); + + if (ty.isUnsignedInt(mod)) { + const int_bits = ty.intInfo(mod).bits; + const wasm_bits = toWasmBits(int_bits) orelse { + return func.fail("TODO: `@divCeil` for unsigned integers larger than 64 bits ({d} bits requested)", .{int_bits}); + }; + + if (wasm_bits > 64) { + return func.fail("TODO: `@divCeil` for unsigned integers larger than 64 bits ({d} bits requested)", .{int_bits}); + } + + _ = try func.binOp(lhs, rhs, ty, .div); + _ = try func.binOp(lhs, rhs, ty, .rem); + + switch (wasm_bits) { + 32 => { + _ = try func.cmp(.stack, WValue{ .imm32 = 0 }, ty, .neq); + try func.addTag(.i32_add); + }, + 64 => { + _ = try func.cmp(.stack, WValue{ .imm64 = 0 }, ty, .neq); + try func.addTag(.i64_extend_i32_u); + try func.addTag(.i64_add); + }, + else => @panic("Unexpected wasm integer width"), + } + } else if (ty.isSignedInt(mod)) { + const int_bits = ty.intInfo(mod).bits; + const wasm_bits = toWasmBits(int_bits) orelse { + return func.fail("TODO: `@divCeil` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); + }; + + if (wasm_bits > 64) { + return func.fail("TODO: `@divCeil` for signed integers larger than 64 bits ({d} bits requested)", .{int_bits}); + } + + const lhs_wasm = if (wasm_bits != int_bits) + try (try func.signAbsValue(lhs, ty)).toLocal(func, ty) + else + lhs; + const rhs_wasm = if (wasm_bits != int_bits) + try (try func.signAbsValue(rhs, ty)).toLocal(func, ty) + else + rhs; + + const zero = switch (wasm_bits) { + 32 => WValue{ .imm32 = 0 }, + 64 => WValue{ .imm64 = 0 }, + else => @panic("Unexpected wasm integer width"), + }; + + _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .div); + + // 1 if lhs and rhs have the same sign, 0 otherwise. + _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .xor); + _ = try func.cmp(.stack, zero, ty, .gte); + + _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .rem); + _ = try func.cmp(.stack, zero, ty, .neq); + + try func.addTag(.i32_and); + + // Comparisons produce 32 bit integers, which must be extended in the 64 bit case. + if (wasm_bits == 64) { + try func.addTag(.i64_extend_i32_u); + } + + _ = try func.binOp(.stack, .stack, ty, .add); + + // We need to zero the high bits because N bit comparisons consider all 32 or 64 bits, and + // expect all but the lowest N bits to be 0. + // TODO: Should we be zeroing the high bits here or should we be ignoring the high bits + // when performing comparisons? + if (wasm_bits != int_bits) { + _ = try func.wrapOperand(.stack, ty); + } + } else { + const float_bits = ty.floatBits(func.target); + if (float_bits > 64) { + return func.fail("TODO: `@divCeil` for floats larger than 64 bits ({d} bits requested)", .{float_bits}); + } + const is_f16 = float_bits == 16; + const lhs_wasm = if (is_f16) try func.fpext(lhs, Type.f16, Type.f32) else lhs; + const rhs_wasm = if (is_f16) try func.fpext(rhs, Type.f16, Type.f32) else rhs; + + try func.emitWValue(lhs_wasm); + try func.emitWValue(rhs_wasm); + + switch (float_bits) { + 16, 32 => { + try func.addTag(.f32_div); + try func.addTag(.f32_ceil); + }, + 64 => { + try func.addTag(.f64_div); + try func.addTag(.f64_ceil); + }, + else => @panic("Unexpected float width"), + } + + if (is_f16) { + _ = try func.fptrunc(.stack, Type.f32, Type.f16); + } + } + + const result = try func.allocLocal(ty); + try func.addLabel(.local_set, result.local.value); + func.finishAir(inst, result, &.{ bin_op.lhs, bin_op.rhs }); } fn divSigned(func: *CodeGen, lhs: WValue, rhs: WValue, ty: Type) InnerError!WValue { From 61213538e547ee188bcdd5242fee4ac294145f0d Mon Sep 17 00:00:00 2001 From: riverbl <94326797+riverbl@users.noreply.github.com> Date: Wed, 23 Aug 2023 16:20:42 +0100 Subject: [PATCH 3/5] Update documentation, remove `@panic`s, add tests Add section on `@divCeil` to language reference Replace some `@panic`s with `unreachable`s Add test cases for `@divCeil` for the stage2 wasm backend Fix breakage after rebase --- doc/langref.html.in | 19 ++++++++++++++++--- lib/std/zig/AstRlAnnotate.zig | 1 + src/arch/wasm/CodeGen.zig | 11 ++++++----- src/codegen/llvm.zig | 2 +- test/behavior/stage2_wasm_div.zig | 20 +++++++++++++++++++- 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 9cd1844e5118..df63eaaecc1d 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -8228,7 +8228,7 @@ test "main" {
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#}
@@ -8242,7 +8242,20 @@ test "main" {
For a function that returns a possible error code, use {#syntax#}@import("std").math.divFloor{#endsyntax#}.
- {#see_also|@divTrunc|@divExact#} + {#see_also|@divTrunc|@divExact|@divCeil#} + {#header_close#} + {#header_open|@divCeil#} +{#syntax#}@divCeil(numerator: T, denominator: T) T{#endsyntax#}
+ + Ceiling 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|@divTrunc|@divExact|@divFloor#} {#header_close#} {#header_open|@divTrunc#}{#syntax#}@divTrunc(numerator: T, denominator: T) T{#endsyntax#}
@@ -8256,7 +8269,7 @@ test "main" {
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|@embedFile#} diff --git a/lib/std/zig/AstRlAnnotate.zig b/lib/std/zig/AstRlAnnotate.zig index a43de68951c1..5c5272993f81 100644 --- a/lib/std/zig/AstRlAnnotate.zig +++ b/lib/std/zig/AstRlAnnotate.zig @@ -962,6 +962,7 @@ fn builtinCall(astrl: *AstRlAnnotate, block: ?*Block, ri: ResultInfo, node: Ast. }, .div_exact, .div_floor, + .div_ceil, .div_trunc, .mod, .rem, diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig index 89f9d70a572a..1bcd5a711041 100644 --- a/src/arch/wasm/CodeGen.zig +++ b/src/arch/wasm/CodeGen.zig @@ -6843,7 +6843,7 @@ fn airDivCeil(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { try func.addTag(.i64_extend_i32_u); try func.addTag(.i64_add); }, - else => @panic("Unexpected wasm integer width"), + else => unreachable, } } else if (ty.isSignedInt(mod)) { const int_bits = ty.intInfo(mod).bits; @@ -6856,18 +6856,19 @@ fn airDivCeil(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { } const lhs_wasm = if (wasm_bits != int_bits) - try (try func.signAbsValue(lhs, ty)).toLocal(func, ty) + try (try func.signExtendInt(lhs, ty)).toLocal(func, ty) else lhs; + const rhs_wasm = if (wasm_bits != int_bits) - try (try func.signAbsValue(rhs, ty)).toLocal(func, ty) + try (try func.signExtendInt(rhs, ty)).toLocal(func, ty) else rhs; const zero = switch (wasm_bits) { 32 => WValue{ .imm32 = 0 }, 64 => WValue{ .imm64 = 0 }, - else => @panic("Unexpected wasm integer width"), + else => unreachable, }; _ = try func.binOp(lhs_wasm, rhs_wasm, ty, .div); @@ -6916,7 +6917,7 @@ fn airDivCeil(func: *CodeGen, inst: Air.Inst.Index) InnerError!void { try func.addTag(.f64_div); try func.addTag(.f64_ceil); }, - else => @panic("Unexpected float width"), + else => unreachable, } if (is_f16) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 013cc7524a4f..8545345b8d02 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -7973,7 +7973,7 @@ pub const FuncGen = struct { return try self.wip.bin(.@"add nuw", quotient, correction, ""); } else { - @panic("Unexpected scalar type"); + unreachable; } } diff --git a/test/behavior/stage2_wasm_div.zig b/test/behavior/stage2_wasm_div.zig index ed09578fabb5..92aee63b94a3 100644 --- a/test/behavior/stage2_wasm_div.zig +++ b/test/behavior/stage2_wasm_div.zig @@ -3,7 +3,8 @@ const builtin = @import("builtin"); const expect = std.testing.expect; test "wasm integer division" { - // This test is copied from int_div.zig, with additional test cases for @divFloor on floats. + // This test is copied from int_div.zig, with additional test cases for @divFloor and @divCeil + // on floats. // TODO: Remove this test once the division tests in math.zig and int_div.zig pass with the // stage2 wasm backend. if (builtin.zig_backend != .stage2_wasm) return error.SkipZigTest; @@ -30,6 +31,20 @@ fn testDivision() !void { try expect(divFloor(f16, -43.0, 12.0) == -4.0); try expect(divFloor(f64, -90.0, -9.0) == 10.0); + try expect(divCeil(i8, 5, 3) == 2); + try expect(divCeil(i16, -5, 3) == -1); + try expect(divCeil(i64, -0x80000000, -2) == 0x40000000); + try expect(divCeil(i32, 0, -0x80000000) == 0); + try expect(divCeil(i64, -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(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(mod(u32, 10, 12) == 10); try expect(mod(i32, 10, 12) == 10); try expect(mod(i64, -14, 12) == 10); @@ -46,6 +61,9 @@ fn div(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 mod(comptime T: type, a: T, b: T) T { return @mod(a, b); } From cf88cf17a0c748433615b003201f8fd70823976c Mon Sep 17 00:00:00 2001 From: riverbl <94326797+riverbl@users.noreply.github.com> Date: Tue, 29 Aug 2023 21:34:43 +0100 Subject: [PATCH 4/5] Update documentation, improve formatting Add `@divCeil` to binary operators list in documentation Fix breakage after rebase --- lib/docs/main.js | 197 ++++++++++++++++++++++++----------------------- src/AstGen.zig | 2 +- src/Sema.zig | 6 +- 3 files changed, 104 insertions(+), 101 deletions(-) diff --git a/lib/docs/main.js b/lib/docs/main.js index 4991c31b57f9..548257d002ca 100644 --- a/lib/docs/main.js +++ b/lib/docs/main.js @@ -1,16 +1,16 @@ "use strict"; var zigAnalysis = { - typeKinds, - rootMod, - modules, - astNodes, - calls, - files, - decls, - exprs, + typeKinds, + rootMod, + modules, + astNodes, + calls, + files, + decls, + exprs, types, - comptimeExprs, + comptimeExprs, guideSections }; @@ -459,18 +459,18 @@ var scrollHistory = {}; let separator = ":"; const components = text.split("."); let curDeclOrType = undefined; - + let curContext = context; let limit = 10000; while (curContext) { limit -= 1; - + if (limit == 0) { throw "too many iterations"; } - + curDeclOrType = findSubDecl(curContext, components[0]); - + if (!curDeclOrType) { if (curContext.parent_container == null) break; curContext = getType(curContext.parent_container); @@ -484,16 +484,16 @@ var scrollHistory = {}; // We had to go up, which means we need a new path! const canonPath = getCanonDeclPath(curDeclOrType.find_subdecl_idx); if (!canonPath) return; - + let lastModName = canonPath.modNames[canonPath.modNames.length - 1]; let fullPath = lastModName + ":" + canonPath.declNames.join("."); - + separator = '.'; result = "#A;" + fullPath; } break; - } + } if (!curDeclOrType) { for (let i = 0; i < zigAnalysis.modules.length; i += 1){ @@ -507,7 +507,7 @@ var scrollHistory = {}; } if (!curDeclOrType) return null; - + for (let i = 1; i < components.length; i += 1) { curDeclOrType = findSubDecl(curDeclOrType, components[i]); if (!curDeclOrType) return null; @@ -516,9 +516,9 @@ var scrollHistory = {}; } return result; - + } - + function renderGuides() { renderTitle(); @@ -550,8 +550,8 @@ var scrollHistory = {}; } - // navigation bar - + // navigation bar + const guideIndexDom = domListNavGuides.children[0].children[0]; const guideDom = domListNavGuides.children[1].children[0]; if (activeGuide){ @@ -562,7 +562,7 @@ var scrollHistory = {}; } else { guideDom.classList.add("hidden"); guideIndexDom.classList.add("active"); - } + } // main content domGuidesMenuTitle.textContent = "Table of Contents"; @@ -579,7 +579,7 @@ var scrollHistory = {}; ev.stopPropagation(); } for (let a of domGuideTocList.querySelectorAll("a")) { - a.addEventListener('click', onLinkClick, false); + a.addEventListener('click', onLinkClick, false); } domGuideTocList.classList.remove("hidden"); domGuideTocListEmtpy.classList.add("hidden"); @@ -587,16 +587,16 @@ var scrollHistory = {}; domGuideTocListEmtpy.classList.remove("hidden"); domGuideTocList.classList.add("hidden"); } - + let reader = new commonmark.Parser({ smart: true, autoDoc: { detectDeclPath: detectDeclPath, } }); - let ast = reader.parse(activeGuide.body); - let writer = new commonmark.HtmlRenderer(); - let result = writer.render(ast); + let ast = reader.parse(activeGuide.body); + let writer = new commonmark.HtmlRenderer(); + let result = writer.render(ast); domActiveGuide.innerHTML = result; if (curNav.activeGuideScrollTo !== null) { scrollToHeading(curNav.activeGuideScrollTo, false); @@ -604,7 +604,7 @@ var scrollHistory = {}; } else { domGuideTocList.classList.add("hidden"); domGuideTocListEmtpy.classList.remove("hidden"); - + if (zigAnalysis.guideSections.length > 1 || (zigAnalysis.guideSections[0].guides.length > 0)) { renderGuidesIndex(); } else { @@ -626,7 +626,7 @@ var scrollHistory = {}; } function renderGuidesIndex() { - // main content + // main content { let html = ""; for (let i = 0; i < zigAnalysis.guideSections.length; i += 1) { @@ -662,16 +662,16 @@ var scrollHistory = {}; ev.stopPropagation(); } for (let a of domGuideTocList.querySelectorAll("a")) { - a.addEventListener('click', onLinkClick, false); + a.addEventListener('click', onLinkClick, false); } - + domGuideTocList.classList.remove("hidden"); domGuideTocListEmtpy.classList.add("hidden"); } else { domGuideTocList.classList.add("hidden"); domGuideTocListEmtpy.classList.remove("hidden"); } - } + } } function noGuidesAtAll() { @@ -685,8 +685,8 @@ These autodocs don't contain any guide. While the API section is a reference guide autogenerated from Zig source code, guides are meant to be handwritten explanations that provide for example: -- how-to explanations for common use-cases -- technical documentation +- how-to explanations for common use-cases +- technical documentation - information about advanced usage patterns You can add guides by specifying which markdown files to include @@ -706,7 +706,7 @@ You can also create sections to group guides together: //!zig-autodoc-guide: cli-basics.md //!zig-autodoc-guide: cli-advanced.md \`\`\` - + **Note that this feature is still under heavy development so expect bugs** **and missing features!** @@ -714,8 +714,8 @@ You can also create sections to group guides together: Happy writing! `); - let writer = new commonmark.HtmlRenderer(); - let result = writer.render(ast); + let writer = new commonmark.HtmlRenderer(); + let result = writer.render(ast); domActiveGuide.innerHTML = result; } @@ -1417,7 +1417,7 @@ Happy writing! } yield { src: ")", tag: Tag.r_paren }; return; - } + } case "sizeOf": { const sizeOf = zigAnalysis.exprs[expr.sizeOf]; yield { src: "@sizeOf", tag: Tag.builtin }; @@ -1506,7 +1506,7 @@ Happy writing! yield Tok.r_bracket; return; } - + case "sliceIndex": { const slice = zigAnalysis.exprs[expr.sliceIndex]; yield* ex(slice, opts); @@ -1631,7 +1631,7 @@ Happy writing! } return; } - + case "fieldVal": { const fv = expr.fieldVal; const field_name = fv.name; @@ -1904,6 +1904,10 @@ Happy writing! builtinName += "divFloor"; break; } + case "div_ceil": { + builtinName += "divCeil"; + break; + } case "div_trunc": { builtinName += "divTrunc"; break; @@ -2394,9 +2398,9 @@ Happy writing! } if (enumObj.nonexhaustive) { for (let j = 0; j < indent; j += 1) yield Tok.tab; - + yield { src: "_", tag: Tag.identifier }; - + if (fields_len > 1) { yield Tok.comma; yield Tok.enter; @@ -2877,7 +2881,7 @@ Happy writing! resolvedValue.expr.call !== undefined || resolvedValue.expr.comptimeExpr !== undefined ) { - // TODO: we're using the resolved value but + // TODO: we're using the resolved value but // not keeping track of how we got there // that's important context that should // be shown to the user! @@ -2931,7 +2935,7 @@ Happy writing! let docs = getAstNode(decl.src).docs; if (docs != null) { - // TODO: it shouldn't just be decl.parent_container, but rather + // TODO: it shouldn't just be decl.parent_container, but rather // the type that the decl holds (if the value is a type) domTldDocs.innerHTML = markdown(docs, decl); @@ -3061,11 +3065,11 @@ Happy writing! if (typeIsErrSet(declValue.expr.type)) { errSetsList.push(decl); } else if (typeIsStructWithNoFields(declValue.expr.type)) { - + let docs = getAstNode(decl.src).docs; if (!docs) { // If this is a re-export, try to fetch docs from the actual definition - const { value, seenDecls } = resolveValue(decl.value, true); + const { value, seenDecls } = resolveValue(decl.value, true); if (seenDecls.length > 0) { const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]); docs = getAstNode(definitionDecl.src).docs; @@ -3073,7 +3077,7 @@ Happy writing! docs = getAstNode(getType(value.expr.type).src).docs; } } - + if (docs) { namespacesWithDocsList.push({decl, docs}); } else { @@ -3092,7 +3096,7 @@ Happy writing! let docs = getAstNode(decl.src).docs; if (!docs) { // If this is a re-export, try to fetch docs from the actual definition - const { value, seenDecls } = resolveValue(decl.value, true); + const { value, seenDecls } = resolveValue(decl.value, true); if (seenDecls.length > 0) { const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]); docs = getAstNode(definitionDecl.src).docs; @@ -3244,12 +3248,12 @@ Happy writing! let decl = typesList[i]; aDom.textContent = decl.name; aDom.setAttribute("href", navLinkDecl(decl.name)); - + let descDom = liDom.children[1]; let docs = getAstNode(decl.src).docs; if (!docs) { // If this is a re-export, try to fetch docs from the actual definition - const { value, seenDecls } = resolveValue(decl.value, true); + const { value, seenDecls } = resolveValue(decl.value, true); if (seenDecls.length > 0) { const definitionDecl = getDecl(seenDecls[seenDecls.length - 1]); docs = getAstNode(definitionDecl.src).docs; @@ -3260,7 +3264,7 @@ Happy writing! } } } - + if (docs) { descDom.innerHTML = markdown(shortDesc(docs)); } else { @@ -3273,13 +3277,13 @@ Happy writing! } domSectTypes.classList.remove("hidden"); } - + if (namespacesWithDocsList.length !== 0) { const splitPoint = Math.ceil(namespacesWithDocsList.length / 2); const template = '