aboutsummaryrefslogtreecommitdiff
path: root/src/arch/wasm/CodeGen.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2022-05-16 20:40:57 -0400
committerGitHub <noreply@github.com>2022-05-16 20:40:57 -0400
commit5888446c03b1f77a031f5a8093488a6a2f6decb6 (patch)
treecb084cae72edc4c581c0ffa37cdc1130c6c2a8fc /src/arch/wasm/CodeGen.zig
parent7a4758ed7868096c12fec8dacba0bfd4a37fdd13 (diff)
parentf33b3fc3eae54b9d1159fc5a7a69a4b0e4aceca6 (diff)
downloadzig-5888446c03b1f77a031f5a8093488a6a2f6decb6.tar.gz
zig-5888446c03b1f77a031f5a8093488a6a2f6decb6.zip
Merge pull request #11316 from wsengir/stage2-overflow-safety
stage2: vectorized overflow arithmetic, integer overflow safety, left-shift overflow safety
Diffstat (limited to 'src/arch/wasm/CodeGen.zig')
-rw-r--r--src/arch/wasm/CodeGen.zig161
1 files changed, 84 insertions, 77 deletions
diff --git a/src/arch/wasm/CodeGen.zig b/src/arch/wasm/CodeGen.zig
index 947174aaed..8e84b7d1fe 100644
--- a/src/arch/wasm/CodeGen.zig
+++ b/src/arch/wasm/CodeGen.zig
@@ -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),
@@ -3941,25 +3941,22 @@ 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) {
@@ -3967,83 +3964,93 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
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);