| 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 | 279 | _ = @import("compiler_rt/divmodei4.zig"); |
| 280 | 280 | _ = @import("compiler_rt/udivmodei4.zig"); |
| 281 | 281 | |
| 282 | _ = @import("compiler_rt/limb64.zig"); | |
| 283 | ||
| 282 | 284 | // extra |
| 283 | 285 | _ = @import("compiler_rt/os_version_check.zig"); |
| 284 | 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 | 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 | 505 | /// Appends entries to `mir_extra` based on the type of `extra`. |
| 502 | 506 | /// Returns the index into `mir_extra` |
| 503 | 507 | fn addExtra(cg: *CodeGen, extra: anytype) error{OutOfMemory}!u32 { |
| ... | ... | @@ -1010,6 +1014,24 @@ fn allocStack(cg: *CodeGen, ty: Type) !WValue { |
| 1010 | 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 | 1035 | /// From a given AIR instruction generates a pointer to the stack where |
| 1014 | 1036 | /// the value of its type will live. |
| 1015 | 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 | 2415 | try cg.store(result, tmp_op, Type.u64, 8); |
| 2394 | 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 | 2468 | try cg.store(result, tmp_op, Type.u64, 8); |
| 2436 | 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 | 3479 | ov: WValue, |
| 3435 | 3480 | }; |
| 3436 | 3481 | |
| 3437 | fn intAddOverflow(cg: *CodeGen, int_ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { | |
| 3438 | switch (int_ty.bits) { | |
| 3482 | fn intAddOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { | |
| 3483 | switch (ty.bits) { | |
| 3439 | 3484 | 0 => unreachable, |
| 3440 | 3485 | 1...128 => { |
| 3441 | const raw_result = try cg.intAdd(int_ty, lhs, rhs); | |
| 3442 | const op_result = try cg.intWrap(int_ty, raw_result); | |
| 3443 | const op_tmp = try cg.toLocalInt(op_result, int_ty); | |
| 3444 | ||
| 3445 | const overflow_bit = if (int_ty.is_signed) blk: { | |
| 3446 | const zero = try cg.intZeroValue(int_ty); | |
| 3447 | const rhs_is_neg = try cg.intCmp(int_ty, .lt, rhs, zero); | |
| 3448 | const overflow_cmp = try cg.intCmp(int_ty, .lt, op_tmp, lhs); | |
| 3486 | const raw_result = try cg.intAdd(ty, lhs, rhs); | |
| 3487 | const op_result = try cg.intWrap(ty, raw_result); | |
| 3488 | const op_tmp = try cg.toLocalInt(op_result, ty); | |
| 3489 | ||
| 3490 | const overflow_bit = if (ty.is_signed) blk: { | |
| 3491 | const zero = try cg.intZeroValue(ty); | |
| 3492 | const rhs_is_neg = try cg.intCmp(ty, .lt, rhs, zero); | |
| 3493 | const overflow_cmp = try cg.intCmp(ty, .lt, op_tmp, lhs); | |
| 3449 | 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 | 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 { | |
| 3459 | switch (int_ty.bits) { | |
| 3514 | fn intSubOverflow(cg: *CodeGen, ty: IntType, lhs: WValue, rhs: WValue) InnerError!OverflowResult { | |
| 3515 | switch (ty.bits) { | |
| 3460 | 3516 | 0 => unreachable, |
| 3461 | 3517 | 1...128 => { |
| 3462 | const raw_result = try cg.intSub(int_ty, lhs, rhs); | |
| 3463 | const op_result = try cg.intWrap(int_ty, raw_result); | |
| 3464 | const op_tmp = try cg.toLocalInt(op_result, int_ty); | |
| 3465 | ||
| 3466 | const overflow_bit = if (int_ty.is_signed) blk: { | |
| 3467 | const zero = try cg.intZeroValue(int_ty); | |
| 3468 | const rhs_is_neg = try cg.intCmp(int_ty, .lt, rhs, zero); | |
| 3469 | const overflow_cmp = try cg.intCmp(int_ty, .gt, op_tmp, lhs); | |
| 3518 | const raw_result = try cg.intSub(ty, lhs, rhs); | |
| 3519 | const op_result = try cg.intWrap(ty, raw_result); | |
| 3520 | const op_tmp = try cg.toLocalInt(op_result, ty); | |
| 3521 | ||
| 3522 | const overflow_bit = if (ty.is_signed) blk: { | |
| 3523 | const zero = try cg.intZeroValue(ty); | |
| 3524 | const rhs_is_neg = try cg.intCmp(ty, .lt, rhs, zero); | |
| 3525 | const overflow_cmp = try cg.intCmp(ty, .gt, op_tmp, lhs); | |
| 3470 | 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 | 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 | 4830 | |
| 4765 | 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 | 352 | .end, |
| 353 | 353 | .@"return", |
| 354 | 354 | .@"unreachable", |
| 355 | .drop, | |
| 355 | 356 | .select, |
| 356 | 357 | .i32_eqz, |
| 357 | 358 | .i32_eq, |
src/codegen/wasm/Mir.zig+7| ... | ... | @@ -169,6 +169,10 @@ pub const Inst = struct { |
| 169 | 169 | call_tag_name, |
| 170 | 170 | /// Lowers to a `call` instruction, using `intrinsic`. |
| 171 | 171 | call_intrinsic, |
| 172 | /// Pops a value from the stack, and discards it. | |
| 173 | /// | |
| 174 | /// Uses `tag` (no additional data). | |
| 175 | drop = 0x1A, | |
| 172 | 176 | /// Pops three values from the stack and pushes |
| 173 | 177 | /// the first or second value dependent on the third value. |
| 174 | 178 | /// Uses `tag` |
| ... | ... | @@ -1000,4 +1004,7 @@ pub const Intrinsic = enum(u32) { |
| 1000 | 1004 | tanf, |
| 1001 | 1005 | tanq, |
| 1002 | 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 | 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 | 1265 | test "reference to inferred local variable works as expected" { |
| 1252 | 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 | 14 | testIntEdges(i4); |
| 15 | 15 | |
| 16 | 16 | // TODO: support int types > 128 bits wide in other backends |
| 17 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO | |
| 18 | 17 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 19 | 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 | 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 | 881 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 882 | 882 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 883 | 883 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
| ... | ... | @@ -920,6 +920,50 @@ test "@addWithOverflow > 64 bits" { |
| 920 | 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 | 967 | test "small int addition" { |
| 924 | 968 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 925 | 969 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| ... | ... | @@ -1119,7 +1163,7 @@ test "@subWithOverflow" { |
| 1119 | 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 | 1167 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO |
| 1124 | 1168 | if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO |
| 1125 | 1169 | if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; |
| ... | ... | @@ -1160,6 +1204,48 @@ test "@subWithOverflow > 64 bits" { |
| 1160 | 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 | 1249 | fn testShlWithOverflow(comptime T: type, a: T, b: math.Log2Int(T), shl: T, bit: u1) !void { |
| 1164 | 1250 | const ov = @shlWithOverflow(a, b); |
| 1165 | 1251 | try expect(ov[0] == shl); |