diff --git a/src/codegen/c.zig b/src/codegen/c.zig index db84f13c1a..31c7fa76d3 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -846,15 +846,15 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. .add => try genBinOp(o, inst.castTag(.add).?, " + "), - .addwrap => try genWrapOp(o, .add, inst.castTag(.addwrap).?), + .addwrap => try genWrapOp(o, inst.castTag(.addwrap).?, " + ", "addw_"), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. .sub => try genBinOp(o, inst.castTag(.sub).?, " - "), - .subwrap => try genWrapOp(o, .sub, inst.castTag(.subwrap).?), + .subwrap => try genWrapOp(o, inst.castTag(.subwrap).?, " - ", "subw_"), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. .mul => try genBinOp(o, inst.castTag(.sub).?, " * "), - .mulwrap => try genWrapOp(o, .mul, inst.castTag(.mulwrap).?), + .mulwrap => try genWrapOp(o, inst.castTag(.mulwrap).?, " * ", "mulw_"), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. .div => try genBinOp(o, inst.castTag(.div).?, " / "), @@ -1039,44 +1039,44 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { return CValue.none; } -const WrappingOp = enum { - add, - sub, - mul, -}; - -fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue { +fn genWrapOp(o: *Object, inst: *Inst.BinOp, str_op: [*:0]const u8, fn_op: [*:0]const u8) !CValue { if (inst.base.isUnused()) return CValue.none; - const is_signed = inst.base.ty.isSignedInt(); + const int_info = inst.base.ty.intInfo(o.dg.module.getTarget()); + const bits = int_info.bits; // if it's an unsigned int with non-arbitrary bit size then we can just add - if (!is_signed and inst.base.ty.tag() != .int_unsigned) { - return try genBinOp(o, inst, switch (op) { - .add => " + ", - .sub => " - ", - .mul => " * ", - }); + if (int_info.signedness == .unsigned) { + const ok_bits = switch (bits) { + 8, 16, 32, 64, 128 => true, + else => false, + }; + if (ok_bits or inst.base.ty.tag() != .int_unsigned) { + return try genBinOp(o, inst, str_op); + } + } + + if (bits > 64) { + return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: genWrapOp for large integers", .{}); } var min_buf: [80]u8 = undefined; - const min = if (!is_signed) - "0" - else switch (inst.base.ty.tag()) { - .c_short => "SHRT_MIN", - .c_int => "INT_MIN", - .c_long => "LONG_MIN", - .c_longlong => "LLONG_MIN", - .isize => "INTPTR_MIN", - else => blk: { - // should be able to use undefined here since all the target specifics are handled - const bits = inst.base.ty.intInfo(@as(std.Target, undefined)).bits; - assert(bits <= 64); // TODO: large integers - const val = -1 * std.math.pow(i64, 2, @intCast(i64, bits - 1)); - break :blk std.fmt.bufPrint(&min_buf, "{}", .{val}) catch |e| - // doesn't fit in some upwards error set, but should never happen - return if (e == error.NoSpaceLeft) unreachable else e; + const min = switch (int_info.signedness) { + .unsigned => "0", + else => switch (inst.base.ty.tag()) { + .c_short => "SHRT_MIN", + .c_int => "INT_MIN", + .c_long => "LONG_MIN", + .c_longlong => "LLONG_MIN", + .isize => "INTPTR_MIN", + else => blk: { + const val = -1 * std.math.pow(i64, 2, @intCast(i64, bits - 1)); + break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + else => |e| return e, + }; + }, }, }; @@ -1093,13 +1093,15 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue { .isize => "INTPTR_MAX", .usize => "UINTPTR_MAX", else => blk: { - // should be able to use undefined here since all the target specifics are handled - const bits = inst.base.ty.intInfo(@as(std.Target, undefined)).bits; - assert(bits <= 64); // TODO: large integers - const val = std.math.pow(u64, 2, if (is_signed) (bits - 1) else bits) - 1; - break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |e| - // doesn't fit in some upwards error set, but should never happen - return if (e == error.NoSpaceLeft) unreachable else e; + const pow_bits = switch (int_info.signedness) { + .signed => bits - 1, + .unsigned => bits, + }; + const val = std.math.pow(u64, 2, pow_bits) - 1; + break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) { + error.NoSpaceLeft => unreachable, + else => |e| return e, + }; }, }; @@ -1108,45 +1110,28 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue { const w = o.writer(); const ret = try o.allocLocal(inst.base.ty, .Mut); - try w.writeAll(" = zig_"); - try w.writeAll(switch (op) { - .add => "addw_", - .sub => "subw_", - .mul => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement wrapping multiplication operator", .{}), - }); + try w.print(" = zig_{s}", .{fn_op}); switch (inst.base.ty.tag()) { - .u8 => try w.writeAll("u8"), - .i8 => try w.writeAll("i8"), - .u16 => try w.writeAll("u16"), - .i16 => try w.writeAll("i16"), - .u32 => try w.writeAll("u32"), - .i32 => try w.writeAll("i32"), - .u64 => try w.writeAll("u64"), - .i64 => try w.writeAll("i64"), .isize => try w.writeAll("isize"), .c_short => try w.writeAll("short"), .c_int => try w.writeAll("int"), .c_long => try w.writeAll("long"), .c_longlong => try w.writeAll("longlong"), - .int_signed, .int_unsigned => { - if (is_signed) { - try w.writeByte('i'); - } else { - try w.writeByte('u'); - } - - const info_bits = inst.base.ty.intInfo(@as(std.Target, undefined)).bits; - inline for (.{ 8, 16, 32, 64 }) |nbits| { - if (info_bits <= nbits) { - try w.print("{d}", .{nbits}); + else => { + const prefix_byte: u8 = switch (int_info.signedness) { + .signed => 'i', + .unsigned => 'u', + }; + for ([_]u8{ 8, 16, 32, 64 }) |nbits| { + if (bits <= nbits) { + try w.print("{c}{d}", .{ prefix_byte, nbits }); break; } } else { - return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement integer types larger than 64 bits", .{}); + unreachable; } }, - else => unreachable, } try w.writeByte('('); @@ -1154,7 +1139,7 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue { try w.writeAll(", "); try o.writeCValue(w, rhs); - if (is_signed) { + if (int_info.signedness == .signed) { try w.print(", {s}", .{min}); } @@ -1164,7 +1149,7 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue { return ret; } -fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { +fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: [*:0]const u8) !CValue { if (inst.base.isUnused()) return CValue.none; @@ -1176,7 +1161,7 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { try writer.writeAll(" = "); try o.writeCValue(writer, lhs); - try writer.writeAll(operator); + try writer.print("{s}", .{operator}); try o.writeCValue(writer, rhs); try writer.writeAll(";\n"); diff --git a/src/link/C/zig.h b/src/link/C/zig.h index 83e6d8006b..232fb6bd0c 100644 --- a/src/link/C/zig.h +++ b/src/link/C/zig.h @@ -60,51 +60,6 @@ #define zig_breakpoint() raise(SIGTRAP) #endif - -#define ZIG_UADDW(Type, lhs, rhs, max) \ - Type thresh = max - rhs; \ - if (lhs > thresh) { \ - return lhs - thresh - 1; \ - } else { \ - return lhs + rhs; \ - } - -#define ZIG_SADDW(Type, lhs, rhs, min, max) \ - if ((lhs > 0) && (rhs > 0)) { \ - Type thresh = max - rhs; \ - if (lhs > thresh) { \ - return min + lhs - thresh - 1; \ - } \ - } else if ((lhs < 0) && (rhs < 0)) { \ - Type thresh = min - rhs; \ - if (lhs < thresh) { \ - return max + lhs - thresh + 1; \ - } \ - } \ - \ - return lhs + rhs; - -#define ZIG_USUBW(lhs, rhs, max) \ - if (lhs < rhs) { \ - return max - rhs - lhs + 1; \ - } else { \ - return lhs - rhs; \ - } - -#define ZIG_SSUBW(Type, lhs, rhs, min, max) \ - if ((lhs > 0) && (rhs < 0)) { \ - Type thresh = lhs - max; \ - if (rhs < thresh) { \ - return min + (thresh - rhs - 1); \ - } \ - } else if ((lhs < 0) && (rhs > 0)) { \ - Type thresh = lhs - min; \ - if (rhs > thresh) { \ - return max - (rhs - thresh - 1); \ - } \ - } \ - return lhs - rhs; - #include #include #include @@ -112,37 +67,100 @@ #define uint128_t unsigned __int128 ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t); -/* Wrapping addition operators */ static inline uint8_t zig_addw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) { - ZIG_UADDW(uint8_t, lhs, rhs, max); + uint8_t thresh = max - rhs; + if (lhs > thresh) { + return lhs - thresh - 1; + } else { + return lhs + rhs; + } } static inline int8_t zig_addw_i8(int8_t lhs, int8_t rhs, int8_t min, int8_t max) { - ZIG_SADDW(int8_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs > 0)) { + int8_t thresh = max - rhs; + if (lhs > thresh) { + return min + lhs - thresh - 1; + } + } else if ((lhs < 0) && (rhs < 0)) { + int8_t thresh = min - rhs; + if (lhs < thresh) { + return max + lhs - thresh + 1; + } + } + return lhs + rhs; } static inline uint16_t zig_addw_u16(uint16_t lhs, uint16_t rhs, uint16_t max) { - ZIG_UADDW(uint16_t, lhs, rhs, max); + uint16_t thresh = max - rhs; + if (lhs > thresh) { + return lhs - thresh - 1; + } else { + return lhs + rhs; + } } static inline int16_t zig_addw_i16(int16_t lhs, int16_t rhs, int16_t min, int16_t max) { - ZIG_SADDW(int16_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs > 0)) { + int16_t thresh = max - rhs; + if (lhs > thresh) { + return min + lhs - thresh - 1; + } + } else if ((lhs < 0) && (rhs < 0)) { + int16_t thresh = min - rhs; + if (lhs < thresh) { + return max + lhs - thresh + 1; + } + } + return lhs + rhs; } static inline uint32_t zig_addw_u32(uint32_t lhs, uint32_t rhs, uint32_t max) { - ZIG_UADDW(uint32_t, lhs, rhs, max); + uint32_t thresh = max - rhs; + if (lhs > thresh) { + return lhs - thresh - 1; + } else { + return lhs + rhs; + } } static inline int32_t zig_addw_i32(int32_t lhs, int32_t rhs, int32_t min, int32_t max) { - ZIG_SADDW(int32_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs > 0)) { + int32_t thresh = max - rhs; + if (lhs > thresh) { + return min + lhs - thresh - 1; + } + } else if ((lhs < 0) && (rhs < 0)) { + int32_t thresh = min - rhs; + if (lhs < thresh) { + return max + lhs - thresh + 1; + } + } + return lhs + rhs; } static inline uint64_t zig_addw_u64(uint64_t lhs, uint64_t rhs, uint64_t max) { - ZIG_UADDW(uint64_t, lhs, rhs, max); + uint64_t thresh = max - rhs; + if (lhs > thresh) { + return lhs - thresh - 1; + } else { + return lhs + rhs; + } } static inline int64_t zig_addw_i64(int64_t lhs, int64_t rhs, int64_t min, int64_t max) { - ZIG_SADDW(int64_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs > 0)) { + int64_t thresh = max - rhs; + if (lhs > thresh) { + return min + lhs - thresh - 1; + } + } else if ((lhs < 0) && (rhs < 0)) { + int64_t thresh = min - rhs; + if (lhs < thresh) { + return max + lhs - thresh + 1; + } + } + return lhs + rhs; } static inline intptr_t zig_addw_isize(intptr_t lhs, intptr_t rhs, intptr_t min, intptr_t max) { @@ -165,37 +183,96 @@ static inline long long zig_addw_longlong(long long lhs, long long rhs, long lon return (long long)(((unsigned long long)lhs) + ((unsigned long long)rhs)); } -/* Wrapping subtraction operators */ static inline uint8_t zig_subw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) { - ZIG_USUBW(lhs, rhs, max); + if (lhs < rhs) { + return max - rhs - lhs + 1; + } else { + return lhs - rhs; + } } static inline int8_t zig_subw_i8(int8_t lhs, int8_t rhs, int8_t min, int8_t max) { - ZIG_SSUBW(int8_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs < 0)) { + int8_t thresh = lhs - max; + if (rhs < thresh) { + return min + (thresh - rhs - 1); + } + } else if ((lhs < 0) && (rhs > 0)) { + int8_t thresh = lhs - min; + if (rhs > thresh) { + return max - (rhs - thresh - 1); + } + } + return lhs - rhs; } static inline uint16_t zig_subw_u16(uint16_t lhs, uint16_t rhs, uint16_t max) { - ZIG_USUBW(lhs, rhs, max); + if (lhs < rhs) { + return max - rhs - lhs + 1; + } else { + return lhs - rhs; + } } static inline int16_t zig_subw_i16(int16_t lhs, int16_t rhs, int16_t min, int16_t max) { - ZIG_SSUBW(int16_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs < 0)) { + int16_t thresh = lhs - max; + if (rhs < thresh) { + return min + (thresh - rhs - 1); + } + } else if ((lhs < 0) && (rhs > 0)) { + int16_t thresh = lhs - min; + if (rhs > thresh) { + return max - (rhs - thresh - 1); + } + } + return lhs - rhs; } static inline uint32_t zig_subw_u32(uint32_t lhs, uint32_t rhs, uint32_t max) { - ZIG_USUBW(lhs, rhs, max); + if (lhs < rhs) { + return max - rhs - lhs + 1; + } else { + return lhs - rhs; + } } static inline int32_t zig_subw_i32(int32_t lhs, int32_t rhs, int32_t min, int32_t max) { - ZIG_SSUBW(int32_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs < 0)) { + int32_t thresh = lhs - max; + if (rhs < thresh) { + return min + (thresh - rhs - 1); + } + } else if ((lhs < 0) && (rhs > 0)) { + int32_t thresh = lhs - min; + if (rhs > thresh) { + return max - (rhs - thresh - 1); + } + } + return lhs - rhs; } static inline uint64_t zig_subw_u64(uint64_t lhs, uint64_t rhs, uint64_t max) { - ZIG_USUBW(lhs, rhs, max); + if (lhs < rhs) { + return max - rhs - lhs + 1; + } else { + return lhs - rhs; + } } static inline int64_t zig_subw_i64(int64_t lhs, int64_t rhs, int64_t min, int64_t max) { - ZIG_SSUBW(int64_t, lhs, rhs, min, max); + if ((lhs > 0) && (rhs < 0)) { + int64_t thresh = lhs - max; + if (rhs < thresh) { + return min + (thresh - rhs - 1); + } + } else if ((lhs < 0) && (rhs > 0)) { + int64_t thresh = lhs - min; + if (rhs > thresh) { + return max - (rhs - thresh - 1); + } + } + return lhs - rhs; } static inline intptr_t zig_subw_isize(intptr_t lhs, intptr_t rhs, intptr_t min, intptr_t max) { diff --git a/test/stage2/cbe.zig b/test/stage2/cbe.zig index abf8b0165d..6427e2e3b8 100644 --- a/test/stage2/cbe.zig +++ b/test/stage2/cbe.zig @@ -825,52 +825,53 @@ pub fn addCases(ctx: *TestContext) !void { } { - // TODO: move these cases into the programs themselves once stage 2 has array literals // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64) - var case = ctx.exeFromCompiledC("Wrapping operations", .{}); - const programs = comptime blk: { - const cases = .{ - // Addition - .{ u3, "+%", 1, 1, 2 }, - .{ u3, "+%", 7, 1, 0 }, - .{ i3, "+%", 1, 1, 2 }, - .{ i3, "+%", 3, 2, -3 }, - .{ i3, "+%", -3, -2, 3 }, - .{ c_int, "+%", 1, 1, 2 }, - .{ c_int, "+%", std.math.maxInt(c_int), 2, std.math.minInt(c_int) + 1 }, - .{ c_int, "+%", std.math.minInt(c_int) + 1, -2, std.math.maxInt(c_int) }, - - // Subtraction - .{ u3, "-%", 2, 1, 1 }, - .{ u3, "-%", 0, 1, 7 }, - .{ i3, "-%", 2, 1, 1 }, - .{ i3, "-%", 3, -2, -3 }, - .{ i3, "-%", -3, 2, 3 }, - .{ c_int, "-%", 2, 1, 1 }, - .{ c_int, "-%", std.math.maxInt(c_int), -2, std.math.minInt(c_int) + 1 }, - .{ c_int, "-%", std.math.minInt(c_int) + 1, 2, std.math.maxInt(c_int) }, - }; - - var ret: [cases.len][:0]const u8 = undefined; - for (cases) |c, i| ret[i] = std.fmt.comptimePrint( - \\export fn main() i32 {{ - \\ var lhs: {0} = {2}; - \\ var rhs: {0} = {3}; - \\ var expected: {0} = {4}; - \\ - \\ if (expected != lhs {1s} rhs) {{ - \\ return 1; - \\ }} else {{ - \\ return 0; - \\ }} - \\}} - \\ - , c); - - break :blk ret; - }; - - inline for (programs) |prog| case.addCompareOutput(prog, ""); + var case = ctx.exeFromCompiledC("add/sub wrapping operations", .{}); + case.addCompareOutput( + \\pub export fn main() c_int { + \\ // Addition + \\ if (!add_u3(1, 1, 2)) return 1; + \\ if (!add_u3(7, 1, 0)) return 1; + \\ if (!add_i3(1, 1, 2)) return 1; + \\ if (!add_i3(3, 2, -3)) return 1; + \\ if (!add_i3(-3, -2, 3)) return 1; + \\ if (!add_c_int(1, 1, 2)) return 1; + \\ // TODO enable these when stage2 supports std.math.maxInt + \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1; + \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1; + \\ + \\ // Subtraction + \\ if (!sub_u3(2, 1, 1)) return 1; + \\ if (!sub_u3(0, 1, 7)) return 1; + \\ if (!sub_i3(2, 1, 1)) return 1; + \\ if (!sub_i3(3, -2, -3)) return 1; + \\ if (!sub_i3(-3, 2, 3)) return 1; + \\ if (!sub_c_int(2, 1, 1)) return 1; + \\ // TODO enable these when stage2 supports std.math.maxInt + \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1; + \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1; + \\ + \\ return 0; + \\} + \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool { + \\ return expected == lhs +% rhs; + \\} + \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool { + \\ return expected == lhs +% rhs; + \\} + \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool { + \\ return expected == lhs +% rhs; + \\} + \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool { + \\ return expected == lhs -% rhs; + \\} + \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool { + \\ return expected == lhs -% rhs; + \\} + \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool { + \\ return expected == lhs -% rhs; + \\} + , ""); } ctx.h("simple header", linux_x64,