| author | |
| committer | |
| log | c616141241047d6d6c811d43f644eb1b7d2b26ce |
| tree | 61a6a9aa8405340f8174e5900370b99ef9f67d34 |
| parent | bd32206b4449e329c9ef6ba4fd19746234f474f8 |
Perform C-style arithmetic conversions on operands to division operator
in macros
Closes #131625 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 { | ... | @@ -40,6 +40,17 @@ pub fn cast(comptime DestType: type, target: anytype) DestType { |
| 40 | .Fn => { | 40 | .Fn => { |
| 41 | return castInt(DestType, @ptrToInt(&target)); | 41 | return castInt(DestType, @ptrToInt(&target)); |
| 42 | }, | 42 | }, |
| 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)), | ||
| 43 | else => {}, | 54 | else => {}, |
| 44 | } | 55 | } |
| 45 | }, | 56 | }, |
| ... | @@ -446,6 +457,121 @@ pub const Macros = struct { | ... | @@ -446,6 +457,121 @@ pub const Macros = struct { |
| 446 | } | 457 | } |
| 447 | }; | 458 | }; |
| 448 | 459 | ||
| 460 | /// Integer promotion described in C11 6.3.1.1.2 | ||
| 461 | fn 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 | ||
| 477 | fn 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 | |||
| 489 | fn 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 | ||
| 499 | fn 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 | |||
| 531 | test "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 | |||
| 562 | pub 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 | |||
| 449 | test "Macro suffix functions" { | 575 | test "Macro suffix functions" { |
| 450 | try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32); | 576 | try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32); |
| 451 | 577 |
src/translate_c.zig+1-1| ... | @@ -6232,7 +6232,7 @@ fn parseCMulExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node { | ... | @@ -6232,7 +6232,7 @@ fn parseCMulExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node { |
| 6232 | .Slash => { | 6232 | .Slash => { |
| 6233 | const lhs = try macroBoolToInt(c, node); | 6233 | const lhs = try macroBoolToInt(c, node); |
| 6234 | const rhs = try macroBoolToInt(c, try parseCCastExpr(c, m, scope)); | 6234 | 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 }); |
| 6236 | }, | 6236 | }, |
| 6237 | .Percent => { | 6237 | .Percent => { |
| 6238 | const lhs = try macroBoolToInt(c, node); | 6238 | const lhs = try macroBoolToInt(c, node); |
src/translate_c/ast.zig+24| ... | @@ -159,6 +159,9 @@ pub const Node = extern union { | ... | @@ -159,6 +159,9 @@ pub const Node = extern union { |
| 159 | /// @shuffle(type, a, b, mask) | 159 | /// @shuffle(type, a, b, mask) |
| 160 | shuffle, | 160 | shuffle, |
| 161 | 161 | ||
| 162 | /// @import("std").zig.c_translation.MacroArithmetic.<op>(lhs, rhs) | ||
| 163 | macro_arithmetic, | ||
| 164 | |||
| 162 | asm_simple, | 165 | asm_simple, |
| 163 | 166 | ||
| 164 | negate, | 167 | negate, |
| ... | @@ -370,6 +373,7 @@ pub const Node = extern union { | ... | @@ -370,6 +373,7 @@ pub const Node = extern union { |
| 370 | .field_access => Payload.FieldAccess, | 373 | .field_access => Payload.FieldAccess, |
| 371 | .string_slice => Payload.StringSlice, | 374 | .string_slice => Payload.StringSlice, |
| 372 | .shuffle => Payload.Shuffle, | 375 | .shuffle => Payload.Shuffle, |
| 376 | .macro_arithmetic => Payload.MacroArithmetic, | ||
| 373 | }; | 377 | }; |
| 374 | } | 378 | } |
| 375 | 379 | ||
| ... | @@ -713,6 +717,19 @@ pub const Payload = struct { | ... | @@ -713,6 +717,19 @@ pub const Payload = struct { |
| 713 | mask_vector: Node, | 717 | mask_vector: Node, |
| 714 | }, | 718 | }, |
| 715 | }; | 719 | }; |
| 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 | }; | ||
| 716 | }; | 733 | }; |
| 717 | 734 | ||
| 718 | /// Converts the nodes into a Zig Ast. | 735 | /// Converts the nodes into a Zig Ast. |
| ... | @@ -1408,6 +1425,12 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { | ... | @@ -1408,6 +1425,12 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { |
| 1408 | payload.mask_vector, | 1425 | payload.mask_vector, |
| 1409 | }); | 1426 | }); |
| 1410 | }, | 1427 | }, |
| 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 | }, | ||
| 1411 | .alignof => { | 1434 | .alignof => { |
| 1412 | const payload = node.castTag(.alignof).?.data; | 1435 | const payload = node.castTag(.alignof).?.data; |
| 1413 | return renderBuiltinCall(c, "@alignOf", &.{payload}); | 1436 | return renderBuiltinCall(c, "@alignOf", &.{payload}); |
| ... | @@ -2349,6 +2372,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { | ... | @@ -2349,6 +2372,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { |
| 2349 | .shuffle, | 2372 | .shuffle, |
| 2350 | .static_local_var, | 2373 | .static_local_var, |
| 2351 | .mut_str, | 2374 | .mut_str, |
| 2375 | .macro_arithmetic, | ||
| 2352 | => { | 2376 | => { |
| 2353 | // no grouping needed | 2377 | // no grouping needed |
| 2354 | return renderNode(c, node); | 2378 | return renderNode(c, node); |
test/behavior/translate_c_macros.h+3| ... | @@ -53,3 +53,6 @@ typedef _Bool uintptr_t; | ... | @@ -53,3 +53,6 @@ typedef _Bool uintptr_t; |
| 53 | #define LARGE_INT 18446744073709550592 | 53 | #define LARGE_INT 18446744073709550592 |
| 54 | 54 | ||
| 55 | #define EMBEDDED_TAB "hello	" | 55 | #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" { | ... | @@ -147,3 +147,37 @@ test "string and char literals that are not UTF-8 encoded. Issue #12784" { |
| 147 | try expectEqual(@as(u8, '\xA9'), latin1.UNPRINTABLE_CHAR); | 147 | try expectEqual(@as(u8, '\xA9'), latin1.UNPRINTABLE_CHAR); |
| 148 | try expectEqualStrings("\xA9\xA9\xA9", latin1.UNPRINTABLE_STRING); | 148 | try expectEqualStrings("\xA9\xA9\xA9", latin1.UNPRINTABLE_STRING); |
| 149 | } | 149 | } |
| 150 | |||
| 151 | test "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 | } |