Skip to content

stage2: vectorized overflow arithmetic, integer overflow safety, left-shift overflow safety #11316

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
May 17, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
c641fb8
stage2: fix {add,sub,mul}_with_overflow vectorization in LLVM backend
sengir Mar 26, 2022
c2cb9b7
stage2: vectorize shl_with_overflow in LLVM backend
sengir Mar 26, 2022
ca1ab38
stage2: add global `Type` constant for `u1`
sengir Mar 26, 2022
6b5c879
stage2: handle vectors in `Value.intFitsInType`
sengir Mar 26, 2022
e8117ba
stage2: clean up creation of boolean `Value`s
sengir Mar 26, 2022
86a928c
stage2: perform comptime vectorization of `*_with_overflow` in `Value`
sengir Mar 26, 2022
eb06c78
Sema: vectorize overflow arithmetic
sengir Mar 26, 2022
c2980f3
Sema: implement integer overflow safety for add, sub, mul
sengir Mar 26, 2022
afc714d
stage2: implement runtime safety checks for shl_exact
sengir Mar 26, 2022
21be3d9
stage2: add vectorized overflow arithmetic behavior tests
sengir Mar 26, 2022
bb3532e
stage2: add more vector overflow tests
sengir Mar 28, 2022
a5ea22d
LLVM: correctly pad result tuple of `airOverflow`
sengir Apr 23, 2022
03ed0f0
C backend: implement overflow arithmetic
andrewrk May 12, 2022
316bf4f
disable 5 failing stage2_wasm tests
andrewrk May 13, 2022
b94d165
x64: fix capacity prealloc limit in lowerToMrEnc helper
kubkon May 13, 2022
0a2d3d4
wasm: Improve overflow add/sub for ints <= 64bits
Luukdegram May 13, 2022
160aa4c
wasm: Improve shl_with_overflow
Luukdegram May 13, 2022
a84be7e
zig.h: improve overflow shl
andrewrk May 14, 2022
852c820
aarch64: sub_with_overflow should always track V flag
kubkon May 14, 2022
7f96ca1
arm: sub_with_overflow should always track V flag
kubkon May 14, 2022
a0de0ad
arm: disable recursive fibonacci
kubkon May 14, 2022
f33b3fc
zig.h: add casts for overflow arithmetic operations
andrewrk May 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions lib/std/builtin.zig
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,7 @@ pub fn default_panic(msg: []const u8, error_return_trace: ?*StackTrace) noreturn

// Until self-hosted catches up with stage1 language features, we have a simpler
// default panic function:
if ((builtin.zig_backend == .stage2_llvm and builtin.link_libc) or
builtin.zig_backend == .stage2_c or
if (builtin.zig_backend == .stage2_c or
builtin.zig_backend == .stage2_wasm or
builtin.zig_backend == .stage2_arm or
builtin.zig_backend == .stage2_aarch64 or
Expand Down
231 changes: 172 additions & 59 deletions src/Sema.zig

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/arch/aarch64/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1901,6 +1901,10 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void {
}
};

if (tag == .sub_with_overflow) {
break :result MCValue{ .register_v_flag = dest.register };
}

switch (int_info.signedness) {
.unsigned => break :result MCValue{ .register_c_flag = dest.register },
.signed => break :result MCValue{ .register_v_flag = dest.register },
Expand Down
4 changes: 4 additions & 0 deletions src/arch/arm/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,10 @@ fn airOverflow(self: *Self, inst: Air.Inst.Index) !void {
}
};

if (tag == .sub_with_overflow) {
break :result MCValue{ .register_v_flag = dest.register };
}

switch (int_info.signedness) {
.unsigned => break :result MCValue{ .register_c_flag = dest.register },
.signed => break :result MCValue{ .register_v_flag = dest.register },
Expand Down
161 changes: 84 additions & 77 deletions src/arch/wasm/CodeGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1450,9 +1450,9 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
.min => self.airMaxMin(inst, .min),
.mul_add => self.airMulAdd(inst),

.add_with_overflow => self.airBinOpOverflow(inst, .add),
.sub_with_overflow => self.airBinOpOverflow(inst, .sub),
.shl_with_overflow => self.airBinOpOverflow(inst, .shl),
.add_with_overflow => self.airAddSubWithOverflow(inst, .add),
.sub_with_overflow => self.airAddSubWithOverflow(inst, .sub),
.shl_with_overflow => self.airShlWithOverflow(inst),
.mul_with_overflow => self.airMulWithOverflow(inst),

.clz => self.airClz(inst),
Expand Down Expand Up @@ -3941,109 +3941,116 @@ fn airPtrSliceFieldPtr(self: *Self, inst: Air.Inst.Index, offset: u32) InnerErro
return self.buildPointerOffset(slice_ptr, offset, .new);
}

fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
if (self.liveness.isUnused(inst)) return WValue{ .none = {} };

fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
assert(op == .add or op == .sub);
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
const lhs = try self.resolveInst(extra.lhs);
const rhs = try self.resolveInst(extra.rhs);
const lhs_op = try self.resolveInst(extra.lhs);
const rhs_op = try self.resolveInst(extra.rhs);
const lhs_ty = self.air.typeOf(extra.lhs);

if (lhs_ty.zigTypeTag() == .Vector) {
return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
}

// We store the bit if it's overflowed or not in this. As it's zero-initialized
// we only need to update it if an overflow (or underflow) occured.
const overflow_bit = try self.allocLocal(Type.initTag(.u1));
const int_info = lhs_ty.intInfo(self.target);
const is_signed = int_info.signedness == .signed;
const wasm_bits = toWasmBits(int_info.bits) orelse {
return self.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits});
return self.fail("TODO: Implement {{add/sub}}_with_overflow for integer bitsize: {d}", .{int_info.bits});
};

