authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-08 11:21:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-07-08 11:21:06-07:00
log9dbe68485489e576795fee04c83e69a2b6cf776a
treeade71b33286590a21600e113046c62e89a4954c7
parentfb16633ecb496f3f30cdac11987baad40b7793b2

C backend: cleanups to wrapping int operations

* less branching by passing parameters in the main op code switch. * properly pass the target when asking the type system for int info. * handle u8, i16, etc when it is represented using int_unsigned/int_signed tag. * compile error instead of assertion failure for unimplemented cases (greater than 64 bits integer). * control flow cleanups * zig.h: expand macros into inline functions * reduce the complexity of the test case by making it one test case that calls multiple functions. Also fix the problem of c_int max value mismatch between host and target.

3 files changed, 241 insertions(+), 178 deletions(-)

src/codegen/c.zig+55-70
......@@ -846,15 +846,15 @@ pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!voi
846846 // TODO use a different strategy for add that communicates to the optimizer
847847 // that wrapping is UB.
848848 .add => try genBinOp(o, inst.castTag(.add).?, " + "),
849 .addwrap => try genWrapOp(o, .add, inst.castTag(.addwrap).?),
849 .addwrap => try genWrapOp(o, inst.castTag(.addwrap).?, " + ", "addw_"),
850850 // TODO use a different strategy for sub that communicates to the optimizer
851851 // that wrapping is UB.
852852 .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
853 .subwrap => try genWrapOp(o, .sub, inst.castTag(.subwrap).?),
853 .subwrap => try genWrapOp(o, inst.castTag(.subwrap).?, " - ", "subw_"),
854854 // TODO use a different strategy for mul that communicates to the optimizer
855855 // that wrapping is UB.
856856 .mul => try genBinOp(o, inst.castTag(.sub).?, " * "),
857 .mulwrap => try genWrapOp(o, .mul, inst.castTag(.mulwrap).?),
857 .mulwrap => try genWrapOp(o, inst.castTag(.mulwrap).?, " * ", "mulw_"),
858858 // TODO use a different strategy for div that communicates to the optimizer
859859 // that wrapping is UB.
860860 .div => try genBinOp(o, inst.castTag(.div).?, " / "),
......@@ -1039,44 +1039,44 @@ fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
10391039 return CValue.none;
10401040}
10411041
1042const WrappingOp = enum {
1043 add,
1044 sub,
1045 mul,
1046};
1047
1048fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue {
1042fn genWrapOp(o: *Object, inst: *Inst.BinOp, str_op: [*:0]const u8, fn_op: [*:0]const u8) !CValue {
10491043 if (inst.base.isUnused())
10501044 return CValue.none;
10511045
1052 const is_signed = inst.base.ty.isSignedInt();
1046 const int_info = inst.base.ty.intInfo(o.dg.module.getTarget());
1047 const bits = int_info.bits;
10531048
10541049 // 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 });
1050 if (int_info.signedness == .unsigned) {
1051 const ok_bits = switch (bits) {
1052 8, 16, 32, 64, 128 => true,
1053 else => false,
1054 };
1055 if (ok_bits or inst.base.ty.tag() != .int_unsigned) {
1056 return try genBinOp(o, inst, str_op);
1057 }
1058 }
1059
1060 if (bits > 64) {
1061 return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: genWrapOp for large integers", .{});
10611062 }
10621063
10631064 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;
1065 const min = switch (int_info.signedness) {
1066 .unsigned => "0",
1067 else => switch (inst.base.ty.tag()) {
1068 .c_short => "SHRT_MIN",
1069 .c_int => "INT_MIN",
1070 .c_long => "LONG_MIN",
1071 .c_longlong => "LLONG_MIN",
1072 .isize => "INTPTR_MIN",
1073 else => blk: {
1074 const val = -1 * std.math.pow(i64, 2, @intCast(i64, bits - 1));
1075 break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) {
1076 error.NoSpaceLeft => unreachable,
1077 else => |e| return e,
1078 };
1079 },
10801080 },
10811081 };
10821082
......@@ -1093,13 +1093,15 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue {
10931093 .isize => "INTPTR_MAX",
10941094 .usize => "UINTPTR_MAX",
10951095 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;
1096 const pow_bits = switch (int_info.signedness) {
1097 .signed => bits - 1,
1098 .unsigned => bits,
1099 };
1100 const val = std.math.pow(u64, 2, pow_bits) - 1;
1101 break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) {
1102 error.NoSpaceLeft => unreachable,
1103 else => |e| return e,
1104 };
11031105 },
11041106 };
11051107
......@@ -1108,45 +1110,28 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue {
11081110 const w = o.writer();
11091111
11101112 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 });
1113 try w.print(" = zig_{s}", .{fn_op});
11171114
11181115 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"),
11271116 .isize => try w.writeAll("isize"),
11281117 .c_short => try w.writeAll("short"),
11291118 .c_int => try w.writeAll("int"),
11301119 .c_long => try w.writeAll("long"),
11311120 .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});
1121 else => {
1122 const prefix_byte: u8 = switch (int_info.signedness) {
1123 .signed => 'i',
1124 .unsigned => 'u',
1125 };
1126 for ([_]u8{ 8, 16, 32, 64 }) |nbits| {
1127 if (bits <= nbits) {
1128 try w.print("{c}{d}", .{ prefix_byte, nbits });
11431129 break;
11441130 }
11451131 } else {
1146 return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement integer types larger than 64 bits", .{});
1132 unreachable;
11471133 }
11481134 },
1149 else => unreachable,
11501135 }
11511136
11521137 try w.writeByte('(');
......@@ -1154,7 +1139,7 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue {
11541139 try w.writeAll(", ");
11551140 try o.writeCValue(w, rhs);
11561141
1157 if (is_signed) {
1142 if (int_info.signedness == .signed) {
11581143 try w.print(", {s}", .{min});
11591144 }
11601145
......@@ -1164,7 +1149,7 @@ fn genWrapOp(o: *Object, op: WrappingOp, inst: *Inst.BinOp) !CValue {
11641149 return ret;
11651150}
11661151
1167fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
1152fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: [*:0]const u8) !CValue {
11681153 if (inst.base.isUnused())
11691154 return CValue.none;
11701155
......@@ -1176,7 +1161,7 @@ fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
11761161
11771162 try writer.writeAll(" = ");
11781163 try o.writeCValue(writer, lhs);
1179 try writer.writeAll(operator);
1164 try writer.print("{s}", .{operator});
11801165 try o.writeCValue(writer, rhs);
11811166 try writer.writeAll(";\n");
11821167
src/link/C/zig.h+140-63
......@@ -60,51 +60,6 @@
6060#define zig_breakpoint() raise(SIGTRAP)
6161#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
10863#include <stdint.h>
10964#include <stddef.h>
11065#include <limits.h>
......@@ -112,37 +67,100 @@
11267#define uint128_t unsigned __int128
11368ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t);
11469
115/* Wrapping addition operators */
11670static inline uint8_t zig_addw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) {
117 ZIG_UADDW(uint8_t, lhs, rhs, max);
71 uint8_t thresh = max - rhs;
72 if (lhs > thresh) {
73 return lhs - thresh - 1;
74 } else {
75 return lhs + rhs;
76 }
11877}
11978
12079static 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);
80 if ((lhs > 0) && (rhs > 0)) {
81 int8_t thresh = max - rhs;
82 if (lhs > thresh) {
83 return min + lhs - thresh - 1;
84 }
85 } else if ((lhs < 0) && (rhs < 0)) {
86 int8_t thresh = min - rhs;
87 if (lhs < thresh) {
88 return max + lhs - thresh + 1;
89 }
90 }
91 return lhs + rhs;
12292}
12393
12494static inline uint16_t zig_addw_u16(uint16_t lhs, uint16_t rhs, uint16_t max) {
125 ZIG_UADDW(uint16_t, lhs, rhs, max);
95 uint16_t thresh = max - rhs;
96 if (lhs > thresh) {
97 return lhs - thresh - 1;
98 } else {
99 return lhs + rhs;
100 }
126101}
127102
128103static 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);
104 if ((lhs > 0) && (rhs > 0)) {
105 int16_t thresh = max - rhs;
106 if (lhs > thresh) {
107 return min + lhs - thresh - 1;
108 }
109 } else if ((lhs < 0) && (rhs < 0)) {
110 int16_t thresh = min - rhs;
111 if (lhs < thresh) {
112 return max + lhs - thresh + 1;
113 }
114 }
115 return lhs + rhs;
130116}
131117
132118static inline uint32_t zig_addw_u32(uint32_t lhs, uint32_t rhs, uint32_t max) {
133 ZIG_UADDW(uint32_t, lhs, rhs, max);
119 uint32_t thresh = max - rhs;
120 if (lhs > thresh) {
121 return lhs - thresh - 1;
122 } else {
123 return lhs + rhs;
124 }
134125}
135126
136127static 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);
128 if ((lhs > 0) && (rhs > 0)) {
129 int32_t thresh = max - rhs;
130 if (lhs > thresh) {
131 return min + lhs - thresh - 1;
132 }
133 } else if ((lhs < 0) && (rhs < 0)) {
134 int32_t thresh = min - rhs;
135 if (lhs < thresh) {
136 return max + lhs - thresh + 1;
137 }
138 }
139 return lhs + rhs;
138140}
139141
140142static inline uint64_t zig_addw_u64(uint64_t lhs, uint64_t rhs, uint64_t max) {
141 ZIG_UADDW(uint64_t, lhs, rhs, max);
143 uint64_t thresh = max - rhs;
144 if (lhs > thresh) {
145 return lhs - thresh - 1;
146 } else {
147 return lhs + rhs;
148 }
142149}
143150
144151static 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);
152 if ((lhs > 0) && (rhs > 0)) {
153 int64_t thresh = max - rhs;
154 if (lhs > thresh) {
155 return min + lhs - thresh - 1;
156 }
157 } else if ((lhs < 0) && (rhs < 0)) {
158 int64_t thresh = min - rhs;
159 if (lhs < thresh) {
160 return max + lhs - thresh + 1;
161 }
162 }
163 return lhs + rhs;
146164}
147165
148166static 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
165183 return (long long)(((unsigned long long)lhs) + ((unsigned long long)rhs));
166184}
167185
168/* Wrapping subtraction operators */
169186static inline uint8_t zig_subw_u8(uint8_t lhs, uint8_t rhs, uint8_t max) {
170 ZIG_USUBW(lhs, rhs, max);
187 if (lhs < rhs) {
188 return max - rhs - lhs + 1;
189 } else {
190 return lhs - rhs;
191 }
171192}
172193
173194static 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);
195 if ((lhs > 0) && (rhs < 0)) {
196 int8_t thresh = lhs - max;
197 if (rhs < thresh) {
198 return min + (thresh - rhs - 1);
199 }
200 } else if ((lhs < 0) && (rhs > 0)) {
201 int8_t thresh = lhs - min;
202 if (rhs > thresh) {
203 return max - (rhs - thresh - 1);
204 }
205 }
206 return lhs - rhs;
175207}
176208
177209static inline uint16_t zig_subw_u16(uint16_t lhs, uint16_t rhs, uint16_t max) {
178 ZIG_USUBW(lhs, rhs, max);
210 if (lhs < rhs) {
211 return max - rhs - lhs + 1;
212 } else {
213 return lhs - rhs;
214 }
179215}
180216
181217static 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);
218 if ((lhs > 0) && (rhs < 0)) {
219 int16_t thresh = lhs - max;
220 if (rhs < thresh) {
221 return min + (thresh - rhs - 1);
222 }
223 } else if ((lhs < 0) && (rhs > 0)) {
224 int16_t thresh = lhs - min;
225 if (rhs > thresh) {
226 return max - (rhs - thresh - 1);
227 }
228 }
229 return lhs - rhs;
183230}
184231
185232static inline uint32_t zig_subw_u32(uint32_t lhs, uint32_t rhs, uint32_t max) {
186 ZIG_USUBW(lhs, rhs, max);
233 if (lhs < rhs) {
234 return max - rhs - lhs + 1;
235 } else {
236 return lhs - rhs;
237 }
187238}
188239
189240static 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);
241 if ((lhs > 0) && (rhs < 0)) {
242 int32_t thresh = lhs - max;
243 if (rhs < thresh) {
244 return min + (thresh - rhs - 1);
245 }
246 } else if ((lhs < 0) && (rhs > 0)) {
247 int32_t thresh = lhs - min;
248 if (rhs > thresh) {
249 return max - (rhs - thresh - 1);
250 }
251 }
252 return lhs - rhs;
191253}
192254
193255static inline uint64_t zig_subw_u64(uint64_t lhs, uint64_t rhs, uint64_t max) {
194 ZIG_USUBW(lhs, rhs, max);
256 if (lhs < rhs) {
257 return max - rhs - lhs + 1;
258 } else {
259 return lhs - rhs;
260 }
195261}
196262
197263static 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);
264 if ((lhs > 0) && (rhs < 0)) {
265 int64_t thresh = lhs - max;
266 if (rhs < thresh) {
267 return min + (thresh - rhs - 1);
268 }
269 } else if ((lhs < 0) && (rhs > 0)) {
270 int64_t thresh = lhs - min;
271 if (rhs > thresh) {
272 return max - (rhs - thresh - 1);
273 }
274 }
275 return lhs - rhs;
199276}
200277
201278static inline intptr_t zig_subw_isize(intptr_t lhs, intptr_t rhs, intptr_t min, intptr_t max) {
test/stage2/cbe.zig+46-45
......@@ -825,52 +825,53 @@ pub fn addCases(ctx: *TestContext) !void {
825825 }
826826
827827 {
828 // TODO: move these cases into the programs themselves once stage 2 has array literals
829828 // 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, "");
829 var case = ctx.exeFromCompiledC("add/sub wrapping operations", .{});
830 case.addCompareOutput(
831 \\pub export fn main() c_int {
832 \\ // Addition
833 \\ if (!add_u3(1, 1, 2)) return 1;
834 \\ if (!add_u3(7, 1, 0)) return 1;
835 \\ if (!add_i3(1, 1, 2)) return 1;
836 \\ if (!add_i3(3, 2, -3)) return 1;
837 \\ if (!add_i3(-3, -2, 3)) return 1;
838 \\ if (!add_c_int(1, 1, 2)) return 1;
839 \\ // TODO enable these when stage2 supports std.math.maxInt
840 \\ //if (!add_c_int(maxInt(c_int), 2, minInt(c_int) + 1)) return 1;
841 \\ //if (!add_c_int(maxInt(c_int) + 1, -2, maxInt(c_int))) return 1;
842 \\
843 \\ // Subtraction
844 \\ if (!sub_u3(2, 1, 1)) return 1;
845 \\ if (!sub_u3(0, 1, 7)) return 1;
846 \\ if (!sub_i3(2, 1, 1)) return 1;
847 \\ if (!sub_i3(3, -2, -3)) return 1;
848 \\ if (!sub_i3(-3, 2, 3)) return 1;
849 \\ if (!sub_c_int(2, 1, 1)) return 1;
850 \\ // TODO enable these when stage2 supports std.math.maxInt
851 \\ //if (!sub_c_int(maxInt(c_int), -2, minInt(c_int) + 1)) return 1;
852 \\ //if (!sub_c_int(minInt(c_int) + 1, 2, maxInt(c_int))) return 1;
853 \\
854 \\ return 0;
855 \\}
856 \\fn add_u3(lhs: u3, rhs: u3, expected: u3) bool {
857 \\ return expected == lhs +% rhs;
858 \\}
859 \\fn add_i3(lhs: i3, rhs: i3, expected: i3) bool {
860 \\ return expected == lhs +% rhs;
861 \\}
862 \\fn add_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
863 \\ return expected == lhs +% rhs;
864 \\}
865 \\fn sub_u3(lhs: u3, rhs: u3, expected: u3) bool {
866 \\ return expected == lhs -% rhs;
867 \\}
868 \\fn sub_i3(lhs: i3, rhs: i3, expected: i3) bool {
869 \\ return expected == lhs -% rhs;
870 \\}
871 \\fn sub_c_int(lhs: c_int, rhs: c_int, expected: c_int) bool {
872 \\ return expected == lhs -% rhs;
873 \\}
874 , "");
874875 }
875876
876877 ctx.h("simple header", linux_x64,