authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-05-13 21:25:23+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-05-16 13:55:26-07:00
log160aa4c11dcb0413796d08fd623ce7bbeabaf04b
tree6a099fce5aef5a2e58e273277f72ad2711843a3a
parent0a2d3d41556a3bbe836dafa5321439fa6da9b464

wasm: Improve shl_with_overflow

This re-implements the shl_with_overflow operation from scratch, making it a lot more robust and outputs the equal code to the LLVM backend.

3 files changed, 49 insertions(+), 120 deletions(-)

src/arch/wasm/CodeGen.zig+49-115
......@@ -1452,7 +1452,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
14521452
14531453 .add_with_overflow => self.airAddSubWithOverflow(inst, .add),
14541454 .sub_with_overflow => self.airAddSubWithOverflow(inst, .sub),
1455 .shl_with_overflow => self.airBinOpOverflow(inst, .shl),
1455 .shl_with_overflow => self.airShlWithOverflow(inst),
14561456 .mul_with_overflow => self.airMulWithOverflow(inst),
14571457
14581458 .clz => self.airClz(inst),
......@@ -3941,115 +3941,6 @@ fn airPtrSliceFieldPtr(self: *Self, inst: Air.Inst.Index, offset: u32) InnerErro
39413941 return self.buildPointerOffset(slice_ptr, offset, .new);
39423942}
39433943
3944fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
3945 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
3946
3947 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
3948 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3949 const lhs = try self.resolveInst(extra.lhs);
3950 const rhs = try self.resolveInst(extra.rhs);
3951 const lhs_ty = self.air.typeOf(extra.lhs);
3952
3953 if (lhs_ty.zigTypeTag() == .Vector) {
3954 return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
3955 }
3956
3957 // We store the bit if it's overflowed or not in this. As it's zero-initialized
3958 // we only need to update it if an overflow (or underflow) occured.
3959 const overflow_bit = try self.allocLocal(Type.initTag(.u1));
3960 const int_info = lhs_ty.intInfo(self.target);
3961 const wasm_bits = toWasmBits(int_info.bits) orelse {
3962 return self.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits});
3963 };
3964
3965 const zero = switch (wasm_bits) {
3966 32 => WValue{ .imm32 = 0 },
3967 64 => WValue{ .imm64 = 0 },
3968 else => unreachable,
3969 };
3970 const int_max = (@as(u65, 1) << @intCast(u7, int_info.bits - @boolToInt(int_info.signedness == .signed))) - 1;
3971 const int_max_wvalue = switch (wasm_bits) {
3972 32 => WValue{ .imm32 = @intCast(u32, int_max) },
3973 64 => WValue{ .imm64 = @intCast(u64, int_max) },
3974 else => unreachable,
3975 };
3976 const int_min = if (int_info.signedness == .unsigned)
3977 @as(i64, 0)
3978 else
3979 -@as(i64, 1) << @intCast(u6, int_info.bits - 1);
3980 const int_min_wvalue = switch (wasm_bits) {
3981 32 => WValue{ .imm32 = @bitCast(u32, @intCast(i32, int_min)) },
3982 64 => WValue{ .imm64 = @bitCast(u64, int_min) },
3983 else => unreachable,
3984 };
3985
3986 if (int_info.signedness == .unsigned and op == .add) {
3987 const diff = try self.binOp(int_max_wvalue, lhs, lhs_ty, .sub);
3988 const cmp_res = try self.cmp(rhs, diff, lhs_ty, .gt);
3989 try self.emitWValue(cmp_res);
3990 try self.addLabel(.local_set, overflow_bit.local);
3991 } else if (op == .sub) {
3992 const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt);
3993 try self.emitWValue(cmp_res);
3994 try self.addLabel(.local_set, overflow_bit.local);
3995 } else if (int_info.signedness == .signed and op != .shl) {
3996 // for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow.
3997 // We first create an outer block, where we handle overflow.
3998 // Then we create an inner block, where underflow is handled.
3999 try self.startBlock(.block, wasm.block_empty);
4000 try self.startBlock(.block, wasm.block_empty);
4001 {
4002 try self.emitWValue(lhs);
4003 const cmp_result = try self.cmp(lhs, zero, lhs_ty, .lt);
4004 try self.emitWValue(cmp_result);
4005 }
4006 try self.addLabel(.br_if, 0); // break to outer block, and handle underflow
4007
4008 // handle overflow
4009 {
4010 const diff = try self.binOp(int_max_wvalue, lhs, lhs_ty, .sub);
4011 const cmp_res = try self.cmp(rhs, diff, lhs_ty, if (op == .add) .gt else .lt);
4012 try self.emitWValue(cmp_res);
4013 try self.addLabel(.local_set, overflow_bit.local);
4014 }
4015 try self.addLabel(.br, 1); // break from blocks, and continue regular flow.
4016 try self.endBlock();
4017
4018 // handle underflow
4019 {
4020 const diff = try self.binOp(int_min_wvalue, lhs, lhs_ty, .sub);
4021 const cmp_res = try self.cmp(rhs, diff, lhs_ty, if (op == .add) .lt else .gt);
4022 try self.emitWValue(cmp_res);
4023 try self.addLabel(.local_set, overflow_bit.local);
4024 }
4025 try self.endBlock();
4026 }
4027
4028 const bin_op = if (op == .shl) blk: {
4029 const tmp_val = try self.binOp(lhs, rhs, lhs_ty, op);
4030 const cmp_res = try self.cmp(tmp_val, int_max_wvalue, lhs_ty, .gt);
4031 try self.emitWValue(cmp_res);
4032 try self.addLabel(.local_set, overflow_bit.local);
4033
4034 try self.emitWValue(tmp_val);
4035 try self.emitWValue(int_max_wvalue);
4036 switch (wasm_bits) {
4037 32 => try self.addTag(.i32_and),
4038 64 => try self.addTag(.i64_and),
4039 else => unreachable,
4040 }
4041 try self.addLabel(.local_set, tmp_val.local);
4042 break :blk tmp_val;
4043 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);
4044
4045 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
4046 try self.store(result_ptr, bin_op, lhs_ty, 0);
4047 const offset = @intCast(u32, lhs_ty.abiSize(self.target));
4048 try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);
4049
4050 return result_ptr;
4051}
4052
40533944fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
40543945 assert(op == .add or op == .sub);
40553946 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
......@@ -4065,13 +3956,9 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W
40653956 const int_info = lhs_ty.intInfo(self.target);
40663957 const is_signed = int_info.signedness == .signed;
40673958 const wasm_bits = toWasmBits(int_info.bits) orelse {
4068 return self.fail("TODO: Implement sub_with_overflow for integer bitsize: {d}", .{int_info.bits});
3959 return self.fail("TODO: Implement {{add/sub}}_with_overflow for integer bitsize: {d}", .{int_info.bits});
40693960 };
40703961
4071 if (wasm_bits == 128) {
4072 return self.fail("TODO: Implement sub_with_overflow for 128 bit integers", .{});
4073 }
4074
40753962 const zero = switch (wasm_bits) {
40763963 32 => WValue{ .imm32 = 0 },
40773964 64 => WValue{ .imm64 = 0 },
......@@ -4123,6 +4010,53 @@ fn airAddSubWithOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!W
41234010 return result_ptr;
41244011}
41254012
4013fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
4014 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
4015 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
4016 const lhs = try self.resolveInst(extra.lhs);
4017 const rhs = try self.resolveInst(extra.rhs);
4018 const lhs_ty = self.air.typeOf(extra.lhs);
4019
4020 if (lhs_ty.zigTypeTag() == .Vector) {
4021 return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
4022 }
4023
4024 const int_info = lhs_ty.intInfo(self.target);
4025 const is_signed = int_info.signedness == .signed;
4026 const wasm_bits = toWasmBits(int_info.bits) orelse {
4027 return self.fail("TODO: Implement shl_with_overflow for integer bitsize: {d}", .{int_info.bits});
4028 };
4029
4030 const shl = try self.binOp(lhs, rhs, lhs_ty, .shl);
4031 const result = if (wasm_bits != int_info.bits) blk: {
4032 break :blk try self.wrapOperand(shl, lhs_ty);
4033 } else shl;
4034
4035 const overflow_bit = if (wasm_bits != int_info.bits and is_signed) blk: {
4036 const shift_amt = wasm_bits - int_info.bits;
4037 const shift_val = switch (wasm_bits) {
4038 32 => WValue{ .imm32 = shift_amt },
4039 64 => WValue{ .imm64 = shift_amt },
4040 else => unreachable,
4041 };
4042
4043 const secondary_shl = try self.binOp(shl, shift_val, lhs_ty, .shl);
4044 const initial_shr = try self.binOp(secondary_shl, shift_val, lhs_ty, .shr);
4045 const shr = try self.wrapBinOp(initial_shr, rhs, lhs_ty, .shr);
4046 break :blk try self.cmp(lhs, shr, lhs_ty, .neq);
4047 } else blk: {
4048 const shr = try self.binOp(result, rhs, lhs_ty, .shr);
4049 break :blk try self.cmp(lhs, shr, lhs_ty, .neq);
4050 };
4051
4052 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
4053 try self.store(result_ptr, result, lhs_ty, 0);
4054 const offset = @intCast(u32, lhs_ty.abiSize(self.target));
4055 try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);
4056
4057 return result_ptr;
4058}
4059
41264060fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
41274061 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
41284062 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
test/behavior/union.zig-2
......@@ -212,7 +212,6 @@ test "union with specified enum tag" {
212212 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
213213 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
214214 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
215 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
216215
217216 try doTest();
218217 comptime try doTest();
......@@ -222,7 +221,6 @@ test "packed union generates correctly aligned type" {
222221 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
223222 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
224223 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
225 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
226224 if (builtin.zig_backend == .stage1) return error.SkipZigTest;
227225
228226 const U = packed union {
test/behavior/while.zig-3
......@@ -146,7 +146,6 @@ test "while with optional as condition" {
146146 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
147147 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
148148 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
149 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
150149
151150 numbers_left = 10;
152151 var sum: i32 = 0;
......@@ -160,7 +159,6 @@ test "while with optional as condition with else" {
160159 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
161160 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
162161 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
163 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
164162
165163 numbers_left = 10;
166164 var sum: i32 = 0;
......@@ -179,7 +177,6 @@ test "while with error union condition" {
179177 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
180178 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
181179 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
182 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
183180
184181 numbers_left = 10;
185182 var sum: i32 = 0;