stage2: more division support

AIR:
 * div is renamed to div_trunc.
 * Add div_float, div_floor, div_exact.
   - Implemented in Sema and LLVM codegen. C backend has a stub.

Improvements to std.math.big.Int:
 * Add `eqZero` function to `Mutable`.
 * Fix incorrect results for `divFloor`.

Compiler-rt:
 * Add muloti4 to the stage2 section.
This commit is contained in:
Andrew Kelley
2021-10-21 19:05:26 -07:00
parent a3c9bfef30
commit 7f70c27e9d
24 changed files with 1027 additions and 557 deletions

View File

@@ -135,6 +135,11 @@ pub const Mutable = struct {
};
}
/// Returns true if `a == 0`.
pub fn eqZero(self: Mutable) bool {
return self.toConst().eqZero();
}
/// Asserts that the allocator owns the limbs memory. If this is not the case,
/// use `toConst().toManaged()`.
pub fn toManaged(self: Mutable, allocator: *Allocator) Managed {
@@ -773,12 +778,15 @@ pub const Mutable = struct {
div(q, r, a, b, limbs_buffer, allocator);
// Trunc -> Floor.
if (!q.positive) {
if (a.positive and b.positive) return;
if ((!q.positive or q.eqZero()) and !r.eqZero()) {
const one: Const = .{ .limbs = &[_]Limb{1}, .positive = true };
q.sub(q.toConst(), one);
r.add(q.toConst(), one);
}
r.positive = b.positive;
r.mulNoAlias(q.toConst(), b, allocator);
r.sub(a, r.toConst());
}
/// q = a / b (rem r)
@@ -1220,12 +1228,12 @@ pub const Mutable = struct {
var x: Mutable = .{
.limbs = x_limbs,
.positive = a.positive,
.positive = true,
.len = a.limbs.len - ab_zero_limb_count,
};
var y: Mutable = .{
.limbs = y_limbs,
.positive = b.positive,
.positive = true,
.len = b.limbs.len - ab_zero_limb_count,
};

View File

@@ -1399,6 +1399,63 @@ test "big.int div floor single-single -/-" {
try testing.expect((try r.to(i32)) == er);
}
test "big.int div floor no remainder negative quotient" {
const u: i32 = -0x80000000;
const v: i32 = 1;
var a = try Managed.initSet(testing.allocator, u);
defer a.deinit();
var b = try Managed.initSet(testing.allocator, v);
defer b.deinit();
var q = try Managed.init(testing.allocator);
defer q.deinit();
var r = try Managed.init(testing.allocator);
defer r.deinit();
try Managed.divFloor(&q, &r, a.toConst(), b.toConst());
try testing.expect((try q.to(i32)) == -0x80000000);
try testing.expect((try r.to(i32)) == 0);
}
test "big.int div floor negative close to zero" {
const u: i32 = -2;
const v: i32 = 12;
var a = try Managed.initSet(testing.allocator, u);
defer a.deinit();
var b = try Managed.initSet(testing.allocator, v);
defer b.deinit();
var q = try Managed.init(testing.allocator);
defer q.deinit();
var r = try Managed.init(testing.allocator);
defer r.deinit();
try Managed.divFloor(&q, &r, a.toConst(), b.toConst());
try testing.expect((try q.to(i32)) == -1);
try testing.expect((try r.to(i32)) == 10);
}
test "big.int div floor positive close to zero" {
const u: i32 = 10;
const v: i32 = 12;
var a = try Managed.initSet(testing.allocator, u);
defer a.deinit();
var b = try Managed.initSet(testing.allocator, v);
defer b.deinit();
var q = try Managed.init(testing.allocator);
defer q.deinit();
var r = try Managed.init(testing.allocator);
defer r.deinit();
try Managed.divFloor(&q, &r, a.toConst(), b.toConst());
try testing.expect((try q.to(i32)) == 0);
try testing.expect((try r.to(i32)) == 10);
}
test "big.int div multi-multi with rem" {
var a = try Managed.initSet(testing.allocator, 0x8888999911110000ffffeeeeddddccccbbbbaaaa9999);
defer a.deinit();

View File

@@ -74,6 +74,11 @@ comptime {
@export(__getf2, .{ .name = "__gttf2", .linkage = linkage });
@export(__extendhfsf2, .{ .name = "__gnu_h2f_ieee", .linkage = linkage });
const __muloti4 = @import("compiler_rt/muloti4.zig").__muloti4;
@export(__muloti4, .{ .name = "__muloti4", .linkage = linkage });
const __mulodi4 = @import("compiler_rt/mulodi4.zig").__mulodi4;
@export(__mulodi4, .{ .name = "__mulodi4", .linkage = linkage });
}
if (!builtin.zig_is_stage2) {
@@ -621,10 +626,6 @@ comptime {
const __umodti3 = @import("compiler_rt/umodti3.zig").__umodti3;
@export(__umodti3, .{ .name = "__umodti3", .linkage = linkage });
}
const __muloti4 = @import("compiler_rt/muloti4.zig").__muloti4;
@export(__muloti4, .{ .name = "__muloti4", .linkage = linkage });
const __mulodi4 = @import("compiler_rt/mulodi4.zig").__mulodi4;
@export(__mulodi4, .{ .name = "__mulodi4", .linkage = linkage });
_ = @import("compiler_rt/atomics.zig");

View File

@@ -80,11 +80,27 @@ pub const Inst = struct {
/// is the same as both operands.
/// Uses the `bin_op` field.
mul_sat,
/// Integer or float division. For integers, wrapping is undefined behavior.
/// Float 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.
div,
div_float,
/// Truncating integer or float division. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
div_trunc,
/// Flooring integer or float division. For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
div_floor,
/// Integer or float division. Guaranteed no remainder.
/// For integers, wrapping is undefined behavior.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
/// Uses the `bin_op` field.
div_exact,
/// Integer or float remainder division.
/// Both operands are guaranteed to be the same type, and the result type
/// is the same as both operands.
@@ -644,7 +660,10 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.mul,
.mulwrap,
.mul_sat,
.div,
.div_float,
.div_trunc,
.div_floor,
.div_exact,
.rem,
.mod,
.bit_and,

View File

@@ -233,7 +233,10 @@ fn analyzeInst(
.mul,
.mulwrap,
.mul_sat,
.div,
.div_float,
.div_trunc,
.div_floor,
.div_exact,
.rem,
.mod,
.ptr_add,

View File

@@ -641,9 +641,6 @@ pub fn analyzeBody(
.pop_count => try sema.zirPopCount(block, inst),
.byte_swap => try sema.zirByteSwap(block, inst),
.bit_reverse => try sema.zirBitReverse(block, inst),
.div_exact => try sema.zirDivExact(block, inst),
.div_floor => try sema.zirDivFloor(block, inst),
.div_trunc => try sema.zirDivTrunc(block, inst),
.shr_exact => try sema.zirShrExact(block, inst),
.bit_offset_of => try sema.zirBitOffsetOf(block, inst),
.offset_of => try sema.zirOffsetOf(block, inst),
@@ -683,19 +680,22 @@ 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, .add),
.addwrap => try sema.zirArithmetic(block, inst, .addwrap),
.add_sat => try sema.zirArithmetic(block, inst, .add_sat),
.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),
.mul_sat => try sema.zirArithmetic(block, inst, .mul_sat),
.sub => try sema.zirArithmetic(block, inst, .sub),
.subwrap => try sema.zirArithmetic(block, inst, .subwrap),
.sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
.add => try sema.zirArithmetic(block, inst, .add),
.addwrap => try sema.zirArithmetic(block, inst, .addwrap),
.add_sat => try sema.zirArithmetic(block, inst, .add_sat),
.div => try sema.zirArithmetic(block, inst, .div),
.div_exact => try sema.zirArithmetic(block, inst, .div_exact),
.div_floor => try sema.zirArithmetic(block, inst, .div_floor),
.div_trunc => try sema.zirArithmetic(block, inst, .div_trunc),
.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),
.mul_sat => try sema.zirArithmetic(block, inst, .mul_sat),
.sub => try sema.zirArithmetic(block, inst, .sub),
.subwrap => try sema.zirArithmetic(block, inst, .subwrap),
.sub_sat => try sema.zirArithmetic(block, inst, .sub_sat),
.maximum => try sema.zirMinMax(block, inst, .max),
.minimum => try sema.zirMinMax(block, inst, .min),
@@ -7350,6 +7350,9 @@ fn analyzeArithmetic(
} else break :rs .{ .src = lhs_src, .air_tag = .sub_sat };
},
.div => {
// TODO: emit compile error when .div is used on integers and there would be an
// ambiguous result between div_floor and div_trunc.
// 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.
@@ -7359,9 +7362,11 @@ fn analyzeArithmetic(
// * 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
// possible value (-min_int / -1) for which division would be
// illegal behavior.
// * if lhs type is unsigned, undef is returned regardless of rhs.
// TODO: emit runtime safety for division by zero
//
// For floats:
// If the rhs is zero, compile error for division by zero.
// If the rhs is undefined, compile error because there is a possible
@@ -7407,8 +7412,198 @@ fn analyzeArithmetic(
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 };
} else {
if (is_int) {
break :rs .{ .src = rhs_src, .air_tag = .div_trunc };
} else {
break :rs .{ .src = rhs_src, .air_tag = .div_float };
}
}
} else {
if (is_int) {
break :rs .{ .src = lhs_src, .air_tag = .div_trunc };
} else {
break :rs .{ .src = lhs_src, .air_tag = .div_float };
}
}
},
.div_trunc => {
// For integers:
// If the lhs is zero, then zero is returned regardless of rhs.
// If the rhs is zero, compile error for division by zero.
// If the rhs is undefined, compile error because there is a possible
// value (zero) for which the division would be illegal behavior.
// If the lhs is undefined:
// * if lhs type is signed:
// * if rhs is comptime-known and not -1, result is undefined
// * if rhs is -1 or runtime-known, compile error because there is a
// possible value (-min_int / -1) for which division would be
// illegal behavior.
// * if lhs type is unsigned, undef is returned regardless of rhs.
// TODO: emit runtime safety for division by zero
//
// For floats:
// If the rhs is zero, compile error for division by zero.
// If the rhs is undefined, compile error because there is a possible
// value (zero) for which the division would be illegal behavior.
// If the lhs is undefined, result is undefined.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef()) {
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);
}
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.floatDivTrunc(rhs_val, scalar_type, sema.arena),
);
}
} else break :rs .{ .src = rhs_src, .air_tag = .div_trunc };
} else break :rs .{ .src = lhs_src, .air_tag = .div_trunc };
},
.div_floor => {
// For integers:
// If the lhs is zero, then zero is returned regardless of rhs.
// If the rhs is zero, compile error for division by zero.
// If the rhs is undefined, compile error because there is a possible
// value (zero) for which the division would be illegal behavior.
// If the lhs is undefined:
// * if lhs type is signed:
// * if rhs is comptime-known and not -1, result is undefined
// * if rhs is -1 or runtime-known, compile error because there is a
// possible value (-min_int / -1) for which division would be
// illegal behavior.
// * if lhs type is unsigned, undef is returned regardless of rhs.
// TODO: emit runtime safety for division by zero
//
// For floats:
// If the rhs is zero, compile error for division by zero.
// If the rhs is undefined, compile error because there is a possible
// value (zero) for which the division would be illegal behavior.
// If the lhs is undefined, result is undefined.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef()) {
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);
}
if (maybe_rhs_val) |rhs_val| {
if (is_int) {
return sema.addConstant(
scalar_type,
try lhs_val.intDivFloor(rhs_val, sema.arena),
);
} else {
return sema.addConstant(
scalar_type,
try lhs_val.floatDivFloor(rhs_val, scalar_type, sema.arena),
);
}
} else break :rs .{ .src = rhs_src, .air_tag = .div_floor };
} else break :rs .{ .src = lhs_src, .air_tag = .div_floor };
},
.div_exact => {
// 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, compile error because there is a possible
// value for which the division would result in a remainder.
// TODO: emit runtime safety for if there is a remainder
// TODO: emit runtime safety for division by zero
//
// For floats:
// If the rhs is zero, compile error for division by zero.
// If the rhs is undefined, compile error because there is a possible
// value (zero) for which the division would be illegal behavior.
// If the lhs is undefined, compile error because there is a possible
// value for which the division would result in a remainder.
if (maybe_lhs_val) |lhs_val| {
if (lhs_val.isUndef()) {
return sema.failWithUseOfUndef(block, rhs_src);
} else {
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 (maybe_rhs_val) |rhs_val| {
if (is_int) {
// TODO: emit compile error if there is a remainder
return sema.addConstant(
scalar_type,
try lhs_val.intDiv(rhs_val, sema.arena),
);
} else {
// TODO: emit compile error if there is a remainder
return sema.addConstant(
scalar_type,
try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena),
);
}
} else break :rs .{ .src = rhs_src, .air_tag = .div_exact };
} else break :rs .{ .src = lhs_src, .air_tag = .div_exact };
},
.mul => {
// For integers:
@@ -8824,7 +9019,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, .mod, .rem, .mod_rem => true,
.add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .mod, .rem, .mod_rem => true,
else => false,
};
}
@@ -9602,24 +9797,6 @@ fn zirBitReverse(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!
return sema.fail(block, src, "TODO: Sema.zirBitReverse", .{});
}
fn zirDivExact(sema: *Sema, block: *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.fail(block, src, "TODO: Sema.zirDivExact", .{});
}
fn zirDivFloor(sema: *Sema, block: *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.fail(block, src, "TODO: Sema.zirDivFloor", .{});
}
fn zirDivTrunc(sema: *Sema, block: *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.fail(block, src, "TODO: Sema.zirDivTrunc", .{});
}
fn zirShrExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
const src = inst_data.src();

