mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 14:25:16 +00:00
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:
parent
1e805df81d
commit
79bc5891c1
11
src/Air.zig
11
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,
|
||||
|
||||
@ -232,6 +232,7 @@ fn analyzeInst(
|
||||
.mulwrap,
|
||||
.div,
|
||||
.rem,
|
||||
.mod,
|
||||
.ptr_add,
|
||||
.ptr_sub,
|
||||
.bit_and,
|
||||
|
||||
661
src/Sema.zig
661
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();
|
||||
|
||||
22
src/Zir.zig
22
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`.
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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, " > "),
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -110,6 +110,7 @@ const Writer = struct {
|
||||
.mulwrap,
|
||||
.div,
|
||||
.rem,
|
||||
.mod,
|
||||
.ptr_add,
|
||||
.ptr_sub,
|
||||
.bit_and,
|
||||
|
||||
138
src/value.zig
138
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,
|
||||
};
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
855
test/behavior/math_stage1.zig
Normal file
855
test/behavior/math_stage1.zig
Normal 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();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user