Skip to content

Commit

Permalink
stage2: more arithmetic support
Browse files Browse the repository at this point in the history
 * 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
  • Loading branch information
andrewrk committed Sep 28, 2021
1 parent 1e805df commit 79bc589
Show file tree
Hide file tree
Showing 12 changed files with 1,604 additions and 974 deletions.
11 changes: 9 additions & 2 deletions src/Air.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -568,6 +574,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.mulwrap,
.div,
.rem,
.mod,
.bit_and,
.bit_or,
.xor,
Expand Down
1 change: 1 addition & 0 deletions src/Liveness.zig
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ fn analyzeInst(
.mulwrap,
.div,
.rem,
.mod,
.ptr_add,
.ptr_sub,
.bit_and,
Expand Down
659 changes: 547 additions & 112 deletions src/Sema.zig

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions src/Zir.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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`.
Expand Down
9 changes: 9 additions & 0 deletions src/codegen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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) {
Expand Down
2 changes: 2 additions & 0 deletions src/codegen/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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, " > "),
Expand Down
29 changes: 29 additions & 0 deletions src/codegen/llvm.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/print_air.zig
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const Writer = struct {
.mulwrap,
.div,
.rem,
.mod,
.ptr_add,
.ptr_sub,
.bit_and,
Expand Down
138 changes: 138 additions & 0 deletions src/value.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
};
3 changes: 2 additions & 1 deletion test/behavior.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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");
Expand Down
Loading

0 comments on commit 79bc589

Please sign in to comment.