translate-c: Better support for division in macros

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

Closes #13162
This commit is contained in:
Evan Haas
2022-10-27 23:53:08 -07:00
committed by Andrew Kelley
parent bd32206b44
commit c616141241
5 changed files with 188 additions and 1 deletions

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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)

View File

@@ -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),
),
);
}