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
This commit is contained in:
Andrew Kelley 2021-09-28 15:45:58 -07:00
parent 1e805df81d
commit 79bc5891c1
12 changed files with 1605 additions and 975 deletions

View File

@ -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,

View File

@ -232,6 +232,7 @@ fn analyzeInst(
.mulwrap,
.div,
.rem,
.mod,
.ptr_add,
.ptr_sub,
.bit_and,

View File

@ -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();

View File

@ -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`.

View File

@ -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) {

View File

@ -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, " > "),

View File

@ -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;

View File

@ -110,6 +110,7 @@ const Writer = struct {
.mulwrap,
.div,
.rem,
.mod,
.ptr_add,
.ptr_sub,
.bit_and,

View File

@ -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,
};

View File

@ -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");

View File

@ -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();
}

View File

@ -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();
}