diff --git a/lib/std/zig/c_translation.zig b/lib/std/zig/c_translation.zig index 7851525bb7..071ce01396 100644 --- a/lib/std/zig/c_translation.zig +++ b/lib/std/zig/c_translation.zig @@ -350,3 +350,83 @@ test "Flexible Array Type" { 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); } + +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 (@TypeOf(number)) { + comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)), + comptime_float => return c_longdouble, + else => @compileError("Invalid value for L suffix"), + } + } + pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) { + switch (@TypeOf(number)) { + comptime_int => return promoteIntLiteral(c_long, number, .decimal), + 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(@TypeOf(sample.*), member, ptr); + } +}; + +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, + }; + var x = S{}; + var y = S{}; + var ptr = Macros.WL_CONTAINER_OF(&x.b, &y, "b"); + try testing.expectEqual(&x, ptr); +} diff --git a/src/translate_c.zig b/src/translate_c.zig index 707f8cc485..2ac720a209 100644 --- a/src/translate_c.zig +++ b/src/translate_c.zig @@ -2,6 +2,7 @@ //! and stage2. const std = @import("std"); +const testing = std.testing; const assert = std.debug.assert; const clang = @import("clang.zig"); const ctok = std.c.tokenizer; @@ -18,6 +19,7 @@ const CallingConvention = std.builtin.CallingConvention; pub const ClangErrMsg = clang.Stage2ErrorMsg; pub const Error = std.mem.Allocator.Error; +const MacroProcessingError = Error || error{UnexpectedMacroToken}; const TypeError = Error || error{UnsupportedType}; const TransError = TypeError || error{UnsupportedTranslation}; @@ -27,6 +29,10 @@ const AliasList = std.ArrayList(struct { name: []const u8, }); +// Maps macro parameter names to token position, for determining if different +// identifiers refer to the same positional argument in different macros. +const ArgsPositionMap = std.StringArrayHashMapUnmanaged(usize); + const Scope = struct { id: Id, parent: ?*Scope, @@ -322,6 +328,8 @@ pub const Context = struct { /// up front in a pre-processing step. global_names: std.StringArrayHashMapUnmanaged(void) = .{}, + pattern_list: PatternList, + fn getMangle(c: *Context) u32 { c.mangle_count += 1; return c.mangle_count; @@ -375,6 +383,7 @@ pub fn translate( .alias_list = AliasList.init(gpa), .global_scope = try arena.allocator.create(Scope.Root), .clang_context = ast_unit.getASTContext(), + .pattern_list = try PatternList.init(gpa), }; context.global_scope.* = Scope.Root.init(&context); defer { @@ -385,6 +394,7 @@ pub fn translate( context.unnamed_typedefs.deinit(gpa); context.typedefs.deinit(gpa); context.global_scope.deinit(); + context.pattern_list.deinit(gpa); } try context.global_scope.nodes.append(Tag.usingnamespace_builtins.init()); @@ -4829,6 +4839,217 @@ fn isZigPrimitiveType(name: []const u8) bool { return @import("AstGen.zig").simple_types.has(name); } +const PatternList = struct { + patterns: []Pattern, + + /// Templates must be function-like macros + /// first element is macro source, second element is the name of the function + /// in std.lib.zig.c_translation.Macros which implements it + const templates = [_][2][]const u8{ + [2][]const u8{ "f_SUFFIX(X) (X ## f)", "F_SUFFIX" }, + [2][]const u8{ "F_SUFFIX(X) (X ## F)", "F_SUFFIX" }, + + [2][]const u8{ "u_SUFFIX(X) (X ## u)", "U_SUFFIX" }, + [2][]const u8{ "U_SUFFIX(X) (X ## U)", "U_SUFFIX" }, + + [2][]const u8{ "l_SUFFIX(X) (X ## l)", "L_SUFFIX" }, + [2][]const u8{ "L_SUFFIX(X) (X ## L)", "L_SUFFIX" }, + + [2][]const u8{ "ul_SUFFIX(X) (X ## ul)", "UL_SUFFIX" }, + [2][]const u8{ "uL_SUFFIX(X) (X ## uL)", "UL_SUFFIX" }, + [2][]const u8{ "Ul_SUFFIX(X) (X ## Ul)", "UL_SUFFIX" }, + [2][]const u8{ "UL_SUFFIX(X) (X ## UL)", "UL_SUFFIX" }, + + [2][]const u8{ "ll_SUFFIX(X) (X ## ll)", "LL_SUFFIX" }, + [2][]const u8{ "LL_SUFFIX(X) (X ## LL)", "LL_SUFFIX" }, + + [2][]const u8{ "ull_SUFFIX(X) (X ## ull)", "ULL_SUFFIX" }, + [2][]const u8{ "uLL_SUFFIX(X) (X ## uLL)", "ULL_SUFFIX" }, + [2][]const u8{ "Ull_SUFFIX(X) (X ## Ull)", "ULL_SUFFIX" }, + [2][]const u8{ "ULL_SUFFIX(X) (X ## ULL)", "ULL_SUFFIX" }, + + [2][]const u8{ + \\wl_container_of(ptr, sample, member) \ + \\(__typeof__(sample))((char *)(ptr) - \ + \\ offsetof(__typeof__(*sample), member)) + , + "WL_CONTAINER_OF", + }, + }; + + /// Assumes that `ms` represents a tokenized function-like macro. + fn buildArgsHash(allocator: *mem.Allocator, ms: MacroSlicer, hash: *ArgsPositionMap) MacroProcessingError!void { + assert(ms.tokens.len > 2); + assert(ms.tokens[0].id == .Identifier); + assert(ms.tokens[1].id == .LParen); + + var i: usize = 2; + while (true) : (i += 1) { + const token = ms.tokens[i]; + switch (token.id) { + .RParen => break, + .Comma => continue, + .Identifier => { + const identifier = ms.slice(token); + try hash.put(allocator, identifier, i); + }, + else => return error.UnexpectedMacroToken, + } + } + } + + const Pattern = struct { + tokens: []const CToken, + source: []const u8, + impl: []const u8, + args_hash: ArgsPositionMap, + + fn init(self: *Pattern, allocator: *mem.Allocator, template: [2][]const u8) Error!void { + const source = template[0]; + const impl = template[1]; + + var tok_list = std.ArrayList(CToken).init(allocator); + defer tok_list.deinit(); + try tokenizeMacro(source, &tok_list); + const tokens = try allocator.dupe(CToken, tok_list.items); + + self.* = .{ + .tokens = tokens, + .source = source, + .impl = impl, + .args_hash = .{}, + }; + const ms = MacroSlicer{ .source = source, .tokens = tokens }; + buildArgsHash(allocator, ms, &self.args_hash) catch |err| switch (err) { + error.UnexpectedMacroToken => unreachable, + else => |e| return e, + }; + } + + fn deinit(self: *Pattern, allocator: *mem.Allocator) void { + self.args_hash.deinit(allocator); + allocator.free(self.tokens); + } + + /// This function assumes that `ms` has already been validated to contain a function-like + /// macro, and that the parsed template macro in `self` also contains a function-like + /// macro. Please review this logic carefully if changing that assumption. Two + /// function-like macros are considered equivalent if and only if they contain the same + /// list of tokens, modulo parameter names. + fn isEquivalent(self: Pattern, ms: MacroSlicer, args_hash: ArgsPositionMap) bool { + if (self.tokens.len != ms.tokens.len) return false; + if (args_hash.count() != self.args_hash.count()) return false; + + var i: usize = 2; + while (self.tokens[i].id != .RParen) : (i += 1) {} + + const pattern_slicer = MacroSlicer{ .source = self.source, .tokens = self.tokens }; + while (i < self.tokens.len) : (i += 1) { + const pattern_token = self.tokens[i]; + const macro_token = ms.tokens[i]; + if (meta.activeTag(pattern_token.id) != meta.activeTag(macro_token.id)) return false; + + const pattern_bytes = pattern_slicer.slice(pattern_token); + const macro_bytes = ms.slice(macro_token); + switch (pattern_token.id) { + .Identifier => { + const pattern_arg_index = self.args_hash.get(pattern_bytes); + const macro_arg_index = args_hash.get(macro_bytes); + + if (pattern_arg_index == null and macro_arg_index == null) { + if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; + } else if (pattern_arg_index != null and macro_arg_index != null) { + if (pattern_arg_index.? != macro_arg_index.?) return false; + } else { + return false; + } + }, + .MacroString, .StringLiteral, .CharLiteral, .IntegerLiteral, .FloatLiteral => { + if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false; + }, + else => { + // other tags correspond to keywords and operators that do not contain a "payload" + // that can vary + }, + } + } + return true; + } + }; + + fn init(allocator: *mem.Allocator) Error!PatternList { + const patterns = try allocator.alloc(Pattern, templates.len); + for (templates) |template, i| { + try patterns[i].init(allocator, template); + } + return PatternList{ .patterns = patterns }; + } + + fn deinit(self: *PatternList, allocator: *mem.Allocator) void { + for (self.patterns) |*pattern| pattern.deinit(allocator); + allocator.free(self.patterns); + } + + fn match(self: PatternList, allocator: *mem.Allocator, ms: MacroSlicer) Error!?Pattern { + var args_hash: ArgsPositionMap = .{}; + defer args_hash.deinit(allocator); + + buildArgsHash(allocator, ms, &args_hash) catch |err| switch (err) { + error.UnexpectedMacroToken => return null, + else => |e| return e, + }; + + for (self.patterns) |pattern| if (pattern.isEquivalent(ms, args_hash)) return pattern; + return null; + } +}; + +const MacroSlicer = struct { + source: []const u8, + tokens: []const CToken, + fn slice(self: MacroSlicer, token: CToken) []const u8 { + return self.source[token.start..token.end]; + } +}; + +// Testing here instead of test/translate_c.zig allows us to also test that the +// mapped function exists in `std.zig.c_translation.Macros` +test "Macro matching" { + const helper = struct { + const MacroFunctions = @import("std").zig.c_translation.Macros; + fn checkMacro(allocator: *mem.Allocator, pattern_list: PatternList, source: []const u8, comptime expected_match: ?[]const u8) !void { + var tok_list = std.ArrayList(CToken).init(allocator); + defer tok_list.deinit(); + try tokenizeMacro(source, &tok_list); + const macro_slicer = MacroSlicer{ .source = source, .tokens = tok_list.items }; + const matched = try pattern_list.match(allocator, macro_slicer); + if (expected_match) |expected| { + try testing.expectEqualStrings(expected, matched.?.impl); + try testing.expect(@hasDecl(MacroFunctions, expected)); + } else { + try testing.expectEqual(@as(@TypeOf(matched), null), matched); + } + } + }; + const allocator = std.testing.allocator; + var pattern_list = try PatternList.init(allocator); + defer pattern_list.deinit(allocator); + + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", "F_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", "U_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", "L_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", "LL_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", "UL_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", "ULL_SUFFIX"); + try helper.checkMacro(allocator, pattern_list, + \\container_of(a, b, c) \ + \\(__typeof__(b))((char *)(a) - \ + \\ offsetof(__typeof__(*b), c)) + , "WL_CONTAINER_OF"); + + try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null); +} + const MacroCtx = struct { source: []const u8, list: []const CToken, @@ -4855,8 +5076,30 @@ const MacroCtx = struct { fn fail(self: *MacroCtx, c: *Context, comptime fmt: []const u8, args: anytype) !void { return failDecl(c, self.loc, self.name, fmt, args); } + + fn makeSlicer(self: *const MacroCtx) MacroSlicer { + return MacroSlicer{ .source = self.source, .tokens = self.list }; + } }; +fn tokenizeMacro(source: []const u8, tok_list: *std.ArrayList(CToken)) Error!void { + var tokenizer = std.c.Tokenizer{ + .buffer = source, + }; + while (true) { + const tok = tokenizer.next(); + switch (tok.id) { + .Nl, .Eof => { + try tok_list.append(tok); + break; + }, + .LineComment, .MultiLineComment => continue, + else => {}, + } + try tok_list.append(tok); + } +} + fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void { // TODO if we see #undef, delete it from the table var it = unit.getLocalPreprocessingEntities_begin(); @@ -4888,21 +5131,7 @@ fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void { const slice_len = @ptrToInt(end_c) - @ptrToInt(begin_c); const slice = begin_c[0..slice_len]; - var tokenizer = std.c.Tokenizer{ - .buffer = slice, - }; - while (true) { - const tok = tokenizer.next(); - switch (tok.id) { - .Nl, .Eof => { - try tok_list.append(tok); - break; - }, - .LineComment, .MultiLineComment => continue, - else => {}, - } - try tok_list.append(tok); - } + try tokenizeMacro(slice, &tok_list); var macro_ctx = MacroCtx{ .source = slice, @@ -4960,6 +5189,16 @@ fn transMacroDefine(c: *Context, m: *MacroCtx) ParseError!void { } fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void { + const macro_slicer = m.makeSlicer(); + if (try c.pattern_list.match(c.gpa, macro_slicer)) |pattern| { + const decl = try Tag.pub_var_simple.create(c.arena, .{ + .name = m.name, + .init = try Tag.helpers_macro.create(c.arena, pattern.impl), + }); + try c.global_scope.macro_table.put(m.name, decl); + return; + } + var block_scope = try Scope.Block.init(c, &c.global_scope.base, false); defer block_scope.deinit(); const scope = &block_scope.base; diff --git a/src/translate_c/ast.zig b/src/translate_c/ast.zig index 6db59afd96..fa6b749589 100644 --- a/src/translate_c/ast.zig +++ b/src/translate_c/ast.zig @@ -193,6 +193,8 @@ pub const Node = extern union { helpers_flexible_array_type, /// @import("std").zig.c_translation.shuffleVectorIndex(lhs, rhs) helpers_shuffle_vector_index, + /// @import("std").zig.c_translation.Macro. + helpers_macro, /// @import("std").meta.Vector(lhs, rhs) std_meta_vector, /// @import("std").mem.zeroes(operand) @@ -339,6 +341,7 @@ pub const Node = extern union { .identifier, .warning, .type, + .helpers_macro, => Payload.Value, .discard => Payload.Discard, .@"if" => Payload.If, @@ -1112,6 +1115,16 @@ fn renderNode(c: *Context, node: Node) Allocator.Error!NodeIndex { .data = undefined, }); }, + .helpers_macro => { + const payload = node.castTag(.helpers_macro).?.data; + const chain = [_][]const u8{ + "zig", + "c_translation", + "Macros", + payload, + }; + return renderStdImport(c, &chain); + }, .string_slice => { const payload = node.castTag(.string_slice).?.data; @@ -2310,6 +2323,7 @@ fn renderNodeGrouped(c: *Context, node: Node) !NodeIndex { .bit_or_assign, .bit_xor_assign, .assign, + .helpers_macro, => { // these should never appear in places where grouping might be needed. unreachable; diff --git a/test/translate_c.zig b/test/translate_c.zig index 2ba42fef07..1ceaf15e5c 100644 --- a/test/translate_c.zig +++ b/test/translate_c.zig @@ -3624,4 +3624,10 @@ pub fn addCases(cases: *tests.TranslateCContext) void { , \\pub export var @"_": c_int = 42; }); + + cases.add("Macro matching", + \\#define FOO(X) (X ## U) + , &[_][]const u8{ + \\pub const FOO = @import("std").zig.c_translation.Macros.U_SUFFIX; + }); }