Commit c616141241

Evan Haas <evan@lagerdata.com>
2022-10-28 08:53:08
translate-c: Better support for division in macros
Perform C-style arithmetic conversions on operands to division operator in macros Closes #13162
1 parent bd32206
Changed files (5)
lib/std/zig/c_translation.zig
@@ -40,6 +40,17 @@ pub fn cast(comptime DestType: type, target: anytype) DestType {
                 .Fn => {
                     return castInt(DestType, @ptrToInt(&target));
                 },
+                .Bool => {
+                    return @boolToInt(target);
+                },
+                else => {},
+            }
+        },
+        .Float => {
+            switch (@typeInfo(SourceType)) {
+                .Int => return @intToFloat(DestType, target),
+                .Float => return @floatCast(DestType, target),
+                .Bool => return @intToFloat(DestType, @boolToInt(target)),
                 else => {},
             }
         },
@@ -446,6 +457,121 @@ pub const Macros = struct {
     }
 };
 
+/// Integer promotion described in C11 6.3.1.1.2
+fn PromotedIntType(comptime T: type) type {
+    return switch (T) {
+        bool, u8, i8, c_short => c_int,
+        c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int,
+        c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T,
+        else => if (T == comptime_int) {
+            @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required");
+        } else if (@typeInfo(T) == .Int) {
+            @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required");
+        } else {
+            @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`");
+        },
+    };
+}
+
+/// C11 6.3.1.1.1
+fn integerRank(comptime T: type) u8 {
+    return switch (T) {
+        bool => 0,
+        u8, i8 => 1,
+        c_short, c_ushort => 2,
+        c_int, c_uint => 3,
+        c_long, c_ulong => 4,
+        c_longlong, c_ulonglong => 5,
+        else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"),
+    };
+}
+
+fn ToUnsigned(comptime T: type) type {
+    return switch (T) {
+        c_int => c_uint,
+        c_long => c_ulong,
+        c_longlong => c_ulonglong,
+        else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"),
+    };
+}
+
+/// "Usual arithmetic conversions" from C11 standard 6.3.1.8
+fn ArithmeticConversion(comptime A: type, comptime B: type) type {
+    if (A == c_longdouble or B == c_longdouble) return c_longdouble;
+    if (A == f80 or B == f80) return f80;
+    if (A == f64 or B == f64) return f64;
+    if (A == f32 or B == f32) return f32;
+
+    const A_Promoted = PromotedIntType(A);
+    const B_Promoted = PromotedIntType(B);
+    comptime {
+        std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int));
+        std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int));
+    }
+
+    if (A_Promoted == B_Promoted) return A_Promoted;
+
+    const a_signed = @typeInfo(A_Promoted).Int.signedness == .signed;
+    const b_signed = @typeInfo(B_Promoted).Int.signedness == .signed;
+
+    if (a_signed == b_signed) {
+        return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted;
+    }
+
+    const SignedType = if (a_signed) A_Promoted else B_Promoted;
+    const UnsignedType = if (!a_signed) A_Promoted else B_Promoted;
+
+    if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType;
+
+    if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType;
+
+    return ToUnsigned(SignedType);
+}
+
+test "ArithmeticConversion" {
+    // Promotions not necessarily the same for other platforms
+    if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest;
+
+    const Test = struct {
+        /// Order of operands should not matter for arithmetic conversions
+        fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void {
+            try std.testing.expect(ArithmeticConversion(A, B) == Expected);
+            try std.testing.expect(ArithmeticConversion(B, A) == Expected);
+        }
+    };
+
+    try Test.checkPromotion(c_longdouble, c_int, c_longdouble);
+    try Test.checkPromotion(c_int, f64, f64);
+    try Test.checkPromotion(f32, bool, f32);
+
+    try Test.checkPromotion(bool, c_short, c_int);
+    try Test.checkPromotion(c_int, c_int, c_int);
+    try Test.checkPromotion(c_short, c_int, c_int);
+
+    try Test.checkPromotion(c_int, c_long, c_long);
+
+    try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong);
+
+    try Test.checkPromotion(c_uint, c_int, c_uint);
+
+    try Test.checkPromotion(c_uint, c_long, c_long);
+
+    try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong);
+}
+
+pub const MacroArithmetic = struct {
+    pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) {
+        const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b));
+        const a_casted = cast(ResType, a);
+        const b_casted = cast(ResType, b);
+        switch (@typeInfo(ResType)) {
+            .Float => return a_casted / b_casted,
+            .Int => return @divTrunc(a_casted, b_casted),
+            else => unreachable,
+        }
+    }
+};
+
 test "Macro suffix functions" {
     try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32);
 
src/translate_c/ast.zig
@@ -159,6 +159,9 @@ pub const Node = extern union {
         /// @shuffle(type, a, b, mask)
         shuffle,
 
+        /// @import("std").zig.c_translation.MacroArithmetic.<op>(lhs, rhs)
+        macro_arithmetic,
+
         asm_simple,
 
         negate,
@@ -370,6 +373,7 @@ pub const Node = extern union {
                 .field_access => Payload.FieldAccess,
                 .string_slice => Payload.StringSlice,
                 .shuffle => Payload.Shuffle,
+                .macro_arithmetic => Payload.MacroArithmetic,
             };
         }
 
@@ -713,6 +717,19 @@ pub const Payload = struct {
             mask_vector: Node,
         },
     };
+
+    pub const MacroArithmetic = struct {
+        base: Payload,
+        data: struct {
+            op: Operator,
+            lhs: Node,
+            rhs: Node,
+        },
+
+        pub const Operator = enum {
+            div,
+        };
+    };
 };
 
 /// Converts the nodes into a Zig Ast.
@@ -1408,6 +1425,12 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex {
                 payload.mask_vector,
             });
         },
+        .macro_arithmetic => {
+            const payload = node.castTag(.macro_arithmetic).?.data;
+            const op = @tagName(payload.op);
+            const import_node = try renderStdImport(c, &.{ "zig", "c_translation", "MacroArithmetic", op });
+            return renderCall(c, import_node, &.{ payload.lhs, payload.rhs });
+        },
         .alignof => {
             const payload = node.castTag(.alignof).?.data;
             return renderBuiltinCall(c, "@alignOf", &.{payload});
@@ -2349,6 +2372,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex {
         .shuffle,
         .static_local_var,
         .mut_str,
+        .macro_arithmetic,
         => {
             // no grouping needed
             return renderNode(c, node);
src/translate_c.zig
@@ -6232,7 +6232,7 @@ fn parseCMulExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
             .Slash => {
                 const lhs = try macroBoolToInt(c, node);
                 const rhs = try macroBoolToInt(c, try parseCCastExpr(c, m, scope));
-                node = try Tag.div.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
+                node = try Tag.macro_arithmetic.create(c.arena, .{ .op = .div, .lhs = lhs, .rhs = rhs });
             },
             .Percent => {
                 const lhs = try macroBoolToInt(c, node);
test/behavior/translate_c_macros.h
@@ -53,3 +53,6 @@ typedef _Bool uintptr_t;
 #define LARGE_INT 18446744073709550592
 
 #define EMBEDDED_TAB "hello	"
+
+#define DIVIDE_CONSTANT(version) (version / 1000)
+#define DIVIDE_ARGS(A, B) (A / B)
test/behavior/translate_c_macros.zig
@@ -147,3 +147,37 @@ test "string and char literals that are not UTF-8 encoded. Issue #12784" {
     try expectEqual(@as(u8, '\xA9'), latin1.UNPRINTABLE_CHAR);
     try expectEqualStrings("\xA9\xA9\xA9", latin1.UNPRINTABLE_STRING);
 }
+
+test "Macro that uses division operator. Issue #13162" {
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO
+
+    try expectEqual(@as(c_int, 42), h.DIVIDE_CONSTANT(@as(c_int, 42_000)));
+    try expectEqual(@as(c_uint, 42), h.DIVIDE_CONSTANT(@as(c_uint, 42_000)));
+
+    try expectEqual(
+        @as(f64, 42.0),
+        h.DIVIDE_ARGS(
+            @as(f64, 42.0),
+            true,
+        ),
+    );
+    try expectEqual(
+        @as(c_int, 21),
+        h.DIVIDE_ARGS(
+            @as(i8, 42),
+            @as(i8, 2),
+        ),
+    );
+
+    try expectEqual(
+        @as(c_int, 21),
+        h.DIVIDE_ARGS(
+            @as(c_ushort, 42),
+            @as(c_ushort, 2),
+        ),
+    );
+}