| author | |
| committer | |
| log | 557caecaaa78a1a434b72c38195b40a82fe42c74 |
| tree | 609494590d67cf13e1e804d6a0e1cc74d8c9f598 |
| parent | 3e0f55fcc7f90feb441b62cd8e84735646e50176 |
8 files changed, 487 insertions(+), 30 deletions(-)
lib/compiler_rt.zig+2| ... | @@ -279,6 +279,8 @@ comptime { | ... | @@ -279,6 +279,8 @@ comptime { |
| 279 | _ = @import("compiler_rt/divmodei4.zig"); | 279 | _ = @import("compiler_rt/divmodei4.zig"); |
| 280 | _ = @import("compiler_rt/udivmodei4.zig"); | 280 | _ = @import("compiler_rt/udivmodei4.zig"); |
| 281 | 281 | ||
| 282 | _ = @import("compiler_rt/limb64.zig"); | ||
| 283 | |||
| 282 | // extra | 284 | // extra |
| 283 | _ = @import("compiler_rt/os_version_check.zig"); | 285 | _ = @import("compiler_rt/os_version_check.zig"); |
| 284 | _ = @import("compiler_rt/emutls.zig"); | 286 | _ = @import("compiler_rt/emutls.zig"); |
lib/compiler_rt/limb64.zig created+266| ... | @@ -0,0 +1,266 @@ | ||
| 1 | const std = @import("std"); | ||
| 2 | const testing = std.testing; | ||
| 3 | const assert = std.debug.assert; | ||
| 4 | const maxInt = std.math.maxInt; | ||
| 5 | const minInt = std.math.minInt; | ||
| 6 | const divCeil = std.math.divCeil; | ||
| 7 | |||
| 8 | const builtin = @import("builtin"); | ||
| 9 | const compiler_rt = @import("../compiler_rt.zig"); | ||
| 10 | |||
| 11 | const endian = builtin.cpu.arch.endian(); | ||
| 12 | |||
| 13 | inline fn limbGet(limbs: []const u64, i: usize) u64 { | ||
| 14 | return switch (endian) { | ||
| 15 | .little => limbs[i], | ||
| 16 | .big => limbs[limbs.len - 1 - i], | ||
| 17 | }; | ||
| 18 | } | ||
| 19 | |||
| 20 | inline fn limbSet(limbs: []u64, i: usize, value: u64) void { | ||
| 21 | switch (endian) { | ||
| 22 | .little => limbs[i] = value, | ||
| 23 | .big => limbs[limbs.len - 1 - i] = value, | ||
| 24 | } | ||
| 25 | } | ||
| 26 | |||
| 27 | fn limbCount(bits: u16) u16 { | ||
| 28 | return divCeil(u16, bits, 64) catch unreachable; | ||
| 29 | } | ||
| 30 | |||
| 31 | fn Limbs(T: type) type { | ||
| 32 | const int_info = @typeInfo(T).int; | ||
| 33 | const limb_cnt = comptime limbCount(int_info.bits); | ||
| 34 | return [limb_cnt]u64; | ||
| 35 | } | ||
| 36 | |||
| 37 | fn asLimbs(v: anytype) Limbs(@TypeOf(v)) { | ||
| 38 | const T = @TypeOf(v); | ||
| 39 | const int_info = @typeInfo(T).int; | ||
| 40 | const limb_cnt = comptime limbCount(int_info.bits); | ||
| 41 | const ET = @Int(int_info.signedness, limb_cnt * 64); | ||
| 42 | return @bitCast(@as(ET, v)); | ||
| 43 | } | ||
| 44 | |||
| 45 | fn limbWrap(limb: u64, is_signed: bool, bits: u16) u64 { | ||
| 46 | assert(bits % 64 != 0); | ||
| 47 | const pad_bits: u6 = @intCast(64 - bits % 64); | ||
| 48 | if (!is_signed) { | ||
| 49 | const s = limb << pad_bits; | ||
| 50 | return s >> pad_bits; | ||
| 51 | } else { | ||
| 52 | const s = @as(i64, @bitCast(limb)) << pad_bits; | ||
| 53 | return @bitCast(s >> pad_bits); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | comptime { | ||
| 58 | @export(&__addo_limb64, .{ .name = "__addo_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility }); | ||
| 59 | } | ||
| 60 | |||
| 61 | fn __addo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool { | ||
| 62 | const limb_cnt = limbCount(bits); | ||
| 63 | const out = out_ptr[0..limb_cnt]; | ||
| 64 | const a = a_ptr[0..limb_cnt]; | ||
| 65 | const b = b_ptr[0..limb_cnt]; | ||
| 66 | |||
| 67 | var carry: u1 = 0; | ||
| 68 | var i: usize = 0; | ||
| 69 | while (i < limb_cnt - 1) : (i += 1) { | ||
| 70 | const s1 = @addWithOverflow(limbGet(a, i), limbGet(b, i)); | ||
| 71 | const s2 = @addWithOverflow(s1[0], carry); | ||
| 72 | carry = s1[1] | s2[1]; | ||
| 73 | limbSet(out, i, s2[0]); | ||
| 74 | } | ||
| 75 | |||
| 76 | const limb: u64 = b: { | ||
| 77 | if (!is_signed) { | ||
| 78 | const s1 = @addWithOverflow(limbGet(a, i), limbGet(b, i)); | ||
| 79 | const s2 = @addWithOverflow(s1[0], carry); | ||
| 80 | carry = s1[1] | s2[1]; | ||
| 81 | break :b s2[0]; | ||
| 82 | } else { | ||
| 83 | const as: i64 = @bitCast(limbGet(a, i)); | ||
| 84 | const bs: i64 = @bitCast(limbGet(b, i)); | ||
| 85 | const s1 = @addWithOverflow(as, bs); | ||
| 86 | const s2 = @addWithOverflow(s1[0], carry); | ||
| 87 | carry = s1[1] | s2[1]; | ||
| 88 | break :b @bitCast(s2[0]); | ||
| 89 | } | ||
| 90 | }; | ||
| 91 | |||
| 92 | if (bits % 64 == 0) { | ||
| 93 | limbSet(out, i, limb); | ||
| 94 | return carry != 0; | ||
| 95 | } else { | ||
| 96 | assert(carry == 0); | ||
| 97 | const wrapped_limb = limbWrap(limb, is_signed, bits); | ||
| 98 | limbSet(out, i, wrapped_limb); | ||
| 99 | return wrapped_limb != limb; | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | fn test__addo_limb64(comptime T: type, a: T, b: T, expected: struct { T, bool }) !void { | ||
| 104 | const int_info = @typeInfo(T).int; | ||
| 105 | const is_signed = int_info.signedness == .signed; | ||
| 106 | |||
| 107 | var a_limbs = asLimbs(a); | ||
| 108 | var b_limbs = asLimbs(b); | ||
| 109 | var out: Limbs(T) = undefined; | ||
| 110 | const overflow = __addo_limb64(&out, &a_limbs, &b_limbs, is_signed, int_info.bits); | ||
| 111 | |||
| 112 | const expected_limbs = asLimbs(expected[0]); | ||
| 113 | try testing.expectEqual(expected_limbs, out); | ||
| 114 | try testing.expectEqual(expected[1], overflow); | ||
| 115 | } | ||
| 116 | |||
| 117 | test __addo_limb64 { | ||
| 118 | try test__addo_limb64(u64, 1, 2, .{ 3, false }); | ||
| 119 | try test__addo_limb64(u64, maxInt(u64), 2, .{ 1, true }); | ||
| 120 | try test__addo_limb64(u65, maxInt(u65), 2, .{ 1, true }); | ||
| 121 | try test__addo_limb64(u255, 1, 2, .{ 3, false }); | ||
| 122 | |||
| 123 | try test__addo_limb64(i64, 1, 2, .{ 3, false }); | ||
| 124 | try test__addo_limb64(i64, maxInt(i64), 1, .{ minInt(i64), true }); | ||
| 125 | try test__addo_limb64(i65, maxInt(i65), 1, .{ minInt(i65), true }); | ||
| 126 | try test__addo_limb64(i255, -3, 2, .{ -1, false }); | ||
| 127 | } | ||
| 128 | |||
| 129 | comptime { | ||
| 130 | @export(&__subo_limb64, .{ .name = "__subo_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility }); | ||
| 131 | } | ||
| 132 | |||
| 133 | fn __subo_limb64(out_ptr: [*]u64, a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) bool { | ||
| 134 | const limb_cnt = limbCount(bits); | ||
| 135 | const out = out_ptr[0..limb_cnt]; | ||
| 136 | const a = a_ptr[0..limb_cnt]; | ||
| 137 | const b = b_ptr[0..limb_cnt]; | ||
| 138 | |||
| 139 | var borrow: u1 = 0; | ||
| 140 | var i: usize = 0; | ||
| 141 | while (i < limb_cnt - 1) : (i += 1) { | ||
| 142 | const s1 = @subWithOverflow(limbGet(a, i), limbGet(b, i)); | ||
| 143 | const s2 = @subWithOverflow(s1[0], borrow); | ||
| 144 | borrow = s1[1] | s2[1]; | ||
| 145 | limbSet(out, i, s2[0]); | ||
| 146 | } | ||
| 147 | |||
| 148 | const limb: u64 = b: { | ||
| 149 | if (!is_signed) { | ||
| 150 | const s1 = @subWithOverflow(limbGet(a, i), limbGet(b, i)); | ||
| 151 | const s2 = @subWithOverflow(s1[0], borrow); | ||
| 152 | borrow = s1[1] | s2[1]; | ||
| 153 | break :b s2[0]; | ||
| 154 | } else { | ||
| 155 | const as: i64 = @bitCast(limbGet(a, i)); | ||
| 156 | const bs: i64 = @bitCast(limbGet(b, i)); | ||
| 157 | const s1 = @subWithOverflow(as, bs); | ||
| 158 | const s2 = @subWithOverflow(s1[0], borrow); | ||
| 159 | borrow = s1[1] | s2[1]; | ||
| 160 | break :b @bitCast(s2[0]); | ||
| 161 | } | ||
| 162 | }; | ||
| 163 | |||
| 164 | if (bits % 64 == 0) { | ||
| 165 | limbSet(out, i, limb); | ||
| 166 | return borrow != 0; | ||
| 167 | } else { | ||
| 168 | const wrapped_limb = limbWrap(limb, is_signed, bits); | ||
| 169 | limbSet(out, i, wrapped_limb); | ||
| 170 | return borrow != 0 or wrapped_limb != limb; | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | fn test__subo_limb64(comptime T: type, a: T, b: T, expected: struct { T, bool }) !void { | ||
| 175 | const int_info = @typeInfo(T).int; | ||
| 176 | const is_signed = int_info.signedness == .signed; | ||
| 177 | |||
| 178 | var a_limbs = asLimbs(a); | ||
| 179 | var b_limbs = asLimbs(b); | ||
| 180 | var out: Limbs(T) = undefined; | ||
| 181 | const overflow = __subo_limb64(&out, &a_limbs, &b_limbs, is_signed, int_info.bits); | ||
| 182 | |||
| 183 | const expected_limbs = asLimbs(expected[0]); | ||
| 184 | try testing.expectEqual(expected_limbs, out); | ||
| 185 | try testing.expectEqual(expected[1], overflow); | ||
| 186 | } | ||
| 187 | |||
| 188 | test __subo_limb64 { | ||
| 189 | try test__subo_limb64(u64, 3, 2, .{ 1, false }); | ||
| 190 | try test__subo_limb64(u64, 0, 1, .{ maxInt(u64), true }); | ||
| 191 | try test__subo_limb64(u65, 0, 1, .{ maxInt(u65), true }); | ||
| 192 | try test__subo_limb64(u255, 3, 2, .{ 1, false }); | ||
| 193 | |||
| 194 | try test__subo_limb64(i64, 1, 2, .{ -1, false }); | ||
| 195 | try test__subo_limb64(i64, minInt(i64), 1, .{ maxInt(i64), true }); | ||
| 196 | try test__subo_limb64(i65, minInt(i65), 1, .{ maxInt(i65), true }); | ||
| 197 | try test__subo_limb64(i255, -1, 2, .{ -3, false }); | ||
| 198 | } | ||
| 199 | |||
| 200 | comptime { | ||
| 201 | @export(&__cmp_limb64, .{ .name = "__cmp_limb64", .linkage = compiler_rt.linkage, .visibility = compiler_rt.visibility }); | ||
| 202 | } | ||
| 203 | |||
| 204 | // a < b -> -1 | ||
| 205 | // a == b -> 0 | ||
| 206 | // a > b -> 1 | ||
| 207 | fn __cmp_limb64(a_ptr: [*]const u64, b_ptr: [*]const u64, is_signed: bool, bits: u16) callconv(.c) i8 { | ||
| 208 | const limb_cnt = limbCount(bits); | ||
| 209 | const a = a_ptr[0..limb_cnt]; | ||
| 210 | const b = b_ptr[0..limb_cnt]; | ||
| 211 | |||
| 212 | var i: usize = 0; | ||
| 213 | if (is_signed) { | ||
| 214 | const sa: i64 = @bitCast(limbGet(a, limb_cnt - 1)); | ||
| 215 | const sb: i64 = @bitCast(limbGet(b, limb_cnt - 1)); | ||
| 216 | if (sa < sb) return -1; | ||
| 217 | if (sa > sb) return 1; | ||
| 218 | i += 1; | ||
| 219 | } | ||
| 220 | |||
| 221 | while (i < limb_cnt) : (i += 1) { | ||
| 222 | const ai = limbGet(a, limb_cnt - 1 - i); | ||
| 223 | const bi = limbGet(b, limb_cnt - 1 - i); | ||
| 224 | if (ai < bi) return -1; | ||
| 225 | if (ai > bi) return 1; | ||
| 226 | } | ||
| 227 | |||
| 228 | return 0; | ||
| 229 | } | ||
| 230 | |||
| 231 | fn test__cmp_limb64(comptime T: type, a: T, b: T, expected: i8) !void { | ||
| 232 | const int_info = @typeInfo(T).int; | ||
| 233 | const is_signed = int_info.signedness == .signed; | ||
| 234 | |||
| 235 | var a_limbs = asLimbs(a); | ||
| 236 | var b_limbs = asLimbs(b); | ||
| 237 | const actual = __cmp_limb64(&a_limbs, &b_limbs, is_signed, int_info.bits); | ||
| 238 | |||
| 239 | try testing.expectEqual(expected, actual); | ||
| 240 | } | ||
| 241 | |||
| 242 | test __cmp_limb64 { | ||
| 243 | try test__cmp_limb64(u64, 1, 2, -1); | ||
| 244 | try test__cmp_limb64(u64, 2, 2, 0); | ||
| 245 | try test__cmp_limb64(u64, 3, 2, 1); | ||
| 246 | |||
| 247 | try test__cmp_limb64(u65, 1, 2, -1); | ||
| 248 | try test__cmp_limb64(u65, maxInt(u65), maxInt(u65), 0); | ||
| 249 | try test__cmp_limb64(u65, maxInt(u65), maxInt(u65) - 1, 1); | ||
| 250 | |||
| 251 | try test__cmp_limb64(u255, 1, 2, -1); | ||
| 252 | try test__cmp_limb64(u255, 7, 7, 0); | ||
| 253 | try test__cmp_limb64(u255, maxInt(u255), maxInt(u255) - 1, 1); | ||
| 254 | |||
| 255 | try test__cmp_limb64(i64, -1, 0, -1); | ||
| 256 | try test__cmp_limb64(i64, 0, 0, 0); | ||
| 257 | try test__cmp_limb64(i64, 1, 0, 1); | ||
| 258 | |||
| 259 | try test__cmp_limb64(i65, minInt(i65), maxInt(i65), -1); | ||
| 260 | try test__cmp_limb64(i65, -1, -1, 0); | ||
| 261 | try test__cmp_limb64(i65, maxInt(i65), minInt(i65), 1); | ||
| 262 | |||
| 263 | try test__cmp_limb64(i255, -3, 2, -1); | ||
| 264 | try test__cmp_limb64(i255, -5, -5, 0); | ||
| 265 | try test__cmp_limb64(i255, 2, -3, 1); | ||
| 266 | } | ||
src/codegen/wasm/CodeGen.zig+109-27| ... | @@ -498,6 +498,10 @@ fn addAtomicTag(cg: *CodeGen, tag: std.wasm.AtomicsOpcode) error{OutOfMemory}!vo | ... | @@ -498,6 +498,10 @@ fn addAtomicTag(cg: *CodeGen, tag: std.wasm.AtomicsOpcode) error{OutOfMemory}!vo |
| 498 | try cg.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); | 498 | try cg.addInst(.{ .tag = .atomics_prefix, .data = .{ .payload = extra_index } }); |
| 499 | } | 499 | } |
| 500 | 500 | ||
| 501 | fn addCallIntrinsic(cg: *CodeGen, intrinsic: Mir.Intrinsic) error{OutOfMemory}!void { | ||
| 502 | try cg.addInst(.{ .tag = .call_intrinsic, .data = .{ .intrinsic = intrinsic } }); | ||
| 503 | } | ||
| 504 | |||
| 501 | /// Appends entries to `mir_extra` based on the type of `extra`. | 505 | /// Appends entries to `mir_extra` based on the type of `extra`. |
| 502 | /// Returns the index into `mir_extra` | 506 | /// Returns the index into `mir_extra` |
| 503 | fn addExtra(cg: *CodeGen, extra: anytype) error{OutOfMemory}!u32 { | 507 | fn addExtra(cg: *CodeGen, extra: anytype) error{OutOfMemory}!u32 { |
| ... | @@ -1010,6 +1014,24 @@ fn allocStack(cg: *CodeGen, ty: Type) !WValue { | ... | @@ -1010,6 +1014,24 @@ fn allocStack(cg: *CodeGen, ty: Type) !WValue { |
| 1010 | return .{ .stack_offset = .{ .value = offset, .references = 1 } }; | 1014 | return .{ .stack_offset = .{ .value = offset, .references = 1 } }; |
| 1011 | } | 1015 | } |
| 1012 | 1016 | ||
| 1017 | fn allocInt(cg: *CodeGen, int_ty: IntType) !WValue { | ||
| 1018 | if (cg.initial_stack_value == .none) { | ||
| 1019 | try cg.initializeStack(); | ||
| 1020 | } | ||
| 1021 | |||
| 1022 | const abi_size = std.math.cast(u32, std.zig.target.intByteSize(cg.target, int_ty.bits)) orelse { | ||
| 1023 | return cg.fail("Integer ABI size exceeds max stack size", .{}); | ||
| 1024 | }; | ||
| 1025 | const abi_align: Alignment = .fromByteUnits(std.zig.target.intAlignment(cg.target, int_ty.bits)); | ||
| 1026 | |||
| 1027 | cg.stack_alignment = cg.stack_alignment.max(abi_align); | ||
| 1028 | |||
| 1029 | const offset: u32 = @intCast(abi_align.forward(cg.stack_size)); | ||
| 1030 | defer cg.stack_size = offset + abi_size; | ||
| 1031 | |||
| 1032 | return .{ .stack_offset = .{ .value = offset, .references = 1 } }; | ||
| 1033 | } | ||
| 1034 | |||
| 1013 | /// From a given AIR instruction generates a pointer to the stack where | 1035 | /// From a given AIR instruction generates a pointer to the stack where |
| 1014 | /// the value of its type will live. | 1036 | /// the value of its type will live. |
| 1015 | /// This is different from allocStack where this will use the pointer's alignment | 1037 | /// This is different from allocStack where this will use the pointer's alignment |
| ... | @@ -2393,7 +2415,18 @@ fn intAdd(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue | ... | @@ -2393,7 +2415,18 @@ fn intAdd(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue |
| 2393 | try cg.store(result, tmp_op, Type.u64, 8); | 2415 | try cg.store(result, tmp_op, Type.u64, 8); |
| 2394 | return result; | 2416 | return result; |
| 2395 | }, | 2417 | }, |
| 2396 | else => return cg.fail("TODO: Support intAdd for integer bitsize: {d}", .{ty.bits}), | 2418 | else => { |
| 2419 | const result = try cg.allocInt(ty); | ||
| 2420 | |||
| 2421 | try cg.lowerToStack(result); | ||
| 2422 | try cg.lowerToStack(lhs); | ||
| 2423 | try cg.lowerToStack(rhs); | ||
| 2424 | try cg.addImm32(@intFromBool(ty.is_signed)); | ||
| 2425 | try cg.addImm32(ty.bits); | ||
| 2426 | try cg.addCallIntrinsic(.__addo_limb64); | ||
| 2427 | try cg.addTag(.drop); | ||
| 2428 | return result; | ||
| 2429 | }, | ||
| 2397 | } | 2430 | } |
| 2398 | } | 2431 | } |
| 2399 | 2432 | ||
| ... | @@ -2435,7 +2468,19 @@ fn intSub(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue | ... | @@ -2435,7 +2468,19 @@ fn intSub(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!WValue |
| 2435 | try cg.store(result, tmp_op, Type.u64, 8); | 2468 | try cg.store(result, tmp_op, Type.u64, 8); |
| 2436 | return result; | 2469 | return result; |
| 2437 | }, | 2470 | }, |
| 2438 | else => return cg.fail("TODO: Support intSub for integer bitsize: {d}", .{ty.bits}), | 2471 | else => { |
| 2472 | const result = try cg.allocInt(ty); | ||
| 2473 | |||
| 2474 | try cg.lowerToStack(result); | ||
| 2475 | try cg.lowerToStack(lhs); | ||
| 2476 | try cg.lowerToStack(rhs); | ||
| 2477 | try cg.addImm32(@intFromBool(ty.is_signed)); | ||
| 2478 | try cg.addImm32(ty.bits); | ||
| 2479 | try cg.addCallIntrinsic(.__subo_limb64); | ||
| 2480 | try cg.addTag(.drop); | ||
| 2481 | |||
| 2482 | return result; | ||
| 2483 | }, | ||
| 2439 | } | 2484 | } |
| 2440 | } | 2485 | } |
| 2441 | 2486 | ||
| ... | @@ -3434,45 +3479,66 @@ const OverflowResult = struct { | ... | @@ -3434,45 +3479,66 @@ const OverflowResult = struct { |
| 3434 | ov: WValue, | 3479 | ov: WValue, |
| 3435 | }; | 3480 | }; |
| 3436 | 3481 | ||
| 3437 | fn intAddOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { | 3482 | fn intAddOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { |
| 3438 | switch (int_ty.bits) { | 3483 | switch (ty.bits) { |
| 3439 | 0 => unreachable, | 3484 | 0 => unreachable, |
| 3440 | 1...128 => { | 3485 | 1...128 => { |
| 3441 | const raw_result = try cg.intAdd(int_ty, lhs, rhs); | 3486 | const raw_result = try cg.intAdd(ty, lhs, rhs); |
| 3442 | const op_result = try cg.intWrap(int_ty, raw_result); | 3487 | const op_result = try cg.intWrap(ty, raw_result); |
| 3443 | const op_tmp = try cg.toLocalInt(op_result, int_ty); | 3488 | const op_tmp = try cg.toLocalInt(op_result, ty); |
| 3444 | 3489 | ||
| 3445 | const overflow_bit = if (int_ty.is_signed) blk: { | 3490 | const overflow_bit = if (ty.is_signed) blk: { |
| 3446 | const zero = try cg.intZeroValue(int_ty); | 3491 | const zero = try cg.intZeroValue(ty); |
| 3447 | const rhs_is_neg = try cg.intCmp(int_ty, .lt, rhs, zero); | 3492 | const rhs_is_neg = try cg.intCmp(ty, .lt, rhs, zero); |
| 3448 | const overflow_cmp = try cg.intCmp(int_ty, .lt, op_tmp, lhs); | 3493 | const overflow_cmp = try cg.intCmp(ty, .lt, op_tmp, lhs); |
| 3449 | break :blk try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp); | 3494 | break :blk try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp); |
| 3450 | } else try cg.intCmp(int_ty, .lt, op_tmp, lhs); | 3495 | } else try cg.intCmp(ty, .lt, op_tmp, lhs); |
| 3451 | 3496 | ||
| 3452 | return .{ .result = op_tmp, .ov = overflow_bit }; | 3497 | return .{ .result = op_tmp, .ov = overflow_bit }; |
| 3453 | }, | 3498 | }, |
| 3454 | else => return cg.fail("TODO: Support intAddOverflow for integer bitsize: {d}", .{int_ty.bits}), | 3499 | else => { |
| 3500 | const result = try cg.allocInt(ty); | ||
| 3501 | |||
| 3502 | try cg.lowerToStack(result); | ||
| 3503 | try cg.lowerToStack(lhs); | ||
| 3504 | try cg.lowerToStack(rhs); | ||
| 3505 | try cg.addImm32(@intFromBool(ty.is_signed)); | ||
| 3506 | try cg.addImm32(ty.bits); | ||
| 3507 | try cg.addCallIntrinsic(.__addo_limb64); | ||
| 3508 | |||
| 3509 | return .{ .result = result, .ov = .stack }; | ||
| 3510 | }, | ||
| 3455 | } | 3511 | } |
| 3456 | } | 3512 | } |
| 3457 | 3513 | ||
| 3458 | fn intSubOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { | 3514 | fn intSubOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { |
| 3459 | switch (int_ty.bits) { | 3515 | switch (ty.bits) { |
| 3460 | 0 => unreachable, | 3516 | 0 => unreachable, |
| 3461 | 1...128 => { | 3517 | 1...128 => { |
| 3462 | const raw_result = try cg.intSub(int_ty, lhs, rhs); | 3518 | const raw_result = try cg.intSub(ty, lhs, rhs); |
| 3463 | const op_result = try cg.intWrap(int_ty, raw_result); | 3519 | const op_result = try cg.intWrap(ty, raw_result); |
| 3464 | const op_tmp = try cg.toLocalInt(op_result, int_ty); | 3520 | const op_tmp = try cg.toLocalInt(op_result, ty); |
| 3465 | 3521 | ||
| 3466 | const overflow_bit = if (int_ty.is_signed) blk: { | 3522 | const overflow_bit = if (ty.is_signed) blk: { |
| 3467 | const zero = try cg.intZeroValue(int_ty); | 3523 | const zero = try cg.intZeroValue(ty); |
| 3468 | const rhs_is_neg = try cg.intCmp(int_ty, .lt, rhs, zero); | 3524 | const rhs_is_neg = try cg.intCmp(ty, .lt, rhs, zero); |
| 3469 | const overflow_cmp = try cg.intCmp(int_ty, .gt, op_tmp, lhs); | 3525 | const overflow_cmp = try cg.intCmp(ty, .gt, op_tmp, lhs); |
| 3470 | break :blk try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp); | 3526 | break :blk try cg.intCmp(.u32, .neq, rhs_is_neg, overflow_cmp); |
| 3471 | } else try cg.intCmp(int_ty, .gt, op_tmp, lhs); | 3527 | } else try cg.intCmp(ty, .gt, op_tmp, lhs); |
| 3472 | 3528 | ||
| 3473 | return .{ .result = op_tmp, .ov = overflow_bit }; | 3529 | return .{ .result = op_tmp, .ov = overflow_bit }; |
| 3474 | }, | 3530 | }, |
| 3475 | else => return cg.fail("TODO: Support intSubOverflow for integer bitsize: {d}", .{int_ty.bits}), | 3531 | else => { |
| 3532 | const result = try cg.allocInt(ty); | ||
| 3533 | |||
| 3534 | try cg.lowerToStack(result); | ||
| 3535 | try cg.lowerToStack(lhs); | ||
| 3536 | try cg.lowerToStack(rhs); | ||
| 3537 | try cg.addImm32(@intFromBool(ty.is_signed)); | ||
| 3538 | try cg.addImm32(ty.bits); | ||
| 3539 | try cg.addCallIntrinsic(.__subo_limb64); | ||
| 3540 | return .{ .result = result, .ov = .stack }; | ||
| 3541 | }, | ||
| 3476 | } | 3542 | } |
| 3477 | } | 3543 | } |
| 3478 | 3544 | ||
| ... | @@ -4764,7 +4830,23 @@ fn intCmp(cg: *CodeGen, ty: IntType, op: std.math.CompareOperator, lhs: WValue, | ... | @@ -4764,7 +4830,23 @@ fn intCmp(cg: *CodeGen, ty: IntType, op: std.math.CompareOperator, lhs: WValue, |
| 4764 | 4830 | ||
| 4765 | return .stack; | 4831 | return .stack; |
| 4766 | }, | 4832 | }, |
| 4767 | else => return cg.fail("TODO: Support intCmp for integer bitsize: {d}", .{ty.bits}), | 4833 | else => { |
| 4834 | try cg.lowerToStack(lhs); | ||
| 4835 | try cg.lowerToStack(rhs); | ||
| 4836 | try cg.addImm32(@intFromBool(ty.is_signed)); | ||
| 4837 | try cg.addImm32(ty.bits); | ||
| 4838 | try cg.addCallIntrinsic(.__cmp_limb64); | ||
| 4839 | try cg.addImm32(0); | ||
| 4840 | try cg.addTag(switch (op) { | ||
| 4841 | .eq => .i32_eq, | ||
| 4842 | .neq => .i32_ne, | ||
| 4843 | .lt => .i32_lt_s, | ||
| 4844 | .lte => .i32_le_s, | ||
| 4845 | .gte => .i32_ge_s, | ||
| 4846 | .gt => .i32_gt_s, | ||
| 4847 | }); | ||
| 4848 | return .stack; | ||
| 4849 | }, | ||
| 4768 | } | 4850 | } |
| 4769 | } | 4851 | } |
| 4770 | 4852 |
src/codegen/wasm/Emit.zig+1| ... | @@ -352,6 +352,7 @@ pub fn lowerToCode(emit: *Emit) Error!void { | ... | @@ -352,6 +352,7 @@ pub fn lowerToCode(emit: *Emit) Error!void { |
| 352 | .end, | 352 | .end, |
| 353 | .@"return", | 353 | .@"return", |
| 354 | .@"unreachable", | 354 | .@"unreachable", |
| 355 | .drop, | ||
| 355 | .select, | 356 | .select, |
| 356 | .i32_eqz, | 357 | .i32_eqz, |
| 357 | .i32_eq, | 358 | .i32_eq, |
src/codegen/wasm/Mir.zig+7| ... | @@ -169,6 +169,10 @@ pub const Inst = struct { | ... | @@ -169,6 +169,10 @@ pub const Inst = struct { |
| 169 | call_tag_name, | 169 | call_tag_name, |
| 170 | /// Lowers to a `call` instruction, using `intrinsic`. | 170 | /// Lowers to a `call` instruction, using `intrinsic`. |
| 171 | call_intrinsic, | 171 | call_intrinsic, |
| 172 | /// Pops a value from the stack, and discards it. | ||
| 173 | /// | ||
| 174 | /// Uses `tag` (no additional data). | ||
| 175 | drop = 0x1A, | ||
| 172 | /// Pops three values from the stack and pushes | 176 | /// Pops three values from the stack and pushes |
| 173 | /// the first or second value dependent on the third value. | 177 | /// the first or second value dependent on the third value. |
| 174 | /// Uses `tag` | 178 | /// Uses `tag` |
| ... | @@ -1000,4 +1004,7 @@ pub const Intrinsic = enum(u32) { | ... | @@ -1000,4 +1004,7 @@ pub const Intrinsic = enum(u32) { |
| 1000 | tanf, | 1004 | tanf, |
| 1001 | tanq, | 1005 | tanq, |
| 1002 | truncq, | 1006 | truncq, |
| 1007 | __addo_limb64, | ||
| 1008 | __subo_limb64, | ||
| 1009 | __cmp_limb64, | ||
| 1003 | }; | 1010 | }; |
test/behavior/basic.zig+14| ... | @@ -1248,6 +1248,20 @@ test "integer compare <= 128 bits" { | ... | @@ -1248,6 +1248,20 @@ test "integer compare <= 128 bits" { |
| 1248 | } | 1248 | } |
| 1249 | } | 1249 | } |
| 1250 | 1250 | ||
| 1251 | test "integer compare > 128 bits" { | ||
| 1252 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | ||
| 1253 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | ||
| 1254 | |||
| 1255 | inline for (.{ u129, u255, u512, u800 }) |T| { | ||
| 1256 | try testUnsignedCmp(T); | ||
| 1257 | try comptime testUnsignedCmp(T); | ||
| 1258 | } | ||
| 1259 | inline for (.{ i129, i255, i512, i800 }) |T| { | ||
| 1260 | try testSignedCmp(T); | ||
| 1261 | try comptime testSignedCmp(T); | ||
| 1262 | } | ||
| 1263 | } | ||
| 1264 | |||
| 1251 | test "reference to inferred local variable works as expected" { | 1265 | test "reference to inferred local variable works as expected" { |
| 1252 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 1266 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1253 | 1267 |
test/behavior/int_comparison_elision.zig-1| ... | @@ -14,7 +14,6 @@ test "int comparison elision" { | ... | @@ -14,7 +14,6 @@ test "int comparison elision" { |
| 14 | testIntEdges(i4); | 14 | testIntEdges(i4); |
| 15 | 15 | ||
| 16 | // TODO: support int types > 128 bits wide in other backends | 16 | // TODO: support int types > 128 bits wide in other backends |
| 17 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO | ||
| 18 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | 17 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 19 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; | 18 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; |
| 20 | 19 |
test/behavior/math.zig+88-2| ... | @@ -877,7 +877,7 @@ test "@addWithOverflow" { | ... | @@ -877,7 +877,7 @@ test "@addWithOverflow" { |
| 877 | try testAddWithOverflow(isize, minInt(isize), -6, maxInt(isize) - 5, 1); | 877 | try testAddWithOverflow(isize, minInt(isize), -6, maxInt(isize) - 5, 1); |
| 878 | } | 878 | } |
| 879 | 879 | ||
| 880 | test "@addWithOverflow > 64 bits" { | 880 | test "@addWithOverflow <= 128 bits" { |
| 881 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | 881 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 882 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 882 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 883 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | 883 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
| ... | @@ -920,6 +920,50 @@ test "@addWithOverflow > 64 bits" { | ... | @@ -920,6 +920,50 @@ test "@addWithOverflow > 64 bits" { |
| 920 | try testAddWithOverflow(i128, maxInt(i128), maxInt(i128) - 1, -3, 1); | 920 | try testAddWithOverflow(i128, maxInt(i128), maxInt(i128) - 1, -3, 1); |
| 921 | } | 921 | } |
| 922 | 922 | ||
| 923 | test "@addWithOverflow > 128 bits" { | ||
| 924 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | ||
| 925 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | ||
| 926 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | ||
| 927 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO | ||
| 928 | if (builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) return error.SkipZigTest; | ||
| 929 | |||
| 930 | try testAddWithOverflow(u129, 4, 105, 109, 0); | ||
| 931 | try testAddWithOverflow(u129, 1000, 100, 1100, 0); | ||
| 932 | try testAddWithOverflow(u129, 100, maxInt(u129) - 99, 0, 1); | ||
| 933 | try testAddWithOverflow(u129, maxInt(u129), maxInt(u129), maxInt(u129) - 1, 1); | ||
| 934 | try testAddWithOverflow(u129, maxInt(u129) - 1, maxInt(u129), maxInt(u129) - 2, 1); | ||
| 935 | try testAddWithOverflow(u129, maxInt(u129), maxInt(u129) - 1, maxInt(u129) - 2, 1); | ||
| 936 | |||
| 937 | try testAddWithOverflow(u400, 4, 105, 109, 0); | ||
| 938 | try testAddWithOverflow(u400, 1000, 100, 1100, 0); | ||
| 939 | try testAddWithOverflow(u400, 100, maxInt(u400) - 99, 0, 1); | ||
| 940 | try testAddWithOverflow(u400, maxInt(u400), maxInt(u400), maxInt(u400) - 1, 1); | ||
| 941 | try testAddWithOverflow(u400, maxInt(u400) - 1, maxInt(u400), maxInt(u400) - 2, 1); | ||
| 942 | try testAddWithOverflow(u400, maxInt(u400), maxInt(u400) - 1, maxInt(u400) - 2, 1); | ||
| 943 | |||
| 944 | try testAddWithOverflow(i129, 4, -105, -101, 0); | ||
| 945 | try testAddWithOverflow(i129, 1000, 100, 1100, 0); | ||
| 946 | try testAddWithOverflow(i129, minInt(i129), 1, minInt(i129) + 1, 0); | ||
| 947 | try testAddWithOverflow(i129, maxInt(i129), minInt(i129), -1, 0); | ||
| 948 | try testAddWithOverflow(i129, minInt(i129), maxInt(i129), -1, 0); | ||
| 949 | try testAddWithOverflow(i129, maxInt(i129), -2, maxInt(i129) - 2, 0); | ||
| 950 | try testAddWithOverflow(i129, maxInt(i129), maxInt(i129), -2, 1); | ||
| 951 | try testAddWithOverflow(i129, minInt(i129), minInt(i129), 0, 1); | ||
| 952 | try testAddWithOverflow(i129, maxInt(i129) - 1, maxInt(i129), -3, 1); | ||
| 953 | try testAddWithOverflow(i129, maxInt(i129), maxInt(i129) - 1, -3, 1); | ||
| 954 | |||
| 955 | try testAddWithOverflow(i400, 4, -105, -101, 0); | ||
| 956 | try testAddWithOverflow(i400, 1000, 100, 1100, 0); | ||
| 957 | try testAddWithOverflow(i400, minInt(i400), 1, minInt(i400) + 1, 0); | ||
| 958 | try testAddWithOverflow(i400, maxInt(i400), minInt(i400), -1, 0); | ||
| 959 | try testAddWithOverflow(i400, minInt(i400), maxInt(i400), -1, 0); | ||
| 960 | try testAddWithOverflow(i400, maxInt(i400), -2, maxInt(i400) - 2, 0); | ||
| 961 | try testAddWithOverflow(i400, maxInt(i400), maxInt(i400), -2, 1); | ||
| 962 | try testAddWithOverflow(i400, minInt(i400), minInt(i400), 0, 1); | ||
| 963 | try testAddWithOverflow(i400, maxInt(i400) - 1, maxInt(i400), -3, 1); | ||
| 964 | try testAddWithOverflow(i400, maxInt(i400), maxInt(i400) - 1, -3, 1); | ||
| 965 | } | ||
| 966 | |||
| 923 | test "small int addition" { | 967 | test "small int addition" { |
| 924 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | 968 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 925 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 969 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| ... | @@ -1119,7 +1163,7 @@ test "@subWithOverflow" { | ... | @@ -1119,7 +1163,7 @@ test "@subWithOverflow" { |
| 1119 | try testSubWithOverflow(isize, minInt(isize), 6, maxInt(isize) - 5, 1); | 1163 | try testSubWithOverflow(isize, minInt(isize), 6, maxInt(isize) - 5, 1); |
| 1120 | } | 1164 | } |
| 1121 | 1165 | ||
| 1122 | test "@subWithOverflow > 64 bits" { | 1166 | test "@subWithOverflow <= 128 bits" { |
| 1123 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | 1167 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 1124 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | 1168 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1125 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | 1169 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
| ... | @@ -1160,6 +1204,48 @@ test "@subWithOverflow > 64 bits" { | ... | @@ -1160,6 +1204,48 @@ test "@subWithOverflow > 64 bits" { |
| 1160 | try testSubWithOverflow(i128, maxInt(i128), -2, minInt(i128) + 1, 1); | 1204 | try testSubWithOverflow(i128, maxInt(i128), -2, minInt(i128) + 1, 1); |
| 1161 | } | 1205 | } |
| 1162 | 1206 | ||
| 1207 | test "@subWithOverflow > 128 bits" { | ||
| 1208 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO | ||
| 1209 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO | ||
| 1210 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; | ||
| 1211 | if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest; // TODO | ||
| 1212 | if (builtin.zig_backend == .stage2_c and builtin.target.abi == .msvc) return error.SkipZigTest; | ||
| 1213 | |||
| 1214 | try testSubWithOverflow(u129, 4, 105, maxInt(u129) - 100, 1); | ||
| 1215 | try testSubWithOverflow(u129, 1000, 100, 900, 0); | ||
| 1216 | try testSubWithOverflow(u129, maxInt(u129), maxInt(u129), 0, 0); | ||
| 1217 | try testSubWithOverflow(u129, maxInt(u129) - 1, maxInt(u129), maxInt(u129), 1); | ||
| 1218 | try testSubWithOverflow(u129, maxInt(u129), maxInt(u129) - 1, 1, 0); | ||
| 1219 | |||
| 1220 | try testSubWithOverflow(u400, 4, 105, maxInt(u400) - 100, 1); | ||
| 1221 | try testSubWithOverflow(u400, 1000, 100, 900, 0); | ||
| 1222 | try testSubWithOverflow(u400, maxInt(u400), maxInt(u400), 0, 0); | ||
| 1223 | try testSubWithOverflow(u400, maxInt(u400) - 1, maxInt(u400), maxInt(u400), 1); | ||
| 1224 | try testSubWithOverflow(u400, maxInt(u400), maxInt(u400) - 1, 1, 0); | ||
| 1225 | |||
| 1226 | try testSubWithOverflow(i129, 4, 105, -101, 0); | ||
| 1227 | try testSubWithOverflow(i129, 1000, 100, 900, 0); | ||
| 1228 | try testSubWithOverflow(i129, maxInt(i129), maxInt(i129), 0, 0); | ||
| 1229 | try testSubWithOverflow(i129, minInt(i129), minInt(i129), 0, 0); | ||
| 1230 | try testSubWithOverflow(i129, maxInt(i129) - 1, maxInt(i129), -1, 0); | ||
| 1231 | try testSubWithOverflow(i129, maxInt(i129), maxInt(i129) - 1, 1, 0); | ||
| 1232 | try testSubWithOverflow(i129, minInt(i129), 1, maxInt(i129), 1); | ||
| 1233 | try testSubWithOverflow(i129, maxInt(i129), minInt(i129), -1, 1); | ||
| 1234 | try testSubWithOverflow(i129, minInt(i129), maxInt(i129), 1, 1); | ||
| 1235 | try testSubWithOverflow(i129, maxInt(i129), -2, minInt(i129) + 1, 1); | ||
| 1236 | |||
| 1237 | try testSubWithOverflow(i400, 4, 105, -101, 0); | ||
| 1238 | try testSubWithOverflow(i400, 1000, 100, 900, 0); | ||
| 1239 | try testSubWithOverflow(i400, maxInt(i400), maxInt(i400), 0, 0); | ||
| 1240 | try testSubWithOverflow(i400, minInt(i400), minInt(i400), 0, 0); | ||
| 1241 | try testSubWithOverflow(i400, maxInt(i400) - 1, maxInt(i400), -1, 0); | ||
| 1242 | try testSubWithOverflow(i400, maxInt(i400), maxInt(i400) - 1, 1, 0); | ||
| 1243 | try testSubWithOverflow(i400, minInt(i400), 1, maxInt(i400), 1); | ||
| 1244 | try testSubWithOverflow(i400, maxInt(i400), minInt(i400), -1, 1); | ||
| 1245 | try testSubWithOverflow(i400, minInt(i400), maxInt(i400), 1, 1); | ||
| 1246 | try testSubWithOverflow(i400, maxInt(i400), -2, minInt(i400) + 1, 1); | ||
| 1247 | } | ||
| 1248 | |||
| 1163 | fn testShlWithOverflow(comptime T: type, a: T, b: math.Log2Int(T), shl: T, bit: u1) !void { | 1249 | fn testShlWithOverflow(comptime T: type, a: T, b: math.Log2Int(T), shl: T, bit: u1) !void { |
| 1164 | const ov = @shlWithOverflow(a, b); | 1250 | const ov = @shlWithOverflow(a, b); |
| 1165 | try expect(ov[0] == shl); | 1251 | try expect(ov[0] == shl); |