| ... | @@ -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), |
| 1428 | | 1428 | |
| 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 |
| 1822 | | 1822 | |
| 1823 | const opcode = buildOpcode(.{ | 1823 | const opcode = buildOpcode(.{ |
| 1824 | .valtype1 = valtype, | 1824 | .valtype1 = valtype, |
| 1825 | .width = abi_size * 8, // use bitsize instead of byte size | 1825 | .width = abi_size * 8, |
| 1826 | .op = .store, | 1826 | .op = .store, |
| 1827 | }); | 1827 | }); |
| 1828 | | 1828 | |
| ... | @@ -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 { |
| 1852 | fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue { | 1852 | fn load(self: *Self, operand: WValue, ty: Type, offset: u32) InnerError!WValue { |
| 1853 | // load local's value from memory by its stack position | 1853 | // 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; | | |
| 1862 | | 1855 | |
| 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 size | 1859 | .width = abi_size * 8, |
| 1868 | .op = .load, | 1860 | .op = .load, |
| 1869 | .signedness = signedness, | 1861 | .signedness = .unsigned, |
| 1870 | }); | 1862 | }); |
| 1871 | | 1863 | |
| 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); |
| 1937 | | 1929 | |
| 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 | } |
| 1940 | | 1939 | |
| 1941 | fn wrapBinOp(self: *Self, lhs: WValue, rhs: WValue, ty: Type, op: Op) InnerError!WValue { | 1940 | fn 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. |
| | 1957 | fn 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 | } |
| 1984 | | 1973 | |
| 1985 | fn lowerParentPtr(self: *Self, ptr_val: Value, ptr_child_ty: Type) InnerError!WValue { | 1974 | fn 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 | } |
| 2100 | | 2089 | |
| | 2090 | /// Converts a signed integer to its 2's complement form and returns |
| | 2091 | /// an unsigned integer instead. |
| | 2092 | /// Asserts bitsize <= 64 |
| | 2093 | fn 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 | |
| 2101 | fn lowerConstant(self: *Self, val: Value, ty: Type) InnerError!WValue { | 2106 | fn 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 | } |
| 2838 | | 2846 | |
| 2839 | const op_bits = toWasmBits(ref_info.bits) orelse | 2847 | 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) orelse | 2849 | |
| 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 |
| | 2853 | fn 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); |
| 2843 | | 2858 | |
| 2844 | // hot path | 2859 | 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; |
| 2846 | | 2862 | |
| | 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; |
| 2857 | | 2872 | |
| 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 | } |
| 3073 | | 3088 | |
| 3074 | fn airTrunc(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 3089 | fn 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; | | |
| 3083 | | 3096 | |
| 3084 | const wasm_bits = toWasmBits(wanted_bits) orelse | 3097 | _ = 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}); |
| 3086 | | 3099 | }; |
| 3087 | // Use wasm's instruction to wrap from 64bit to 32bit integer when possible | 3100 | 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 | } |
| 3133 | | 3102 | |
| 3134 | fn airBoolToInt(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 3103 | fn 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 { |
| 3418 | | 3387 | |
| 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 | } |
| 3423 | | 3393 | |
| 3424 | fn airIntToFloat(self: *Self, inst: Air.Inst.Index) InnerError!WValue { | 3394 | fn 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); |
| 3924 | | 3894 | |
| | 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-initialized | 3899 | // 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 | |
| | 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); |
| | 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 | }; |
| 4029 | | 4079 | |
| 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); |