diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2025-09-25 14:18:49 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-25 14:18:49 -0700 |
| commit | 2a88a6a456bebcf6c28d7110a5cd60213f43508e (patch) | |
| tree | 05c944a0fb7837d2db65aacee07abea80b4a2e24 /lib/std | |
| parent | a73f246b2963de0024b9b7070448862e855d5a04 (diff) | |
| parent | 5f763b7dc581d1f9078edbb668a48af40283a120 (diff) | |
| download | zig-2a88a6a456bebcf6c28d7110a5cd60213f43508e.tar.gz zig-2a88a6a456bebcf6c28d7110a5cd60213f43508e.zip | |
Merge pull request #24497 from ziglang/aro-translate-c
compiler: update aro and translate-c to latest; delete clang translate-c
Diffstat (limited to 'lib/std')
| -rw-r--r-- | lib/std/Build/Cache.zig | 14 | ||||
| -rw-r--r-- | lib/std/zig.zig | 7 | ||||
| -rw-r--r-- | lib/std/zig/c_translation.zig | 699 | ||||
| -rw-r--r-- | lib/std/zig/c_translation/builtins.zig (renamed from lib/std/zig/c_builtins.zig) | 339 | ||||
| -rw-r--r-- | lib/std/zig/c_translation/helpers.zig | 413 |
5 files changed, 610 insertions, 862 deletions
diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index 80170e1752..14063001a2 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -459,9 +459,9 @@ pub const Manifest = struct { } } - pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_basename: []const u8) !void { + pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void { assert(self.manifest_file == null); - return self.addDepFileMaybePost(dir, dep_file_basename); + return self.addDepFileMaybePost(dir, dep_file_sub_path); } pub const HitError = error{ @@ -1049,14 +1049,14 @@ pub const Manifest = struct { self.hash.hasher.update(&new_file.bin_digest); } - pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_basename: []const u8) !void { + pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void { assert(self.manifest_file != null); - return self.addDepFileMaybePost(dir, dep_file_basename); + return self.addDepFileMaybePost(dir, dep_file_sub_path); } - fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_basename: []const u8) !void { + fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void { const gpa = self.cache.gpa; - const dep_file_contents = try dir.readFileAlloc(dep_file_basename, gpa, .limited(manifest_file_size_max)); + const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max)); defer gpa.free(dep_file_contents); var error_buf: std.ArrayListUnmanaged(u8) = .empty; @@ -1083,7 +1083,7 @@ pub const Manifest = struct { }, else => |err| { try err.printError(gpa, &error_buf); - log.err("failed parsing {s}: {s}", .{ dep_file_basename, error_buf.items }); + log.err("failed parsing {s}: {s}", .{ dep_file_sub_path, error_buf.items }); return error.InvalidDepFile; }, } diff --git a/lib/std/zig.zig b/lib/std/zig.zig index c8eea995cd..9fc64460ec 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -36,9 +36,10 @@ pub const ParsedCharLiteral = string_literal.ParsedCharLiteral; pub const parseCharLiteral = string_literal.parseCharLiteral; pub const parseNumberLiteral = number_literal.parseNumberLiteral; -// Files needed by translate-c. -pub const c_builtins = @import("zig/c_builtins.zig"); -pub const c_translation = @import("zig/c_translation.zig"); +pub const c_translation = struct { + pub const builtins = @import("zig/c_translation/builtins.zig"); + pub const helpers = @import("zig/c_translation/helpers.zig"); +}; pub const SrcHasher = std.crypto.hash.Blake3; pub const SrcHash = [16]u8; diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig deleted file mode 100644 index be0019c596..0000000000 --- a/lib/std/zig/c_translation.zig +++ /dev/null @@ -1,699 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const testing = std.testing; -const math = std.math; -const mem = std.mem; - -/// Given a type and value, cast the value to the type as c would. -pub fn cast(comptime DestType: type, target: anytype) DestType { - // this function should behave like transCCast in translate-c, except it's for macros - const SourceType = @TypeOf(target); - switch (@typeInfo(DestType)) { - .@"fn" => return castToPtr(*const DestType, SourceType, target), - .pointer => return castToPtr(DestType, SourceType, target), - .optional => |dest_opt| { - if (@typeInfo(dest_opt.child) == .pointer) { - return castToPtr(DestType, SourceType, target); - } else if (@typeInfo(dest_opt.child) == .@"fn") { - return castToPtr(?*const dest_opt.child, SourceType, target); - } - }, - .int => { - switch (@typeInfo(SourceType)) { - .pointer => { - return castInt(DestType, @intFromPtr(target)); - }, - .optional => |opt| { - if (@typeInfo(opt.child) == .pointer) { - return castInt(DestType, @intFromPtr(target)); - } - }, - .int => { - return castInt(DestType, target); - }, - .@"fn" => { - return castInt(DestType, @intFromPtr(&target)); - }, - .bool => { - return @intFromBool(target); - }, - else => {}, - } - }, - .float => { - switch (@typeInfo(SourceType)) { - .int => return @as(DestType, @floatFromInt(target)), - .float => return @as(DestType, @floatCast(target)), - .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), - else => {}, - } - }, - .@"union" => |info| { - inline for (info.fields) |field| { - if (field.type == SourceType) return @unionInit(DestType, field.name, target); - } - @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); - }, - .bool => return cast(usize, target) != 0, - else => {}, - } - return @as(DestType, target); -} - -fn castInt(comptime DestType: type, target: anytype) DestType { - const dest = @typeInfo(DestType).int; - const source = @typeInfo(@TypeOf(target)).int; - - if (dest.bits < source.bits) - return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), @truncate(target)))) - else - return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), target))); -} - -fn castPtr(comptime DestType: type, target: anytype) DestType { - return @ptrCast(@alignCast(@constCast(@volatileCast(target)))); -} - -fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { - switch (@typeInfo(SourceType)) { - .int => { - return @as(DestType, @ptrFromInt(castInt(usize, target))); - }, - .comptime_int => { - if (target < 0) - return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) - else - return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); - }, - .pointer => { - return castPtr(DestType, target); - }, - .@"fn" => { - return castPtr(DestType, &target); - }, - .optional => |target_opt| { - if (@typeInfo(target_opt.child) == .pointer) { - return castPtr(DestType, target); - } - }, - else => {}, - } - return @as(DestType, target); -} - -fn ptrInfo(comptime PtrType: type) std.builtin.Type.Pointer { - return switch (@typeInfo(PtrType)) { - .optional => |opt_info| @typeInfo(opt_info.child).pointer, - .pointer => |ptr_info| ptr_info, - else => unreachable, - }; -} - -test "cast" { - var i = @as(i64, 10); - - try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16))); - try testing.expect(cast(*u64, &i).* == @as(u64, 10)); - try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i); - - try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2))); - try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i); - try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i); - - try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4)))); - try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4)))); - try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10))); - - try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000))); - - try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2)))); - try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2)))); - - try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2)))); - - var foo: c_int = -1; - _ = &foo; - try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); - - const FnPtr = ?*align(1) const fn (*anyopaque) void; - try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0)))); - try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))))); -} - -/// Given a value returns its size as C's sizeof operator would. -pub fn sizeof(target: anytype) usize { - const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); - switch (@typeInfo(T)) { - .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), - .@"fn" => { - // sizeof(main) in C returns 1 - return 1; - }, - .null => return @sizeOf(*anyopaque), - .void => { - // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. - return 1; - }, - .@"opaque" => { - if (T == anyopaque) { - // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. - return 1; - } else { - @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); - } - }, - .optional => |opt| { - if (@typeInfo(opt.child) == .pointer) { - return sizeof(opt.child); - } else { - @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); - } - }, - .pointer => |ptr| { - if (ptr.size == .slice) { - @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); - } - // for strings, sizeof("a") returns 2. - // normal pointer decay scenarios from C are handled - // in the .array case above, but strings remain literals - // and are therefore always pointers, so they need to be - // specially handled here. - if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { - const array_info = @typeInfo(ptr.child).array; - if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { - // length of the string plus one for the null terminator. - return (array_info.len + 1) * @sizeOf(array_info.child); - } - } - // When zero sized pointers are removed, this case will no - // longer be reachable and can be deleted. - if (@sizeOf(T) == 0) { - return @sizeOf(*anyopaque); - } - return @sizeOf(T); - }, - .comptime_float => return @sizeOf(f64), // TODO c_double #3999 - .comptime_int => { - // TODO to get the correct result we have to translate - // `1073741824 * 4` as `int(1073741824) *% int(4)` since - // sizeof(1073741824 * 4) != sizeof(4294967296). - - // TODO test if target fits in int, long or long long - return @sizeOf(c_int); - }, - else => @compileError("std.meta.sizeof does not support type " ++ @typeName(T)), - } -} - -test "sizeof" { - const S = extern struct { a: u32 }; - - const ptr_size = @sizeOf(*anyopaque); - - try testing.expect(sizeof(u32) == 4); - try testing.expect(sizeof(@as(u32, 2)) == 4); - try testing.expect(sizeof(2) == @sizeOf(c_int)); - - try testing.expect(sizeof(2.0) == @sizeOf(f64)); - - try testing.expect(sizeof(S) == 4); - - try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12); - try testing.expect(sizeof([3]u32) == 12); - try testing.expect(sizeof([3:0]u32) == 16); - try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size); - - try testing.expect(sizeof(*u32) == ptr_size); - try testing.expect(sizeof([*]u32) == ptr_size); - try testing.expect(sizeof([*c]u32) == ptr_size); - try testing.expect(sizeof(?*u32) == ptr_size); - try testing.expect(sizeof(?[*]u32) == ptr_size); - try testing.expect(sizeof(*anyopaque) == ptr_size); - try testing.expect(sizeof(*void) == ptr_size); - try testing.expect(sizeof(null) == ptr_size); - - try testing.expect(sizeof("foobar") == 7); - try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14); - try testing.expect(sizeof(*const [4:0]u8) == 5); - try testing.expect(sizeof(*[4:0]u8) == ptr_size); - try testing.expect(sizeof([*]const [4:0]u8) == ptr_size); - try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size); - try testing.expect(sizeof(*const [4]u8) == ptr_size); - - if (false) { // TODO - try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof))); - try testing.expect(sizeof(sizeof) == 1); - } - - try testing.expect(sizeof(void) == 1); - try testing.expect(sizeof(anyopaque) == 1); -} - -pub const CIntLiteralBase = enum { decimal, octal, hex }; - -fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { - const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; - const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; - const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; - - const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) - &unsigned - else if (base == .decimal) - &signed_decimal - else - &signed_oct_hex; - - var pos = mem.indexOfScalar(type, list, SuffixType).?; - - while (pos < list.len) : (pos += 1) { - if (number >= math.minInt(list[pos]) and number <= math.maxInt(list[pos])) { - return list[pos]; - } - } - @compileError("Integer literal is too large"); -} - -/// Promote the type of an integer literal until it fits as C would. -pub fn promoteIntLiteral( - comptime SuffixType: type, - comptime number: comptime_int, - comptime base: CIntLiteralBase, -) PromoteIntLiteralReturnType(SuffixType, number, base) { - return number; -} - -test "promoteIntLiteral" { - const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex); - try testing.expectEqual(c_uint, @TypeOf(signed_hex)); - - if (math.maxInt(c_longlong) == math.maxInt(c_int)) return; - - const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal); - const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex); - - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expectEqual(c_long, @TypeOf(signed_decimal)); - try testing.expectEqual(c_ulong, @TypeOf(unsigned)); - } else { - try testing.expectEqual(c_longlong, @TypeOf(signed_decimal)); - try testing.expectEqual(c_ulonglong, @TypeOf(unsigned)); - } -} - -/// Convert from clang __builtin_shufflevector index to Zig @shuffle index -/// clang requires __builtin_shufflevector index arguments to be integer constants. -/// negative values for `this_index` indicate "don't care". -/// clang enforces that `this_index` is less than the total number of vector elements -/// See https://ziglang.org/documentation/master/#shuffle -/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector -pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { - const positive_index = std.math.cast(usize, this_index) orelse return undefined; - if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); - const b_index = positive_index - source_vector_len; - return ~@as(i32, @intCast(b_index)); -} - -test "shuffleVectorIndex" { - const vector_len: usize = 4; - - _ = shuffleVectorIndex(-1, vector_len); - - try testing.expect(shuffleVectorIndex(0, vector_len) == 0); - try testing.expect(shuffleVectorIndex(1, vector_len) == 1); - try testing.expect(shuffleVectorIndex(2, vector_len) == 2); - try testing.expect(shuffleVectorIndex(3, vector_len) == 3); - - try testing.expect(shuffleVectorIndex(4, vector_len) == -1); - try testing.expect(shuffleVectorIndex(5, vector_len) == -2); - try testing.expect(shuffleVectorIndex(6, vector_len) == -3); - try testing.expect(shuffleVectorIndex(7, vector_len) == -4); -} - -/// Constructs a [*c] pointer with the const and volatile annotations -/// from SelfType for pointing to a C flexible array of ElementType. -pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { - switch (@typeInfo(SelfType)) { - .pointer => |ptr| { - return @Type(.{ .pointer = .{ - .size = .c, - .is_const = ptr.is_const, - .is_volatile = ptr.is_volatile, - .alignment = @alignOf(ElementType), - .address_space = .generic, - .child = ElementType, - .is_allowzero = true, - .sentinel_ptr = null, - } }); - }, - else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), - } -} - -test "Flexible Array Type" { - const Container = extern struct { - size: usize, - }; - - try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int); - try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int); - try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int); - try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int); -} - -/// C `%` operator for signed integers -/// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" -/// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for -/// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety -/// checked undefined behavior -pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { - std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); - if (denominator > 0) return @rem(numerator, denominator); - return numerator - @divTrunc(numerator, denominator) * denominator; -} - -pub const Macros = struct { - pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { - return promoteIntLiteral(c_uint, n, .decimal); - } - - fn L_SUFFIX_ReturnType(comptime number: anytype) type { - switch (@typeInfo(@TypeOf(number))) { - .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), - .float, .comptime_float => return c_longdouble, - else => @compileError("Invalid value for L suffix"), - } - } - pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { - switch (@typeInfo(@TypeOf(number))) { - .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), - .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), - else => @compileError("Invalid value for L suffix"), - } - } - - pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { - return promoteIntLiteral(c_ulong, n, .decimal); - } - - pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { - return promoteIntLiteral(c_longlong, n, .decimal); - } - - pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { - return promoteIntLiteral(c_ulonglong, n, .decimal); - } - - pub fn F_SUFFIX(comptime f: comptime_float) f32 { - return @as(f32, f); - } - - pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { - return @fieldParentPtr(member, ptr); - } - - /// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) - /// could be either: cast B to A, or call A with the value B. - pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { - .type => a, - .@"fn" => |fn_info| fn_info.return_type orelse void, - else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), - } { - switch (@typeInfo(@TypeOf(a))) { - .type => return cast(a, b), - .@"fn" => return a(b), - else => unreachable, // return type will be a compile error otherwise - } - } - - pub inline fn DISCARD(x: anytype) void { - _ = x; - } -}; - -/// Integer promotion described in C11 6.3.1.1.2 -fn PromotedIntType(comptime T: type) type { - return switch (T) { - bool, 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 => switch (@typeInfo(T)) { - .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), - // promote to c_int if it can represent all values of T - .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) - c_int - // otherwise, restore the original C type - else if (int_info.bits == @bitSizeOf(c_int)) - if (int_info.signedness == .unsigned) c_uint else c_int - else if (int_info.bits <= @bitSizeOf(c_long)) - if (int_info.signedness == .unsigned) c_ulong else c_long - else if (int_info.bits <= @bitSizeOf(c_longlong)) - if (int_info.signedness == .unsigned) c_ulonglong else c_longlong - else - @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); - - // stdint.h - try Test.checkPromotion(u8, i8, c_int); - try Test.checkPromotion(u16, i16, c_int); - try Test.checkPromotion(i32, c_int, c_int); - try Test.checkPromotion(u32, c_int, c_uint); - try Test.checkPromotion(i64, c_int, c_long); - try Test.checkPromotion(u64, c_int, c_ulong); - try Test.checkPromotion(isize, c_int, c_long); - try Test.checkPromotion(usize, c_int, c_ulong); -} - -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, - } - } - - pub fn rem(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)) { - .int => { - if (@typeInfo(ResType).int.signedness == .signed) { - return signedRemainder(a_casted, b_casted); - } else { - return a_casted % b_casted; - } - }, - else => unreachable, - } - } -}; - -test "Macro suffix functions" { - try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32); - - try testing.expect(@TypeOf(Macros.U_SUFFIX(1)) == c_uint); - if (math.maxInt(c_ulong) > math.maxInt(c_uint)) { - try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong); - } - if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { - try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); - } - - try testing.expect(@TypeOf(Macros.L_SUFFIX(1)) == c_long); - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_int) + 1)) == c_long); - } - if (math.maxInt(c_longlong) > math.maxInt(c_long)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); - } - - try testing.expect(@TypeOf(Macros.UL_SUFFIX(1)) == c_ulong); - if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) { - try testing.expect(@TypeOf(Macros.UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong); - } - - try testing.expect(@TypeOf(Macros.LL_SUFFIX(1)) == c_longlong); - try testing.expect(@TypeOf(Macros.ULL_SUFFIX(1)) == c_ulonglong); -} - -test "WL_CONTAINER_OF" { - const S = struct { - a: u32 = 0, - b: u32 = 0, - }; - const x = S{}; - const y = S{}; - const ptr = Macros.WL_CONTAINER_OF(&x.b, &y, "b"); - try testing.expectEqual(&x, ptr); -} - -test "CAST_OR_CALL casting" { - const arg: c_int = 1000; - const casted = Macros.CAST_OR_CALL(u8, arg); - try testing.expectEqual(cast(u8, arg), casted); - - const S = struct { - x: u32 = 0, - }; - var s: S = .{}; - const casted_ptr = Macros.CAST_OR_CALL(*u8, &s); - try testing.expectEqual(cast(*u8, &s), casted_ptr); -} - -test "CAST_OR_CALL calling" { - const Helper = struct { - var last_val: bool = false; - fn returnsVoid(val: bool) void { - last_val = val; - } - fn returnsBool(f: f32) bool { - return f > 0; - } - fn identity(self: c_uint) c_uint { - return self; - } - }; - - Macros.CAST_OR_CALL(Helper.returnsVoid, true); - try testing.expectEqual(true, Helper.last_val); - Macros.CAST_OR_CALL(Helper.returnsVoid, false); - try testing.expectEqual(false, Helper.last_val); - - try testing.expectEqual(Helper.returnsBool(1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, 1))); - try testing.expectEqual(Helper.returnsBool(-1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, -1))); - - try testing.expectEqual(Helper.identity(@as(c_uint, 100)), Macros.CAST_OR_CALL(Helper.identity, @as(c_uint, 100))); -} - -test "Extended C ABI casting" { - if (math.maxInt(c_long) > math.maxInt(c_char)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char - } - if (math.maxInt(c_long) > math.maxInt(c_short)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short - } - - if (math.maxInt(c_long) > math.maxInt(c_ushort)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort - } - - if (math.maxInt(c_long) > math.maxInt(c_int)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int - } - - if (math.maxInt(c_long) > math.maxInt(c_uint)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long - } - - if (math.maxInt(c_longlong) > math.maxInt(c_long)) { - try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long - try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong - } -} - -// Function with complex signature for testing the SDL case -fn complexFunction(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize { - return 0; -} - -test "function pointer casting" { - const SDL_FunctionPointer = ?*const fn () callconv(.c) void; - const fn_ptr = cast(SDL_FunctionPointer, complexFunction); - try testing.expect(fn_ptr != null); -} diff --git a/lib/std/zig/c_builtins.zig b/lib/std/zig/c_translation/builtins.zig index 2f6c2e8aa5..c704b65e23 100644 --- a/lib/std/zig/c_builtins.zig +++ b/lib/std/zig/c_translation/builtins.zig @@ -1,182 +1,176 @@ const std = @import("std"); -pub inline fn __builtin_bswap16(val: u16) u16 { - return @byteSwap(val); +/// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub inline fn abs(val: c_int) c_int { + return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); } -pub inline fn __builtin_bswap32(val: u32) u32 { - return @byteSwap(val); + +pub inline fn assume(cond: bool) void { + if (!cond) unreachable; } -pub inline fn __builtin_bswap64(val: u64) u64 { + +pub inline fn bswap16(val: u16) u16 { return @byteSwap(val); } -pub inline fn __builtin_signbit(val: f64) c_int { - return @intFromBool(std.math.signbit(val)); +pub inline fn bswap32(val: u32) u32 { + return @byteSwap(val); } -pub inline fn __builtin_signbitf(val: f32) c_int { - return @intFromBool(std.math.signbit(val)); + +pub inline fn bswap64(val: u64) u64 { + return @byteSwap(val); } -pub inline fn __builtin_popcount(val: c_uint) c_int { - // popcount of a c_uint will never exceed the capacity of a c_int - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); +pub inline fn ceilf(val: f32) f32 { + return @ceil(val); } -pub inline fn __builtin_ctz(val: c_uint) c_int { - // Returns the number of trailing 0-bits in val, starting at the least significant bit position. - // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint - @setRuntimeSafety(false); - return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); + +pub inline fn ceil(val: f64) f64 { + return @ceil(val); } -pub inline fn __builtin_clz(val: c_uint) c_int { - // Returns the number of leading 0-bits in x, starting at the most significant bit position. - // In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint + +/// Returns the number of leading 0-bits in x, starting at the most significant bit position. +/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint +pub inline fn clz(val: c_uint) c_int { @setRuntimeSafety(false); return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); } -pub inline fn __builtin_sqrt(val: f64) f64 { - return @sqrt(val); -} -pub inline fn __builtin_sqrtf(val: f32) f32 { - return @sqrt(val); +pub inline fn constant_p(expr: anytype) c_int { + _ = expr; + return @intFromBool(false); } -pub inline fn __builtin_sin(val: f64) f64 { - return @sin(val); -} -pub inline fn __builtin_sinf(val: f32) f32 { - return @sin(val); -} -pub inline fn __builtin_cos(val: f64) f64 { +pub inline fn cosf(val: f32) f32 { return @cos(val); } -pub inline fn __builtin_cosf(val: f32) f32 { + +pub inline fn cos(val: f64) f64 { return @cos(val); } -pub inline fn __builtin_exp(val: f64) f64 { - return @exp(val); -} -pub inline fn __builtin_expf(val: f32) f32 { - return @exp(val); +/// Returns the number of trailing 0-bits in val, starting at the least significant bit position. +/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint +pub inline fn ctz(val: c_uint) c_int { + @setRuntimeSafety(false); + return @as(c_int, @bitCast(@as(c_uint, @ctz(val)))); } -pub inline fn __builtin_exp2(val: f64) f64 { + +pub inline fn exp2f(val: f32) f32 { return @exp2(val); } -pub inline fn __builtin_exp2f(val: f32) f32 { + +pub inline fn exp2(val: f64) f64 { return @exp2(val); } -pub inline fn __builtin_log(val: f64) f64 { - return @log(val); -} -pub inline fn __builtin_logf(val: f32) f32 { - return @log(val); -} -pub inline fn __builtin_log2(val: f64) f64 { - return @log2(val); -} -pub inline fn __builtin_log2f(val: f32) f32 { - return @log2(val); -} -pub inline fn __builtin_log10(val: f64) f64 { - return @log10(val); -} -pub inline fn __builtin_log10f(val: f32) f32 { - return @log10(val); -} -// Standard C Library bug: The absolute value of the most negative integer remains negative. -pub inline fn __builtin_abs(val: c_int) c_int { - return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); +pub inline fn expf(val: f32) f32 { + return @exp(val); } -pub inline fn __builtin_labs(val: c_long) c_long { - return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); + +pub inline fn exp(val: f64) f64 { + return @exp(val); } -pub inline fn __builtin_llabs(val: c_longlong) c_longlong { - return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); + +/// The return value of __builtin_expect is `expr`. `c` is the expected value +/// of `expr` and is used as a hint to the compiler in C. Here it is unused. +pub inline fn expect(expr: c_long, c: c_long) c_long { + _ = c; + return expr; } -pub inline fn __builtin_fabs(val: f64) f64 { + +pub inline fn fabsf(val: f32) f32 { return @abs(val); } -pub inline fn __builtin_fabsf(val: f32) f32 { + +pub inline fn fabs(val: f64) f64 { return @abs(val); } -pub inline fn __builtin_floor(val: f64) f64 { +pub inline fn floorf(val: f32) f32 { return @floor(val); } -pub inline fn __builtin_floorf(val: f32) f32 { + +pub inline fn floor(val: f64) f64 { return @floor(val); } -pub inline fn __builtin_ceil(val: f64) f64 { - return @ceil(val); + +pub inline fn has_builtin(func: anytype) c_int { + _ = func; + return @intFromBool(true); } -pub inline fn __builtin_ceilf(val: f32) f32 { - return @ceil(val); + +pub inline fn huge_valf() f32 { + return std.math.inf(f32); } -pub inline fn __builtin_trunc(val: f64) f64 { - return @trunc(val); + +pub inline fn inff() f32 { + return std.math.inf(f32); } -pub inline fn __builtin_truncf(val: f32) f32 { - return @trunc(val); + +/// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. +pub inline fn isinf_sign(x: anytype) c_int { + if (!std.math.isInf(x)) return 0; + return if (std.math.isPositiveInf(x)) 1 else -1; } -pub inline fn __builtin_round(val: f64) f64 { - return @round(val); + +pub inline fn isinf(x: anytype) c_int { + return @intFromBool(std.math.isInf(x)); } -pub inline fn __builtin_roundf(val: f32) f32 { - return @round(val); + +pub inline fn isnan(x: anytype) c_int { + return @intFromBool(std.math.isNan(x)); } -pub inline fn __builtin_strlen(s: [*c]const u8) usize { - return std.mem.sliceTo(s, 0).len; +/// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub inline fn labs(val: c_long) c_long { + return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val)); } -pub inline fn __builtin_strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { - return switch (std.mem.orderZ(u8, s1, s2)) { - .lt => -1, - .eq => 0, - .gt => 1, - }; + +/// Standard C Library bug: The absolute value of the most negative integer remains negative. +pub inline fn llabs(val: c_longlong) c_longlong { + return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val)); } -pub inline fn __builtin_object_size(ptr: ?*const anyopaque, ty: c_int) usize { - _ = ptr; - // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html - // If it is not possible to determine which objects ptr points to at compile time, - // __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 - // for type 2 or 3. - if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); - if (ty == 2 or ty == 3) return 0; - unreachable; +pub inline fn log10f(val: f32) f32 { + return @log10(val); } -pub inline fn __builtin___memset_chk( - dst: ?*anyopaque, - val: c_int, - len: usize, - remaining: usize, -) ?*anyopaque { - if (len > remaining) @panic("std.c.builtins.memset_chk called with len > remaining"); - return __builtin_memset(dst, val, len); +pub inline fn log10(val: f64) f64 { + return @log10(val); } -pub inline fn __builtin_memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { - const dst_cast = @as([*c]u8, @ptrCast(dst)); - @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); - return dst; +pub inline fn log2f(val: f32) f32 { + return @log2(val); } -pub inline fn __builtin___memcpy_chk( +pub inline fn log2(val: f64) f64 { + return @log2(val); +} + +pub inline fn logf(val: f32) f32 { + return @log(val); +} + +pub inline fn log(val: f64) f64 { + return @log(val); +} + +pub inline fn memcpy_chk( noalias dst: ?*anyopaque, noalias src: ?*const anyopaque, len: usize, remaining: usize, ) ?*anyopaque { - if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining"); - return __builtin_memcpy(dst, src, len); + if (len > remaining) @panic("__builtin___memcpy_chk called with len > remaining"); + if (len > 0) @memcpy( + @as([*]u8, @ptrCast(dst.?))[0..len], + @as([*]const u8, @ptrCast(src.?)), + ); + return dst; } -pub inline fn __builtin_memcpy( +pub inline fn memcpy( noalias dst: ?*anyopaque, noalias src: ?*const anyopaque, len: usize, @@ -188,16 +182,33 @@ pub inline fn __builtin_memcpy( return dst; } -/// The return value of __builtin_expect is `expr`. `c` is the expected value -/// of `expr` and is used as a hint to the compiler in C. Here it is unused. -pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long { - _ = c; - return expr; +pub inline fn memset_chk( + dst: ?*anyopaque, + val: c_int, + len: usize, + remaining: usize, +) ?*anyopaque { + if (len > remaining) @panic("__builtin___memset_chk called with len > remaining"); + const dst_cast = @as([*c]u8, @ptrCast(dst)); + @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); + return dst; +} + +pub inline fn memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { + const dst_cast = @as([*c]u8, @ptrCast(dst)); + @memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val))))); + return dst; +} + +pub fn mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { + const res = @mulWithOverflow(a, b); + result.* = res[0]; + return res[1]; } /// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an /// implementation-defined way. -/// This implementation is based on the description for __builtin_nan provided in the GCC docs at +/// This implementation is based on the description for nan provided in the GCC docs at /// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan /// Comment is reproduced below: /// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description @@ -210,59 +221,81 @@ pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long { /// /// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero. /// If tagp is empty, the function returns a NaN whose significand is zero. -pub inline fn __builtin_nanf(tagp: []const u8) f32 { +pub inline fn nanf(tagp: []const u8) f32 { const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0; const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32)))); } -pub inline fn __builtin_huge_valf() f32 { - return std.math.inf(f32); +pub inline fn object_size(ptr: ?*const anyopaque, ty: c_int) usize { + _ = ptr; + // clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html + // If it is not possible to determine which objects ptr points to at compile time, + // object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0 + // for type 2 or 3. + if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1))); + if (ty == 2 or ty == 3) return 0; + unreachable; } -pub inline fn __builtin_inff() f32 { - return std.math.inf(f32); +/// popcount of a c_uint will never exceed the capacity of a c_int +pub inline fn popcount(val: c_uint) c_int { + @setRuntimeSafety(false); + return @as(c_int, @bitCast(@as(c_uint, @popCount(val)))); } -pub inline fn __builtin_isnan(x: anytype) c_int { - return @intFromBool(std.math.isNan(x)); +pub inline fn roundf(val: f32) f32 { + return @round(val); } -pub inline fn __builtin_isinf(x: anytype) c_int { - return @intFromBool(std.math.isInf(x)); +pub inline fn round(val: f64) f64 { + return @round(val); } -/// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf. -pub inline fn __builtin_isinf_sign(x: anytype) c_int { - if (!std.math.isInf(x)) return 0; - return if (std.math.isPositiveInf(x)) 1 else -1; +pub inline fn signbitf(val: f32) c_int { + return @intFromBool(std.math.signbit(val)); } -pub inline fn __has_builtin(func: anytype) c_int { - _ = func; - return @intFromBool(true); +pub inline fn signbit(val: f64) c_int { + return @intFromBool(std.math.signbit(val)); } -pub inline fn __builtin_assume(cond: bool) void { - if (!cond) unreachable; +pub inline fn sinf(val: f32) f32 { + return @sin(val); } -pub inline fn __builtin_unreachable() noreturn { - unreachable; +pub inline fn sin(val: f64) f64 { + return @sin(val); } -pub inline fn __builtin_constant_p(expr: anytype) c_int { - _ = expr; - return @intFromBool(false); +pub inline fn sqrtf(val: f32) f32 { + return @sqrt(val); } -pub fn __builtin_mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int { - const res = @mulWithOverflow(a, b); - result.* = res[0]; - return res[1]; + +pub inline fn sqrt(val: f64) f64 { + return @sqrt(val); +} + +pub inline fn strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int { + return switch (std.mem.orderZ(u8, s1, s2)) { + .lt => -1, + .eq => 0, + .gt => 1, + }; +} + +pub inline fn strlen(s: [*c]const u8) usize { + return std.mem.sliceTo(s, 0).len; +} + +pub inline fn truncf(val: f32) f32 { + return @trunc(val); +} + +pub inline fn trunc(val: f64) f64 { + return @trunc(val); } -// __builtin_alloca_with_align is not currently implemented. -// It is used in a run-translated-c test and a test-translate-c test to ensure that non-implemented -// builtins are correctly demoted. If you implement __builtin_alloca_with_align, please update the -// run-translated-c test and the test-translate-c test to use a different non-implemented builtin. -// pub inline fn __builtin_alloca_with_align(size: usize, alignment: usize) *anyopaque {} +pub inline fn @"unreachable"() noreturn { + unreachable; +} diff --git a/lib/std/zig/c_translation/helpers.zig b/lib/std/zig/c_translation/helpers.zig new file mode 100644 index 0000000000..5f6a5561df --- /dev/null +++ b/lib/std/zig/c_translation/helpers.zig @@ -0,0 +1,413 @@ +const std = @import("std"); + +/// "Usual arithmetic conversions" from C11 standard 6.3.1.8 +pub 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); +} + +/// Integer promotion described in C11 6.3.1.1.2 +fn PromotedIntType(comptime T: type) type { + return switch (T) { + bool, 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 => switch (@typeInfo(T)) { + .comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"), + // promote to c_int if it can represent all values of T + .int => |int_info| if (int_info.bits < @bitSizeOf(c_int)) + c_int + // otherwise, restore the original C type + else if (int_info.bits == @bitSizeOf(c_int)) + if (int_info.signedness == .unsigned) c_uint else c_int + else if (int_info.bits <= @bitSizeOf(c_long)) + if (int_info.signedness == .unsigned) c_ulong else c_long + else if (int_info.bits <= @bitSizeOf(c_longlong)) + if (int_info.signedness == .unsigned) c_ulonglong else c_longlong + else + @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"), + }; +} + +/// Constructs a [*c] pointer with the const and volatile annotations +/// from SelfType for pointing to a C flexible array of ElementType. +pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type { + switch (@typeInfo(SelfType)) { + .pointer => |ptr| { + return @Type(.{ .pointer = .{ + .size = .c, + .is_const = ptr.is_const, + .is_volatile = ptr.is_volatile, + .alignment = @alignOf(ElementType), + .address_space = .generic, + .child = ElementType, + .is_allowzero = true, + .sentinel_ptr = null, + } }); + }, + else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)), + } +} + +/// Promote the type of an integer literal until it fits as C would. +pub fn promoteIntLiteral( + comptime SuffixType: type, + comptime number: comptime_int, + comptime base: CIntLiteralBase, +) PromoteIntLiteralReturnType(SuffixType, number, base) { + return number; +} + +const CIntLiteralBase = enum { decimal, octal, hex }; + +fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type { + const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong }; + const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong }; + const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong }; + + const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned) + &unsigned + else if (base == .decimal) + &signed_decimal + else + &signed_oct_hex; + + var pos = std.mem.indexOfScalar(type, list, SuffixType).?; + while (pos < list.len) : (pos += 1) { + if (number >= std.math.minInt(list[pos]) and number <= std.math.maxInt(list[pos])) { + return list[pos]; + } + } + + @compileError("Integer literal is too large"); +} + +/// Convert from clang __builtin_shufflevector index to Zig @shuffle index +/// clang requires __builtin_shufflevector index arguments to be integer constants. +/// negative values for `this_index` indicate "don't care". +/// clang enforces that `this_index` is less than the total number of vector elements +/// See https://ziglang.org/documentation/master/#shuffle +/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector +pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 { + const positive_index = std.math.cast(usize, this_index) orelse return undefined; + if (positive_index < source_vector_len) return @as(i32, @intCast(this_index)); + const b_index = positive_index - source_vector_len; + return ~@as(i32, @intCast(b_index)); +} + +/// C `%` operator for signed integers +/// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a" +/// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for +/// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety +/// checked undefined behavior +pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) { + std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed); + if (denominator > 0) return @rem(numerator, denominator); + return numerator - @divTrunc(numerator, denominator) * denominator; +} + +/// Given a type and value, cast the value to the type as c would. +pub fn cast(comptime DestType: type, target: anytype) DestType { + // this function should behave like transCCast in translate-c, except it's for macros + const SourceType = @TypeOf(target); + switch (@typeInfo(DestType)) { + .@"fn" => return castToPtr(*const DestType, SourceType, target), + .pointer => return castToPtr(DestType, SourceType, target), + .optional => |dest_opt| { + if (@typeInfo(dest_opt.child) == .pointer) { + return castToPtr(DestType, SourceType, target); + } else if (@typeInfo(dest_opt.child) == .@"fn") { + return castToPtr(?*const dest_opt.child, SourceType, target); + } + }, + .int => { + switch (@typeInfo(SourceType)) { + .pointer => { + return castInt(DestType, @intFromPtr(target)); + }, + .optional => |opt| { + if (@typeInfo(opt.child) == .pointer) { + return castInt(DestType, @intFromPtr(target)); + } + }, + .int => { + return castInt(DestType, target); + }, + .@"fn" => { + return castInt(DestType, @intFromPtr(&target)); + }, + .bool => { + return @intFromBool(target); + }, + else => {}, + } + }, + .float => { + switch (@typeInfo(SourceType)) { + .int => return @as(DestType, @floatFromInt(target)), + .float => return @as(DestType, @floatCast(target)), + .bool => return @as(DestType, @floatFromInt(@intFromBool(target))), + else => {}, + } + }, + .@"union" => |info| { + inline for (info.fields) |field| { + if (field.type == SourceType) return @unionInit(DestType, field.name, target); + } + + @compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union"); + }, + .bool => return cast(usize, target) != 0, + else => {}, + } + + return @as(DestType, target); +} + +fn castInt(comptime DestType: type, target: anytype) DestType { + const dest = @typeInfo(DestType).int; + const source = @typeInfo(@TypeOf(target)).int; + + const Int = @Type(.{ .int = .{ .bits = dest.bits, .signedness = source.signedness } }); + + if (dest.bits < source.bits) + return @as(DestType, @bitCast(@as(Int, @truncate(target)))) + else + return @as(DestType, @bitCast(@as(Int, target))); +} + +fn castPtr(comptime DestType: type, target: anytype) DestType { + return @ptrCast(@alignCast(@constCast(@volatileCast(target)))); +} + +fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType { + switch (@typeInfo(SourceType)) { + .int => { + return @as(DestType, @ptrFromInt(castInt(usize, target))); + }, + .comptime_int => { + if (target < 0) + return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target)))))) + else + return @as(DestType, @ptrFromInt(@as(usize, @intCast(target)))); + }, + .pointer => { + return castPtr(DestType, target); + }, + .@"fn" => { + return castPtr(DestType, &target); + }, + .optional => |target_opt| { + if (@typeInfo(target_opt.child) == .pointer) { + return castPtr(DestType, target); + } + }, + else => {}, + } + + return @as(DestType, target); +} + +/// Given a value returns its size as C's sizeof operator would. +pub fn sizeof(target: anytype) usize { + const T: type = if (@TypeOf(target) == type) target else @TypeOf(target); + switch (@typeInfo(T)) { + .float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T), + .@"fn" => { + // sizeof(main) in C returns 1 + return 1; + }, + .null => return @sizeOf(*anyopaque), + .void => { + // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. + return 1; + }, + .@"opaque" => { + if (T == anyopaque) { + // Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC. + return 1; + } else { + @compileError("Cannot use C sizeof on opaque type " ++ @typeName(T)); + } + }, + .optional => |opt| { + if (@typeInfo(opt.child) == .pointer) { + return sizeof(opt.child); + } else { + @compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T)); + } + }, + .pointer => |ptr| { + if (ptr.size == .slice) { + @compileError("Cannot use C sizeof on slice type " ++ @typeName(T)); + } + + // for strings, sizeof("a") returns 2. + // normal pointer decay scenarios from C are handled + // in the .array case above, but strings remain literals + // and are therefore always pointers, so they need to be + // specially handled here. + if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) { + const array_info = @typeInfo(ptr.child).array; + if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) { + // length of the string plus one for the null terminator. + return (array_info.len + 1) * @sizeOf(array_info.child); + } + } + + // When zero sized pointers are removed, this case will no + // longer be reachable and can be deleted. + if (@sizeOf(T) == 0) { + return @sizeOf(*anyopaque); + } + + return @sizeOf(T); + }, + .comptime_float => return @sizeOf(f64), // TODO c_double #3999 + .comptime_int => { + // TODO to get the correct result we have to translate + // `1073741824 * 4` as `int(1073741824) *% int(4)` since + // sizeof(1073741824 * 4) != sizeof(4294967296). + + // TODO test if target fits in int, long or long long + return @sizeOf(c_int); + }, + else => @compileError("__helpers.sizeof does not support type " ++ @typeName(T)), + } +} + +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, + } +} + +pub fn rem(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)) { + .int => { + if (@typeInfo(ResType).int.signedness == .signed) { + return signedRemainder(a_casted, b_casted); + } else { + return a_casted % b_casted; + } + }, + else => unreachable, + } +} + +/// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B) +/// could be either: cast B to A, or call A with the value B. +pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) { + .type => a, + .@"fn" => |fn_info| fn_info.return_type orelse void, + else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)), +} { + switch (@typeInfo(@TypeOf(a))) { + .type => return cast(a, b), + .@"fn" => return a(b), + else => unreachable, // return type will be a compile error otherwise + } +} + +pub inline fn DISCARD(x: anytype) void { + _ = x; +} + +pub fn F_SUFFIX(comptime f: comptime_float) f32 { + return @as(f32, f); +} + +fn L_SUFFIX_ReturnType(comptime number: anytype) type { + switch (@typeInfo(@TypeOf(number))) { + .int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), + .float, .comptime_float => return c_longdouble, + else => @compileError("Invalid value for L suffix"), + } +} + +pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { + switch (@typeInfo(@TypeOf(number))) { + .int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal), + .float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"), + else => @compileError("Invalid value for L suffix"), + } +} + +pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) { + return promoteIntLiteral(c_longlong, n, .decimal); +} + +pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) { + return promoteIntLiteral(c_uint, n, .decimal); +} + +pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) { + return promoteIntLiteral(c_ulong, n, .decimal); +} + +pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) { + return promoteIntLiteral(c_ulonglong, n, .decimal); +} + +pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) { + return @fieldParentPtr(member, ptr); +} |
