From 79bc5891c1c4cde0592fe1b10b6c9a85914155cf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 28 Sep 2021 15:45:58 -0700 Subject: [PATCH] stage2: more arithmetic support * AIR: add `mod` instruction for modulus division - Implement for LLVM backend * Sema: implement `@mod`, `@rem`, and `%`. * Sema: fix comptime switch evaluation * Sema: implement comptime shift left * Sema: fix the logic inside analyzeArithmetic to handle all the nuances between the different mathematical operations. - Implement comptime wrapping operations --- src/Air.zig | 11 +- src/Liveness.zig | 1 + src/Sema.zig | 661 +++++++++++++++++++++----- src/Zir.zig | 22 +- src/codegen.zig | 9 + src/codegen/c.zig | 2 + src/codegen/llvm.zig | 29 ++ src/print_air.zig | 1 + src/value.zig | 138 ++++++ test/behavior.zig | 3 +- test/behavior/math.zig | 848 --------------------------------- test/behavior/math_stage1.zig | 855 ++++++++++++++++++++++++++++++++++ 12 files changed, 1605 insertions(+), 975 deletions(-) create mode 100644 test/behavior/math_stage1.zig diff --git a/src/Air.zig b/src/Air.zig index b4552f9d7b..b5d19127a0 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -69,10 +69,16 @@ pub const Inst = struct { /// is the same as both operands. /// Uses the `bin_op` field. div, - /// Integer or float remainder. - /// Both operands are guaranteed to be the same type, and the result type is the same as both operands. + /// Integer or float remainder division. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. /// Uses the `bin_op` field. rem, + /// Integer or float modulus division. + /// Both operands are guaranteed to be the same type, and the result type + /// is the same as both operands. + /// Uses the `bin_op` field. + mod, /// Add an offset to a pointer, returning a new pointer. /// The offset is in element type units, not bytes. /// Wrapping is undefined behavior. @@ -568,6 +574,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type { .mulwrap, .div, .rem, + .mod, .bit_and, .bit_or, .xor, diff --git a/src/Liveness.zig b/src/Liveness.zig index a9ff586aeb..25dd29b0f6 100644 --- a/src/Liveness.zig +++ b/src/Liveness.zig @@ -232,6 +232,7 @@ fn analyzeInst( .mulwrap, .div, .rem, + .mod, .ptr_add, .ptr_sub, .bit_and, diff --git a/src/Sema.zig b/src/Sema.zig index 35a434eb35..de94a8c6b8 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -319,8 +319,6 @@ pub fn analyzeBody( .div_exact => try sema.zirDivExact(block, inst), .div_floor => try sema.zirDivFloor(block, inst), .div_trunc => try sema.zirDivTrunc(block, inst), - .mod => try sema.zirMod(block, inst), - .rem => try sema.zirRem(block, inst), .shl_exact => try sema.zirShlExact(block, inst), .shr_exact => try sema.zirShrExact(block, inst), .bit_offset_of => try sema.zirBitOffsetOf(block, inst), @@ -363,14 +361,16 @@ pub fn analyzeBody( .error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon), .error_set_decl_func => try sema.zirErrorSetDecl(block, inst, .func), - .add => try sema.zirArithmetic(block, inst), - .addwrap => try sema.zirArithmetic(block, inst), - .div => try sema.zirArithmetic(block, inst), - .mod_rem => try sema.zirArithmetic(block, inst), - .mul => try sema.zirArithmetic(block, inst), - .mulwrap => try sema.zirArithmetic(block, inst), - .sub => try sema.zirArithmetic(block, inst), - .subwrap => try sema.zirArithmetic(block, inst), + .add => try sema.zirArithmetic(block, inst, .add), + .addwrap => try sema.zirArithmetic(block, inst, .addwrap), + .div => try sema.zirArithmetic(block, inst, .div), + .mod_rem => try sema.zirArithmetic(block, inst, .mod_rem), + .mod => try sema.zirArithmetic(block, inst, .mod), + .rem => try sema.zirArithmetic(block, inst, .rem), + .mul => try sema.zirArithmetic(block, inst, .mul), + .mulwrap => try sema.zirArithmetic(block, inst, .mulwrap), + .sub => try sema.zirArithmetic(block, inst, .sub), + .subwrap => try sema.zirArithmetic(block, inst, .subwrap), // Instructions that we know to *always* be noreturn based solely on their tag. // These functions match the return type of analyzeBody so that we can @@ -886,6 +886,14 @@ fn failWithUseOfUndef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) Compile return sema.mod.fail(&block.base, src, "use of undefined value here causes undefined behavior", .{}); } +fn failWithDivideByZero(sema: *Sema, block: *Scope.Block, src: LazySrcLoc) CompileError { + return sema.mod.fail(&block.base, src, "division by zero here causes undefined behavior", .{}); +} + +fn failWithModRemNegative(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, lhs_ty: Type, rhs_ty: Type) CompileError { + return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty }); +} + /// Appropriate to call when the coercion has already been done by result /// location semantics. Asserts the value fits in the provided `Int` type. /// Only supports `Int` types 64 bits or less. @@ -2366,8 +2374,12 @@ fn resolveBlockBody( body: []const Zir.Inst.Index, merges: *Scope.Block.Merges, ) CompileError!Air.Inst.Ref { - _ = try sema.analyzeBody(child_block, body); - return sema.analyzeBlockBody(parent_block, src, child_block, merges); + if (child_block.is_comptime) { + return sema.resolveBody(child_block, body); + } else { + _ = try sema.analyzeBody(child_block, body); + return sema.analyzeBlockBody(parent_block, src, child_block, merges); + } } fn analyzeBlockBody( @@ -5867,23 +5879,36 @@ fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!A defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; 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 = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); - if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(sema.typeOf(lhs)); - } - return sema.mod.fail(&block.base, src, "TODO implement comptime shl", .{}); - } - } + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); - try sema.requireRuntimeBlock(block, src); + const runtime_src = if (maybe_lhs_val) |lhs_val| rs: { + const lhs_ty = sema.typeOf(lhs); + + if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty); + const rhs_val = maybe_rhs_val orelse break :rs rhs_src; + if (rhs_val.isUndef()) return sema.addConstUndef(lhs_ty); + + // If rhs is 0, return lhs without doing any calculations. + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(lhs_ty, lhs_val); + } + const val = try lhs_val.shl(rhs_val, sema.arena); + return sema.addConstant(lhs_ty, val); + } else rs: { + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) return sema.addConstUndef(sema.typeOf(lhs)); + } + break :rs lhs_src; + }; + + try sema.requireRuntimeBlock(block, runtime_src); return block.addBinOp(.shl, lhs, rhs); } @@ -6141,11 +6166,15 @@ fn zirNegate( return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src); } -fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirArithmetic( + sema: *Sema, + block: *Scope.Block, + inst: Zir.Inst.Index, + zir_tag: Zir.Inst.Tag, +) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - const tag_override = block.sema.code.instructions.items(.tag)[inst]; const inst_data = sema.code.instructions.items(.data)[inst].pl_node; sema.src = .{ .node_offset_bin_op = inst_data.src_node }; const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; @@ -6154,7 +6183,7 @@ fn zirArithmetic(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Compile const lhs = sema.resolveInst(extra.lhs); const rhs = sema.resolveInst(extra.rhs); - return sema.analyzeArithmetic(block, tag_override, lhs, rhs, sema.src, lhs_src, rhs_src); + return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); } fn zirOverflowArithmetic( @@ -6187,6 +6216,7 @@ fn zirSatArithmetic( fn analyzeArithmetic( sema: *Sema, block: *Scope.Block, + /// TODO performance investigation: make this comptime? zir_tag: Zir.Inst.Tag, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, @@ -6204,7 +6234,7 @@ fn analyzeArithmetic( lhs_ty.arrayLen(), rhs_ty.arrayLen(), }); } - return sema.mod.fail(&block.base, src, "TODO implement support for vectors in zirBinOp", .{}); + return sema.mod.fail(&block.base, src, "TODO implement support for vectors in Sema.analyzeArithmetic", .{}); } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) { return sema.mod.fail(&block.base, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{ lhs_ty, rhs_ty, @@ -6247,7 +6277,9 @@ fn analyzeArithmetic( }; const instructions = &[_]Air.Inst.Ref{ lhs, rhs }; - const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ lhs_src, rhs_src } }); + 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); @@ -6267,86 +6299,499 @@ fn analyzeArithmetic( }); } - if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { - if (lhs_val.isUndef() or rhs_val.isUndef()) { - return sema.addConstUndef(resolved_type); - } - // incase rhs is 0, simply return lhs without doing any calculations - // TODO Once division is implemented we should throw an error when dividing by 0. - if (rhs_val.compareWithZero(.eq)) { - switch (zir_tag) { - .add, .addwrap, .sub, .subwrap => { - return sema.addConstant(scalar_type, lhs_val); - }, - else => {}, + const target = sema.mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs); + const rs: struct { src: LazySrcLoc, air_tag: Air.Inst.Tag } = rs: { + switch (zir_tag) { + .add => { + // For integers: + // If either of the operands are zero, then the other operand is + // returned, even if it is undefined. + // If either of the operands are undefined, it's a compile error + // because there is a possible value for which the addition would + // overflow (max_int), causing illegal behavior. + // For floats: either operand being undef makes the result undef. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + return casted_rhs; + } } - } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, rhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, lhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intAdd(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = rhs_src, .air_tag = .add }; + } else break :rs .{ .src = lhs_src, .air_tag = .add }; + }, + .addwrap => { + // Integers only; floats are checked above. + // If either of the operands are zero, then the other operand is + // returned, even if it is undefined. + // If either of the operands are undefined, the result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + return casted_rhs; + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.numberAddWrap(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = lhs_src, .air_tag = .addwrap }; + } else break :rs .{ .src = rhs_src, .air_tag = .addwrap }; + }, + .sub => { + // For integers: + // If the rhs is zero, then the other operand is + // returned, even if it is undefined. + // If either of the operands are undefined, it's a compile error + // because there is a possible value for which the subtraction would + // overflow, causing illegal behavior. + // For floats: either operand being undef makes the result undef. + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, rhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, lhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intSub(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatSub(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = rhs_src, .air_tag = .sub }; + } else break :rs .{ .src = lhs_src, .air_tag = .sub }; + }, + .subwrap => { + // Integers only; floats are checked above. + // If the RHS is zero, then the other operand is returned, even if it is undefined. + // If either of the operands are undefined, the result is undefined. + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return casted_lhs; + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.numberSubWrap(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = rhs_src, .air_tag = .subwrap }; + } else break :rs .{ .src = lhs_src, .air_tag = .subwrap }; + }, + .div => { + // 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. + // 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()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (lhs_ty.isSignedInt() and rhs_ty.isSignedInt()) { + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.compare(.neq, Value.negative_one, scalar_type)) { + return sema.addConstUndef(scalar_type); + } + } + return sema.failWithUseOfUndef(block, rhs_src); + } + return sema.addConstUndef(scalar_type); + } - const value = switch (zir_tag) { - .add => blk: { - const val = if (is_int) - try lhs_val.intAdd(rhs_val, sema.arena) - else - try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - .sub => blk: { - const val = if (is_int) - try lhs_val.intSub(rhs_val, sema.arena) - else - try lhs_val.floatSub(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - .div => blk: { - const val = if (is_int) - try lhs_val.intDiv(rhs_val, sema.arena) - else - try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - .mul => blk: { - const val = if (is_int) - try lhs_val.intMul(rhs_val, sema.arena) - else - try lhs_val.floatMul(rhs_val, scalar_type, sema.arena); - break :blk val; - }, - else => return sema.mod.fail(&block.base, src, "TODO implement comptime arithmetic for operand '{s}'", .{@tagName(zir_tag)}), - }; - - log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value }); - - return sema.addConstant(scalar_type, value); - } else { - try sema.requireRuntimeBlock(block, rhs_src); + if (maybe_rhs_val) |rhs_val| { + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intDiv(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = rhs_src, .air_tag = .div }; + } else break :rs .{ .src = lhs_src, .air_tag = .div }; + }, + .mul => { + // For integers: + // If either of the operands are zero, the result is zero. + // If either of the operands are one, the result is the other + // operand, even if it is undefined. + // If either of the operands are undefined, it's a compile error + // because there is a possible value for which the addition would + // overflow (max_int), causing illegal behavior. + // For floats: either operand being undef makes the result undef. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (lhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_rhs; + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, rhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (rhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + if (is_int) { + return sema.failWithUseOfUndef(block, lhs_src); + } else { + return sema.addConstUndef(scalar_type); + } + } + if (is_int) { + return sema.addConstant( + scalar_type, + try lhs_val.intMul(rhs_val, sema.arena), + ); + } else { + return sema.addConstant( + scalar_type, + try lhs_val.floatMul(rhs_val, scalar_type, sema.arena), + ); + } + } else break :rs .{ .src = lhs_src, .air_tag = .mul }; + } else break :rs .{ .src = rhs_src, .air_tag = .mul }; + }, + .mulwrap => { + // Integers only; floats are handled above. + // If either of the operands are zero, the result is zero. + // If either of the operands are one, the result is the other + // operand, even if it is undefined. + // If either of the operands are undefined, the result is undefined. + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (lhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (lhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_rhs; + } + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.addConstant(scalar_type, Value.zero); + } + if (rhs_val.compare(.eq, Value.one, scalar_type)) { + return casted_lhs; + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + return sema.addConstant( + scalar_type, + try lhs_val.numberMulWrap(rhs_val, scalar_type, sema.arena, target), + ); + } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap }; + } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap }; + }, + .mod_rem => { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // 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. + // + // For either one: if the result would be different between @mod and @rem, + // then emit a compile error saying you have to pick one. + if (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + if (lhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + } else if (lhs_ty.isSignedInt()) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (rhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.intRem(rhs_val, sema.arena), + ); + } + break :rs .{ .src = lhs_src, .air_tag = .rem }; + } else if (rhs_ty.isSignedInt()) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } else { + break :rs .{ .src = rhs_src, .air_tag = .rem }; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (rhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef() or lhs_val.compareWithZero(.lt)) { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + return sema.addConstant( + scalar_type, + try lhs_val.floatRem(rhs_val, sema.arena), + ); + } else { + return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); + } + } else { + return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); + } + }, + .rem => { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // 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 (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.intRem(rhs_val, sema.arena), + ); + } + break :rs .{ .src = lhs_src, .air_tag = .rem }; + } else { + break :rs .{ .src = rhs_src, .air_tag = .rem }; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.floatRem(rhs_val, sema.arena), + ); + } else break :rs .{ .src = rhs_src, .air_tag = .rem }; + } else break :rs .{ .src = lhs_src, .air_tag = .rem }; + }, + .mod => { + // For integers: + // Either operand being undef is a compile error because there exists + // a possible value (TODO what is it?) that would invoke illegal behavior. + // TODO: can lhs zero be handled better? + // TODO: can lhs undef be handled better? + // + // 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 (is_int) { + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, lhs_src); + } + } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + if (maybe_lhs_val) |lhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.intMod(rhs_val, sema.arena), + ); + } + break :rs .{ .src = lhs_src, .air_tag = .mod }; + } else { + break :rs .{ .src = rhs_src, .air_tag = .mod }; + } + } + // float operands + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (rhs_val.compareWithZero(.eq)) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + if (maybe_lhs_val) |lhs_val| { + if (lhs_val.isUndef()) { + return sema.addConstUndef(scalar_type); + } + if (maybe_rhs_val) |rhs_val| { + return sema.addConstant( + scalar_type, + try lhs_val.floatMod(rhs_val, sema.arena), + ); + } else break :rs .{ .src = rhs_src, .air_tag = .mod }; + } else break :rs .{ .src = lhs_src, .air_tag = .mod }; + }, + else => unreachable, } - } else { - try sema.requireRuntimeBlock(block, lhs_src); - } - - if (zir_tag == .mod_rem) { - const dirty_lhs = lhs_ty.isSignedInt() or lhs_ty.isRuntimeFloat(); - const dirty_rhs = rhs_ty.isSignedInt() or rhs_ty.isRuntimeFloat(); - if (dirty_lhs or dirty_rhs) { - return sema.mod.fail(&block.base, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty }); - } - } - - const air_tag: Air.Inst.Tag = switch (zir_tag) { - .add => .add, - .addwrap => .addwrap, - .sub => .sub, - .subwrap => .subwrap, - .mul => .mul, - .mulwrap => .mulwrap, - .div => .div, - .mod_rem => .rem, - .rem => .rem, - else => return sema.mod.fail(&block.base, src, "TODO implement arithmetic for operand '{s}'", .{@tagName(zir_tag)}), }; - return block.addBinOp(air_tag, casted_lhs, casted_rhs); + try sema.requireRuntimeBlock(block, rs.src); + return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); } fn zirLoad(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7401,7 +7846,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 => true, + .add, .sub, .mul, .div, .mod, .rem, .mod_rem => true, else => false, }; } @@ -8068,16 +8513,6 @@ fn zirDivTrunc(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileEr return sema.mod.fail(&block.base, src, "TODO: Sema.zirDivTrunc", .{}); } -fn zirMod(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.mod.fail(&block.base, src, "TODO: Sema.zirMod", .{}); -} - -fn zirRem(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - return sema.zirArithmetic(block, inst); -} - fn zirShlExact(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); diff --git a/src/Zir.zig b/src/Zir.zig index 483880c9b6..7c171e736d 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -395,17 +395,6 @@ pub const Inst = struct { /// Merge two error sets into one, `E1 || E2`. /// Uses the `pl_node` field with payload `Bin`. merge_error_sets, - /// Ambiguously remainder division or modulus. If the computation would possibly have - /// a different value depending on whether the operation is remainder division or modulus, - /// a compile error is emitted. Otherwise the computation is performed. - /// Uses the `pl_node` union field. Payload is `Bin`. - mod_rem, - /// Arithmetic multiplication. Asserts no integer overflow. - /// Uses the `pl_node` union field. Payload is `Bin`. - mul, - /// Twos complement wrapping integer multiplication. - /// Uses the `pl_node` union field. Payload is `Bin`. - mulwrap, /// Turns an R-Value into a const L-Value. In other words, it takes a value, /// stores it in a memory location, and returns a const pointer to it. If the value /// is `comptime`, the memory location is global static constant data. Otherwise, @@ -828,6 +817,17 @@ pub const Inst = struct { /// Implements the `@rem` builtin. /// Uses the `pl_node` union field with payload `Bin`. rem, + /// Ambiguously remainder division or modulus. If the computation would possibly have + /// a different value depending on whether the operation is remainder division or modulus, + /// a compile error is emitted. Otherwise the computation is performed. + /// Uses the `pl_node` union field. Payload is `Bin`. + mod_rem, + /// Arithmetic multiplication. Asserts no integer overflow. + /// Uses the `pl_node` union field. Payload is `Bin`. + mul, + /// Twos complement wrapping integer multiplication. + /// Uses the `pl_node` union field. Payload is `Bin`. + mulwrap, /// Integer shift-left. Zeroes are shifted in from the right hand side. /// Uses the `pl_node` union field. Payload is `Bin`. diff --git a/src/codegen.zig b/src/codegen.zig index 4eda3f2594..7c359e90c0 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -832,6 +832,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { .mulwrap => try self.airMulWrap(inst), .div => try self.airDiv(inst), .rem => try self.airRem(inst), + .mod => try self.airMod(inst), .cmp_lt => try self.airCmp(inst, .lt), .cmp_lte => try self.airCmp(inst, .lte), @@ -1353,6 +1354,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); } + fn airMod(self: *Self, inst: Air.Inst.Index) !void { + const bin_op = self.air.instructions.items(.data)[inst].bin_op; + const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { + else => return self.fail("TODO implement mod for {}", .{self.target.cpu.arch}), + }; + return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); + } + fn airBitAnd(self: *Self, inst: Air.Inst.Index) !void { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a6534b1eba..4964f17cd3 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -897,6 +897,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO // that wrapping is UB. .div => try airBinOp( f, inst, " / "), .rem => try airBinOp( f, inst, " % "), + // TODO implement modulus division + .mod => try airBinOp( f, inst, " mod "), .cmp_eq => try airBinOp(f, inst, " == "), .cmp_gt => try airBinOp(f, inst, " > "), diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 4a0d218ead..f65e0f6d72 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1244,6 +1244,7 @@ pub const FuncGen = struct { .mulwrap => try self.airMul(inst, true), .div => try self.airDiv(inst), .rem => try self.airRem(inst), + .mod => try self.airMod(inst), .ptr_add => try self.airPtrAdd(inst), .ptr_sub => try self.airPtrSub(inst), @@ -2095,6 +2096,34 @@ pub const FuncGen = struct { return self.builder.buildURem(lhs, rhs, ""); } + fn airMod(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { + if (self.liveness.isUnused(inst)) return null; + + 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.air.typeOfIndex(inst); + const inst_llvm_ty = try self.dg.llvmType(inst_ty); + + if (inst_ty.isRuntimeFloat()) { + const a = self.builder.buildFRem(lhs, rhs, ""); + const b = self.builder.buildFAdd(a, rhs, ""); + const c = self.builder.buildFRem(b, rhs, ""); + const zero = inst_llvm_ty.constNull(); + const ltz = self.builder.buildFCmp(.OLT, lhs, zero, ""); + return self.builder.buildSelect(ltz, c, a, ""); + } + if (inst_ty.isSignedInt()) { + const a = self.builder.buildSRem(lhs, rhs, ""); + const b = self.builder.buildNSWAdd(a, rhs, ""); + const c = self.builder.buildSRem(b, rhs, ""); + const zero = inst_llvm_ty.constNull(); + const ltz = self.builder.buildICmp(.SLT, lhs, zero, ""); + return self.builder.buildSelect(ltz, c, a, ""); + } + return self.builder.buildURem(lhs, rhs, ""); + } + fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value { if (self.liveness.isUnused(inst)) return null; diff --git a/src/print_air.zig b/src/print_air.zig index 2a7538f81a..90df06760b 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -110,6 +110,7 @@ const Writer = struct { .mulwrap, .div, .rem, + .mod, .ptr_add, .ptr_sub, .bit_and, diff --git a/src/value.zig b/src/value.zig index 336f5f9cf7..29d8fa8db9 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1616,6 +1616,34 @@ pub const Value = extern union { return result; } + /// Supports both floats and ints; handles undefined. + pub fn numberMulWrap( + lhs: Value, + rhs: Value, + ty: Type, + arena: *Allocator, + target: Target, + ) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.isAnyFloat()) { + return floatMul(lhs, rhs, ty, arena); + } + const result = try intMul(lhs, rhs, arena); + + const max = try ty.maxInt(arena, target); + if (compare(result, .gt, max, ty)) { + @panic("TODO comptime wrapping integer multiplication"); + } + + const min = try ty.minInt(arena, target); + if (compare(result, .lt, min, ty)) { + @panic("TODO comptime wrapping integer multiplication"); + } + + return result; + } + /// Supports both floats and ints; handles undefined. pub fn numberMax(lhs: Value, rhs: Value, arena: *Allocator) !Value { if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); @@ -1840,6 +1868,82 @@ pub const Value = extern union { } } + pub fn intRem(lhs: Value, rhs: Value, allocator: *Allocator) !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); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + lhs_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.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); + const result_limbs = result_r.limbs[0..result_r.len]; + + if (result_r.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn intMod(lhs: Value, rhs: Value, allocator: *Allocator) !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); + const rhs_bigint = rhs.toBigInt(&rhs_space); + const limbs_q = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1, + ); + const limbs_r = try allocator.alloc( + std.math.big.Limb, + lhs_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.divFloor(&result_r, lhs_bigint, rhs_bigint, limbs_buffer, null); + const result_limbs = result_r.limbs[0..result_r.len]; + + if (result_r.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + + pub fn floatRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + _ = lhs; + _ = rhs; + _ = allocator; + @panic("TODO implement Value.floatRem"); + } + + pub fn floatMod(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + _ = lhs; + _ = rhs; + _ = allocator; + @panic("TODO implement Value.floatMod"); + } + pub fn intMul(lhs: Value, rhs: Value, allocator: *Allocator) !Value { // TODO is this a performance issue? maybe we should try the operation without // resorting to BigInt first. @@ -1875,6 +1979,31 @@ pub const Value = extern union { return Tag.int_u64.create(arena, truncated); } + pub fn shl(lhs: Value, rhs: Value, allocator: *Allocator) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = lhs.toBigInt(&lhs_space); + const shift = rhs.toUnsignedInt(); + const limbs = try allocator.alloc( + std.math.big.Limb, + lhs_bigint.limbs.len + (shift / (@sizeOf(std.math.big.Limb) * 8)) + 1, + ); + var result_bigint = BigIntMutable{ + .limbs = limbs, + .positive = undefined, + .len = undefined, + }; + result_bigint.shiftLeft(lhs_bigint, shift); + const result_limbs = result_bigint.limbs[0..result_bigint.len]; + + if (result_bigint.positive) { + return Value.Tag.int_big_positive.create(allocator, result_limbs); + } else { + return Value.Tag.int_big_negative.create(allocator, result_limbs); + } + } + pub fn shr(lhs: Value, rhs: Value, allocator: *Allocator) !Value { // TODO is this a performance issue? maybe we should try the operation without // resorting to BigInt first. @@ -2227,4 +2356,13 @@ pub const Value = extern union { /// are possible without using an allocator. limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb, }; + + pub const zero = initTag(.zero); + pub const one = initTag(.one); + pub const negative_one: Value = .{ .ptr_otherwise = &negative_one_payload.base }; +}; + +var negative_one_payload: Value.Payload.I64 = .{ + .base = .{ .tag = .int_i64 }, + .data = -1, }; diff --git a/test/behavior.zig b/test/behavior.zig index 4bfd947fcf..479e1feffc 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -10,6 +10,7 @@ test { _ = @import("behavior/eval.zig"); _ = @import("behavior/generics.zig"); _ = @import("behavior/if.zig"); + _ = @import("behavior/math.zig"); _ = @import("behavior/member_func.zig"); _ = @import("behavior/pointers.zig"); _ = @import("behavior/sizeof_and_typeof.zig"); @@ -119,7 +120,7 @@ test { _ = @import("behavior/incomplete_struct_param_tld.zig"); _ = @import("behavior/inttoptr.zig"); _ = @import("behavior/ir_block_deps.zig"); - _ = @import("behavior/math.zig"); + _ = @import("behavior/math_stage1.zig"); _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/merge_error_sets.zig"); _ = @import("behavior/misc.zig"); diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 7a5c31f67a..510cc3d438 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -6,171 +6,6 @@ const maxInt = std.math.maxInt; const minInt = std.math.minInt; const mem = std.mem; -test "division" { - try testDivision(); - comptime try testDivision(); -} -fn testDivision() !void { - try expect(div(u32, 13, 3) == 4); - try expect(div(f16, 1.0, 2.0) == 0.5); - try expect(div(f32, 1.0, 2.0) == 0.5); - - try expect(divExact(u32, 55, 11) == 5); - try expect(divExact(i32, -55, 11) == -5); - try expect(divExact(f16, 55.0, 11.0) == 5.0); - try expect(divExact(f16, -55.0, 11.0) == -5.0); - try expect(divExact(f32, 55.0, 11.0) == 5.0); - try expect(divExact(f32, -55.0, 11.0) == -5.0); - - try expect(divFloor(i32, 5, 3) == 1); - try expect(divFloor(i32, -5, 3) == -2); - try expect(divFloor(f16, 5.0, 3.0) == 1.0); - try expect(divFloor(f16, -5.0, 3.0) == -2.0); - try expect(divFloor(f32, 5.0, 3.0) == 1.0); - try expect(divFloor(f32, -5.0, 3.0) == -2.0); - try expect(divFloor(i32, -0x80000000, -2) == 0x40000000); - try expect(divFloor(i32, 0, -0x80000000) == 0); - try expect(divFloor(i32, -0x40000001, 0x40000000) == -2); - try expect(divFloor(i32, -0x80000000, 1) == -0x80000000); - try expect(divFloor(i32, 10, 12) == 0); - try expect(divFloor(i32, -14, 12) == -2); - try expect(divFloor(i32, -2, 12) == -1); - - try expect(divTrunc(i32, 5, 3) == 1); - try expect(divTrunc(i32, -5, 3) == -1); - try expect(divTrunc(f16, 5.0, 3.0) == 1.0); - try expect(divTrunc(f16, -5.0, 3.0) == -1.0); - try expect(divTrunc(f32, 5.0, 3.0) == 1.0); - try expect(divTrunc(f32, -5.0, 3.0) == -1.0); - try expect(divTrunc(f64, 5.0, 3.0) == 1.0); - try expect(divTrunc(f64, -5.0, 3.0) == -1.0); - try expect(divTrunc(i32, 10, 12) == 0); - try expect(divTrunc(i32, -14, 12) == -1); - try expect(divTrunc(i32, -2, 12) == 0); - - try expect(mod(i32, 10, 12) == 10); - try expect(mod(i32, -14, 12) == 10); - try expect(mod(i32, -2, 12) == 10); - - comptime { - try expect( - 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600, - ); - try expect( - @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600, - ); - try expect( - 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2, - ); - try expect( - @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2, - ); - try expect( - @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2, - ); - try expect( - @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2, - ); - try expect( - 4126227191251978491697987544882340798050766755606969681711 % 10 == 1, - ); - } -} -fn div(comptime T: type, a: T, b: T) T { - return a / b; -} -fn divExact(comptime T: type, a: T, b: T) T { - return @divExact(a, b); -} -fn divFloor(comptime T: type, a: T, b: T) T { - return @divFloor(a, b); -} -fn divTrunc(comptime T: type, a: T, b: T) T { - return @divTrunc(a, b); -} -fn mod(comptime T: type, a: T, b: T) T { - return @mod(a, b); -} - -test "@addWithOverflow" { - var result: u8 = undefined; - try expect(@addWithOverflow(u8, 250, 100, &result)); - try expect(!@addWithOverflow(u8, 100, 150, &result)); - try expect(result == 250); -} - -// TODO test mulWithOverflow -// TODO test subWithOverflow - -test "@shlWithOverflow" { - var result: u16 = undefined; - try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result)); - try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result)); - try expect(result == 0b1011111111111100); -} - -test "@*WithOverflow with u0 values" { - var result: u0 = undefined; - try expect(!@addWithOverflow(u0, 0, 0, &result)); - try expect(!@subWithOverflow(u0, 0, 0, &result)); - try expect(!@mulWithOverflow(u0, 0, 0, &result)); - try expect(!@shlWithOverflow(u0, 0, 0, &result)); -} - -test "@clz" { - try testClz(); - comptime try testClz(); -} - -fn testClz() !void { - try expect(@clz(u8, 0b10001010) == 0); - try expect(@clz(u8, 0b00001010) == 4); - try expect(@clz(u8, 0b00011010) == 3); - try expect(@clz(u8, 0b00000000) == 8); - try expect(@clz(u128, 0xffffffffffffffff) == 64); - try expect(@clz(u128, 0x10000000000000000) == 63); -} - -test "@clz vectors" { - try testClzVectors(); - comptime try testClzVectors(); -} - -fn testClzVectors() !void { - @setEvalBranchQuota(10_000); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0))); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4))); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3))); - try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); - try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64))); - try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63))); -} - -test "@ctz" { - try testCtz(); - comptime try testCtz(); -} - -fn testCtz() !void { - try expect(@ctz(u8, 0b10100000) == 5); - try expect(@ctz(u8, 0b10001010) == 1); - try expect(@ctz(u8, 0b00000000) == 8); - try expect(@ctz(u16, 0b00000000) == 16); -} - -test "@ctz vectors" { - try testClzVectors(); - comptime try testClzVectors(); -} - -fn testCtzVectors() !void { - @setEvalBranchQuota(10_000); - try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5))); - try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1))); - try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); - try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16))); -} - test "assignment operators" { var i: u32 = 0; i += 5; @@ -218,686 +53,3 @@ fn testThreeExprInARow(f: bool, t: bool) !void { fn assertFalse(b: bool) !void { try expect(!b); } - -test "const number literal" { - const one = 1; - const eleven = ten + one; - - try expect(eleven == 11); -} -const ten = 10; - -test "unsigned wrapping" { - try testUnsignedWrappingEval(maxInt(u32)); - comptime try testUnsignedWrappingEval(maxInt(u32)); -} -fn testUnsignedWrappingEval(x: u32) !void { - const zero = x +% 1; - try expect(zero == 0); - const orig = zero -% 1; - try expect(orig == maxInt(u32)); -} - -test "signed wrapping" { - try testSignedWrappingEval(maxInt(i32)); - comptime try testSignedWrappingEval(maxInt(i32)); -} -fn testSignedWrappingEval(x: i32) !void { - const min_val = x +% 1; - try expect(min_val == minInt(i32)); - const max_val = min_val -% 1; - try expect(max_val == maxInt(i32)); -} - -test "signed negation wrapping" { - try testSignedNegationWrappingEval(minInt(i16)); - comptime try testSignedNegationWrappingEval(minInt(i16)); -} -fn testSignedNegationWrappingEval(x: i16) !void { - try expect(x == -32768); - const neg = -%x; - try expect(neg == -32768); -} - -test "unsigned negation wrapping" { - try testUnsignedNegationWrappingEval(1); - comptime try testUnsignedNegationWrappingEval(1); -} -fn testUnsignedNegationWrappingEval(x: u16) !void { - try expect(x == 1); - const neg = -%x; - try expect(neg == maxInt(u16)); -} - -test "unsigned 64-bit division" { - try test_u64_div(); - comptime try test_u64_div(); -} -fn test_u64_div() !void { - const result = divWithResult(1152921504606846976, 34359738365); - try expect(result.quotient == 33554432); - try expect(result.remainder == 100663296); -} -fn divWithResult(a: u64, b: u64) DivResult { - return DivResult{ - .quotient = a / b, - .remainder = a % b, - }; -} -const DivResult = struct { - quotient: u64, - remainder: u64, -}; - -test "binary not" { - try expect(comptime x: { - break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101; - }); - try expect(comptime x: { - break :x ~@as(u64, 2147483647) == 18446744071562067968; - }); - try testBinaryNot(0b1010101010101010); -} - -fn testBinaryNot(x: u16) !void { - try expect(~x == 0b0101010101010101); -} - -test "small int addition" { - var x: u2 = 0; - try expect(x == 0); - - x += 1; - try expect(x == 1); - - x += 1; - try expect(x == 2); - - x += 1; - try expect(x == 3); - - var result: @TypeOf(x) = 3; - try expect(@addWithOverflow(@TypeOf(x), x, 1, &result)); - - try expect(result == 0); -} - -test "float equality" { - const x: f64 = 0.012; - const y: f64 = x + 1.0; - - try testFloatEqualityImpl(x, y); - comptime try testFloatEqualityImpl(x, y); -} - -fn testFloatEqualityImpl(x: f64, y: f64) !void { - const y2 = x + 1.0; - try expect(y == y2); -} - -test "allow signed integer division/remainder when values are comptime known and positive or exact" { - try expect(5 / 3 == 1); - try expect(-5 / -3 == 1); - try expect(-6 / 3 == -2); - - try expect(5 % 3 == 2); - try expect(-6 % 3 == 0); -} - -test "hex float literal parsing" { - comptime try expect(0x1.0 == 1.0); -} - -test "quad hex float literal parsing in range" { - const a = 0x1.af23456789bbaaab347645365cdep+5; - const b = 0x1.dedafcff354b6ae9758763545432p-9; - const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534; - const d = 0x1.edcbff8ad76ab5bf46463233214fp-435; - if (false) { - a; - b; - c; - d; - } -} - -test "quad hex float literal parsing accurate" { - const a: f128 = 0x1.1111222233334444555566667777p+0; - - // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing. - const expected: u128 = 0x3fff1111222233334444555566667777; - try expect(@bitCast(u128, a) == expected); - - // non-normalized - const b: f128 = 0x11.111222233334444555566667777p-4; - try expect(@bitCast(u128, b) == expected); - - const S = struct { - fn doTheTest() !void { - { - var f: f128 = 0x1.2eab345678439abcdefea56782346p+5; - try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234); - } - { - var f: f128 = 0x1.edcb34a235253948765432134674fp-1; - try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674); - } - { - var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50; - try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50); - } - { - var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9; - try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568); - } - const exp2ft = [_]f64{ - 0x1.6a09e667f3bcdp-1, - 0x1.7a11473eb0187p-1, - 0x1.8ace5422aa0dbp-1, - 0x1.9c49182a3f090p-1, - 0x1.ae89f995ad3adp-1, - 0x1.c199bdd85529cp-1, - 0x1.d5818dcfba487p-1, - 0x1.ea4afa2a490dap-1, - 0x1.0000000000000p+0, - 0x1.0b5586cf9890fp+0, - 0x1.172b83c7d517bp+0, - 0x1.2387a6e756238p+0, - 0x1.306fe0a31b715p+0, - 0x1.3dea64c123422p+0, - 0x1.4bfdad5362a27p+0, - 0x1.5ab07dd485429p+0, - 0x1.8p23, - 0x1.62e430p-1, - 0x1.ebfbe0p-3, - 0x1.c6b348p-5, - 0x1.3b2c9cp-7, - 0x1.0p127, - -0x1.0p-149, - }; - - const answers = [_]u64{ - 0x3fe6a09e667f3bcd, - 0x3fe7a11473eb0187, - 0x3fe8ace5422aa0db, - 0x3fe9c49182a3f090, - 0x3feae89f995ad3ad, - 0x3fec199bdd85529c, - 0x3fed5818dcfba487, - 0x3feea4afa2a490da, - 0x3ff0000000000000, - 0x3ff0b5586cf9890f, - 0x3ff172b83c7d517b, - 0x3ff2387a6e756238, - 0x3ff306fe0a31b715, - 0x3ff3dea64c123422, - 0x3ff4bfdad5362a27, - 0x3ff5ab07dd485429, - 0x4168000000000000, - 0x3fe62e4300000000, - 0x3fcebfbe00000000, - 0x3fac6b3480000000, - 0x3f83b2c9c0000000, - 0x47e0000000000000, - 0xb6a0000000000000, - }; - - for (exp2ft) |x, i| { - try expect(@bitCast(u64, x) == answers[i]); - } - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "underscore separator parsing" { - try expect(0_0_0_0 == 0); - try expect(1_234_567 == 1234567); - try expect(001_234_567 == 1234567); - try expect(0_0_1_2_3_4_5_6_7 == 1234567); - - try expect(0b0_0_0_0 == 0); - try expect(0b1010_1010 == 0b10101010); - try expect(0b0000_1010_1010 == 0b10101010); - try expect(0b1_0_1_0_1_0_1_0 == 0b10101010); - - try expect(0o0_0_0_0 == 0); - try expect(0o1010_1010 == 0o10101010); - try expect(0o0000_1010_1010 == 0o10101010); - try expect(0o1_0_1_0_1_0_1_0 == 0o10101010); - - try expect(0x0_0_0_0 == 0); - try expect(0x1010_1010 == 0x10101010); - try expect(0x0000_1010_1010 == 0x10101010); - try expect(0x1_0_1_0_1_0_1_0 == 0x10101010); - - try expect(123_456.789_000e1_0 == 123456.789000e10); - try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10); - - try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10); - try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10); -} - -test "hex float literal within range" { - const a = 0x1.0p16383; - const b = 0x0.1p16387; - const c = 0x1.0p-16382; - if (false) { - a; - b; - c; - } -} - -test "truncating shift left" { - try testShlTrunc(maxInt(u16)); - comptime try testShlTrunc(maxInt(u16)); -} -fn testShlTrunc(x: u16) !void { - const shifted = x << 1; - try expect(shifted == 65534); -} - -test "truncating shift right" { - try testShrTrunc(maxInt(u16)); - comptime try testShrTrunc(maxInt(u16)); -} -fn testShrTrunc(x: u16) !void { - const shifted = x >> 1; - try expect(shifted == 32767); -} - -test "exact shift left" { - try testShlExact(0b00110101); - comptime try testShlExact(0b00110101); -} -fn testShlExact(x: u8) !void { - const shifted = @shlExact(x, 2); - try expect(shifted == 0b11010100); -} - -test "exact shift right" { - try testShrExact(0b10110100); - comptime try testShrExact(0b10110100); -} -fn testShrExact(x: u8) !void { - const shifted = @shrExact(x, 2); - try expect(shifted == 0b00101101); -} - -test "shift left/right on u0 operand" { - const S = struct { - fn doTheTest() !void { - var x: u0 = 0; - var y: u0 = 0; - try expectEqual(@as(u0, 0), x << 0); - try expectEqual(@as(u0, 0), x >> 0); - try expectEqual(@as(u0, 0), x << y); - try expectEqual(@as(u0, 0), x >> y); - try expectEqual(@as(u0, 0), @shlExact(x, 0)); - try expectEqual(@as(u0, 0), @shrExact(x, 0)); - try expectEqual(@as(u0, 0), @shlExact(x, y)); - try expectEqual(@as(u0, 0), @shrExact(x, y)); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "comptime_int addition" { - comptime { - try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); - try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380); - } -} - -test "comptime_int multiplication" { - comptime { - try expect( - 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567, - ); - try expect( - 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016, - ); - } -} - -test "comptime_int shifting" { - comptime { - try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000); - } -} - -test "comptime_int multi-limb shift and mask" { - comptime { - var a = 0xefffffffa0000001eeeeeeefaaaaaaab; - - try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xa0000001); - a >>= 32; - try expect(@as(u32, a & 0xffffffff) == 0xefffffff); - a >>= 32; - - try expect(a == 0); - } -} - -test "comptime_int multi-limb partial shift right" { - comptime { - var a = 0x1ffffffffeeeeeeee; - a >>= 16; - try expect(a == 0x1ffffffffeeee); - } -} - -test "xor" { - try test_xor(); - comptime try test_xor(); -} - -fn test_xor() !void { - try expect(0xFF ^ 0x00 == 0xFF); - try expect(0xF0 ^ 0x0F == 0xFF); - try expect(0xFF ^ 0xF0 == 0x0F); - try expect(0xFF ^ 0x0F == 0xF0); - try expect(0xFF ^ 0xFF == 0x00); -} - -test "comptime_int xor" { - comptime { - try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF); - try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000); - try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000); - try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF); - try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000); - } -} - -test "f128" { - try test_f128(); - comptime try test_f128(); -} - -fn make_f128(x: f128) f128 { - return x; -} - -fn test_f128() !void { - try expect(@sizeOf(f128) == 16); - try expect(make_f128(1.0) == 1.0); - try expect(make_f128(1.0) != 1.1); - try expect(make_f128(1.0) > 0.9); - try expect(make_f128(1.0) >= 0.9); - try expect(make_f128(1.0) >= 1.0); - try should_not_be_zero(1.0); -} - -fn should_not_be_zero(x: f128) !void { - try expect(x != 0.0); -} - -test "comptime float rem int" { - comptime { - var x = @as(f32, 1) % 2; - try expect(x == 1.0); - } -} - -test "remainder division" { - comptime try remdiv(f16); - comptime try remdiv(f32); - comptime try remdiv(f64); - comptime try remdiv(f128); - try remdiv(f16); - try remdiv(f64); - try remdiv(f128); -} - -fn remdiv(comptime T: type) !void { - try expect(@as(T, 1) == @as(T, 1) % @as(T, 2)); - try expect(@as(T, 1) == @as(T, 7) % @as(T, 3)); -} - -test "@sqrt" { - try testSqrt(f64, 12.0); - comptime try testSqrt(f64, 12.0); - try testSqrt(f32, 13.0); - comptime try testSqrt(f32, 13.0); - try testSqrt(f16, 13.0); - comptime try testSqrt(f16, 13.0); - - const x = 14.0; - const y = x * x; - const z = @sqrt(y); - comptime try expect(z == x); -} - -fn testSqrt(comptime T: type, x: T) !void { - try expect(@sqrt(x * x) == x); -} - -test "@fabs" { - try testFabs(f128, 12.0); - comptime try testFabs(f128, 12.0); - try testFabs(f64, 12.0); - comptime try testFabs(f64, 12.0); - try testFabs(f32, 12.0); - comptime try testFabs(f32, 12.0); - try testFabs(f16, 12.0); - comptime try testFabs(f16, 12.0); - - const x = 14.0; - const y = -x; - const z = @fabs(y); - comptime try expectEqual(x, z); -} - -fn testFabs(comptime T: type, x: T) !void { - const y = -x; - const z = @fabs(y); - try expectEqual(x, z); -} - -test "@floor" { - // FIXME: Generates a floorl function call - // testFloor(f128, 12.0); - comptime try testFloor(f128, 12.0); - try testFloor(f64, 12.0); - comptime try testFloor(f64, 12.0); - try testFloor(f32, 12.0); - comptime try testFloor(f32, 12.0); - try testFloor(f16, 12.0); - comptime try testFloor(f16, 12.0); - - const x = 14.0; - const y = x + 0.7; - const z = @floor(y); - comptime try expectEqual(x, z); -} - -fn testFloor(comptime T: type, x: T) !void { - const y = x + 0.6; - const z = @floor(y); - try expectEqual(x, z); -} - -test "@ceil" { - // FIXME: Generates a ceill function call - //testCeil(f128, 12.0); - comptime try testCeil(f128, 12.0); - try testCeil(f64, 12.0); - comptime try testCeil(f64, 12.0); - try testCeil(f32, 12.0); - comptime try testCeil(f32, 12.0); - try testCeil(f16, 12.0); - comptime try testCeil(f16, 12.0); - - const x = 14.0; - const y = x - 0.7; - const z = @ceil(y); - comptime try expectEqual(x, z); -} - -fn testCeil(comptime T: type, x: T) !void { - const y = x - 0.8; - const z = @ceil(y); - try expectEqual(x, z); -} - -test "@trunc" { - // FIXME: Generates a truncl function call - //testTrunc(f128, 12.0); - comptime try testTrunc(f128, 12.0); - try testTrunc(f64, 12.0); - comptime try testTrunc(f64, 12.0); - try testTrunc(f32, 12.0); - comptime try testTrunc(f32, 12.0); - try testTrunc(f16, 12.0); - comptime try testTrunc(f16, 12.0); - - const x = 14.0; - const y = x + 0.7; - const z = @trunc(y); - comptime try expectEqual(x, z); -} - -fn testTrunc(comptime T: type, x: T) !void { - { - const y = x + 0.8; - const z = @trunc(y); - try expectEqual(x, z); - } - - { - const y = -x - 0.8; - const z = @trunc(y); - try expectEqual(-x, z); - } -} - -test "@round" { - // FIXME: Generates a roundl function call - //testRound(f128, 12.0); - comptime try testRound(f128, 12.0); - try testRound(f64, 12.0); - comptime try testRound(f64, 12.0); - try testRound(f32, 12.0); - comptime try testRound(f32, 12.0); - try testRound(f16, 12.0); - comptime try testRound(f16, 12.0); - - const x = 14.0; - const y = x + 0.4; - const z = @round(y); - comptime try expectEqual(x, z); -} - -fn testRound(comptime T: type, x: T) !void { - const y = x - 0.5; - const z = @round(y); - try expectEqual(x, z); -} - -test "comptime_int param and return" { - const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); - try expect(a == 137114567242441932203689521744947848950); - - const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768); - try expect(b == 985095453608931032642182098849559179469148836107390954364380); -} - -fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int { - return a + b; -} - -test "vector integer addition" { - const S = struct { - fn doTheTest() !void { - var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 }; - var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 }; - var result = a + b; - var result_array: [4]i32 = result; - const expected = [_]i32{ 6, 8, 10, 12 }; - try expectEqualSlices(i32, &expected, &result_array); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "NaN comparison" { - try testNanEqNan(f16); - try testNanEqNan(f32); - try testNanEqNan(f64); - try testNanEqNan(f128); - comptime try testNanEqNan(f16); - comptime try testNanEqNan(f32); - comptime try testNanEqNan(f64); - comptime try testNanEqNan(f128); -} - -fn testNanEqNan(comptime F: type) !void { - var nan1 = std.math.nan(F); - var nan2 = std.math.nan(F); - try expect(nan1 != nan2); - try expect(!(nan1 == nan2)); - try expect(!(nan1 > nan2)); - try expect(!(nan1 >= nan2)); - try expect(!(nan1 < nan2)); - try expect(!(nan1 <= nan2)); -} - -test "128-bit multiplication" { - var a: i128 = 3; - var b: i128 = 2; - var c = a * b; - try expect(c == 6); -} - -test "vector comparison" { - const S = struct { - fn doTheTest() !void { - var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 }; - var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 }; - try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false })); - try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false })); - try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false })); - try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true })); - try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true })); - try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true })); - } - }; - try S.doTheTest(); - comptime try S.doTheTest(); -} - -test "compare undefined literal with comptime_int" { - var x = undefined == 1; - // x is now undefined with type bool - x = true; - try expect(x); -} - -test "signed zeros are represented properly" { - const S = struct { - fn doTheTest() !void { - inline for ([_]type{ f16, f32, f64, f128 }) |T| { - const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); - var as_fp_val = -@as(T, 0.0); - var as_uint_val = @bitCast(ST, as_fp_val); - // Ensure the sign bit is set. - try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1); - } - } - }; - - try S.doTheTest(); - comptime try S.doTheTest(); -} diff --git a/test/behavior/math_stage1.zig b/test/behavior/math_stage1.zig new file mode 100644 index 0000000000..9f412930b5 --- /dev/null +++ b/test/behavior/math_stage1.zig @@ -0,0 +1,855 @@ +const std = @import("std"); +const expect = std.testing.expect; +const expectEqual = std.testing.expectEqual; +const expectEqualSlices = std.testing.expectEqualSlices; +const maxInt = std.math.maxInt; +const minInt = std.math.minInt; +const mem = std.mem; + +test "division" { + try testDivision(); + comptime try testDivision(); +} +fn testDivision() !void { + try expect(div(u32, 13, 3) == 4); + try expect(div(f16, 1.0, 2.0) == 0.5); + try expect(div(f32, 1.0, 2.0) == 0.5); + + try expect(divExact(u32, 55, 11) == 5); + try expect(divExact(i32, -55, 11) == -5); + try expect(divExact(f16, 55.0, 11.0) == 5.0); + try expect(divExact(f16, -55.0, 11.0) == -5.0); + try expect(divExact(f32, 55.0, 11.0) == 5.0); + try expect(divExact(f32, -55.0, 11.0) == -5.0); + + try expect(divFloor(i32, 5, 3) == 1); + try expect(divFloor(i32, -5, 3) == -2); + try expect(divFloor(f16, 5.0, 3.0) == 1.0); + try expect(divFloor(f16, -5.0, 3.0) == -2.0); + try expect(divFloor(f32, 5.0, 3.0) == 1.0); + try expect(divFloor(f32, -5.0, 3.0) == -2.0); + try expect(divFloor(i32, -0x80000000, -2) == 0x40000000); + try expect(divFloor(i32, 0, -0x80000000) == 0); + try expect(divFloor(i32, -0x40000001, 0x40000000) == -2); + try expect(divFloor(i32, -0x80000000, 1) == -0x80000000); + try expect(divFloor(i32, 10, 12) == 0); + try expect(divFloor(i32, -14, 12) == -2); + try expect(divFloor(i32, -2, 12) == -1); + + try expect(divTrunc(i32, 5, 3) == 1); + try expect(divTrunc(i32, -5, 3) == -1); + try expect(divTrunc(f16, 5.0, 3.0) == 1.0); + try expect(divTrunc(f16, -5.0, 3.0) == -1.0); + try expect(divTrunc(f32, 5.0, 3.0) == 1.0); + try expect(divTrunc(f32, -5.0, 3.0) == -1.0); + try expect(divTrunc(f64, 5.0, 3.0) == 1.0); + try expect(divTrunc(f64, -5.0, 3.0) == -1.0); + try expect(divTrunc(i32, 10, 12) == 0); + try expect(divTrunc(i32, -14, 12) == -1); + try expect(divTrunc(i32, -2, 12) == 0); + + try expect(mod(i32, 10, 12) == 10); + try expect(mod(i32, -14, 12) == 10); + try expect(mod(i32, -2, 12) == 10); + + comptime { + try expect( + 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600, + ); + try expect( + @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600, + ); + try expect( + 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2, + ); + try expect( + @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2, + ); + try expect( + @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2, + ); + try expect( + @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2, + ); + try expect( + 4126227191251978491697987544882340798050766755606969681711 % 10 == 1, + ); + } +} +fn div(comptime T: type, a: T, b: T) T { + return a / b; +} +fn divExact(comptime T: type, a: T, b: T) T { + return @divExact(a, b); +} +fn divFloor(comptime T: type, a: T, b: T) T { + return @divFloor(a, b); +} +fn divTrunc(comptime T: type, a: T, b: T) T { + return @divTrunc(a, b); +} +fn mod(comptime T: type, a: T, b: T) T { + return @mod(a, b); +} + +test "@addWithOverflow" { + var result: u8 = undefined; + try expect(@addWithOverflow(u8, 250, 100, &result)); + try expect(!@addWithOverflow(u8, 100, 150, &result)); + try expect(result == 250); +} + +// TODO test mulWithOverflow +// TODO test subWithOverflow + +test "@shlWithOverflow" { + var result: u16 = undefined; + try expect(@shlWithOverflow(u16, 0b0010111111111111, 3, &result)); + try expect(!@shlWithOverflow(u16, 0b0010111111111111, 2, &result)); + try expect(result == 0b1011111111111100); +} + +test "@*WithOverflow with u0 values" { + var result: u0 = undefined; + try expect(!@addWithOverflow(u0, 0, 0, &result)); + try expect(!@subWithOverflow(u0, 0, 0, &result)); + try expect(!@mulWithOverflow(u0, 0, 0, &result)); + try expect(!@shlWithOverflow(u0, 0, 0, &result)); +} + +test "@clz" { + try testClz(); + comptime try testClz(); +} + +fn testClz() !void { + try expect(@clz(u8, 0b10001010) == 0); + try expect(@clz(u8, 0b00001010) == 4); + try expect(@clz(u8, 0b00011010) == 3); + try expect(@clz(u8, 0b00000000) == 8); + try expect(@clz(u128, 0xffffffffffffffff) == 64); + try expect(@clz(u128, 0x10000000000000000) == 63); +} + +test "@clz vectors" { + try testClzVectors(); + comptime try testClzVectors(); +} + +fn testClzVectors() !void { + @setEvalBranchQuota(10_000); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 0))); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00001010))), @splat(64, @as(u4, 4))); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00011010))), @splat(64, @as(u4, 3))); + try expectEqual(@clz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); + try expectEqual(@clz(u128, @splat(64, @as(u128, 0xffffffffffffffff))), @splat(64, @as(u8, 64))); + try expectEqual(@clz(u128, @splat(64, @as(u128, 0x10000000000000000))), @splat(64, @as(u8, 63))); +} + +test "@ctz" { + try testCtz(); + comptime try testCtz(); +} + +fn testCtz() !void { + try expect(@ctz(u8, 0b10100000) == 5); + try expect(@ctz(u8, 0b10001010) == 1); + try expect(@ctz(u8, 0b00000000) == 8); + try expect(@ctz(u16, 0b00000000) == 16); +} + +test "@ctz vectors" { + try testClzVectors(); + comptime try testClzVectors(); +} + +fn testCtzVectors() !void { + @setEvalBranchQuota(10_000); + try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10100000))), @splat(64, @as(u4, 5))); + try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b10001010))), @splat(64, @as(u4, 1))); + try expectEqual(@ctz(u8, @splat(64, @as(u8, 0b00000000))), @splat(64, @as(u4, 8))); + try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16))); +} + +test "const number literal" { + const one = 1; + const eleven = ten + one; + + try expect(eleven == 11); +} +const ten = 10; + +test "unsigned wrapping" { + try testUnsignedWrappingEval(maxInt(u32)); + comptime try testUnsignedWrappingEval(maxInt(u32)); +} +fn testUnsignedWrappingEval(x: u32) !void { + const zero = x +% 1; + try expect(zero == 0); + const orig = zero -% 1; + try expect(orig == maxInt(u32)); +} + +test "signed wrapping" { + try testSignedWrappingEval(maxInt(i32)); + comptime try testSignedWrappingEval(maxInt(i32)); +} +fn testSignedWrappingEval(x: i32) !void { + const min_val = x +% 1; + try expect(min_val == minInt(i32)); + const max_val = min_val -% 1; + try expect(max_val == maxInt(i32)); +} + +test "signed negation wrapping" { + try testSignedNegationWrappingEval(minInt(i16)); + comptime try testSignedNegationWrappingEval(minInt(i16)); +} +fn testSignedNegationWrappingEval(x: i16) !void { + try expect(x == -32768); + const neg = -%x; + try expect(neg == -32768); +} + +test "unsigned negation wrapping" { + try testUnsignedNegationWrappingEval(1); + comptime try testUnsignedNegationWrappingEval(1); +} +fn testUnsignedNegationWrappingEval(x: u16) !void { + try expect(x == 1); + const neg = -%x; + try expect(neg == maxInt(u16)); +} + +test "unsigned 64-bit division" { + try test_u64_div(); + comptime try test_u64_div(); +} +fn test_u64_div() !void { + const result = divWithResult(1152921504606846976, 34359738365); + try expect(result.quotient == 33554432); + try expect(result.remainder == 100663296); +} +fn divWithResult(a: u64, b: u64) DivResult { + return DivResult{ + .quotient = a / b, + .remainder = a % b, + }; +} +const DivResult = struct { + quotient: u64, + remainder: u64, +}; + +test "binary not" { + try expect(comptime x: { + break :x ~@as(u16, 0b1010101010101010) == 0b0101010101010101; + }); + try expect(comptime x: { + break :x ~@as(u64, 2147483647) == 18446744071562067968; + }); + try testBinaryNot(0b1010101010101010); +} + +fn testBinaryNot(x: u16) !void { + try expect(~x == 0b0101010101010101); +} + +test "small int addition" { + var x: u2 = 0; + try expect(x == 0); + + x += 1; + try expect(x == 1); + + x += 1; + try expect(x == 2); + + x += 1; + try expect(x == 3); + + var result: @TypeOf(x) = 3; + try expect(@addWithOverflow(@TypeOf(x), x, 1, &result)); + + try expect(result == 0); +} + +test "float equality" { + const x: f64 = 0.012; + const y: f64 = x + 1.0; + + try testFloatEqualityImpl(x, y); + comptime try testFloatEqualityImpl(x, y); +} + +fn testFloatEqualityImpl(x: f64, y: f64) !void { + const y2 = x + 1.0; + try expect(y == y2); +} + +test "allow signed integer division/remainder when values are comptime known and positive or exact" { + try expect(5 / 3 == 1); + try expect(-5 / -3 == 1); + try expect(-6 / 3 == -2); + + try expect(5 % 3 == 2); + try expect(-6 % 3 == 0); +} + +test "hex float literal parsing" { + comptime try expect(0x1.0 == 1.0); +} + +test "quad hex float literal parsing in range" { + const a = 0x1.af23456789bbaaab347645365cdep+5; + const b = 0x1.dedafcff354b6ae9758763545432p-9; + const c = 0x1.2f34dd5f437e849b4baab754cdefp+4534; + const d = 0x1.edcbff8ad76ab5bf46463233214fp-435; + if (false) { + a; + b; + c; + d; + } +} + +test "quad hex float literal parsing accurate" { + const a: f128 = 0x1.1111222233334444555566667777p+0; + + // implied 1 is dropped, with an exponent of 0 (0x3fff) after biasing. + const expected: u128 = 0x3fff1111222233334444555566667777; + try expect(@bitCast(u128, a) == expected); + + // non-normalized + const b: f128 = 0x11.111222233334444555566667777p-4; + try expect(@bitCast(u128, b) == expected); + + const S = struct { + fn doTheTest() !void { + { + var f: f128 = 0x1.2eab345678439abcdefea56782346p+5; + try expect(@bitCast(u128, f) == 0x40042eab345678439abcdefea5678234); + } + { + var f: f128 = 0x1.edcb34a235253948765432134674fp-1; + try expect(@bitCast(u128, f) == 0x3ffeedcb34a235253948765432134674); + } + { + var f: f128 = 0x1.353e45674d89abacc3a2ebf3ff4ffp-50; + try expect(@bitCast(u128, f) == 0x3fcd353e45674d89abacc3a2ebf3ff50); + } + { + var f: f128 = 0x1.ed8764648369535adf4be3214567fp-9; + try expect(@bitCast(u128, f) == 0x3ff6ed8764648369535adf4be3214568); + } + const exp2ft = [_]f64{ + 0x1.6a09e667f3bcdp-1, + 0x1.7a11473eb0187p-1, + 0x1.8ace5422aa0dbp-1, + 0x1.9c49182a3f090p-1, + 0x1.ae89f995ad3adp-1, + 0x1.c199bdd85529cp-1, + 0x1.d5818dcfba487p-1, + 0x1.ea4afa2a490dap-1, + 0x1.0000000000000p+0, + 0x1.0b5586cf9890fp+0, + 0x1.172b83c7d517bp+0, + 0x1.2387a6e756238p+0, + 0x1.306fe0a31b715p+0, + 0x1.3dea64c123422p+0, + 0x1.4bfdad5362a27p+0, + 0x1.5ab07dd485429p+0, + 0x1.8p23, + 0x1.62e430p-1, + 0x1.ebfbe0p-3, + 0x1.c6b348p-5, + 0x1.3b2c9cp-7, + 0x1.0p127, + -0x1.0p-149, + }; + + const answers = [_]u64{ + 0x3fe6a09e667f3bcd, + 0x3fe7a11473eb0187, + 0x3fe8ace5422aa0db, + 0x3fe9c49182a3f090, + 0x3feae89f995ad3ad, + 0x3fec199bdd85529c, + 0x3fed5818dcfba487, + 0x3feea4afa2a490da, + 0x3ff0000000000000, + 0x3ff0b5586cf9890f, + 0x3ff172b83c7d517b, + 0x3ff2387a6e756238, + 0x3ff306fe0a31b715, + 0x3ff3dea64c123422, + 0x3ff4bfdad5362a27, + 0x3ff5ab07dd485429, + 0x4168000000000000, + 0x3fe62e4300000000, + 0x3fcebfbe00000000, + 0x3fac6b3480000000, + 0x3f83b2c9c0000000, + 0x47e0000000000000, + 0xb6a0000000000000, + }; + + for (exp2ft) |x, i| { + try expect(@bitCast(u64, x) == answers[i]); + } + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "underscore separator parsing" { + try expect(0_0_0_0 == 0); + try expect(1_234_567 == 1234567); + try expect(001_234_567 == 1234567); + try expect(0_0_1_2_3_4_5_6_7 == 1234567); + + try expect(0b0_0_0_0 == 0); + try expect(0b1010_1010 == 0b10101010); + try expect(0b0000_1010_1010 == 0b10101010); + try expect(0b1_0_1_0_1_0_1_0 == 0b10101010); + + try expect(0o0_0_0_0 == 0); + try expect(0o1010_1010 == 0o10101010); + try expect(0o0000_1010_1010 == 0o10101010); + try expect(0o1_0_1_0_1_0_1_0 == 0o10101010); + + try expect(0x0_0_0_0 == 0); + try expect(0x1010_1010 == 0x10101010); + try expect(0x0000_1010_1010 == 0x10101010); + try expect(0x1_0_1_0_1_0_1_0 == 0x10101010); + + try expect(123_456.789_000e1_0 == 123456.789000e10); + try expect(0_1_2_3_4_5_6.7_8_9_0_0_0e0_0_1_0 == 123456.789000e10); + + try expect(0x1234_5678.9ABC_DEF0p-1_0 == 0x12345678.9ABCDEF0p-10); + try expect(0x1_2_3_4_5_6_7_8.9_A_B_C_D_E_F_0p-0_0_0_1_0 == 0x12345678.9ABCDEF0p-10); +} + +test "hex float literal within range" { + const a = 0x1.0p16383; + const b = 0x0.1p16387; + const c = 0x1.0p-16382; + if (false) { + a; + b; + c; + } +} + +test "truncating shift left" { + try testShlTrunc(maxInt(u16)); + comptime try testShlTrunc(maxInt(u16)); +} +fn testShlTrunc(x: u16) !void { + const shifted = x << 1; + try expect(shifted == 65534); +} + +test "truncating shift right" { + try testShrTrunc(maxInt(u16)); + comptime try testShrTrunc(maxInt(u16)); +} +fn testShrTrunc(x: u16) !void { + const shifted = x >> 1; + try expect(shifted == 32767); +} + +test "exact shift left" { + try testShlExact(0b00110101); + comptime try testShlExact(0b00110101); +} +fn testShlExact(x: u8) !void { + const shifted = @shlExact(x, 2); + try expect(shifted == 0b11010100); +} + +test "exact shift right" { + try testShrExact(0b10110100); + comptime try testShrExact(0b10110100); +} +fn testShrExact(x: u8) !void { + const shifted = @shrExact(x, 2); + try expect(shifted == 0b00101101); +} + +test "shift left/right on u0 operand" { + const S = struct { + fn doTheTest() !void { + var x: u0 = 0; + var y: u0 = 0; + try expectEqual(@as(u0, 0), x << 0); + try expectEqual(@as(u0, 0), x >> 0); + try expectEqual(@as(u0, 0), x << y); + try expectEqual(@as(u0, 0), x >> y); + try expectEqual(@as(u0, 0), @shlExact(x, 0)); + try expectEqual(@as(u0, 0), @shrExact(x, 0)); + try expectEqual(@as(u0, 0), @shlExact(x, y)); + try expectEqual(@as(u0, 0), @shrExact(x, y)); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "comptime_int addition" { + comptime { + try expect(35361831660712422535336160538497375248 + 101752735581729509668353361206450473702 == 137114567242441932203689521744947848950); + try expect(594491908217841670578297176641415611445982232488944558774612 + 390603545391089362063884922208143568023166603618446395589768 == 985095453608931032642182098849559179469148836107390954364380); + } +} + +test "comptime_int multiplication" { + comptime { + try expect( + 45960427431263824329884196484953148229 * 128339149605334697009938835852565949723 == 5898522172026096622534201617172456926982464453350084962781392314016180490567, + ); + try expect( + 594491908217841670578297176641415611445982232488944558774612 * 390603545391089362063884922208143568023166603618446395589768 == 232210647056203049913662402532976186578842425262306016094292237500303028346593132411865381225871291702600263463125370016, + ); + } +} + +test "comptime_int shifting" { + comptime { + try expect((@as(u128, 1) << 127) == 0x80000000000000000000000000000000); + } +} + +test "comptime_int multi-limb shift and mask" { + comptime { + var a = 0xefffffffa0000001eeeeeeefaaaaaaab; + + try expect(@as(u32, a & 0xffffffff) == 0xaaaaaaab); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xeeeeeeef); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xa0000001); + a >>= 32; + try expect(@as(u32, a & 0xffffffff) == 0xefffffff); + a >>= 32; + + try expect(a == 0); + } +} + +test "comptime_int multi-limb partial shift right" { + comptime { + var a = 0x1ffffffffeeeeeeee; + a >>= 16; + try expect(a == 0x1ffffffffeeee); + } +} + +test "xor" { + try test_xor(); + comptime try test_xor(); +} + +fn test_xor() !void { + try expect(0xFF ^ 0x00 == 0xFF); + try expect(0xF0 ^ 0x0F == 0xFF); + try expect(0xFF ^ 0xF0 == 0x0F); + try expect(0xFF ^ 0x0F == 0xF0); + try expect(0xFF ^ 0xFF == 0x00); +} + +test "comptime_int xor" { + comptime { + try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0x00000000000000000000000000000000 == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0x0000000000000000FFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFFFFFFFFFF0000000000000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x0000000000000000FFFFFFFFFFFFFFFF); + try expect(0x0000000000000000FFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFFFFFFFFFF0000000000000000); + try expect(0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000000000000000000000000000); + try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0x00000000FFFFFFFF00000000FFFFFFFF == 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + try expect(0xFFFFFFFF00000000FFFFFFFF00000000 ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0x00000000FFFFFFFF00000000FFFFFFFF); + try expect(0x00000000FFFFFFFF00000000FFFFFFFF ^ 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF == 0xFFFFFFFF00000000FFFFFFFF00000000); + } +} + +test "f128" { + try test_f128(); + comptime try test_f128(); +} + +fn make_f128(x: f128) f128 { + return x; +} + +fn test_f128() !void { + try expect(@sizeOf(f128) == 16); + try expect(make_f128(1.0) == 1.0); + try expect(make_f128(1.0) != 1.1); + try expect(make_f128(1.0) > 0.9); + try expect(make_f128(1.0) >= 0.9); + try expect(make_f128(1.0) >= 1.0); + try should_not_be_zero(1.0); +} + +fn should_not_be_zero(x: f128) !void { + try expect(x != 0.0); +} + +test "comptime float rem int" { + comptime { + var x = @as(f32, 1) % 2; + try expect(x == 1.0); + } +} + +test "remainder division" { + comptime try remdiv(f16); + comptime try remdiv(f32); + comptime try remdiv(f64); + comptime try remdiv(f128); + try remdiv(f16); + try remdiv(f64); + try remdiv(f128); +} + +fn remdiv(comptime T: type) !void { + try expect(@as(T, 1) == @as(T, 1) % @as(T, 2)); + try expect(@as(T, 1) == @as(T, 7) % @as(T, 3)); +} + +test "@sqrt" { + try testSqrt(f64, 12.0); + comptime try testSqrt(f64, 12.0); + try testSqrt(f32, 13.0); + comptime try testSqrt(f32, 13.0); + try testSqrt(f16, 13.0); + comptime try testSqrt(f16, 13.0); + + const x = 14.0; + const y = x * x; + const z = @sqrt(y); + comptime try expect(z == x); +} + +fn testSqrt(comptime T: type, x: T) !void { + try expect(@sqrt(x * x) == x); +} + +test "@fabs" { + try testFabs(f128, 12.0); + comptime try testFabs(f128, 12.0); + try testFabs(f64, 12.0); + comptime try testFabs(f64, 12.0); + try testFabs(f32, 12.0); + comptime try testFabs(f32, 12.0); + try testFabs(f16, 12.0); + comptime try testFabs(f16, 12.0); + + const x = 14.0; + const y = -x; + const z = @fabs(y); + comptime try expectEqual(x, z); +} + +fn testFabs(comptime T: type, x: T) !void { + const y = -x; + const z = @fabs(y); + try expectEqual(x, z); +} + +test "@floor" { + // FIXME: Generates a floorl function call + // testFloor(f128, 12.0); + comptime try testFloor(f128, 12.0); + try testFloor(f64, 12.0); + comptime try testFloor(f64, 12.0); + try testFloor(f32, 12.0); + comptime try testFloor(f32, 12.0); + try testFloor(f16, 12.0); + comptime try testFloor(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @floor(y); + comptime try expectEqual(x, z); +} + +fn testFloor(comptime T: type, x: T) !void { + const y = x + 0.6; + const z = @floor(y); + try expectEqual(x, z); +} + +test "@ceil" { + // FIXME: Generates a ceill function call + //testCeil(f128, 12.0); + comptime try testCeil(f128, 12.0); + try testCeil(f64, 12.0); + comptime try testCeil(f64, 12.0); + try testCeil(f32, 12.0); + comptime try testCeil(f32, 12.0); + try testCeil(f16, 12.0); + comptime try testCeil(f16, 12.0); + + const x = 14.0; + const y = x - 0.7; + const z = @ceil(y); + comptime try expectEqual(x, z); +} + +fn testCeil(comptime T: type, x: T) !void { + const y = x - 0.8; + const z = @ceil(y); + try expectEqual(x, z); +} + +test "@trunc" { + // FIXME: Generates a truncl function call + //testTrunc(f128, 12.0); + comptime try testTrunc(f128, 12.0); + try testTrunc(f64, 12.0); + comptime try testTrunc(f64, 12.0); + try testTrunc(f32, 12.0); + comptime try testTrunc(f32, 12.0); + try testTrunc(f16, 12.0); + comptime try testTrunc(f16, 12.0); + + const x = 14.0; + const y = x + 0.7; + const z = @trunc(y); + comptime try expectEqual(x, z); +} + +fn testTrunc(comptime T: type, x: T) !void { + { + const y = x + 0.8; + const z = @trunc(y); + try expectEqual(x, z); + } + + { + const y = -x - 0.8; + const z = @trunc(y); + try expectEqual(-x, z); + } +} + +test "@round" { + // FIXME: Generates a roundl function call + //testRound(f128, 12.0); + comptime try testRound(f128, 12.0); + try testRound(f64, 12.0); + comptime try testRound(f64, 12.0); + try testRound(f32, 12.0); + comptime try testRound(f32, 12.0); + try testRound(f16, 12.0); + comptime try testRound(f16, 12.0); + + const x = 14.0; + const y = x + 0.4; + const z = @round(y); + comptime try expectEqual(x, z); +} + +fn testRound(comptime T: type, x: T) !void { + const y = x - 0.5; + const z = @round(y); + try expectEqual(x, z); +} + +test "comptime_int param and return" { + const a = comptimeAdd(35361831660712422535336160538497375248, 101752735581729509668353361206450473702); + try expect(a == 137114567242441932203689521744947848950); + + const b = comptimeAdd(594491908217841670578297176641415611445982232488944558774612, 390603545391089362063884922208143568023166603618446395589768); + try expect(b == 985095453608931032642182098849559179469148836107390954364380); +} + +fn comptimeAdd(comptime a: comptime_int, comptime b: comptime_int) comptime_int { + return a + b; +} + +test "vector integer addition" { + const S = struct { + fn doTheTest() !void { + var a: std.meta.Vector(4, i32) = [_]i32{ 1, 2, 3, 4 }; + var b: std.meta.Vector(4, i32) = [_]i32{ 5, 6, 7, 8 }; + var result = a + b; + var result_array: [4]i32 = result; + const expected = [_]i32{ 6, 8, 10, 12 }; + try expectEqualSlices(i32, &expected, &result_array); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "NaN comparison" { + try testNanEqNan(f16); + try testNanEqNan(f32); + try testNanEqNan(f64); + try testNanEqNan(f128); + comptime try testNanEqNan(f16); + comptime try testNanEqNan(f32); + comptime try testNanEqNan(f64); + comptime try testNanEqNan(f128); +} + +fn testNanEqNan(comptime F: type) !void { + var nan1 = std.math.nan(F); + var nan2 = std.math.nan(F); + try expect(nan1 != nan2); + try expect(!(nan1 == nan2)); + try expect(!(nan1 > nan2)); + try expect(!(nan1 >= nan2)); + try expect(!(nan1 < nan2)); + try expect(!(nan1 <= nan2)); +} + +test "128-bit multiplication" { + var a: i128 = 3; + var b: i128 = 2; + var c = a * b; + try expect(c == 6); +} + +test "vector comparison" { + const S = struct { + fn doTheTest() !void { + var a: std.meta.Vector(6, i32) = [_]i32{ 1, 3, -1, 5, 7, 9 }; + var b: std.meta.Vector(6, i32) = [_]i32{ -1, 3, 0, 6, 10, -10 }; + try expect(mem.eql(bool, &@as([6]bool, a < b), &[_]bool{ false, false, true, true, true, false })); + try expect(mem.eql(bool, &@as([6]bool, a <= b), &[_]bool{ false, true, true, true, true, false })); + try expect(mem.eql(bool, &@as([6]bool, a == b), &[_]bool{ false, true, false, false, false, false })); + try expect(mem.eql(bool, &@as([6]bool, a != b), &[_]bool{ true, false, true, true, true, true })); + try expect(mem.eql(bool, &@as([6]bool, a > b), &[_]bool{ true, false, false, false, false, true })); + try expect(mem.eql(bool, &@as([6]bool, a >= b), &[_]bool{ true, true, false, false, false, true })); + } + }; + try S.doTheTest(); + comptime try S.doTheTest(); +} + +test "compare undefined literal with comptime_int" { + var x = undefined == 1; + // x is now undefined with type bool + x = true; + try expect(x); +} + +test "signed zeros are represented properly" { + const S = struct { + fn doTheTest() !void { + inline for ([_]type{ f16, f32, f64, f128 }) |T| { + const ST = std.meta.Int(.unsigned, @typeInfo(T).Float.bits); + var as_fp_val = -@as(T, 0.0); + var as_uint_val = @bitCast(ST, as_fp_val); + // Ensure the sign bit is set. + try expect(as_uint_val >> (@typeInfo(T).Float.bits - 1) == 1); + } + } + }; + + try S.doTheTest(); + comptime try S.doTheTest(); +}