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
signature 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 {...@@ -1424,7 +1424,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue {
1424 .add_with_overflow => self.airBinOpOverflow(inst, .add),1424 .add_with_overflow => self.airBinOpOverflow(inst, .add),
1425 .sub_with_overflow => self.airBinOpOverflow(inst, .sub),1425 .sub_with_overflow => self.airBinOpOverflow(inst, .sub),
1426 .shl_with_overflow => self.airBinOpOverflow(inst, .shl),1426 .shl_with_overflow => self.airBinOpOverflow(inst, .shl),
1427 .mul_with_overflow => self.airBinOpOverflow(inst, .mul),1427 .mul_with_overflow => self.airMulWithOverflow(inst),
14281428
1429 .clz => self.airClz(inst),1429 .clz => self.airClz(inst),
1430 .ctz => self.airCtz(inst),1430 .ctz => self.airCtz(inst),
...@@ -1927,7 +1927,14 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {...@@ -1927,7 +1927,14 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1927 const lhs = try self.resolveInst(bin_op.lhs);1927 const lhs = try self.resolveInst(bin_op.lhs);
1928 const rhs = try self.resolveInst(bin_op.rhs);1928 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);
1931}1938}
19321939
1933fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue {1940fn 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...@@ -1941,31 +1948,8 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError
1941 });1948 });
1942 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));1949 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1943 const bin_local = try self.allocLocal(ty);1950 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
1967 try self.addLabel(.local_set, bin_local.local);1951 try self.addLabel(.local_set, bin_local.local);
1968 return bin_local;1952 return self.wrapOperand(bin_local, ty);
1969}1953}
19701954
1971/// Wraps an operand based on a given type's bitsize.1955/// Wraps an operand based on a given type's bitsize.
...@@ -2855,11 +2839,12 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2855,11 +2839,12 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2855 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2839 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2856 const ty = self.air.getRefType(ty_op.ty);2840 const ty = self.air.getRefType(ty_op.ty);
2857 const operand = try self.resolveInst(ty_op.operand);2841 const operand = try self.resolveInst(ty_op.operand);
2858 const ref_ty = self.air.typeOf(ty_op.operand);2842 const operand_ty = self.air.typeOf(ty_op.operand);
2859 if (ty.abiSize(self.target) > 8 or ref_ty.abiSize(self.target) > 8) {2843 if (ty.abiSize(self.target) > 8 or operand_ty.abiSize(self.target) > 8) {
2860 return self.fail("todo Wasm intcast for bitsize > 64", .{});2844 return self.fail("todo Wasm intcast for bitsize > 64", .{});
2861 }2845 }
2862 return self.intcast(operand, ty, ref_ty);2846
2847 return self.intcast(operand, operand_ty, ty);
2863}2848}
28642849
2865/// Upcasts or downcasts an integer based on the given and wanted types,2850/// 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 {...@@ -3102,63 +3087,17 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
3102}3087}
31033088
3104fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {3089fn 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 = {} };
3106 const ty_op = self.air.instructions.items(.data)[inst].ty_op;3091 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
3107 const operand = try self.resolveInst(ty_op.operand);3092 const operand = try self.resolveInst(ty_op.operand);
3108 const op_ty = self.air.typeOf(ty_op.operand);3093 const wanted_ty = self.air.getRefType(ty_op.ty);
3109 const int_info = self.air.getRefType(ty_op.ty).intInfo(self.target);3094 const int_info = wanted_ty.intInfo(self.target);
3110 const wanted_bits = int_info.bits;3095 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) orelse3097 _ = toWasmBits(wanted_bits) orelse {
3115 return self.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{wanted_bits});3098 return self.fail("TODO: Implement wasm integer truncation for integer bitsize: {d}", .{wanted_bits});
31163099 };
3117 // Use wasm's instruction to wrap from 64bit to 32bit integer when possible3100 return self.wrapOperand(operand, wanted_ty);
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;
3162}3101}
31633102
3164fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {3103fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -3448,7 +3387,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -3448,7 +3387,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
34483387
3449 const result = try self.allocLocal(dest_ty);3388 const result = try self.allocLocal(dest_ty);
3450 try self.addLabel(.local_set, result.local);3389 try self.addLabel(.local_set, result.local);
3451 return result;3390
3391 return self.wrapOperand(result, dest_ty);
3452}3392}
34533393
3454fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {3394fn 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...@@ -3952,6 +3892,10 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
3952 const rhs = try self.resolveInst(extra.rhs);3892 const rhs = try self.resolveInst(extra.rhs);
3953 const lhs_ty = self.air.typeOf(extra.lhs);3893 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
3955 // We store the bit if it's overflowed or not in this. As it's zero-initialized3899 // We store the bit if it's overflowed or not in this. As it's zero-initialized
3956 // we only need to update it if an overflow (or underflow) occured.3900 // we only need to update it if an overflow (or underflow) occured.
3957 const overflow_bit = try self.allocLocal(Type.initTag(.u1));3901 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...@@ -3990,7 +3934,7 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
3990 const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt);3934 const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt);
3991 try self.emitWValue(cmp_res);3935 try self.emitWValue(cmp_res);
3992 try self.addLabel(.local_set, overflow_bit.local);3936 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) {
3994 // for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow.3938 // for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow.
3995 // We first create an outer block, where we handle overflow.3939 // We first create an outer block, where we handle overflow.
3996 // Then we create an inner block, where underflow is handled.3940 // 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...@@ -4038,64 +3982,100 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
4038 }3982 }
4039 try self.addLabel(.local_set, tmp_val.local);3983 try self.addLabel(.local_set, tmp_val.local);
4040 break :blk tmp_val;3984 break :blk tmp_val;
4041 } else if (op == .mul) blk: {3985 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);
4042 // for 32 & 64 bitsize we calculate overflow3986
4043 // differently.3987 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
4044 if (int_info.bits == 32) {3988 try self.store(result_ptr, bin_op, lhs_ty, 0);
4045 const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64;3989 const offset = @intCast(u32, lhs_ty.abiSize(self.target));
4046 const lhs_upcast = try self.intcast(lhs, lhs_ty, new_ty);3990 try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);
4047 const rhs_upcast = try self.intcast(rhs, lhs_ty, new_ty);3991
4048 const bin_op = try self.binOp(lhs_upcast, rhs_upcast, new_ty, op);3992 return result_ptr;
4049 if (int_info.signedness == .unsigned) {3993}
4050 const shr = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);3994
4051 const wrap = try self.intcast(shr, new_ty, lhs_ty);3995fn airMulWithOverflow(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
4052 const cmp_res = try self.cmp(wrap, zero, lhs_ty, .neq);3996 const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
4053 try self.emitWValue(cmp_res);3997 const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
4054 try self.addLabel(.local_set, overflow_bit.local);3998 const lhs = try self.resolveInst(extra.lhs);
4055 break :blk try self.intcast(bin_op, new_ty, lhs_ty);3999 const rhs = try self.resolveInst(extra.rhs);
4056 } else {4000 const lhs_ty = self.air.typeOf(extra.lhs);
4057 const down_cast = try self.intcast(bin_op, new_ty, lhs_ty);4001
4058 const shr = try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr);4002 if (lhs_ty.zigTypeTag() == .Vector) {
40594003 return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
4060 const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);4004 }
4061 const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty);4005
4062 const cmp_res = try self.cmp(down_shr_res, shr, lhs_ty, .neq);4006 // We store the bit if it's overflowed or not in this. As it's zero-initialized
4063 try self.emitWValue(cmp_res);4007 // we only need to update it if an overflow (or underflow) occured.
4064 try self.addLabel(.local_set, overflow_bit.local);4008 const overflow_bit = try self.allocLocal(Type.initTag(.u1));
4065 break :blk down_cast;4009 const int_info = lhs_ty.intInfo(self.target);
4066 }4010 const wasm_bits = toWasmBits(int_info.bits) orelse {
4067 } else if (int_info.signedness == .signed) {4011 return self.fail("TODO: Implement overflow arithmetic for integer bitsize: {d}", .{int_info.bits});
4068 const shift_imm = if (wasm_bits == 32)4012 };
4069 WValue{ .imm32 = wasm_bits - int_info.bits }4013
4070 else4014 if (wasm_bits == 64) {
4071 WValue{ .imm64 = wasm_bits - int_info.bits };4015 return self.fail("TODO: Implement `@mulWithOverflow` for integer bitsize: {d}", .{int_info.bits});
40724016 }
4073 const lhs_shl = try self.binOp(lhs, shift_imm, lhs_ty, .shl);4017
4074 const lhs_shr = try self.binOp(lhs_shl, shift_imm, lhs_ty, .shr);4018 const zero = switch (wasm_bits) {
4075 const rhs_shl = try self.binOp(rhs, shift_imm, lhs_ty, .shl);4019 32 => WValue{ .imm32 = 0 },
4076 const rhs_shr = try self.binOp(rhs_shl, shift_imm, lhs_ty, .shr);4020 64 => WValue{ .imm64 = 0 },
40774021 else => unreachable,
4078 const bin_op = try self.binOp(lhs_shr, rhs_shr, lhs_ty, op);4022 };
4079 const shl = try self.binOp(bin_op, shift_imm, lhs_ty, .shl);4023
4080 const shr = try self.binOp(shl, shift_imm, lhs_ty, .shr);4024 // for 32 bit integers we upcast it to a 64bit integer
40814025 const bin_op = if (int_info.bits == 32) blk: {
4082 const cmp_op = try self.cmp(shr, bin_op, lhs_ty, .neq);4026 const new_ty = if (int_info.signedness == .signed) Type.i64 else Type.u64;
4083 try self.emitWValue(cmp_op);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);
4084 try self.addLabel(.local_set, overflow_bit.local);4035 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);
4086 } else {4037 } else {
4087 const bin_op = try self.binOp(lhs, rhs, lhs_ty, op);4038 const down_cast = try self.intcast(bin_op, new_ty, lhs_ty);
4088 const shift_imm = if (wasm_bits == 32)4039 const shr = try self.binOp(down_cast, .{ .imm32 = int_info.bits - 1 }, lhs_ty, .shr);
4089 WValue{ .imm32 = int_info.bits }4040
4090 else4041 const shr_res = try self.binOp(bin_op, .{ .imm64 = int_info.bits }, new_ty, .shr);
4091 WValue{ .imm64 = int_info.bits };4042 const down_shr_res = try self.intcast(shr_res, new_ty, lhs_ty);
4092 const shr = try self.binOp(bin_op, shift_imm, lhs_ty, .shr);4043 const cmp_res = try self.cmp(down_shr_res, shr, lhs_ty, .neq);
4093 const cmp_op = try self.cmp(shr, zero, lhs_ty, .neq);4044 try self.emitWValue(cmp_res);
4094 try self.emitWValue(cmp_op);
4095 try self.addLabel(.local_set, overflow_bit.local);4045 try self.addLabel(.local_set, overflow_bit.local);
4096 break :blk try self.wrapOperand(bin_op, lhs_ty);4046 break :blk down_cast;
4097 }4047 }
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
4100 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));4080 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
4101 try self.store(result_ptr, bin_op, lhs_ty, 0);4081 try self.store(result_ptr, bin_op, lhs_ty, 0);