| ... | ... | @@ -1424,7 +1424,7 @@ fn genInst(self: *Self, inst: Air.Inst.Index) !WValue { |
| 1424 | 1424 | .add_with_overflow => self.airBinOpOverflow(inst, .add), |
| 1425 | 1425 | .sub_with_overflow => self.airBinOpOverflow(inst, .sub), |
| 1426 | 1426 | .shl_with_overflow => self.airBinOpOverflow(inst, .shl), |
| 1427 | | .mul_with_overflow => self.airBinOpOverflow(inst, .mul), |
| 1427 | .mul_with_overflow => self.airMulWithOverflow(inst), |
| 1428 | 1428 | |
| 1429 | 1429 | .clz => self.airClz(inst), |
| 1430 | 1430 | .ctz => self.airCtz(inst), |
| ... | ... | @@ -1927,7 +1927,14 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue { |
| 1927 | 1927 | const lhs = try self.resolveInst(bin_op.lhs); |
| 1928 | 1928 | const rhs = try self.resolveInst(bin_op.rhs); |
| 1929 | 1929 | |
| 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 | } |
| 1932 | 1939 | |
| 1933 | 1940 | fn 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 | 1948 | }); |
| 1942 | 1949 | try self.addTag(Mir.Inst.Tag.fromOpcode(opcode)); |
| 1943 | 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 | 1951 | try self.addLabel(.local_set, bin_local.local); |
| 1968 | | return bin_local; |
| 1952 | return self.wrapOperand(bin_local, ty); |
| 1969 | 1953 | } |
| 1970 | 1954 | |
| 1971 | 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 | 2839 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 2856 | 2840 | const ty = self.air.getRefType(ty_op.ty); |
| 2857 | 2841 | 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) { |
| 2860 | 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 | } |
| 2864 | 2849 | |
| 2865 | 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 | 3087 | } |
| 3103 | 3088 | |
| 3104 | 3089 | fn 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 | 3091 | const ty_op = self.air.instructions.items(.data)[inst].ty_op; |
| 3107 | 3092 | 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); |
| 3110 | 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; |
| 3113 | 3096 | |
| 3114 | | const wasm_bits = toWasmBits(wanted_bits) orelse |
| 3097 | _ = toWasmBits(wanted_bits) orelse { |
| 3115 | 3098 | 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); |
| 3162 | 3101 | } |
| 3163 | 3102 | |
| 3164 | 3103 | fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| ... | ... | @@ -3448,7 +3387,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { |
| 3448 | 3387 | |
| 3449 | 3388 | const result = try self.allocLocal(dest_ty); |
| 3450 | 3389 | try self.addLabel(.local_set, result.local); |
| 3451 | | return result; |
| 3390 | |
| 3391 | return self.wrapOperand(result, dest_ty); |
| 3452 | 3392 | } |
| 3453 | 3393 | |
| 3454 | 3394 | fn 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 | 3892 | const rhs = try self.resolveInst(extra.rhs); |
| 3953 | 3893 | const lhs_ty = self.air.typeOf(extra.lhs); |
| 3954 | 3894 | |
| 3895 | if (lhs_ty.zigTypeTag() == .Vector) { |
| 3896 | return self.fail("TODO: Implement overflow arithmetic for vectors", .{}); |
| 3897 | } |
| 3898 | |
| 3955 | 3899 | // We store the bit if it's overflowed or not in this. As it's zero-initialized |
| 3956 | 3900 | // we only need to update it if an overflow (or underflow) occured. |
| 3957 | 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 | 3934 | const cmp_res = try self.cmp(lhs, rhs, lhs_ty, .lt); |
| 3991 | 3935 | try self.emitWValue(cmp_res); |
| 3992 | 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 | 3938 | // for overflow, we first check if lhs is > 0 (or lhs < 0 in case of subtraction). If not, we will not overflow. |
| 3995 | 3939 | // We first create an outer block, where we handle overflow. |
| 3996 | 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 | 3982 | } |
| 4039 | 3983 | try self.addLabel(.local_set, tmp_val.local); |
| 4040 | 3984 | 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 | |
| 3995 | fn 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); |
| 4084 | 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 | 4037 | } 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); |
| 4095 | 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 | }; |
| 4099 | 4079 | |
| 4100 | 4080 | const result_ptr = try self.allocStack(self.air.typeOfIndex(inst)); |
| 4101 | 4081 | try self.store(result_ptr, bin_op, lhs_ty, 0); |