authorgravatar for evan@lagerdata.comEvan Haas <evan@lagerdata.com> 2022-10-27 23:53:08-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-10-28 17:59:32-04:00
logc616141241047d6d6c811d43f644eb1b7d2b26ce
tree61a6a9aa8405340f8174e5900370b99ef9f67d34
parentbd32206b4449e329c9ef6ba4fd19746234f474f8

translate-c: Better support for division in macros

Perform C-style arithmetic conversions on operands to division operator in macros Closes #13162

5 files changed, 188 insertions(+), 1 deletions(-)

lib/std/zig/c_translation.zig+126
......@@ -40,6 +40,17 @@ pub fn cast(comptime DestType: type, target: anytype) DestType {
4040 .Fn => {
4141 return castInt(DestType, @ptrToInt(&target));
4242 },
43 .Bool => {
44 return @boolToInt(target);
45 },
46 else => {},
47 }
48 },
49 .Float => {
50 switch (@typeInfo(SourceType)) {
51 .Int => return @intToFloat(DestType, target),
52 .Float => return @floatCast(DestType, target),
53 .Bool => return @intToFloat(DestType, @boolToInt(target)),
4354 else => {},
4455 }
4556 },
......@@ -446,6 +457,121 @@ pub const Macros = struct {
446457 }
447458};
448459
460/// Integer promotion described in C11 6.3.1.1.2
461fn PromotedIntType(comptime T: type) type {
462 return switch (T) {
463 bool, u8, i8, c_short => c_int,
464 c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int,
465 c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T,
466 else => if (T == comptime_int) {
467 @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required");
468 } else if (@typeInfo(T) == .Int) {
469 @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required");
470 } else {
471 @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`");
472 },
473 };
474}
475
476/// C11 6.3.1.1.1
477fn integerRank(comptime T: type) u8 {
478 return switch (T) {
479 bool => 0,
480 u8, i8 => 1,
481 c_short, c_ushort => 2,
482 c_int, c_uint => 3,
483 c_long, c_ulong => 4,
484 c_longlong, c_ulonglong => 5,
485 else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"),
486 };
487}
488
489fn ToUnsigned(comptime T: type) type {
490 return switch (T) {
491 c_int => c_uint,
492 c_long => c_ulong,
493 c_longlong => c_ulonglong,
494 else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"),
495 };
496}
497
498/// "Usual arithmetic conversions" from C11 standard 6.3.1.8
499fn ArithmeticConversion(comptime A: type, comptime B: type) type {
500 if (A == c_longdouble or B == c_longdouble) return c_longdouble;
501 if (A == f80 or B == f80) return f80;
502 if (A == f64 or B == f64) return f64;
503 if (A == f32 or B == f32) return f32;
504
505 const A_Promoted = PromotedIntType(A);
506 const B_Promoted = PromotedIntType(B);
507 comptime {
508 std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int));
509 std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int));
510 }
511
512 if (A_Promoted == B_Promoted) return A_Promoted;
513
514 const a_signed = @typeInfo(A_Promoted).Int.signedness == .signed;
515 const b_signed = @typeInfo(B_Promoted).Int.signedness == .signed;
516
517 if (a_signed == b_signed) {
518 return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted;
519 }
520
521 const SignedType = if (a_signed) A_Promoted else B_Promoted;
522 const UnsignedType = if (!a_signed) A_Promoted else B_Promoted;
523
524 if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType;
525
526 if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType;
527
528 return ToUnsigned(SignedType);
529}
530
531test "ArithmeticConversion" {
532 // Promotions not necessarily the same for other platforms
533 if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest;
534
535 const Test = struct {
536 /// Order of operands should not matter for arithmetic conversions
537 fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void {
538 try std.testing.expect(ArithmeticConversion(A, B) == Expected);
539 try std.testing.expect(ArithmeticConversion(B, A) == Expected);
540 }
541 };
542
543 try Test.checkPromotion(c_longdouble, c_int, c_longdouble);
544 try Test.checkPromotion(c_int, f64, f64);
545 try Test.checkPromotion(f32, bool, f32);
546
547 try Test.checkPromotion(bool, c_short, c_int);
548 try Test.checkPromotion(c_int, c_int, c_int);
549 try Test.checkPromotion(c_short, c_int, c_int);
550
551 try Test.checkPromotion(c_int, c_long, c_long);
552
553 try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong);
554
555 try Test.checkPromotion(c_uint, c_int, c_uint);
556
557 try Test.checkPromotion(c_uint, c_long, c_long);
558
559 try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong);
560}
561
562pub const MacroArithmetic = struct {
563 pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) {
564 const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b));
565 const a_casted = cast(ResType, a);
566 const b_casted = cast(ResType, b);
567 switch (@typeInfo(ResType)) {
568 .Float => return a_casted / b_casted,
569 .Int => return @divTrunc(a_casted, b_casted),
570 else => unreachable,
571 }
572 }
573};
574
449575test "Macro suffix functions" {
450576 try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32);
451577
src/translate_c.zig+1-1
......@@ -6232,7 +6232,7 @@ fn parseCMulExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
62326232 .Slash => {
62336233 const lhs = try macroBoolToInt(c, node);
62346234 const rhs = try macroBoolToInt(c, try parseCCastExpr(c, m, scope));
6235 node = try Tag.div.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
6235 node = try Tag.macro_arithmetic.create(c.arena, .{ .op = .div, .lhs = lhs, .rhs = rhs });
62366236 },
62376237 .Percent => {
62386238 const lhs = try macroBoolToInt(c, node);
src/translate_c/ast.zig+24
......@@ -159,6 +159,9 @@ pub const Node = extern union {
159159 /// @shuffle(type, a, b, mask)
160160 shuffle,
161161
162 /// @import("std").zig.c_translation.MacroArithmetic.<op>(lhs, rhs)
163 macro_arithmetic,
164
162165 asm_simple,
163166
164167 negate,
......@@ -370,6 +373,7 @@ pub const Node = extern union {
370373 .field_access => Payload.FieldAccess,
371374 .string_slice => Payload.StringSlice,
372375 .shuffle => Payload.Shuffle,
376 .macro_arithmetic => Payload.MacroArithmetic,
373377 };
374378 }
375379
......@@ -713,6 +717,19 @@ pub const Payload = struct {
713717 mask_vector: Node,
714718 },
715719 };
720
721 pub const MacroArithmetic = struct {
722 base: Payload,
723 data: struct {
724 op: Operator,
725 lhs: Node,
726 rhs: Node,
727 },
728
729 pub const Operator = enum {
730 div,
731 };
732 };
716733};
717734
718735/// Converts the nodes into a Zig Ast.
......@@ -1408,6 +1425,12 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
14081425 payload.mask_vector,
14091426 });
14101427 },
1428 .macro_arithmetic => {
1429 const payload = node.castTag(.macro_arithmetic).?.data;
1430 const op = @tagName(payload.op);
1431 const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "MacroArithmetic", op });
1432 return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
1433 },
14111434 .alignof => {
14121435 const payload = node.castTag(.alignof).?.data;
14131436 return renderBuiltinCall(c, "@alignOf", &.{payload});
......@@ -2349,6 +2372,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
23492372 .shuffle,
23502373 .static_local_var,
23512374 .mut_str,
2375 .macro_arithmetic,
23522376 => {
23532377 // no grouping needed
23542378 return renderNode(c, node);
test/behavior/translate_c_macros.h+3
......@@ -53,3 +53,6 @@ typedef _Bool uintptr_t;
5353#define LARGE_INT 18446744073709550592
5454
5555#define EMBEDDED_TAB "hello "
56
57#define DIVIDE_CONSTANT(version) (version / 1000)
58#define DIVIDE_ARGS(A, B) (A / B)
test/behavior/translate_c_macros.zig+34
......@@ -147,3 +147,37 @@ test "string and char literals that are not UTF-8 encoded. Issue #12784" {
147147 try expectEqual(@as(u8, '\xA9'), latin1.UNPRINTABLE_CHAR);
148148 try expectEqualStrings("\xA9\xA9\xA9", latin1.UNPRINTABLE_STRING);
149149}
150
151test "Macro that uses division operator. Issue #13162" {
152 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
153 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
154 if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
155 if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
156 if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
157
158 try expectEqual(@as(c_int, 42), h.DIVIDE_CONSTANT(@as(c_int, 42_000)));
159 try expectEqual(@as(c_uint, 42), h.DIVIDE_CONSTANT(@as(c_uint, 42_000)));
160
161 try expectEqual(
162 @as(f64, 42.0),
163 h.DIVIDE_ARGS(
164 @as(f64, 42.0),
165 true,
166 ),
167 );
168 try expectEqual(
169 @as(c_int, 21),
170 h.DIVIDE_ARGS(
171 @as(i8, 42),
172 @as(i8, 2),
173 ),
174 );
175
176 try expectEqual(
177 @as(c_int, 21),
178 h.DIVIDE_ARGS(
179 @as(c_ushort, 42),
180 @as(c_ushort, 2),
181 ),
182 );
183}