const zero = switch (wasm_bits) {
32 => WValue{ .imm32 = 0 },
64 => WValue{ .imm64 = 0 },
else => unreachable,
};
const int_max = (@as(u65, 1) << @intCast(u7, int_info.bits - @boolToInt(int_info.signedness == .signed))) - 1;
const int_max_wvalue = switch (wasm_bits) {
32 => WValue{ .imm32 = @intCast(u32, int_max) },
64 => WValue{ .imm64 = @intCast(u64, int_max) },
else => unreachable,
};
const int_min = if (int_info.signedness == .unsigned)
@as(i64, 0)
else
-@as(i64, 1) << @intCast(u6, int_info.bits - 1);
const int_min_wvalue = switch (wasm_bits) {
32 => WValue{ .imm32 = @bitCast(u32, @intCast(i32, int_min)) },
64 => WValue{ .imm64 = @bitCast(u64, int_min) },
const shift_amt = wasm_bits - int_info.bits;
const shift_val = switch (wasm_bits) {
32 => WValue{ .imm32 = shift_amt },
64 => WValue{ .imm64 = shift_amt },
else => unreachable,
};

if (int_info.signedness == .unsigned and op == .add) {
const diff = try self.binOp(int_max_wvalue, lhs, lhs_ty, .sub);
const cmp_res = try self.cmp(rhs, diff, lhs_ty, .gt);
try self.emitWValue(cmp_res);
try self.addLabel(.local_set, overflow_bit.local);
} else if (int_info.signedness == .unsigned and op == .sub) {
const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt);
try self.emitWValue(cmp_res);
try self.addLabel(.local_set, overflow_bit.local);
} else if (int_info.signedness == .signed and op != .shl) {
// for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow.
// We first create an outer block, where we handle overflow.
// Then we create an inner block, where underflow is handled.
try self.startBlock(.block, wasm.block_empty);
try self.startBlock(.block, wasm.block_empty);
{
try self.emitWValue(lhs);
const cmp_result = try self.cmp(lhs, zero, lhs_ty, .lt);
try self.emitWValue(cmp_result);
// for signed integers, we first apply signed shifts by the difference in bits
// to get the signed value, as we store it internally as 2's complement.
const lhs = if (wasm_bits != int_info.bits and is_signed) blk: {
const shl = try self.binOp(lhs_op, shift_val, lhs_ty, .shl);
break :blk try self.binOp(shl, shift_val, lhs_ty, .shr);
} else lhs_op;
const rhs = if (wasm_bits != int_info.bits and is_signed) blk: {
const shl = try self.binOp(rhs_op, shift_val, lhs_ty, .shl);
break :blk try self.binOp(shl, shift_val, lhs_ty, .shr);
} else rhs_op;

const bin_op = try self.binOp(lhs, rhs, lhs_ty, op);
const result = if (wasm_bits != int_info.bits) blk: {
break :blk try self.wrapOperand(bin_op, lhs_ty);
} else bin_op;

const cmp_op: std.math.CompareOperator = if (op == .sub) .gt else .lt;
const overflow_bit: WValue = if (is_signed) blk: {
if (wasm_bits == int_info.bits) {
const cmp_zero = try self.cmp(rhs, zero, lhs_ty, cmp_op);
const lt = try self.cmp(bin_op, lhs, lhs_ty, .lt);
break :blk try self.binOp(cmp_zero, lt, Type.u32, .xor); // result of cmp_zero and lt is always 32bit
}
try self.addLabel(.br_if, 0); // break to outer block, and handle underflow
const shl = try self.binOp(bin_op, shift_val, lhs_ty, .shl);
const shr = try self.binOp(shl, shift_val, lhs_ty, .shr);
break :blk try self.cmp(shr, bin_op, lhs_ty, .neq);
} else if (wasm_bits == int_info.bits)
try self.cmp(bin_op, lhs, lhs_ty, cmp_op)
else
try self.cmp(bin_op, result, lhs_ty, .neq);

// handle overflow
{
const diff = try self.binOp(int_max_wvalue, lhs, lhs_ty, .sub);
const cmp_res = try self.cmp(rhs, diff, lhs_ty, if (op == .add) .gt else .lt);
try self.emitWValue(cmp_res);
try self.addLabel(.local_set, overflow_bit.local);
}
try self.addLabel(.br, 1); // break from blocks, and continue regular flow.
try self.endBlock();
const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
try self.store(result_ptr, result, lhs_ty, 0);
const offset = @intCast(u32, lhs_ty.abiSize(self.target));
try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);

// handle underflow
{
const diff = try self.binOp(int_min_wvalue, lhs, lhs_ty, .sub);
const cmp_res = try self.cmp(rhs, diff, lhs_ty, if (op == .add) .lt else .gt);
try self.emitWValue(cmp_res);
try self.addLabel(.local_set, overflow_bit.local);
}
try self.endBlock();
return result_ptr;
}

fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
const lhs = try self.resolveInst(extra.lhs);
const rhs = try self.resolveInst(extra.rhs);
const lhs_ty = self.air.typeOf(extra.lhs);

if (lhs_ty.zigTypeTag() == .Vector) {
return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
}

const bin_op = if (op == .shl) blk: {
const tmp_val = try self.binOp(lhs, rhs, lhs_ty, op);
const cmp_res = try self.cmp(tmp_val, int_max_wvalue, lhs_ty, .gt);
try self.emitWValue(cmp_res);
try self.addLabel(.local_set, overflow_bit.local);
const int_info = lhs_ty.intInfo(self.target);
const is_signed = int_info.signedness == .signed;
const wasm_bits = toWasmBits(int_info.bits) orelse {
return self.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits});
};

try self.emitWValue(tmp_val);
try self.emitWValue(int_max_wvalue);
switch (wasm_bits) {
32 => try self.addTag(.i32_and),
64 => try self.addTag(.i64_and),
const shl = try self.binOp(lhs, rhs, lhs_ty, .shl);
const result = if (wasm_bits != int_info.bits) blk: {
break :blk try self.wrapOperand(shl, lhs_ty);
} else shl;

const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: {
const shift_amt = wasm_bits - int_info.bits;
const shift_val = switch (wasm_bits) {
32 => WValue{ .imm32 = shift_amt },
64 => WValue{ .imm64 = shift_amt },
else => unreachable,
}
try self.addLabel(.local_set, tmp_val.local);
break :blk tmp_val;
} else try self.wrapBinOp(lhs, rhs, lhs_ty, op);
};

const secondary_shl = try self.binOp(shl, shift_val, lhs_ty, .shl);
const initial_shr = try self.binOp(secondary_shl, shift_val, lhs_ty, .shr);
const shr = try self.wrapBinOp(initial_shr, rhs, lhs_ty, .shr);
break :blk try self.cmp(lhs, shr, lhs_ty, .neq);
} else blk: {
const shr = try self.binOp(result, rhs, lhs_ty, .shr);
break :blk try self.cmp(lhs, shr, lhs_ty, .neq);
};

const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
try self.store(result_ptr, bin_op, lhs_ty, 0);
try self.store(result_ptr, result, lhs_ty, 0);
const offset = @intCast(u32, lhs_ty.abiSize(self.target));
try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);

Expand Down
2 changes: 1 addition & 1 deletion src/arch/x86_64/Emit.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1896,7 +1896,7 @@ fn lowerToMrEnc(
const opc = getOpCode(tag, .mr, reg.size() == 8 or reg_or_mem.size() == 8).?;
switch (reg_or_mem) {
.register => |dst_reg| {
const encoder = try Encoder.init(code, 3);
const encoder = try Encoder.init(code, 4);
if (dst_reg.size() == 16) {
encoder.prefix16BitMode();
}
Expand Down
Loading