View File

@@ -410,7 +410,6 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.mul => try self.airMul(inst),
.mulwrap => try self.airMulWrap(inst),
.mul_sat => try self.airMulSat(inst),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
.shl, .shl_exact => try self.airShl(inst),
@@ -419,6 +418,8 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
.max => try self.airMax(inst),
.slice => try self.airSlice(inst),
.div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
.cmp_eq => try self.airCmp(inst, .eq),

View File

@@ -758,7 +758,6 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.mul => try self.airMul(inst),
.mulwrap => try self.airMulWrap(inst),
.mul_sat => try self.airMulSat(inst),
.div => try self.airDiv(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
.shl, .shl_exact => try self.airShl(inst),
@@ -767,6 +766,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
.max => try self.airMax(inst),
.slice => try self.airSlice(inst),
.div_float, .div_trunc, .div_floor, .div_exact => try self.airDiv(inst),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
.cmp_eq => try self.airCmp(inst, .eq),

View File

@@ -976,7 +976,8 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
.mul => try airBinOp (f, inst, " * "),
// TODO use a different strategy for div that communicates to the optimizer
// that wrapping is UB.
.div => try airBinOp( f, inst, " / "),
.div_float, .div_exact, .div_trunc => try airBinOp( f, inst, " / "),
.div_floor => try airBinOp( f, inst, " divfloor "),
.rem => try airBinOp( f, inst, " % "),
.mod => try airBinOp( f, inst, " mod "), // TODO implement modulus division

View File

@@ -1667,7 +1667,10 @@ pub const FuncGen = struct {
.mul => try self.airMul(inst),
.mulwrap => try self.airMulWrap(inst),
.mul_sat => try self.airMulSat(inst),
.div => try self.airDiv(inst),
.div_float => try self.airDivFloat(inst),
.div_trunc => try self.airDivTrunc(inst),
.div_floor => try self.airDivFloor(inst),
.div_exact => try self.airDivExact(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
.ptr_add => try self.airPtrAdd(inst),
@@ -2830,9 +2833,70 @@ pub const FuncGen = struct {
return self.builder.buildUMulFixSat(lhs, rhs, "");
}
fn airDiv(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
fn airDivFloat(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);
return self.builder.buildFDiv(lhs, rhs, "");
}
fn airDivTrunc(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);
if (inst_ty.isRuntimeFloat()) {
const result_llvm_ty = try self.dg.llvmType(inst_ty);
const zero = result_llvm_ty.constNull();
const result = self.builder.buildFDiv(lhs, rhs, "");
const ceiled = try self.callCeil(result, inst_ty);
const floored = try self.callFloor(result, inst_ty);
const ltz = self.builder.buildFCmp(.OLT, lhs, zero, "");
return self.builder.buildSelect(ltz, ceiled, floored, "");
}
if (inst_ty.isSignedInt()) return self.builder.buildSDiv(lhs, rhs, "");
return self.builder.buildUDiv(lhs, rhs, "");
}
fn airDivFloor(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);
if (inst_ty.isRuntimeFloat()) {
const result = self.builder.buildFDiv(lhs, rhs, "");
return try self.callFloor(result, inst_ty);
}
if (inst_ty.isSignedInt()) {
// const d = @divTrunc(a, b);
// const r = @rem(a, b);
// return if (r == 0) d else d - ((a < 0) ^ (b < 0));
const result_llvm_ty = try self.dg.llvmType(inst_ty);
const zero = result_llvm_ty.constNull();
const div_trunc = self.builder.buildSDiv(lhs, rhs, "");
const rem = self.builder.buildSRem(lhs, rhs, "");
const rem_eq_0 = self.builder.buildICmp(.EQ, rem, zero, "");
const a_lt_0 = self.builder.buildICmp(.SLT, lhs, zero, "");
const b_lt_0 = self.builder.buildICmp(.SLT, rhs, zero, "");
const a_b_xor = self.builder.buildXor(a_lt_0, b_lt_0, "");
const a_b_xor_ext = self.builder.buildZExt(a_b_xor, div_trunc.typeOf(), "");
const d_sub_xor = self.builder.buildSub(div_trunc, a_b_xor_ext, "");
return self.builder.buildSelect(rem_eq_0, div_trunc, d_sub_xor, "");
}
return self.builder.buildUDiv(lhs, rhs, "");
}
fn airDivExact(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);
@@ -2840,8 +2904,8 @@ pub const FuncGen = struct {
const inst_ty = self.air.typeOfIndex(inst);
if (inst_ty.isRuntimeFloat()) return self.builder.buildFDiv(lhs, rhs, "");
if (inst_ty.isSignedInt()) return self.builder.buildSDiv(lhs, rhs, "");
return self.builder.buildUDiv(lhs, rhs, "");
if (inst_ty.isSignedInt()) return self.builder.buildExactSDiv(lhs, rhs, "");
return self.builder.buildExactUDiv(lhs, rhs, "");
}
fn airRem(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
@@ -3546,6 +3610,33 @@ pub const FuncGen = struct {
}
}
fn callFloor(self: *FuncGen, arg: *const llvm.Value, ty: Type) !*const llvm.Value {
return self.callFloatUnary(arg, ty, "floor");
}
fn callCeil(self: *FuncGen, arg: *const llvm.Value, ty: Type) !*const llvm.Value {
return self.callFloatUnary(arg, ty, "ceil");
}
fn callFloatUnary(self: *FuncGen, arg: *const llvm.Value, ty: Type, name: []const u8) !*const llvm.Value {
const target = self.dg.module.getTarget();
var fn_name_buf: [100]u8 = undefined;
const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.f{d}", .{
name, ty.floatBits(target),
}) catch unreachable;
const llvm_fn = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
const operand_llvm_ty = try self.dg.llvmType(ty);
const param_types = [_]*const llvm.Type{operand_llvm_ty};
const fn_type = llvm.functionType(operand_llvm_ty, &param_types, param_types.len, .False);
break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
};
const args: [1]*const llvm.Value = .{arg};
return self.builder.buildCall(llvm_fn, &args, args.len, .C, .Auto, "");
}
fn fieldPtr(
self: *FuncGen,
inst: Air.Inst.Index,

View File

@@ -756,6 +756,12 @@ pub const Builder = opaque {
pub const buildSMin = ZigLLVMBuildSMin;
extern fn ZigLLVMBuildSMin(builder: *const Builder, LHS: *const Value, RHS: *const Value, name: [*:0]const u8) *const Value;
pub const buildExactUDiv = LLVMBuildExactUDiv;
extern fn LLVMBuildExactUDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
pub const buildExactSDiv = LLVMBuildExactSDiv;
extern fn LLVMBuildExactSDiv(*const Builder, LHS: *const Value, RHS: *const Value, Name: [*:0]const u8) *const Value;
};
pub const IntPredicate = enum(c_uint) {

View File

@@ -669,7 +669,6 @@ pub const DeclGen = struct {
.add, .addwrap => try self.airArithOp(inst, .{.OpFAdd, .OpIAdd, .OpIAdd}),
.sub, .subwrap => try self.airArithOp(inst, .{.OpFSub, .OpISub, .OpISub}),
.mul, .mulwrap => try self.airArithOp(inst, .{.OpFMul, .OpIMul, .OpIMul}),
.div => try self.airArithOp(inst, .{.OpFDiv, .OpSDiv, .OpUDiv}),
.bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),
.bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),

View File

@@ -822,7 +822,7 @@ pub const Context = struct {
.subwrap => self.airWrapBinOp(inst, .sub),
.mul => self.airBinOp(inst, .mul),
.mulwrap => self.airWrapBinOp(inst, .mul),
.div => self.airBinOp(inst, .div),
.div_trunc => self.airBinOp(inst, .div),
.bit_and => self.airBinOp(inst, .@"and"),
.bit_or => self.airBinOp(inst, .@"or"),
.bool_and => self.airBinOp(inst, .@"and"),

View File

@@ -111,7 +111,10 @@ const Writer = struct {
.mul,
.mulwrap,
.mul_sat,
.div,
.div_float,
.div_trunc,
.div_floor,
.div_exact,
.rem,
.mod,
.ptr_add,

View File

@@ -2200,7 +2200,8 @@ pub const Value = extern union {
const rhs_bigint = rhs.toBigInt(&rhs_space);
const limbs = try arena.alloc(
std.math.big.Limb,
std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
// + 1 for negatives
std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
result_bigint.bitAnd(lhs_bigint, rhs_bigint);
@@ -2264,7 +2265,8 @@ pub const Value = extern union {
const rhs_bigint = rhs.toBigInt(&rhs_space);
const limbs = try arena.alloc(
std.math.big.Limb,
std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len),
// + 1 for negatives
std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
result_bigint.bitXor(lhs_bigint, rhs_bigint);
@@ -2352,7 +2354,7 @@ pub const Value = extern union {
}
}
pub fn intRem(lhs: Value, rhs: Value, allocator: *Allocator) !Value {
pub fn intDivFloor(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;
@@ -2373,6 +2375,39 @@ pub const Value = extern union {
);
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_q.limbs[0..result_q.len];
if (result_q.positive) {
return Value.Tag.int_big_positive.create(allocator, result_limbs);
} else {
return Value.Tag.int_big_negative.create(allocator, result_limbs);
}
}
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,
// TODO: audit this size, and also consider reworking Sema to re-use Values rather than
// always producing new Value objects.
rhs_bigint.limbs.len + 1,
);
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];
@@ -2662,6 +2697,68 @@ pub const Value = extern union {
}
}
pub fn floatDivFloor(
lhs: Value,
rhs: Value,
float_type: Type,
arena: *Allocator,
) !Value {
switch (float_type.tag()) {
.f16 => {
const lhs_val = lhs.toFloat(f16);
const rhs_val = rhs.toFloat(f16);
return Value.Tag.float_16.create(arena, @divFloor(lhs_val, rhs_val));
},
.f32 => {
const lhs_val = lhs.toFloat(f32);
const rhs_val = rhs.toFloat(f32);
return Value.Tag.float_32.create(arena, @divFloor(lhs_val, rhs_val));
},
.f64 => {
const lhs_val = lhs.toFloat(f64);
const rhs_val = rhs.toFloat(f64);
return Value.Tag.float_64.create(arena, @divFloor(lhs_val, rhs_val));
},
.f128, .comptime_float, .c_longdouble => {
const lhs_val = lhs.toFloat(f128);
const rhs_val = rhs.toFloat(f128);
return Value.Tag.float_128.create(arena, @divFloor(lhs_val, rhs_val));
},
else => unreachable,
}
}
pub fn floatDivTrunc(
lhs: Value,
rhs: Value,
float_type: Type,
arena: *Allocator,
) !Value {
switch (float_type.tag()) {
.f16 => {
const lhs_val = lhs.toFloat(f16);
const rhs_val = rhs.toFloat(f16);
return Value.Tag.float_16.create(arena, @divTrunc(lhs_val, rhs_val));
},
.f32 => {
const lhs_val = lhs.toFloat(f32);
const rhs_val = rhs.toFloat(f32);
return Value.Tag.float_32.create(arena, @divTrunc(lhs_val, rhs_val));
},
.f64 => {
const lhs_val = lhs.toFloat(f64);
const rhs_val = rhs.toFloat(f64);
return Value.Tag.float_64.create(arena, @divTrunc(lhs_val, rhs_val));
},
.f128, .comptime_float, .c_longdouble => {
const lhs_val = lhs.toFloat(f128);
const rhs_val = rhs.toFloat(f128);
return Value.Tag.float_128.create(arena, @divTrunc(lhs_val, rhs_val));
},
else => unreachable,
}
}
pub fn floatMul(
lhs: Value,
rhs: Value,

View File

@@ -33,6 +33,7 @@ test {
_ = @import("behavior/error.zig");
_ = @import("behavior/eval.zig");
_ = @import("behavior/floatop.zig");
_ = @import("behavior/fn.zig");
_ = @import("behavior/for.zig");
_ = @import("behavior/generics.zig");
_ = @import("behavior/hasdecl.zig");
@@ -126,7 +127,7 @@ test {
_ = @import("behavior/eval_stage1.zig");
_ = @import("behavior/field_parent_ptr.zig");
_ = @import("behavior/floatop_stage1.zig");
_ = @import("behavior/fn.zig");
_ = @import("behavior/fn_stage1.zig");
_ = @import("behavior/fn_delegation.zig");
_ = @import("behavior/fn_in_struct_in_comptime.zig");
_ = @import("behavior/for_stage1.zig");
@@ -150,7 +151,7 @@ test {
_ = @import("behavior/reflection.zig");
{
// Checklist for getting saturating_arithmetic.zig passing for stage2:
// * add __muloti4 to compiler-rt
// * add __udivti3 to compiler-rt
_ = @import("behavior/saturating_arithmetic.zig");
}
_ = @import("behavior/select.zig");

View File

@@ -112,3 +112,55 @@ test "void arrays" {
try expect(@sizeOf(@TypeOf(array)) == 0);
try expect(array.len == 4);
}
test "nested arrays" {
const array_of_strings = [_][]const u8{ "hello", "this", "is", "my", "thing" };
for (array_of_strings) |s, i| {
if (i == 0) try expect(mem.eql(u8, s, "hello"));
if (i == 1) try expect(mem.eql(u8, s, "this"));
if (i == 2) try expect(mem.eql(u8, s, "is"));
if (i == 3) try expect(mem.eql(u8, s, "my"));
if (i == 4) try expect(mem.eql(u8, s, "thing"));
}
}
var s_array: [8]Sub = undefined;
const Sub = struct { b: u8 };
const Str = struct { a: []Sub };
test "set global var array via slice embedded in struct" {
var s = Str{ .a = s_array[0..] };
s.a[0].b = 1;
s.a[1].b = 2;
s.a[2].b = 3;
try expect(s_array[0].b == 1);
try expect(s_array[1].b == 2);
try expect(s_array[2].b == 3);
}
test "implicit comptime in array type size" {
var arr: [plusOne(10)]bool = undefined;
try expect(arr.len == 11);
}
fn plusOne(x: u32) u32 {
return x + 1;
}
test "read/write through global variable array of struct fields initialized via array mult" {
const S = struct {
fn doTheTest() !void {
try expect(storage[0].term == 1);
storage[0] = MyStruct{ .term = 123 };
try expect(storage[0].term == 123);
}
pub const MyStruct = struct {
term: usize,
};
var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1;
};
try S.doTheTest();
}

View File

@@ -4,32 +4,6 @@ const mem = std.mem;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
test "nested arrays" {
const array_of_strings = [_][]const u8{ "hello", "this", "is", "my", "thing" };
for (array_of_strings) |s, i| {
if (i == 0) try expect(mem.eql(u8, s, "hello"));
if (i == 1) try expect(mem.eql(u8, s, "this"));
if (i == 2) try expect(mem.eql(u8, s, "is"));
if (i == 3) try expect(mem.eql(u8, s, "my"));
if (i == 4) try expect(mem.eql(u8, s, "thing"));
}
}
var s_array: [8]Sub = undefined;
const Sub = struct { b: u8 };
const Str = struct { a: []Sub };
test "set global var array via slice embedded in struct" {
var s = Str{ .a = s_array[0..] };
s.a[0].b = 1;
s.a[1].b = 2;
s.a[2].b = 3;
try expect(s_array[0].b == 1);
try expect(s_array[1].b == 2);
try expect(s_array[2].b == 3);
}
test "single-item pointer to array indexing and slicing" {
try testSingleItemPtrArrayIndexSlice();
comptime try testSingleItemPtrArrayIndexSlice();
@@ -75,15 +49,6 @@ test "comptime evaluating function that takes array by value" {
_ = comptime testArrayByValAtComptime(arr);
}
test "implicit comptime in array type size" {
var arr: [plusOne(10)]bool = undefined;
try expect(arr.len == 11);
}
fn plusOne(x: u32) u32 {
return x + 1;
}
test "runtime initialize array elem and then implicit cast to slice" {
var two: i32 = 2;
const x: []const i32 = &[_]i32{two};
@@ -171,23 +136,6 @@ test "double nested array to const slice cast in array literal" {
comptime try S.entry(2);
}
test "read/write through global variable array of struct fields initialized via array mult" {
const S = struct {
fn doTheTest() !void {
try expect(storage[0].term == 1);
storage[0] = MyStruct{ .term = 123 };
try expect(storage[0].term == 123);
}
pub const MyStruct = struct {
term: usize,
};
var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1;
};
try S.doTheTest();
}
test "implicit cast zero sized array ptr to slice" {
{
var b = "".*;

View File

@@ -402,3 +402,52 @@ test "f64 at compile time is lossy" {
test {
comptime try expect(@as(f128, 1 << 113) == 10384593717069655257060992658440192);
}
fn copyWithPartialInline(s: []u32, b: []u8) void {
comptime var i: usize = 0;
inline while (i < 4) : (i += 1) {
s[i] = 0;
s[i] |= @as(u32, b[i * 4 + 0]) << 24;
s[i] |= @as(u32, b[i * 4 + 1]) << 16;
s[i] |= @as(u32, b[i * 4 + 2]) << 8;
s[i] |= @as(u32, b[i * 4 + 3]) << 0;
}
}
test "binary math operator in partially inlined function" {
var s: [4]u32 = undefined;
var b: [16]u8 = undefined;
for (b) |*r, i|
r.* = @intCast(u8, i + 1);
copyWithPartialInline(s[0..], b[0..]);
try expect(s[0] == 0x1020304);
try expect(s[1] == 0x5060708);
try expect(s[2] == 0x90a0b0c);
try expect(s[3] == 0xd0e0f10);
}
test "comptime shl" {
var a: u128 = 3;
var b: u7 = 63;
var c: u128 = 3 << 63;
try expect((a << b) == c);
}
test "comptime bitwise operators" {
comptime {
try expect(3 & 1 == 1);
try expect(3 & -1 == 3);
try expect(-3 & -1 == -3);
try expect(3 | -1 == -1);
try expect(-3 | -1 == -1);
try expect(3 ^ -1 == -4);
try expect(-3 ^ -1 == 2);
try expect(~@as(i8, -1) == 0);
try expect(~@as(i128, -1) == 0);
try expect(18446744073709551615 & 18446744073709551611 == 18446744073709551611);
try expect(-18446744073709551615 & -18446744073709551611 == -18446744073709551615);
try expect(~@as(u128, 0) == 0xffffffffffffffffffffffffffffffff);
}
}

View File

@@ -132,31 +132,6 @@ test "string literal used as comptime slice is memoized" {
comptime try expect(TypeWithCompTimeSlice("link").Node == TypeWithCompTimeSlice("link").Node);
}
fn copyWithPartialInline(s: []u32, b: []u8) void {
comptime var i: usize = 0;
inline while (i < 4) : (i += 1) {
s[i] = 0;
s[i] |= @as(u32, b[i * 4 + 0]) << 24;
s[i] |= @as(u32, b[i * 4 + 1]) << 16;
s[i] |= @as(u32, b[i * 4 + 2]) << 8;
s[i] |= @as(u32, b[i * 4 + 3]) << 0;
}
}
test "binary math operator in partially inlined function" {
var s: [4]u32 = undefined;
var b: [16]u8 = undefined;
for (b) |*r, i|
r.* = @intCast(u8, i + 1);
copyWithPartialInline(s[0..], b[0..]);
try expect(s[0] == 0x1020304);
try expect(s[1] == 0x5060708);
try expect(s[2] == 0x90a0b0c);
try expect(s[3] == 0xd0e0f10);
}
test "comptime function with mutable pointer is not memoized" {
comptime {
var x: i32 = 1;
@@ -203,13 +178,6 @@ test "comptime shlWithOverflow" {
try expect(ct_shifted == rt_shifted);
}
test "comptime shl" {
var a: u128 = 3;
var b: u7 = 63;
var c: u128 = 3 << 63;
try expectEqual(a << b, c);
}
test "runtime 128 bit integer division" {
var a: u128 = 152313999999999991610955792383;
var b: u128 = 10000000000000000000;
@@ -278,23 +246,6 @@ test "bit shift a u1" {
try expect(y == 1);
}
test "comptime bitwise operators" {
comptime {
try expect(3 & 1 == 1);
try expect(3 & -1 == 3);
try expect(-3 & -1 == -3);
try expect(3 | -1 == -1);
try expect(-3 | -1 == -1);
try expect(3 ^ -1 == -4);
try expect(-3 ^ -1 == 2);
try expect(~@as(i8, -1) == 0);
try expect(~@as(i128, -1) == 0);
try expect(18446744073709551615 & 18446744073709551611 == 18446744073709551611);
try expect(-18446744073709551615 & -18446744073709551611 == -18446744073709551615);
try expect(~@as(u128, 0) == 0xffffffffffffffffffffffffffffffff);
}
}
test "*align(1) u16 is the same as *align(1:0:2) u16" {
comptime {
try expect(*align(1:0:2) u16 == *align(1) u16);

View File

@@ -19,17 +19,6 @@ fn testLocVars(b: i32) void {
if (a + b != 3) unreachable;
}
test "void parameters" {
try voidFun(1, void{}, 2, {});
}
fn voidFun(a: i32, b: void, c: i32, d: void) !void {
_ = d;
const v = b;
const vv: void = if (a == 1) v else {};
try expect(a + c == 3);
return vv;
}
test "mutable local variables" {
var zero: i32 = 0;
try expect(zero == 0);
@@ -54,14 +43,6 @@ test "separate block scopes" {
try expect(c == 10);
}
test "call function with empty string" {
acceptsString("");
}
fn acceptsString(foo: []u8) void {
_ = foo;
}
fn @"weird function name"() i32 {
return 1234;
}
@@ -69,51 +50,6 @@ test "weird function name" {
try expect(@"weird function name"() == 1234);
}
test "implicit cast function unreachable return" {
wantsFnWithVoid(fnWithUnreachable);
}
fn wantsFnWithVoid(f: fn () void) void {
_ = f;
}
fn fnWithUnreachable() noreturn {
unreachable;
}
test "function pointers" {
const fns = [_]@TypeOf(fn1){
fn1,
fn2,
fn3,
fn4,
};
for (fns) |f, i| {
try expect(f() == @intCast(u32, i) + 5);
}
}
fn fn1() u32 {
return 5;
}
fn fn2() u32 {
return 6;
}
fn fn3() u32 {
return 7;
}
fn fn4() u32 {
return 8;
}
test "number literal as an argument" {
try numberLiteralArg(3);
comptime try numberLiteralArg(3);
}
fn numberLiteralArg(a: anytype) !void {
try expect(a == 3);
}
test "assign inline fn to const variable" {
const a = inlineFn;
a();
@@ -121,64 +57,6 @@ test "assign inline fn to const variable" {
inline fn inlineFn() void {}
test "pass by non-copying value" {
try expect(addPointCoords(Point{ .x = 1, .y = 2 }) == 3);
}
const Point = struct {
x: i32,
y: i32,
};
fn addPointCoords(pt: Point) i32 {
return pt.x + pt.y;
}
test "pass by non-copying value through var arg" {
try expect((try addPointCoordsVar(Point{ .x = 1, .y = 2 })) == 3);
}
fn addPointCoordsVar(pt: anytype) !i32 {
comptime try expect(@TypeOf(pt) == Point);
return pt.x + pt.y;
}
test "pass by non-copying value as method" {
var pt = Point2{ .x = 1, .y = 2 };
try expect(pt.addPointCoords() == 3);
}
const Point2 = struct {
x: i32,
y: i32,
fn addPointCoords(self: Point2) i32 {
return self.x + self.y;
}
};
test "pass by non-copying value as method, which is generic" {
var pt = Point3{ .x = 1, .y = 2 };
try expect(pt.addPointCoords(i32) == 3);
}
const Point3 = struct {
x: i32,
y: i32,
fn addPointCoords(self: Point3, comptime T: type) i32 {
_ = T;
return self.x + self.y;
}
};
test "pass by non-copying value as method, at comptime" {
comptime {
var pt = Point2{ .x = 1, .y = 2 };
try expect(pt.addPointCoords() == 3);
}
}
fn outer(y: u32) fn (u32) u32 {
const Y = @TypeOf(y);
const st = struct {
@@ -194,43 +72,6 @@ test "return inner function which references comptime variable of outer function
try expect(func(3) == 7);
}
test "extern struct with stdcallcc fn pointer" {
const S = extern struct {
ptr: fn () callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32,
fn foo() callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32 {
return 1234;
}
};
var s: S = undefined;
s.ptr = S.foo;
try expect(s.ptr() == 1234);
}
test "implicit cast fn call result to optional in field result" {
const S = struct {
fn entry() !void {
var x = Foo{
.field = optionalPtr(),
};
try expect(x.field.?.* == 999);
}
const glob: i32 = 999;
fn optionalPtr() *const i32 {
return &glob;
}
const Foo = struct {
field: ?*const i32,
};
};
try S.entry();
comptime try S.entry();
}
test "discard the result of a function that returns a struct" {
const S = struct {
fn entry() void {
@@ -249,45 +90,3 @@ test "discard the result of a function that returns a struct" {
S.entry();
comptime S.entry();
}
test "function call with anon list literal" {
const S = struct {
fn doTheTest() !void {
try consumeVec(.{ 9, 8, 7 });
}
fn consumeVec(vec: [3]f32) !void {
try expect(vec[0] == 9);
try expect(vec[1] == 8);
try expect(vec[2] == 7);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "ability to give comptime types and non comptime types to same parameter" {
const S = struct {
fn doTheTest() !void {
var x: i32 = 1;
try expect(foo(x) == 10);
try expect(foo(i32) == 20);
}
fn foo(arg: anytype) i32 {
if (@typeInfo(@TypeOf(arg)) == .Type and arg == i32) return 20;
return 9 + arg;
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "function with inferred error set but returning no error" {
const S = struct {
fn foo() !void {}
};
const return_ty = @typeInfo(@TypeOf(S.foo)).Fn.return_type.?;
try expectEqual(0, @typeInfo(@typeInfo(return_ty).ErrorUnion.error_set).ErrorSet.?.len);
}

206
test/behavior/fn_stage1.zig Normal file
View File

@@ -0,0 +1,206 @@
const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
const expect = testing.expect;
const expectEqual = testing.expectEqual;
test "void parameters" {
try voidFun(1, void{}, 2, {});
}
fn voidFun(a: i32, b: void, c: i32, d: void) !void {
_ = d;
const v = b;
const vv: void = if (a == 1) v else {};
try expect(a + c == 3);
return vv;
}
test "call function with empty string" {
acceptsString("");
}
fn acceptsString(foo: []u8) void {
_ = foo;
}
test "implicit cast function unreachable return" {
wantsFnWithVoid(fnWithUnreachable);
}
fn wantsFnWithVoid(f: fn () void) void {
_ = f;
}
fn fnWithUnreachable() noreturn {
unreachable;
}
test "function pointers" {
const fns = [_]@TypeOf(fn1){
fn1,
fn2,
fn3,
fn4,
};
for (fns) |f, i| {
try expect(f() == @intCast(u32, i) + 5);
}
}
fn fn1() u32 {
return 5;
}
fn fn2() u32 {
return 6;
}
fn fn3() u32 {
return 7;
}
fn fn4() u32 {
return 8;
}
test "number literal as an argument" {
try numberLiteralArg(3);
comptime try numberLiteralArg(3);
}
fn numberLiteralArg(a: anytype) !void {
try expect(a == 3);
}
test "pass by non-copying value" {
try expect(addPointCoords(Point{ .x = 1, .y = 2 }) == 3);
}
const Point = struct {
x: i32,
y: i32,
};
fn addPointCoords(pt: Point) i32 {
return pt.x + pt.y;
}
test "pass by non-copying value through var arg" {
try expect((try addPointCoordsVar(Point{ .x = 1, .y = 2 })) == 3);
}
fn addPointCoordsVar(pt: anytype) !i32 {
comptime try expect(@TypeOf(pt) == Point);
return pt.x + pt.y;
}
test "pass by non-copying value as method" {
var pt = Point2{ .x = 1, .y = 2 };
try expect(pt.addPointCoords() == 3);
}
const Point2 = struct {
x: i32,
y: i32,
fn addPointCoords(self: Point2) i32 {
return self.x + self.y;
}
};
test "pass by non-copying value as method, which is generic" {
var pt = Point3{ .x = 1, .y = 2 };
try expect(pt.addPointCoords(i32) == 3);
}
const Point3 = struct {
x: i32,
y: i32,
fn addPointCoords(self: Point3, comptime T: type) i32 {
_ = T;
return self.x + self.y;
}
};
test "pass by non-copying value as method, at comptime" {
comptime {
var pt = Point2{ .x = 1, .y = 2 };
try expect(pt.addPointCoords() == 3);
}
}
test "extern struct with stdcallcc fn pointer" {
const S = extern struct {
ptr: fn () callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32,
fn foo() callconv(if (builtin.target.cpu.arch == .i386) .Stdcall else .C) i32 {
return 1234;
}
};
var s: S = undefined;
s.ptr = S.foo;
try expect(s.ptr() == 1234);
}
test "implicit cast fn call result to optional in field result" {
const S = struct {
fn entry() !void {
var x = Foo{
.field = optionalPtr(),
};
try expect(x.field.?.* == 999);
}
const glob: i32 = 999;
fn optionalPtr() *const i32 {
return &glob;
}
const Foo = struct {
field: ?*const i32,
};
};
try S.entry();
comptime try S.entry();
}
test "function call with anon list literal" {
const S = struct {
fn doTheTest() !void {
try consumeVec(.{ 9, 8, 7 });
}
fn consumeVec(vec: [3]f32) !void {
try expect(vec[0] == 9);
try expect(vec[1] == 8);
try expect(vec[2] == 7);
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "ability to give comptime types and non comptime types to same parameter" {
const S = struct {
fn doTheTest() !void {
var x: i32 = 1;
try expect(foo(x) == 10);
try expect(foo(i32) == 20);
}
fn foo(arg: anytype) i32 {
if (@typeInfo(@TypeOf(arg)) == .Type and arg == i32) return 20;
return 9 + arg;
}
};
try S.doTheTest();
comptime try S.doTheTest();
}
test "function with inferred error set but returning no error" {
const S = struct {
fn foo() !void {}
};
const return_ty = @typeInfo(@TypeOf(S.foo)).Fn.return_type.?;
try expectEqual(0, @typeInfo(@typeInfo(return_ty).ErrorUnion.error_set).ErrorSet.?.len);
}

View File

@@ -249,3 +249,190 @@ test "binary not" {
fn testBinaryNot(x: u16) !void {
try expect(~x == 0b0101010101010101);
}
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 "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 "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 "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 "128-bit multiplication" {
var a: i128 = 3;
var b: i128 = 2;
var c = a * b;
try expect(c == 6);
}

View File

@@ -6,92 +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));
@@ -157,68 +71,6 @@ fn testCtzVectors() !void {
try expectEqual(@ctz(u16, @splat(64, @as(u16, 0b00000000))), @splat(64, @as(u5, 16)));
}
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 "small int addition" {
var x: u2 = 0;
try expect(x == 0);
@@ -346,15 +198,6 @@ fn testShlTrunc(x: u16) !void {
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);
@@ -392,29 +235,6 @@ test "shift left/right on u0 operand" {
comptime try S.doTheTest();
}
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;
@@ -614,13 +434,6 @@ fn testNanEqNan(comptime F: type) !void {
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 {