diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig index 6bc664f04c..664cb09ae4 100644 --- a/lib/std/zig/c_translation.zig +++ b/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); diff --git a/src/translate_c.zig b/src/translate_c.zig index 7cc843e17c..693f274e8e 100644 --- a/src/translate_c.zig +++ b/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); diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 4dcdbc4250..4a64c13ce7 100644 --- a/src/translate_c/ast.zig +++ b/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.(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); diff --git a/test/behavior/translate_c_macros.h b/test/behavior/translate_c_macros.h index 5d4cf3473d..fc9aaaaf52 100644 --- a/test/behavior/translate_c_macros.h +++ b/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) diff --git a/test/behavior/translate_c_macros.zig b/test/behavior/translate_c_macros.zig index deda45df91..29cd949508 100644 --- a/test/behavior/translate_c_macros.zig +++ 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), + ), + ); +}