authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-05-07 23:30:08+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2022-05-07 23:30:08+02:00
logf161d3875ad341971c97384587e2e6c2b50bc09c
treeae6e441e067fa881982f568de54b6c9e53f2e777
parente8c85450feac961a147d37394920081214ba9396
parenta11097958271562fe7e64716356c07d9996fad5f
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #11605 from Luukdegram/wasm-mul-overflow

stage2: wasm - Improve `@mulWithOverflow` implementation

5 files changed, 185 insertions(+), 129 deletions(-)

src/arch/wasm/CodeGen.zig+176-126
...@@ -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),
...@@ -1822,7 +1822,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro...@@ -1822,7 +1822,7 @@ fn store(self: *Self, lhs: WValue, rhs: WValue, ty: Type, offset: u32) InnerErro
18221822
1823 const opcode = buildOpcode(.{1823 const opcode = buildOpcode(.{
1824 .valtype1 = valtype,1824 .valtype1 = valtype,
1825 .width = abi_size * 8, // use bitsize instead of byte size1825 .width = abi_size * 8,
1826 .op = .store,1826 .op = .store,
1827 });1827 });
18281828
...@@ -1852,21 +1852,13 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -1852,21 +1852,13 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
1852fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {1852fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue {
1853 // load local's value from memory by its stack position1853 // load local's value from memory by its stack position
1854 try self.emitWValue(operand);1854 try self.emitWValue(operand);
1855 // Build the opcode with the right bitsize
1856 const signedness: std.builtin.Signedness = if (ty.isUnsignedInt() or
1857 ty.zigTypeTag() == .ErrorSet or
1858 ty.zigTypeTag() == .Bool)
1859 .unsigned
1860 else
1861 .signed;
18621855
1863 const abi_size = @intCast(u8, ty.abiSize(self.target));1856 const abi_size = @intCast(u8, ty.abiSize(self.target));
1864
1865 const opcode = buildOpcode(.{1857 const opcode = buildOpcode(.{
1866 .valtype1 = typeToValtype(ty, self.target),1858 .valtype1 = typeToValtype(ty, self.target),
1867 .width = abi_size * 8, // use bitsize instead of byte size1859 .width = abi_size * 8,
1868 .op = .load,1860 .op = .load,
1869 .signedness = signedness,1861 .signedness = .unsigned,
1870 });1862 });
18711863
1872 try self.addMemArg(1864 try self.addMemArg(
...@@ -1935,7 +1927,14 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {...@@ -1935,7 +1927,14 @@ fn airWrapBinOp(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue {
1935 const lhs = try self.resolveInst(bin_op.lhs);1927 const lhs = try self.resolveInst(bin_op.lhs);
1936 const rhs = try self.resolveInst(bin_op.rhs);1928 const rhs = try self.resolveInst(bin_op.rhs);
19371929
1938 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);
1939}1938}
19401939
1941fn 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 {
...@@ -1948,38 +1947,28 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError...@@ -1948,38 +1947,28 @@ fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError
1948 .signedness = if (ty.isSignedInt()) .signed else .unsigned,1947 .signedness = if (ty.isSignedInt()) .signed else .unsigned,
1949 });1948 });
1950 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));1949 try self.addTag(Mir.Inst.Tag.fromOpcode(opcode));
1951
1952 const int_info = ty.intInfo(self.target);
1953 const bitsize = int_info.bits;
1954 const is_signed = int_info.signedness == .signed;
1955 // if target type bitsize is x < 32 and 32 > x < 64, we perform
1956 // result & ((1<<N)-1) where N = bitsize or bitsize -1 incase of signed.
1957 if (bitsize != 32 and bitsize < 64) {
1958 // first check if we can use a single instruction,
1959 // wasm provides those if the integers are signed and 8/16-bit.
1960 // For arbitrary integer sizes, we use the algorithm mentioned above.
1961 if (is_signed and bitsize == 8) {
1962 try self.addTag(.i32_extend8_s);
1963 } else if (is_signed and bitsize == 16) {
1964 try self.addTag(.i32_extend16_s);
1965 } else {
1966 const result = (@as(u64, 1) << @intCast(u6, bitsize - @boolToInt(is_signed))) - 1;
1967 if (bitsize < 32) {
1968 try self.addImm32(@bitCast(i32, @intCast(u32, result)));
1969 try self.addTag(.i32_and);
1970 } else {
1971 try self.addImm64(result);
1972 try self.addTag(.i64_and);
1973 }
1974 }
1975 } else if (int_info.bits > 64) {
1976 return self.fail("TODO wasm: Integer wrapping for bitsizes larger than 64", .{});
1977 }
1978
1979 // save the result in a temporary
1980 const bin_local = try self.allocLocal(ty);1950 const bin_local = try self.allocLocal(ty);
1981 try self.addLabel(.local_set, bin_local.local);1951 try self.addLabel(.local_set, bin_local.local);
1982 return bin_local;1952 return self.wrapOperand(bin_local, ty);
1953}
1954
1955/// Wraps an operand based on a given type's bitsize.
1956/// Asserts `Type` is <= 64bits.
1957fn wrapOperand(self: *Self, operand: WValue, ty: Type) InnerError!WValue {
1958 assert(ty.abiSize(self.target) <= 8);
1959 const result_local = try self.allocLocal(ty);
1960 const bitsize = ty.intInfo(self.target).bits;
1961 const result = @intCast(u64, (@as(u65, 1) << @intCast(u7, bitsize)) - 1);
1962 try self.emitWValue(operand);
1963 if (bitsize <= 32) {
1964 try self.addImm32(@bitCast(i32, @intCast(u32, result)));
1965 try self.addTag(.i32_and);
1966 } else {
1967 try self.addImm64(result);
1968 try self.addTag(.i64_and);
1969 }
1970 try self.addLabel(.local_set, result_local.local);
1971 return result_local;
1983}1972}
19841973
1985fn lowerParentPtr(self: *Self, ptr_val: Value, ptr_child_ty: Type) InnerError!WValue {1974fn lowerParentPtr(self: *Self, ptr_val: Value, ptr_child_ty: Type) InnerError!WValue {
...@@ -2098,6 +2087,22 @@ fn lowerDeclRefValue(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index)...@@ -2098,6 +2087,22 @@ fn lowerDeclRefValue(self: *Self, tv: TypedValue, decl_index: Module.Decl.Index)
2098 } else return WValue{ .memory = target_sym_index };2087 } else return WValue{ .memory = target_sym_index };
2099}2088}
21002089
2090/// Converts a signed integer to its 2's complement form and returns
2091/// an unsigned integer instead.
2092/// Asserts bitsize <= 64
2093fn toTwosComplement(value: anytype, bits: u7) std.meta.Int(.unsigned, @typeInfo(@TypeOf(value)).Int.bits) {
2094 const T = @TypeOf(value);
2095 comptime assert(@typeInfo(T) == .Int);
2096 comptime assert(@typeInfo(T).Int.signedness == .signed);
2097 assert(bits <= 64);
2098 const WantedT = std.meta.Int(.unsigned, @typeInfo(T).Int.bits);
2099 if (value >= 0) return @bitCast(WantedT, value);
2100 const max_value = @intCast(u64, (@as(u65, 1) << bits) - 1);
2101 const flipped = (~-value) + 1;
2102 const result = @bitCast(WantedT, flipped) & max_value;
2103 return @intCast(WantedT, result);
2104}
2105
2101fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {2106fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
2102 if (val.isUndefDeep()) return self.emitUndefined(ty);2107 if (val.isUndefDeep()) return self.emitUndefined(ty);
2103 if (val.castTag(.decl_ref)) |decl_ref| {2108 if (val.castTag(.decl_ref)) |decl_ref| {
...@@ -2114,10 +2119,12 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {...@@ -2114,10 +2119,12 @@ fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue {
2114 switch (ty.zigTypeTag()) {2119 switch (ty.zigTypeTag()) {
2115 .Int => {2120 .Int => {
2116 const int_info = ty.intInfo(self.target);2121 const int_info = ty.intInfo(self.target);
2117 // write constant
2118 switch (int_info.signedness) {2122 switch (int_info.signedness) {
2119 .signed => switch (int_info.bits) {2123 .signed => switch (int_info.bits) {
2120 0...32 => return WValue{ .imm32 = @bitCast(u32, @intCast(i32, val.toSignedInt())) },2124 0...32 => return WValue{ .imm32 = @intCast(u32, toTwosComplement(
2125 val.toSignedInt(),
2126 @intCast(u6, int_info.bits),
2127 )) },
2121 33...64 => return WValue{ .imm64 = @bitCast(u64, val.toSignedInt()) },2128 33...64 => return WValue{ .imm64 = @bitCast(u64, val.toSignedInt()) },
2122 else => unreachable,2129 else => unreachable,
2123 },2130 },
...@@ -2832,30 +2839,38 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -2832,30 +2839,38 @@ fn airIntcast(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
2832 const ty_op = self.air.instructions.items(.data)[inst].ty_op;2839 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
2833 const ty = self.air.getRefType(ty_op.ty);2840 const ty = self.air.getRefType(ty_op.ty);
2834 const operand = try self.resolveInst(ty_op.operand);2841 const operand = try self.resolveInst(ty_op.operand);
2835 const ref_ty = self.air.typeOf(ty_op.operand);2842 const operand_ty = self.air.typeOf(ty_op.operand);
2836 const ref_info = ref_ty.intInfo(self.target);2843 if (ty.abiSize(self.target) > 8 or operand_ty.abiSize(self.target) > 8) {
2837 const wanted_info = ty.intInfo(self.target);2844 return self.fail("todo Wasm intcast for bitsize > 64", .{});
2845 }
28382846
2839 const op_bits = toWasmBits(ref_info.bits) orelse2847 return self.intcast(operand, operand_ty, ty);
2840 return self.fail("TODO: Wasm intcast integer types of bitsize: {d}", .{ref_info.bits});2848}
2841 const wanted_bits = toWasmBits(wanted_info.bits) orelse2849
2842 return self.fail("TODO: Wasm intcast integer types of bitsize: {d}", .{wanted_info.bits});2850/// Upcasts or downcasts an integer based on the given and wanted types,
2851/// and stores the result in a new operand.
2852/// Asserts type's bitsize <= 64
2853fn intcast(self: *Self, operand: WValue, given: Type, wanted: Type) InnerError!WValue {
2854 const given_info = given.intInfo(self.target);
2855 const wanted_info = wanted.intInfo(self.target);
2856 assert(given_info.bits <= 64);
2857 assert(wanted_info.bits <= 64);
28432858
2844 // hot path2859 const op_bits = toWasmBits(given_info.bits).?;
2860 const wanted_bits = toWasmBits(wanted_info.bits).?;
2845 if (op_bits == wanted_bits) return operand;2861 if (op_bits == wanted_bits) return operand;
28462862
2863 try self.emitWValue(operand);
2847 if (op_bits > 32 and wanted_bits == 32) {2864 if (op_bits > 32 and wanted_bits == 32) {
2848 try self.emitWValue(operand);
2849 try self.addTag(.i32_wrap_i64);2865 try self.addTag(.i32_wrap_i64);
2850 } else if (op_bits == 32 and wanted_bits > 32) {2866 } else if (op_bits == 32 and wanted_bits > 32) {
2851 try self.emitWValue(operand);2867 try self.addTag(switch (wanted_info.signedness) {
2852 try self.addTag(switch (ref_info.signedness) {
2853 .signed => .i64_extend_i32_s,2868 .signed => .i64_extend_i32_s,
2854 .unsigned => .i64_extend_i32_u,2869 .unsigned => .i64_extend_i32_u,
2855 });2870 });
2856 } else unreachable;2871 } else unreachable;
28572872
2858 const result = try self.allocLocal(ty);2873 const result = try self.allocLocal(wanted);
2859 try self.addLabel(.local_set, result.local);2874 try self.addLabel(.local_set, result.local);
2860 return result;2875 return result;
2861}2876}
...@@ -3072,63 +3087,17 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -3072,63 +3087,17 @@ fn airSlicePtr(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
3072}3087}
30733088
3074fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {3089fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
3075 if (self.liveness.isUnused(inst)) return WValue.none;3090 if (self.liveness.isUnused(inst)) return WValue{ .none = {} };
3076 const ty_op = self.air.instructions.items(.data)[inst].ty_op;3091 const ty_op = self.air.instructions.items(.data)[inst].ty_op;
3077 const operand = try self.resolveInst(ty_op.operand);3092 const operand = try self.resolveInst(ty_op.operand);
3078 const op_ty = self.air.typeOf(ty_op.operand);3093 const wanted_ty = self.air.getRefType(ty_op.ty);
3079 const int_info = self.air.getRefType(ty_op.ty).intInfo(self.target);3094 const int_info = wanted_ty.intInfo(self.target);
3080 const wanted_bits = int_info.bits;3095 const wanted_bits = int_info.bits;
3081 const result = try self.allocLocal(self.air.getRefType(ty_op.ty));
3082 const op_bits = op_ty.intInfo(self.target).bits;
30833096
3084 const wasm_bits = toWasmBits(wanted_bits) orelse3097 _ = toWasmBits(wanted_bits) orelse {
3085 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});
30863099 };
3087 // Use wasm's instruction to wrap from 64bit to 32bit integer when possible3100 return self.wrapOperand(operand, wanted_ty);
3088 if (op_bits == 64 and wanted_bits == 32) {
3089 try self.emitWValue(operand);
3090 try self.addTag(.i32_wrap_i64);
3091 try self.addLabel(.local_set, result.local);
3092 return result;
3093 }
3094
3095 // Any other truncation must be done manually
3096 if (int_info.signedness == .unsigned) {
3097 const mask = (@as(u65, 1) << @intCast(u7, wanted_bits)) - 1;
3098 try self.emitWValue(operand);
3099 switch (wasm_bits) {
3100 32 => {
3101 try self.addImm32(@bitCast(i32, @intCast(u32, mask)));
3102 try self.addTag(.i32_and);
3103 },
3104 64 => {
3105 try self.addImm64(@intCast(u64, mask));
3106 try self.addTag(.i64_and);
3107 },
3108 else => unreachable,
3109 }
3110 } else {
3111 const shift_bits = wasm_bits - wanted_bits;
3112 try self.emitWValue(operand);
3113 switch (wasm_bits) {
3114 32 => {
3115 try self.addImm32(@bitCast(i16, shift_bits));
3116 try self.addTag(.i32_shl);
3117 try self.addImm32(@bitCast(i16, shift_bits));
3118 try self.addTag(.i32_shr_s);
3119 },
3120 64 => {
3121 try self.addImm64(shift_bits);
3122 try self.addTag(.i64_shl);
3123 try self.addImm64(shift_bits);
3124 try self.addTag(.i64_shr_s);
3125 },
3126 else => unreachable,
3127 }
3128 }
3129
3130 try self.addLabel(.local_set, result.local);
3131 return result;
3132}3101}
31333102
3134fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {3103fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -3418,7 +3387,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {...@@ -3418,7 +3387,8 @@ fn airFloatToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
34183387
3419 const result = try self.allocLocal(dest_ty);3388 const result = try self.allocLocal(dest_ty);
3420 try self.addLabel(.local_set, result.local);3389 try self.addLabel(.local_set, result.local);
3421 return result;3390
3391 return self.wrapOperand(result, dest_ty);
3422}3392}
34233393
3424fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {3394fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!WValue {
...@@ -3922,6 +3892,10 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue...@@ -3922,6 +3892,10 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
3922 const rhs = try self.resolveInst(extra.rhs);3892 const rhs = try self.resolveInst(extra.rhs);
3923 const lhs_ty = self.air.typeOf(extra.lhs);3893 const lhs_ty = self.air.typeOf(extra.lhs);
39243894
3895 if (lhs_ty.zigTypeTag() == .Vector) {
3896 return self.fail("TODO: Implement overflow arithmetic for vectors", .{});
3897 }
3898
3925 // 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
3926 // 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.
3927 const overflow_bit = try self.allocLocal(Type.initTag(.u1));3901 const overflow_bit = try self.allocLocal(Type.initTag(.u1));
...@@ -4008,24 +3982,100 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue...@@ -4008,24 +3982,100 @@ fn airBinOpOverflow(self: *Self, inst: Air.Inst.Index, op: Op) InnerError!WValue
4008 }3982 }
4009 try self.addLabel(.local_set, tmp_val.local);3983 try self.addLabel(.local_set, tmp_val.local);
4010 break :blk tmp_val;3984 break :blk tmp_val;
4011 } else if (op == .mul) blk: {3985 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);
4012 const bin_op = try self.wrapBinOp(lhs, rhs, lhs_ty, op);3986
4013 try self.startBlock(.block, wasm.block_empty);3987 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
4014 // check if 0. true => Break out of block as cannot over -or underflow.3988 try self.store(result_ptr, bin_op, lhs_ty, 0);
4015 try self.emitWValue(lhs);3989 const offset = @intCast(u32, lhs_ty.abiSize(self.target));
4016 switch (wasm_bits) {3990 try self.store(result_ptr, overflow_bit, Type.initTag(.u1), offset);
4017 32 => try self.addTag(.i32_eqz),3991
4018 64 => try self.addTag(.i64_eqz),3992 return result_ptr;
4019 else => unreachable,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);
4035 try self.addLabel(.local_set, overflow_bit.local);
4036 break :blk try self.intcast(bin_op, new_ty, lhs_ty);
4037 } else {
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);
4045 try self.addLabel(.local_set, overflow_bit.local);
4046 break :blk down_cast;
4020 }4047 }
4021 try self.addLabel(.br_if, 0);4048 } else if (int_info.signedness == .signed) blk: {
4022 const div = try self.binOp(bin_op, lhs, lhs_ty, .div);4049 const shift_imm = if (wasm_bits == 32)
4023 const cmp_res = try self.cmp(div, rhs, lhs_ty, .neq);4050 WValue{ .imm32 = wasm_bits - int_info.bits }
4024 try self.emitWValue(cmp_res);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);
4025 try self.addLabel(.local_set, overflow_bit.local);4065 try self.addLabel(.local_set, overflow_bit.local);
4026 try self.endBlock();4066 break :blk try self.wrapOperand(bin_op, lhs_ty);
4027 break :blk bin_op;4067 } else blk: {
4028 } else try self.wrapBinOp(lhs, rhs, lhs_ty, op);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 };
40294079
4030 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));4080 const result_ptr = try self.allocStack(self.air.typeOfIndex(inst));
4031 try self.store(result_ptr, bin_op, lhs_ty, 0);4081 try self.store(result_ptr, bin_op, lhs_ty, 0);
src/type.zig+1
...@@ -6005,6 +6005,7 @@ pub const Type = extern union {...@@ -6005,6 +6005,7 @@ pub const Type = extern union {
6005 pub const @"u64" = initTag(.u64);6005 pub const @"u64" = initTag(.u64);
60066006
6007 pub const @"i32" = initTag(.i32);6007 pub const @"i32" = initTag(.i32);
6008 pub const @"i64" = initTag(.i64);
60086009
6009 pub const @"f16" = initTag(.f16);6010 pub const @"f16" = initTag(.f16);
6010 pub const @"f32" = initTag(.f32);6011 pub const @"f32" = initTag(.f32);
test/behavior/math.zig+6-1
...@@ -687,7 +687,6 @@ test "basic @mulWithOverflow" {...@@ -687,7 +687,6 @@ test "basic @mulWithOverflow" {
687test "extensive @mulWithOverflow" {687test "extensive @mulWithOverflow" {
688 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO688 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
689 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO689 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
690 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
691690
692 {691 {
693 var a: u5 = 3;692 var a: u5 = 3;
...@@ -833,6 +832,12 @@ test "extensive @mulWithOverflow" {...@@ -833,6 +832,12 @@ test "extensive @mulWithOverflow" {
833 try expect(@mulWithOverflow(i32, a, b, &res));832 try expect(@mulWithOverflow(i32, a, b, &res));
834 try expect(res == 0x7fffffff);833 try expect(res == 0x7fffffff);
835 }834 }
835}
836
837test "@mulWithOverflow bitsize > 32" {
838 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
839 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
840 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
836841
837 {842 {
838 var a: u62 = 3;843 var a: u62 = 3;
test/cases/binary_operands.13.zig+1-1
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1pub fn main() void {1pub fn main() void {
2 var i: i4 = 3;2 var i: i4 = 3;
3 if (i *% 3 != 1) unreachable;3 if (i *% 3 != -7) unreachable;
4 return;4 return;
5}5}
66
test/cases/binary_operands.2.zig+1-1
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1pub fn main() void {1pub fn main() void {
2 var i: i4 = 7;2 var i: i4 = 7;
3 if (i +% 1 != 0) unreachable;3 if (i +% 1 != -8) unreachable;
4 return;4 return;
5}5}
66