authorgravatar for mattnite@protonmail.comMatt Knight <mattnite@protonmail.com> 2021-04-10 14:21:59-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-08 09:56:40-07:00
logfb16633ecb496f3f30cdac11987baad40b7793b2
tree96d0b726a2fac3faa5f13777652806d846916025
parent62d27fcfb687e3ab1f10c72513e19529d8ffceed

C backend: add/sub/mul wrapping for the C backend


3 files changed, 329 insertions(+), 6 deletions(-)

src/codegen/c.zig+128-6
...@@ -846,18 +846,15 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi...@@ -846,18 +846,15 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
846 // TODO use a different strategy for add that communicates to the optimizer846 // TODO use a different strategy for add that communicates to the optimizer
847 // that wrapping is UB.847 // that wrapping is UB.
848 .add => try genBinOp(o, inst.castTag(.add).?, " + "),848 .add => try genBinOp(o, inst.castTag(.add).?, " + "),
849 // TODO make this do wrapping arithmetic for signed ints849 .addwrap => try genWrapOp(o, .add, inst.castTag(.addwrap).?),
850 .addwrap => try genBinOp(o, inst.castTag(.add).?, " + "),
851 // TODO use a different strategy for sub that communicates to the optimizer850 // TODO use a different strategy for sub that communicates to the optimizer
852 // that wrapping is UB.851 // that wrapping is UB.
853 .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),852 .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
854 // TODO make this do wrapping arithmetic for signed ints853 .subwrap => try genWrapOp(o, .sub, inst.castTag(.subwrap).?),
855 .subwrap => try genBinOp(o, inst.castTag(.sub).?, " - "),
856 // TODO use a different strategy for mul that communicates to the optimizer854 // TODO use a different strategy for mul that communicates to the optimizer
857 // that wrapping is UB.855 // that wrapping is UB.
858 .mul => try genBinOp(o, inst.castTag(.sub).?, " * "),856 .mul => try genBinOp(o, inst.castTag(.sub).?, " * "),
859 // TODO make this do wrapping multiplication for signed ints857 .mulwrap => try genWrapOp(o, .mul, inst.castTag(.mulwrap).?),
860 .mulwrap => try genBinOp(o, inst.castTag(.sub).?, " * "),
861 // TODO use a different strategy for div that communicates to the optimizer858 // TODO use a different strategy for div that communicates to the optimizer
862 // that wrapping is UB.859 // that wrapping is UB.
863 .div => try genBinOp(o, inst.castTag(.div).?, " / "),860 .div => try genBinOp(o, inst.castTag(.div).?, " / "),
...@@ -1042,6 +1039,131 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {...@@ -1042,6 +1039,131 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
1042 return CValue.none;1039 return CValue.none;
1043}1040}
10441041
1042const WrappingOp = enum {
1043 add,
1044 sub,
1045 mul,
1046};
1047
1048fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue {
1049 if (inst.base.isUnused())
1050 return CValue.none;
1051
1052 const is_signed = inst.base.ty.isSignedInt();
1053
1054 // if it's an unsigned int with non-arbitrary bit size then we can just add
1055 if (!is_signed and inst.base.ty.tag() != .int_unsigned) {
1056 return try genBinOp(o, inst, switch (op) {
1057 .add => " + ",
1058 .sub => " - ",
1059 .mul => " * ",
1060 });
1061 }
1062
1063 var min_buf: [80]u8 = undefined;
1064 const min = if (!is_signed)
1065 "0"
1066 else switch (inst.base.ty.tag()) {
1067 .c_short => "SHRT_MIN",
1068 .c_int => "INT_MIN",
1069 .c_long => "LONG_MIN",
1070 .c_longlong => "LLONG_MIN",
1071 .isize => "INTPTR_MIN",
1072 else => blk: {
1073 // should be able to use undefined here since all the target specifics are handled
1074 const bits = inst.base.ty.intInfo(@as(std.Target, undefined)).bits;
1075 assert(bits <= 64); // TODO: large integers
1076 const val = -1 * std.math.pow(i64, 2, @intCast(i64, bits - 1));
1077 break :blk std.fmt.bufPrint(&min_buf, "{}", .{val}) catch |e|
1078 // doesn't fit in some upwards error set, but should never happen
1079 return if (e == error.NoSpaceLeft) unreachable else e;
1080 },
1081 };
1082
1083 var max_buf: [80]u8 = undefined;
1084 const max = switch (inst.base.ty.tag()) {
1085 .c_short => "SHRT_MAX",
1086 .c_ushort => "USHRT_MAX",
1087 .c_int => "INT_MAX",
1088 .c_uint => "UINT_MAX",
1089 .c_long => "LONG_MAX",
1090 .c_ulong => "ULONG_MAX",
1091 .c_longlong => "LLONG_MAX",
1092 .c_ulonglong => "ULLONG_MAX",
1093 .isize => "INTPTR_MAX",
1094 .usize => "UINTPTR_MAX",
1095 else => blk: {
1096 // should be able to use undefined here since all the target specifics are handled
1097 const bits = inst.base.ty.intInfo(@as(std.Target, undefined)).bits;
1098 assert(bits <= 64); // TODO: large integers
1099 const val = std.math.pow(u64, 2, if (is_signed) (bits - 1) else bits) - 1;
1100 break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |e|
1101 // doesn't fit in some upwards error set, but should never happen
1102 return if (e == error.NoSpaceLeft) unreachable else e;
1103 },
1104 };
1105
1106 const lhs = try o.resolveInst(inst.lhs);
1107 const rhs = try o.resolveInst(inst.rhs);
1108 const w = o.writer();
1109
1110 const ret = try o.allocLocal(inst.base.ty, .Mut);
1111 try w.writeAll(" = zig_");
1112 try w.writeAll(switch (op) {
1113 .add => "addw_",
1114 .sub => "subw_",
1115 .mul => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement wrapping multiplication operator", .{}),
1116 });
1117
1118 switch (inst.base.ty.tag()) {
1119 .u8 => try w.writeAll("u8"),
1120 .i8 => try w.writeAll("i8"),
1121 .u16 => try w.writeAll("u16"),
1122 .i16 => try w.writeAll("i16"),
1123 .u32 => try w.writeAll("u32"),
1124 .i32 => try w.writeAll("i32"),
1125 .u64 => try w.writeAll("u64"),
1126 .i64 => try w.writeAll("i64"),
1127 .isize => try w.writeAll("isize"),
1128 .c_short => try w.writeAll("short"),
1129 .c_int => try w.writeAll("int"),
1130 .c_long => try w.writeAll("long"),
1131 .c_longlong => try w.writeAll("longlong"),
1132 .int_signed, .int_unsigned => {
1133 if (is_signed) {
1134 try w.writeByte('i');
1135 } else {
1136 try w.writeByte('u');
1137 }
1138
1139 const info_bits = inst.base.ty.intInfo(@as(std.Target, undefined)).bits;
1140 inline for (.{ 8, 16, 32, 64 }) |nbits| {
1141 if (info_bits <= nbits) {
1142 try w.print("{d}", .{nbits});
1143 break;
1144 }
1145 } else {
1146 return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement integer types larger than 64 bits", .{});
1147 }
1148 },
1149 else => unreachable,
1150 }
1151
1152 try w.writeByte('(');
1153 try o.writeCValue(w, lhs);
1154 try w.writeAll(", ");
1155 try o.writeCValue(w, rhs);
1156
1157 if (is_signed) {
1158 try w.print(", {s}", .{min});
1159 }
1160
1161 try w.print(", {s});", .{max});
1162 try o.indent_writer.insertNewline();
1163
1164 return ret;
1165}
1166
1045fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {1167fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
1046 if (inst.base.isUnused())1168 if (inst.base.isUnused())
1047 return CValue.none;1169 return CValue.none;
src/link/C/zig.h+152
...@@ -60,9 +60,161 @@...@@ -60,9 +60,161 @@
60#define zig_breakpoint() raise(SIGTRAP)60#define zig_breakpoint() raise(SIGTRAP)
61#endif61#endif
6262
63
64#define ZIG_UADDW(Type, lhs, rhs, max) \
65 Type thresh = max - rhs; \
66 if (lhs > thresh) { \
67 return lhs - thresh - 1; \
68 } else { \
69 return lhs + rhs; \
70 }
71
72#define ZIG_SADDW(Type, lhs, rhs, min, max) \
73 if ((lhs > 0) && (rhs > 0)) { \
74 Type thresh = max - rhs; \
75 if (lhs > thresh) { \
76 return min + lhs - thresh - 1; \
77 } \
78 } else if ((lhs < 0) && (rhs < 0)) { \
79 Type thresh = min - rhs; \
80 if (lhs < thresh) { \
81 return max + lhs - thresh + 1; \
82 } \
83 } \
84 \
85 return lhs + rhs;
86
87#define ZIG_USUBW(lhs, rhs, max) \
88 if (lhs < rhs) { \
89 return max - rhs - lhs + 1; \
90 } else { \
91 return lhs - rhs; \
92 }
93
94#define ZIG_SSUBW(Type, lhs, rhs, min, max) \
95 if ((lhs > 0) && (rhs < 0)) { \
96 Type thresh = lhs - max; \
97 if (rhs < thresh) { \
98 return min + (thresh - rhs - 1); \
99 } \
100 } else if ((lhs < 0) && (rhs > 0)) { \
101 Type thresh = lhs - min; \
102 if (rhs > thresh) { \
103 return max - (rhs - thresh - 1); \
104 } \
105 } \
106 return lhs - rhs;
107
63#include <stdint.h>108#include <stdint.h>
64#include <stddef.h>109#include <stddef.h>
110#include <limits.h>
65#define int128_t __int128111#define int128_t __int128
66#define uint128_t unsigned __int128112#define uint128_t unsigned __int128
67ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t);113ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t);
68114
115/* Wrapping addition operators */
116static inline uint8_t zig_addw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) {
117 ZIG_UADDW(uint8_t, lhs, rhs, max);
118}
119
120static inline int8_t zig_addw_i8(int8_t lhs, int8_t rhs, int8_t min, int8_t max) {
121 ZIG_SADDW(int8_t, lhs, rhs, min, max);
122}
123
124static inline uint16_t zig_addw_u16(uint16_t lhs, uint16_t rhs, uint16_t max) {
125 ZIG_UADDW(uint16_t, lhs, rhs, max);
126}
127
128static inline int16_t zig_addw_i16(int16_t lhs, int16_t rhs, int16_t min, int16_t max) {
129 ZIG_SADDW(int16_t, lhs, rhs, min, max);
130}
131
132static inline uint32_t zig_addw_u32(uint32_t lhs, uint32_t rhs, uint32_t max) {
133 ZIG_UADDW(uint32_t, lhs, rhs, max);
134}
135
136static inline int32_t zig_addw_i32(int32_t lhs, int32_t rhs, int32_t min, int32_t max) {
137 ZIG_SADDW(int32_t, lhs, rhs, min, max);
138}
139
140static inline uint64_t zig_addw_u64(uint64_t lhs, uint64_t rhs, uint64_t max) {
141 ZIG_UADDW(uint64_t, lhs, rhs, max);
142}
143
144static inline int64_t zig_addw_i64(int64_t lhs, int64_t rhs, int64_t min, int64_t max) {
145 ZIG_SADDW(int64_t, lhs, rhs, min, max);
146}
147
148static inline intptr_t zig_addw_isize(intptr_t lhs, intptr_t rhs, intptr_t min, intptr_t max) {
149 return (intptr_t)(((uintptr_t)lhs) + ((uintptr_t)rhs));
150}
151
152static inline short zig_addw_short(short lhs, short rhs, short min, short max) {
153 return (short)(((unsigned short)lhs) + ((unsigned short)rhs));
154}
155
156static inline int zig_addw_int(int lhs, int rhs, int min, int max) {
157 return (int)(((unsigned)lhs) + ((unsigned)rhs));
158}
159
160static inline long zig_addw_long(long lhs, long rhs, long min, long max) {
161 return (long)(((unsigned long)lhs) + ((unsigned long)rhs));
162}
163
164static inline long long zig_addw_longlong(long long lhs, long long rhs, long long min, long long max) {
165 return (long long)(((unsigned long long)lhs) + ((unsigned long long)rhs));
166}
167
168/* Wrapping subtraction operators */
169static inline uint8_t zig_subw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) {
170 ZIG_USUBW(lhs, rhs, max);
171}
172
173static inline int8_t zig_subw_i8(int8_t lhs, int8_t rhs, int8_t min, int8_t max) {
174 ZIG_SSUBW(int8_t, lhs, rhs, min, max);
175}
176
177static inline uint16_t zig_subw_u16(uint16_t lhs, uint16_t rhs, uint16_t max) {
178 ZIG_USUBW(lhs, rhs, max);
179}
180
181static inline int16_t zig_subw_i16(int16_t lhs, int16_t rhs, int16_t min, int16_t max) {
182 ZIG_SSUBW(int16_t, lhs, rhs, min, max);
183}
184
185static inline uint32_t zig_subw_u32(uint32_t lhs, uint32_t rhs, uint32_t max) {
186 ZIG_USUBW(lhs, rhs, max);
187}
188
189static inline int32_t zig_subw_i32(int32_t lhs, int32_t rhs, int32_t min, int32_t max) {
190 ZIG_SSUBW(int32_t, lhs, rhs, min, max);
191}
192
193static inline uint64_t zig_subw_u64(uint64_t lhs, uint64_t rhs, uint64_t max) {
194 ZIG_USUBW(lhs, rhs, max);
195}
196
197static inline int64_t zig_subw_i64(int64_t lhs, int64_t rhs, int64_t min, int64_t max) {
198 ZIG_SSUBW(int64_t, lhs, rhs, min, max);
199}
200
201static inline intptr_t zig_subw_isize(intptr_t lhs, intptr_t rhs, intptr_t min, intptr_t max) {
202 return (intptr_t)(((uintptr_t)lhs) - ((uintptr_t)rhs));
203}
204
205static inline short zig_subw_short(short lhs, short rhs, short min, short max) {
206 return (short)(((unsigned short)lhs) - ((unsigned short)rhs));
207}
208
209static inline int zig_subw_int(int lhs, int rhs, int min, int max) {
210 return (int)(((unsigned)lhs) - ((unsigned)rhs));
211}
212
213static inline long zig_subw_long(long lhs, long rhs, long min, long max) {
214 return (long)(((unsigned long)lhs) - ((unsigned long)rhs));
215}
216
217static inline long long zig_subw_longlong(long long lhs, long long rhs, long long min, long long max) {
218 return (long long)(((unsigned long long)lhs) - ((unsigned long long)rhs));
219}
220
test/stage2/cbe.zig+49
...@@ -824,6 +824,55 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -824,6 +824,55 @@ pub fn addCases(ctx: *TestContext) !void {
824 , "");824 , "");
825 }825 }
826826
827 {
828 // TODO: move these cases into the programs themselves once stage 2 has array literals
829 // TODO: add u64 tests, ran into issues with the literal generated for std.math.maxInt(u64)
830 var case = ctx.exeFromCompiledC("Wrapping operations", .{});
831 const programs = comptime blk: {
832 const cases = .{
833 // Addition
834 .{ u3, "+%", 1, 1, 2 },
835 .{ u3, "+%", 7, 1, 0 },
836 .{ i3, "+%", 1, 1, 2 },
837 .{ i3, "+%", 3, 2, -3 },
838 .{ i3, "+%", -3, -2, 3 },
839 .{ c_int, "+%", 1, 1, 2 },
840 .{ c_int, "+%", std.math.maxInt(c_int), 2, std.math.minInt(c_int) + 1 },
841 .{ c_int, "+%", std.math.minInt(c_int) + 1, -2, std.math.maxInt(c_int) },
842
843 // Subtraction
844 .{ u3, "-%", 2, 1, 1 },
845 .{ u3, "-%", 0, 1, 7 },
846 .{ i3, "-%", 2, 1, 1 },
847 .{ i3, "-%", 3, -2, -3 },
848 .{ i3, "-%", -3, 2, 3 },
849 .{ c_int, "-%", 2, 1, 1 },
850 .{ c_int, "-%", std.math.maxInt(c_int), -2, std.math.minInt(c_int) + 1 },
851 .{ c_int, "-%", std.math.minInt(c_int) + 1, 2, std.math.maxInt(c_int) },
852 };
853
854 var ret: [cases.len][:0]const u8 = undefined;
855 for (cases) |c, i| ret[i] = std.fmt.comptimePrint(
856 \\export fn main() i32 {{
857 \\ var lhs: {0} = {2};
858 \\ var rhs: {0} = {3};
859 \\ var expected: {0} = {4};
860 \\
861 \\ if (expected != lhs {1s} rhs) {{
862 \\ return 1;
863 \\ }} else {{
864 \\ return 0;
865 \\ }}
866 \\}}
867 \\
868 , c);
869
870 break :blk ret;
871 };
872
873 inline for (programs) |prog| case.addCompareOutput(prog, "");
874 }
875
827 ctx.h("simple header", linux_x64,876 ctx.h("simple header", linux_x64,
828 \\export fn start() void{}877 \\export fn start() void{}
829 ,878 ,