authorgravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-05-07 17:04:19+02:00
committergravatar for luuk@degram.devLuuk de Gram <luuk@degram.dev> 2022-05-07 17:04:19+02:00
logad4f0dda8b8c270594ed24a27c808f8bd43924bf
tree13114b5847eaf3f578c43910cb0f772645951406
parent0c51e703f19d04edbfa26b7243b9bc125b5489a6
signaturelock-open Commit is signed but in an unrecognized format.

wasm: Fix `@floatToInt` and split overflow ops

As we now store negative signed integers as two's complement, we must also ensure that when truncating a float, its value is wrapped around the integer's size. This also splits `@mulWithOverflow` into its own function to make the code more maintainable and reduce branching.

1 files changed, 117 insertions(+), 137 deletions(-)

src/arch/wasm/CodeGen.zig+117-137
......@@ -1424,7 +1424,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
14241424 .add_with_overflow => self.airBinOpOverflow(inst, .add),
14251425 .sub_with_overflow => self.airBinOpOverflow(inst, .sub),
14261426 .shl_with_overflow => self.airBinOpOverflow(inst, .shl),
1427 .mul_with_overflow => self.airBinOpOverflow(inst, .mul),
1427 .mul_with_overflow => self.airMulWithOverflow(inst),
14281428
14291429 .clz => self.airClz(inst),
14301430 .ctz => self.airCtz(inst),
......@@ -1927,7 +1927,14 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
19271927 const lhs = try self.resolveInst(bin_op.lhs);
19281928 const rhs = try self.resolveInst(bin_op.rhs);
19291929
1930 return self.wrapBinOp(lhs, rhs, self.air.typeOf(bin_op.lhs), op);
1930 const ty = self.air.typeOf(bin_op.lhs);
1931 if (ty.zigTypeTag() == .Vector) {
1932 return self.fail("TODO: Implement wrapping arithmetic for vectors", .{});
1933 } else if (ty.abiSize(self.target) > 8) {
1934 return self.fail("TODO: Implement wrapping arithmetic for bitsize > 64", .{});
1935 }
1936
1937 return self.wrapBinOp(lhs, rhs, ty, op);
19311938}
19321939
19331940fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue {
......@@ -1941,31 +1948,8 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError
19411948 });
19421949 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
19431950 const bin_local = try self.allocLocal(ty);
1944
1945 const int_info = ty.intInfo(self.target);
1946 const bitsize = int_info.bits;
1947 const is_signed = int_info.signedness == .signed;
1948 // if target type bitsize is x < 32 and 32 > x < 64, we perform
1949 // result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed.
1950 if (bitsize != 32 and bitsize < 64) {
1951 // first check if we can use a single instruction,
1952 // wasm provides those if the integers are signed and 8/16-bit.
1953 // For arbitrary integer sizes, we use the algorithm mentioned above.
1954 if (is_signed and bitsize == 8) {
1955 try self.addTag(.i32_extend8_s);
1956 } else if (is_signed and bitsize == 16) {
1957 try self.addTag(.i32_extend16_s);
1958 } else {
1959 try self.addLabel(.local_set, bin_local.local);
1960 return self.wrapOperand(bin_local, ty);
1961 }
1962 } else if (int_info.bits > 64) {
1963 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
1964 }
1965
1966 // save the result in a temporary
19671951 try self.addLabel(.local_set, bin_local.local);
1968 return bin_local;
1952 return self.wrapOperand(bin_local, ty);
19691953}
19701954
19711955/// Wraps an operand based on a given type's bitsize.
......@@ -2855,11 +2839,12 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
28552839 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
28562840 const ty = self.air.getRefType(ty_op.ty);
28572841 const operand = try self.resolveInst(ty_op.operand);
2858 const ref_ty = self.air.typeOf(ty_op.operand);
2859 if (ty.abiSize(self.target) > 8 or ref_ty.abiSize(self.target) > 8) {
2842 const operand_ty = self.air.typeOf(ty_op.operand);
2843 if (ty.abiSize(self.target) > 8 or operand_ty.abiSize(self.target) > 8) {
28602844 return self.fail("todo Wasm intcast for bitsize > 64", .{});
28612845 }
2862 return self.intcast(operand, ty, ref_ty);
2846
2847 return self.intcast(operand, operand_ty, ty);
28632848}
28642849
28652850/// Upcasts or downcasts an integer based on the given and wanted types,
......@@ -3102,63 +3087,17 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
31023087}
31033088
31043089fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
3105 if (self.liveness.isUnused(inst)) return WValue.none;
3090 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
31063091 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
31073092 const operand = try self.resolveInst(ty_op.operand);
3108 const op_ty = self.air.typeOf(ty_op.operand);
3109 const int_info = self.air.getRefType(ty_op.ty).intInfo(self.target);
3093 const wanted_ty = self.air.getRefType(ty_op.ty);
3094 const int_info = wanted_ty.intInfo(self.target);
31103095 const wanted_bits = int_info.bits;
3111 const result = try self.allocLocal(self.air.getRefType(ty_op.ty));
3112 const op_bits = op_ty.intInfo(self.target).bits;
31133096
3114 const wasm_bits = toWasmBits(wanted_bits) orelse
3097 _ = toWasmBits(wanted_bits) orelse {
31153098 return self.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{wanted_bits});
3116
3117 // Use wasm's instruction to wrap from 64bit to 32bit integer when possible
3118 if (op_bits == 64 and wanted_bits == 32) {
3119 try self.emitWValue(operand);
3120 try self.addTag(.i32_wrap_i64);
3121 try self.addLabel(.local_set, result.local);
3122 return result;
3123 }
3124
3125 // Any other truncation must be done manually
3126 if (int_info.signedness == .unsigned) {
3127 const mask = (@as(u65, 1) << @intCast(u7, wanted_bits)) - 1;
3128 try self.emitWValue(operand);
3129 switch (wasm_bits) {
3130 32 => {
3131 try self.addImm32(@bitCast(i32, @intCast(u32, mask)));
3132 try self.addTag(.i32_and);
3133 },
3134 64 => {
3135 try self.addImm64(@intCast(u64, mask));
3136 try self.addTag(.i64_and);
3137 },
3138 else => unreachable,
3139 }
3140 } else {
3141 const shift_bits = wasm_bits - wanted_bits;
3142 try self.emitWValue(operand);
3143 switch (wasm_bits) {
3144 32 => {
3145 try self.addImm32(@bitCast(i16, shift_bits));
3146 try self.addTag(.i32_shl);
3147 try self.addImm32(@bitCast(i16, shift_bits));
3148 try self.addTag(.i32_shr_s);
3149 },
3150 64 => {
3151 try self.addImm64(shift_bits);
3152 try self.addTag(.i64_shl);
3153 try self.addImm64(shift_bits);
3154 try self.addTag(.i64_shr_s);
3155 },
3156 else => unreachable,
3157 }
3158 }
3159
3160 try self.addLabel(.local_set, result.local);
3161 return result;
3099 };
3100 return self.wrapOperand(operand, wanted_ty);
31623101}
31633102
31643103fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -3448,7 +3387,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
34483387
34493388 const result = try self.allocLocal(dest_ty);
34503389 try self.addLabel(.local_set, result.local);
3451 return result;
3390
3391 return self.wrapOperand(result, dest_ty);
34523392}
34533393
34543394fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
......@@ -3952,6 +3892,10 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
39523892 const rhs = try self.resolveInst(extra.rhs);
39533893 const lhs_ty = self.air.typeOf(extra.lhs);
39543894
3895 if (lhs_ty.zigTypeTag() == .Vector) {
3896 return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
3897 }
3898
39553899 // We store the bit if it's overflowed or not in this. As it's zero-initialized
39563900 // we only need to update it if an overflow (or underflow) occured.
39573901 const overflow_bit = try self.allocLocal(Type.initTag(.u1));
......@@ -3990,7 +3934,7 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
39903934 const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt);
39913935 try self.emitWValue(cmp_res);
39923936 try self.addLabel(.local_set, overflow_bit.local);
3993 } else if (int_info.signedness == .signed and op != .shl and op != .mul) {
3937 } else if (int_info.signedness == .signed and op != .shl) {
39943938 // for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow.
39953939 // We first create an outer block, where we handle overflow.
39963940 // Then we create an inner block, where underflow is handled.
......@@ -4038,64 +3982,100 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
40383982 }
40393983 try self.addLabel(.local_set, tmp_val.local);
40403984 break :blk tmp_val;
4041 } else if (op == .mul) blk: {
4042 // for 32 & 64 bitsize we calculate overflow
4043 // differently.
4044 if (int_info.bits == 32) {
4045 const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64;
4046 const lhs_upcast = try self.intcast(lhs, lhs_ty, new_ty);
4047 const rhs_upcast = try self.intcast(rhs, lhs_ty, new_ty);
4048 const bin_op = try self.binOp(lhs_upcast, rhs_upcast, new_ty, op);
4049 if (int_info.signedness == .unsigned) {
4050 const shr = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
4051 const wrap = try self.intcast(shr, new_ty, lhs_ty);
4052 const cmp_res = try self.cmp(wrap, zero, lhs_ty, .neq);
4053 try self.emitWValue(cmp_res);
4054 try self.addLabel(.local_set, overflow_bit.local);
4055 break :blk try self.intcast(bin_op, new_ty, lhs_ty);
4056 } else {
4057 const down_cast = try self.intcast(bin_op, new_ty, lhs_ty);
4058 const shr = try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr);
4059
4060 const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
4061 const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty);
4062 const cmp_res = try self.cmp(down_shr_res, shr, lhs_ty, .neq);
4063 try self.emitWValue(cmp_res);
4064 try self.addLabel(.local_set, overflow_bit.local);
4065 break :blk down_cast;
4066 }
4067 } else if (int_info.signedness == .signed) {
4068 const shift_imm = if (wasm_bits == 32)
4069 WValue{ .imm32 = wasm_bits - int_info.bits }
4070 else
4071 WValue{ .imm64 = wasm_bits - int_info.bits };
4072
4073 const lhs_shl = try self.binOp(lhs, shift_imm, lhs_ty, .shl);
4074 const lhs_shr = try self.binOp(lhs_shl, shift_imm, lhs_ty, .shr);
4075 const rhs_shl = try self.binOp(rhs, shift_imm, lhs_ty, .shl);
4076 const rhs_shr = try self.binOp(rhs_shl, shift_imm, lhs_ty, .shr);
4077
4078 const bin_op = try self.binOp(lhs_shr, rhs_shr, lhs_ty, op);
4079 const shl = try self.binOp(bin_op, shift_imm, lhs_ty, .shl);
4080 const shr = try self.binOp(shl, shift_imm, lhs_ty, .shr);
4081
4082 const cmp_op = try self.cmp(shr, bin_op, lhs_ty, .neq);
4083 try self.emitWValue(cmp_op);
3985 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);
3986
3987 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
3988 try self.store(result_ptr, bin_op, lhs_ty, 0);
3989 const offset = @intCast(u32, lhs_ty.abiSize(self.target));
3990 try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);
3991
3992 return result_ptr;
3993}
3994
3995fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
3996 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
3997 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
3998 const lhs = try self.resolveInst(extra.lhs);
3999 const rhs = try self.resolveInst(extra.rhs);
4000 const lhs_ty = self.air.typeOf(extra.lhs);
4001
4002 if (lhs_ty.zigTypeTag() == .Vector) {
4003 return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
4004 }
4005
4006 // We store the bit if it's overflowed or not in this. As it's zero-initialized
4007 // we only need to update it if an overflow (or underflow) occured.
4008 const overflow_bit = try self.allocLocal(Type.initTag(.u1));
4009 const int_info = lhs_ty.intInfo(self.target);
4010 const wasm_bits = toWasmBits(int_info.bits) orelse {
4011 return self.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits});
4012 };
4013
4014 if (wasm_bits == 64) {
4015 return self.fail("TODO: Implement `@mulWithOverflow` for integer bitsize: {d}", .{int_info.bits});
4016 }
4017
4018 const zero = switch (wasm_bits) {
4019 32 => WValue{ .imm32 = 0 },
4020 64 => WValue{ .imm64 = 0 },
4021 else => unreachable,
4022 };
4023
4024 // for 32 bit integers we upcast it to a 64bit integer
4025 const bin_op = if (int_info.bits == 32) blk: {
4026 const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64;
4027 const lhs_upcast = try self.intcast(lhs, lhs_ty, new_ty);
4028 const rhs_upcast = try self.intcast(rhs, lhs_ty, new_ty);
4029 const bin_op = try self.binOp(lhs_upcast, rhs_upcast, new_ty, .mul);
4030 if (int_info.signedness == .unsigned) {
4031 const shr = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
4032 const wrap = try self.intcast(shr, new_ty, lhs_ty);
4033 const cmp_res = try self.cmp(wrap, zero, lhs_ty, .neq);
4034 try self.emitWValue(cmp_res);
40844035 try self.addLabel(.local_set, overflow_bit.local);
4085 break :blk try self.wrapOperand(bin_op, lhs_ty);
4036 break :blk try self.intcast(bin_op, new_ty, lhs_ty);
40864037 } else {
4087 const bin_op = try self.binOp(lhs, rhs, lhs_ty, op);
4088 const shift_imm = if (wasm_bits == 32)
4089 WValue{ .imm32 = int_info.bits }
4090 else
4091 WValue{ .imm64 = int_info.bits };
4092 const shr = try self.binOp(bin_op, shift_imm, lhs_ty, .shr);
4093 const cmp_op = try self.cmp(shr, zero, lhs_ty, .neq);
4094 try self.emitWValue(cmp_op);
4038 const down_cast = try self.intcast(bin_op, new_ty, lhs_ty);
4039 const shr = try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr);
4040
4041 const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
4042 const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty);
4043 const cmp_res = try self.cmp(down_shr_res, shr, lhs_ty, .neq);
4044 try self.emitWValue(cmp_res);
40954045 try self.addLabel(.local_set, overflow_bit.local);
4096 break :blk try self.wrapOperand(bin_op, lhs_ty);
4046 break :blk down_cast;
40974047 }
4098 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);
4048 } else if (int_info.signedness == .signed) blk: {
4049 const shift_imm = if (wasm_bits == 32)
4050 WValue{ .imm32 = wasm_bits - int_info.bits }
4051 else
4052 WValue{ .imm64 = wasm_bits - int_info.bits };
4053
4054 const lhs_shl = try self.binOp(lhs, shift_imm, lhs_ty, .shl);
4055 const lhs_shr = try self.binOp(lhs_shl, shift_imm, lhs_ty, .shr);
4056 const rhs_shl = try self.binOp(rhs, shift_imm, lhs_ty, .shl);
4057 const rhs_shr = try self.binOp(rhs_shl, shift_imm, lhs_ty, .shr);
4058
4059 const bin_op = try self.binOp(lhs_shr, rhs_shr, lhs_ty, .mul);
4060 const shl = try self.binOp(bin_op, shift_imm, lhs_ty, .shl);
4061 const shr = try self.binOp(shl, shift_imm, lhs_ty, .shr);
4062
4063 const cmp_op = try self.cmp(shr, bin_op, lhs_ty, .neq);
4064 try self.emitWValue(cmp_op);
4065 try self.addLabel(.local_set, overflow_bit.local);
4066 break :blk try self.wrapOperand(bin_op, lhs_ty);
4067 } else blk: {
4068 const bin_op = try self.binOp(lhs, rhs, lhs_ty, .mul);
4069 const shift_imm = if (wasm_bits == 32)
4070 WValue{ .imm32 = int_info.bits }
4071 else
4072 WValue{ .imm64 = int_info.bits };
4073 const shr = try self.binOp(bin_op, shift_imm, lhs_ty, .shr);
4074 const cmp_op = try self.cmp(shr, zero, lhs_ty, .neq);
4075 try self.emitWValue(cmp_op);
4076 try self.addLabel(.local_set, overflow_bit.local);
4077 break :blk try self.wrapOperand(bin_op, lhs_ty);
4078 };
40994079
41004080 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
41014081 try self.store(result_ptr, bin_op, lhs_ty, 0);