zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

aro_translate_c.zig (76078B) - Raw


      1 const std = @import("std");
      2 const mem = std.mem;
      3 const assert = std.debug.assert;
      4 const CallingConvention = std.builtin.CallingConvention;
      5 const aro = @import("aro");
      6 const CToken = aro.Tokenizer.Token;
      7 const Tree = aro.Tree;
      8 const NodeIndex = Tree.NodeIndex;
      9 const TokenIndex = Tree.TokenIndex;
     10 const Type = aro.Type;
     11 pub const ast = @import("aro_translate_c/ast.zig");
     12 const ZigNode = ast.Node;
     13 const ZigTag = ZigNode.Tag;
     14 const Scope = ScopeExtra(Context, Type);
     15 const Context = @This();
     16 
     17 gpa: mem.Allocator,
     18 arena: mem.Allocator,
     19 decl_table: std.AutoArrayHashMapUnmanaged(usize, []const u8) = .empty,
     20 alias_list: AliasList,
     21 global_scope: *Scope.Root,
     22 mangle_count: u32 = 0,
     23 /// Table of record decls that have been demoted to opaques.
     24 opaque_demotes: std.AutoHashMapUnmanaged(usize, void) = .empty,
     25 /// Table of unnamed enums and records that are child types of typedefs.
     26 unnamed_typedefs: std.AutoHashMapUnmanaged(usize, []const u8) = .empty,
     27 /// Needed to decide if we are parsing a typename
     28 typedefs: std.StringArrayHashMapUnmanaged(void) = .empty,
     29 
     30 /// This one is different than the root scope's name table. This contains
     31 /// a list of names that we found by visiting all the top level decls without
     32 /// translating them. The other maps are updated as we translate; this one is updated
     33 /// up front in a pre-processing step.
     34 global_names: std.StringArrayHashMapUnmanaged(void) = .empty,
     35 
     36 /// This is similar to `global_names`, but contains names which we would
     37 /// *like* to use, but do not strictly *have* to if they are unavailable.
     38 /// These are relevant to types, which ideally we would name like
     39 /// 'struct_foo' with an alias 'foo', but if either of those names is taken,
     40 /// may be mangled.
     41 /// This is distinct from `global_names` so we can detect at a type
     42 /// declaration whether or not the name is available.
     43 weak_global_names: std.StringArrayHashMapUnmanaged(void) = .empty,
     44 
     45 pattern_list: PatternList,
     46 tree: Tree,
     47 comp: *aro.Compilation,
     48 mapper: aro.TypeMapper,
     49 
     50 fn getMangle(c: *Context) u32 {
     51     c.mangle_count += 1;
     52     return c.mangle_count;
     53 }
     54 
     55 /// Convert an aro TokenIndex to a 'file:line:column' string
     56 fn locStr(c: *Context, tok_idx: TokenIndex) ![]const u8 {
     57     const token_loc = c.tree.tokens.items(.loc)[tok_idx];
     58     const source = c.comp.getSource(token_loc.id);
     59     const line_col = source.lineCol(token_loc);
     60     const filename = source.path;
     61 
     62     const line = source.physicalLine(token_loc);
     63     const col = line_col.col;
     64 
     65     return std.fmt.allocPrint(c.arena, "{s}:{d}:{d}", .{ filename, line, col });
     66 }
     67 
     68 fn maybeSuppressResult(c: *Context, used: ResultUsed, result: ZigNode) TransError!ZigNode {
     69     if (used == .used) return result;
     70     return ZigTag.discard.create(c.arena, .{ .should_skip = false, .value = result });
     71 }
     72 
     73 fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: ZigNode) !void {
     74     const gop = try c.global_scope.sym_table.getOrPut(name);
     75     if (!gop.found_existing) {
     76         gop.value_ptr.* = decl_node;
     77         try c.global_scope.nodes.append(decl_node);
     78     }
     79 }
     80 
     81 fn fail(
     82     c: *Context,
     83     err: anytype,
     84     source_loc: TokenIndex,
     85     comptime format: []const u8,
     86     args: anytype,
     87 ) (@TypeOf(err) || error{OutOfMemory}) {
     88     try warn(c, &c.global_scope.base, source_loc, format, args);
     89     return err;
     90 }
     91 
     92 fn failDecl(c: *Context, loc: TokenIndex, name: []const u8, comptime format: []const u8, args: anytype) Error!void {
     93     // location
     94     // pub const name = @compileError(msg);
     95     const fail_msg = try std.fmt.allocPrint(c.arena, format, args);
     96     try addTopLevelDecl(c, name, try ZigTag.fail_decl.create(c.arena, .{ .actual = name, .mangled = fail_msg }));
     97     const str = try c.locStr(loc);
     98     const location_comment = try std.fmt.allocPrint(c.arena, "// {s}", .{str});
     99     try c.global_scope.nodes.append(try ZigTag.warning.create(c.arena, location_comment));
    100 }
    101 
    102 fn warn(c: *Context, scope: *Scope, loc: TokenIndex, comptime format: []const u8, args: anytype) !void {
    103     const str = try c.locStr(loc);
    104     const value = try std.fmt.allocPrint(c.arena, "// {s}: warning: " ++ format, .{str} ++ args);
    105     try scope.appendNode(try ZigTag.warning.create(c.arena, value));
    106 }
    107 
    108 pub fn translate(
    109     gpa: mem.Allocator,
    110     comp: *aro.Compilation,
    111     args: []const []const u8,
    112 ) !std.zig.Ast {
    113     try comp.addDefaultPragmaHandlers();
    114     comp.langopts.setEmulatedCompiler(aro.target_util.systemCompiler(comp.target));
    115 
    116     var driver: aro.Driver = .{ .comp = comp };
    117     defer driver.deinit();
    118 
    119     var macro_buf = std.array_list.Managed(u8).init(gpa);
    120     defer macro_buf.deinit();
    121 
    122     assert(!try driver.parseArgs(std.io.null_writer, macro_buf.writer(), args));
    123     assert(driver.inputs.items.len == 1);
    124     const source = driver.inputs.items[0];
    125 
    126     const builtin_macros = try comp.generateBuiltinMacros(.include_system_defines);
    127     const user_macros = try comp.addSourceFromBuffer("<command line>", macro_buf.items);
    128 
    129     var pp = try aro.Preprocessor.initDefault(comp);
    130     defer pp.deinit();
    131 
    132     try pp.preprocessSources(&.{ source, builtin_macros, user_macros });
    133 
    134     var tree = try pp.parse();
    135     defer tree.deinit();
    136 
    137     // Workaround for https://github.com/Vexu/arocc/issues/603
    138     for (comp.diagnostics.list.items) |msg| {
    139         if (msg.kind == .@"error" or msg.kind == .@"fatal error") return error.ParsingFailed;
    140     }
    141 
    142     const mapper = tree.comp.string_interner.getFastTypeMapper(tree.comp.gpa) catch tree.comp.string_interner.getSlowTypeMapper();
    143     defer mapper.deinit(tree.comp.gpa);
    144 
    145     var arena_allocator = std.heap.ArenaAllocator.init(gpa);
    146     defer arena_allocator.deinit();
    147     const arena = arena_allocator.allocator();
    148 
    149     var context = Context{
    150         .gpa = gpa,
    151         .arena = arena,
    152         .alias_list = AliasList.init(gpa),
    153         .global_scope = try arena.create(Scope.Root),
    154         .pattern_list = try PatternList.init(gpa),
    155         .comp = comp,
    156         .mapper = mapper,
    157         .tree = tree,
    158     };
    159     context.global_scope.* = Scope.Root.init(&context);
    160     defer {
    161         context.decl_table.deinit(gpa);
    162         context.alias_list.deinit();
    163         context.global_names.deinit(gpa);
    164         context.opaque_demotes.deinit(gpa);
    165         context.unnamed_typedefs.deinit(gpa);
    166         context.typedefs.deinit(gpa);
    167         context.global_scope.deinit();
    168         context.pattern_list.deinit(gpa);
    169     }
    170 
    171     @setEvalBranchQuota(2000);
    172     inline for (@typeInfo(std.zig.c_builtins).@"struct".decls) |decl| {
    173         const builtin_fn = try ZigTag.pub_var_simple.create(arena, .{
    174             .name = decl.name,
    175             .init = try ZigTag.import_c_builtin.create(arena, decl.name),
    176         });
    177         try addTopLevelDecl(&context, decl.name, builtin_fn);
    178     }
    179 
    180     try prepopulateGlobalNameTable(&context);
    181     try transTopLevelDecls(&context);
    182 
    183     for (context.alias_list.items) |alias| {
    184         if (!context.global_scope.sym_table.contains(alias.alias)) {
    185             const node = try ZigTag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name });
    186             try addTopLevelDecl(&context, alias.alias, node);
    187         }
    188     }
    189 
    190     return ast.render(gpa, context.global_scope.nodes.items);
    191 }
    192 
    193 fn prepopulateGlobalNameTable(c: *Context) !void {
    194     const node_tags = c.tree.nodes.items(.tag);
    195     const node_types = c.tree.nodes.items(.ty);
    196     const node_data = c.tree.nodes.items(.data);
    197     for (c.tree.root_decls) |node| {
    198         const data = node_data[@intFromEnum(node)];
    199         switch (node_tags[@intFromEnum(node)]) {
    200             .typedef => {},
    201 
    202             .struct_decl_two,
    203             .union_decl_two,
    204             .struct_decl,
    205             .union_decl,
    206             .struct_forward_decl,
    207             .union_forward_decl,
    208             .enum_decl_two,
    209             .enum_decl,
    210             .enum_forward_decl,
    211             => {
    212                 const raw_ty = node_types[@intFromEnum(node)];
    213                 const ty = raw_ty.canonicalize(.standard);
    214                 const name_id = if (ty.isRecord()) ty.data.record.name else ty.data.@"enum".name;
    215                 const decl_name = c.mapper.lookup(name_id);
    216                 const container_prefix = if (ty.is(.@"struct")) "struct" else if (ty.is(.@"union")) "union" else "enum";
    217                 const prefixed_name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_prefix, decl_name });
    218                 // `decl_name` and `prefixed_name` are the preferred names for this type.
    219                 // However, we can name it anything else if necessary, so these are "weak names".
    220                 try c.weak_global_names.ensureUnusedCapacity(c.gpa, 2);
    221                 c.weak_global_names.putAssumeCapacity(decl_name, {});
    222                 c.weak_global_names.putAssumeCapacity(prefixed_name, {});
    223             },
    224 
    225             .fn_proto,
    226             .static_fn_proto,
    227             .inline_fn_proto,
    228             .inline_static_fn_proto,
    229             .fn_def,
    230             .static_fn_def,
    231             .inline_fn_def,
    232             .inline_static_fn_def,
    233             .@"var",
    234             .extern_var,
    235             .static_var,
    236             .threadlocal_var,
    237             .threadlocal_extern_var,
    238             .threadlocal_static_var,
    239             => {
    240                 const decl_name = c.tree.tokSlice(data.decl.name);
    241                 try c.global_names.put(c.gpa, decl_name, {});
    242             },
    243             .static_assert => {},
    244             else => unreachable,
    245         }
    246     }
    247 }
    248 
    249 fn transTopLevelDecls(c: *Context) !void {
    250     for (c.tree.root_decls) |node| {
    251         try transDecl(c, &c.global_scope.base, node);
    252     }
    253 }
    254 
    255 fn transDecl(c: *Context, scope: *Scope, decl: NodeIndex) !void {
    256     const node_tags = c.tree.nodes.items(.tag);
    257     const node_data = c.tree.nodes.items(.data);
    258     const node_ty = c.tree.nodes.items(.ty);
    259     const data = node_data[@intFromEnum(decl)];
    260     switch (node_tags[@intFromEnum(decl)]) {
    261         .typedef => {
    262             try transTypeDef(c, scope, decl);
    263         },
    264 
    265         .struct_decl_two,
    266         .union_decl_two,
    267         => {
    268             try transRecordDecl(c, scope, node_ty[@intFromEnum(decl)]);
    269         },
    270         .struct_decl,
    271         .union_decl,
    272         => {
    273             try transRecordDecl(c, scope, node_ty[@intFromEnum(decl)]);
    274         },
    275 
    276         .enum_decl_two => {
    277             var fields = [2]NodeIndex{ data.bin.lhs, data.bin.rhs };
    278             var field_count: u8 = 0;
    279             if (fields[0] != .none) field_count += 1;
    280             if (fields[1] != .none) field_count += 1;
    281             const enum_decl = node_ty[@intFromEnum(decl)].canonicalize(.standard).data.@"enum";
    282             try transEnumDecl(c, scope, enum_decl, fields[0..field_count]);
    283         },
    284         .enum_decl => {
    285             const fields = c.tree.data[data.range.start..data.range.end];
    286             const enum_decl = node_ty[@intFromEnum(decl)].canonicalize(.standard).data.@"enum";
    287             try transEnumDecl(c, scope, enum_decl, fields);
    288         },
    289 
    290         .enum_field_decl,
    291         .record_field_decl,
    292         .indirect_record_field_decl,
    293         .struct_forward_decl,
    294         .union_forward_decl,
    295         .enum_forward_decl,
    296         => return,
    297 
    298         .fn_proto,
    299         .static_fn_proto,
    300         .inline_fn_proto,
    301         .inline_static_fn_proto,
    302         .fn_def,
    303         .static_fn_def,
    304         .inline_fn_def,
    305         .inline_static_fn_def,
    306         => {
    307             try transFnDecl(c, decl, true);
    308         },
    309 
    310         .@"var",
    311         .extern_var,
    312         .static_var,
    313         .threadlocal_var,
    314         .threadlocal_extern_var,
    315         .threadlocal_static_var,
    316         => {
    317             try transVarDecl(c, decl);
    318         },
    319         .static_assert => try warn(c, &c.global_scope.base, 0, "ignoring _Static_assert declaration", .{}),
    320         else => unreachable,
    321     }
    322 }
    323 
    324 fn transTypeDef(c: *Context, scope: *Scope, typedef_decl: NodeIndex) Error!void {
    325     const ty = c.tree.nodes.items(.ty)[@intFromEnum(typedef_decl)];
    326     const data = c.tree.nodes.items(.data)[@intFromEnum(typedef_decl)];
    327 
    328     const toplevel = scope.id == .root;
    329     const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined;
    330 
    331     var name: []const u8 = c.tree.tokSlice(data.decl.name);
    332     try c.typedefs.put(c.gpa, name, {});
    333 
    334     if (!toplevel) name = try bs.makeMangledName(c, name);
    335 
    336     const typedef_loc = data.decl.name;
    337     const init_node = transType(c, scope, ty, .standard, typedef_loc) catch |err| switch (err) {
    338         error.UnsupportedType => {
    339             return failDecl(c, typedef_loc, name, "unable to resolve typedef child type", .{});
    340         },
    341         error.OutOfMemory => |e| return e,
    342     };
    343 
    344     const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
    345     payload.* = .{
    346         .base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(toplevel)] },
    347         .data = .{
    348             .name = name,
    349             .init = init_node,
    350         },
    351     };
    352     const node = ZigNode.initPayload(&payload.base);
    353 
    354     if (toplevel) {
    355         try addTopLevelDecl(c, name, node);
    356     } else {
    357         try scope.appendNode(node);
    358         if (node.tag() != .pub_var_simple) {
    359             try bs.discardVariable(c, name);
    360         }
    361     }
    362 }
    363 
    364 fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 {
    365     var cur_name = want_name;
    366 
    367     if (!c.weak_global_names.contains(want_name)) {
    368         // This type wasn't noticed by the name detection pass, so nothing has been treating this as
    369         // a weak global name. We must mangle it to avoid conflicts with locals.
    370         cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() });
    371     }
    372 
    373     while (c.global_names.contains(cur_name)) {
    374         cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() });
    375     }
    376     return cur_name;
    377 }
    378 
    379 fn transRecordDecl(c: *Context, scope: *Scope, record_ty: Type) Error!void {
    380     const record_decl = record_ty.getRecord().?;
    381     if (c.decl_table.get(@intFromPtr(record_decl))) |_|
    382         return; // Avoid processing this decl twice
    383     const toplevel = scope.id == .root;
    384     const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined;
    385 
    386     const container_kind: ZigTag = if (record_ty.is(.@"union")) .@"union" else .@"struct";
    387     const container_kind_name: []const u8 = @tagName(container_kind);
    388 
    389     var is_unnamed = false;
    390     var bare_name: []const u8 = c.mapper.lookup(record_decl.name);
    391     var name = bare_name;
    392 
    393     if (c.unnamed_typedefs.get(@intFromPtr(record_decl))) |typedef_name| {
    394         bare_name = typedef_name;
    395         name = typedef_name;
    396     } else {
    397         if (record_ty.isAnonymousRecord(c.comp)) {
    398             bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()});
    399             is_unnamed = true;
    400         }
    401         name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name });
    402         if (toplevel and !is_unnamed) {
    403             name = try mangleWeakGlobalName(c, name);
    404         }
    405     }
    406     if (!toplevel) name = try bs.makeMangledName(c, name);
    407     try c.decl_table.putNoClobber(c.gpa, @intFromPtr(record_decl), name);
    408 
    409     const is_pub = toplevel and !is_unnamed;
    410     const init_node = blk: {
    411         if (record_decl.isIncomplete()) {
    412             try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {});
    413             break :blk ZigTag.opaque_literal.init();
    414         }
    415 
    416         var fields = try std.array_list.Managed(ast.Payload.Record.Field).initCapacity(c.gpa, record_decl.fields.len);
    417         defer fields.deinit();
    418 
    419         // TODO: Add support for flexible array field functions
    420         var functions = std.array_list.Managed(ZigNode).init(c.gpa);
    421         defer functions.deinit();
    422 
    423         var unnamed_field_count: u32 = 0;
    424 
    425         // If a record doesn't have any attributes that would affect the alignment and
    426         // layout, then we can just use a simple `extern` type. If it does have attributes,
    427         // then we need to inspect the layout and assign an `align` value for each field.
    428         const has_alignment_attributes = record_decl.field_attributes != null or
    429             record_ty.hasAttribute(.@"packed") or
    430             record_ty.hasAttribute(.aligned);
    431         const head_field_alignment: ?c_uint = if (has_alignment_attributes) headFieldAlignment(record_decl) else null;
    432 
    433         for (record_decl.fields, 0..) |field, field_index| {
    434             const field_loc = field.name_tok;
    435 
    436             // Demote record to opaque if it contains a bitfield
    437             if (!field.isRegularField()) {
    438                 try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {});
    439                 try warn(c, scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name});
    440                 break :blk ZigTag.opaque_literal.init();
    441             }
    442 
    443             var field_name = c.mapper.lookup(field.name);
    444             if (!field.isNamed()) {
    445                 field_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{unnamed_field_count});
    446                 unnamed_field_count += 1;
    447             }
    448             const field_type = transType(c, scope, field.ty, .preserve_quals, field_loc) catch |err| switch (err) {
    449                 error.UnsupportedType => {
    450                     try c.opaque_demotes.put(c.gpa, @intFromPtr(record_decl), {});
    451                     try warn(c, scope, 0, "{s} demoted to opaque type - unable to translate type of field {s}", .{
    452                         container_kind_name,
    453                         field_name,
    454                     });
    455                     break :blk ZigTag.opaque_literal.init();
    456                 },
    457                 else => |e| return e,
    458             };
    459 
    460             const field_alignment = if (has_alignment_attributes)
    461                 alignmentForField(record_decl, head_field_alignment, field_index)
    462             else
    463                 null;
    464 
    465             // C99 introduced designated initializers for structs. Omitted fields are implicitly
    466             // initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero
    467             // values for translated struct fields permits Zig code to comfortably use such an API.
    468             const default_value = if (container_kind == .@"struct")
    469                 try ZigTag.std_mem_zeroes.create(c.arena, field_type)
    470             else
    471                 null;
    472 
    473             fields.appendAssumeCapacity(.{
    474                 .name = field_name,
    475                 .type = field_type,
    476                 .alignment = field_alignment,
    477                 .default_value = default_value,
    478             });
    479         }
    480 
    481         const record_payload = try c.arena.create(ast.Payload.Record);
    482         record_payload.* = .{
    483             .base = .{ .tag = container_kind },
    484             .data = .{
    485                 .layout = .@"extern",
    486                 .fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items),
    487                 .functions = try c.arena.dupe(ZigNode, functions.items),
    488                 .variables = &.{},
    489             },
    490         };
    491         break :blk ZigNode.initPayload(&record_payload.base);
    492     };
    493 
    494     const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
    495     payload.* = .{
    496         .base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(is_pub)] },
    497         .data = .{
    498             .name = name,
    499             .init = init_node,
    500         },
    501     };
    502     const node = ZigNode.initPayload(&payload.base);
    503     if (toplevel) {
    504         try addTopLevelDecl(c, name, node);
    505         // Only add the alias if the name is available *and* it was caught by
    506         // name detection. Don't bother performing a weak mangle, since a
    507         // mangled name is of no real use here.
    508         if (!is_unnamed and !c.global_names.contains(bare_name) and c.weak_global_names.contains(bare_name))
    509             try c.alias_list.append(.{ .alias = bare_name, .name = name });
    510     } else {
    511         try scope.appendNode(node);
    512         if (node.tag() != .pub_var_simple) {
    513             try bs.discardVariable(c, name);
    514         }
    515     }
    516 }
    517 
    518 fn transFnDecl(c: *Context, fn_decl: NodeIndex, is_pub: bool) Error!void {
    519     const raw_ty = c.tree.nodes.items(.ty)[@intFromEnum(fn_decl)];
    520     const fn_ty = raw_ty.canonicalize(.standard);
    521     const node_data = c.tree.nodes.items(.data)[@intFromEnum(fn_decl)];
    522     if (c.decl_table.get(@intFromPtr(fn_ty.data.func))) |_|
    523         return; // Avoid processing this decl twice
    524 
    525     const fn_name = c.tree.tokSlice(node_data.decl.name);
    526     if (c.global_scope.sym_table.contains(fn_name))
    527         return; // Avoid processing this decl twice
    528 
    529     const fn_decl_loc = 0; // TODO
    530     const has_body = node_data.decl.node != .none;
    531     const is_always_inline = has_body and raw_ty.getAttribute(.always_inline) != null;
    532     const proto_ctx = FnProtoContext{
    533         .fn_name = fn_name,
    534         .is_inline = is_always_inline,
    535         .is_extern = !has_body,
    536         .is_export = switch (c.tree.nodes.items(.tag)[@intFromEnum(fn_decl)]) {
    537             .fn_proto, .fn_def => has_body and !is_always_inline,
    538 
    539             .inline_fn_proto, .inline_fn_def, .inline_static_fn_proto, .inline_static_fn_def, .static_fn_proto, .static_fn_def => false,
    540 
    541             else => unreachable,
    542         },
    543         .is_pub = is_pub,
    544     };
    545 
    546     const proto_node = transFnType(c, &c.global_scope.base, raw_ty, fn_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) {
    547         error.UnsupportedType => {
    548             return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{});
    549         },
    550         error.OutOfMemory => |e| return e,
    551     };
    552 
    553     if (!has_body) {
    554         return addTopLevelDecl(c, fn_name, proto_node);
    555     }
    556     const proto_payload = proto_node.castTag(.func).?;
    557 
    558     // actual function definition with body
    559     const body_stmt = node_data.decl.node;
    560     var block_scope = try Scope.Block.init(c, &c.global_scope.base, false);
    561     block_scope.return_type = fn_ty.data.func.return_type;
    562     defer block_scope.deinit();
    563 
    564     var scope = &block_scope.base;
    565     _ = &scope;
    566 
    567     var param_id: c_uint = 0;
    568     for (proto_payload.data.params, fn_ty.data.func.params) |*param, param_info| {
    569         const param_name = param.name orelse {
    570             proto_payload.data.is_extern = true;
    571             proto_payload.data.is_export = false;
    572             proto_payload.data.is_inline = false;
    573             try warn(c, &c.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name});
    574             return addTopLevelDecl(c, fn_name, proto_node);
    575         };
    576 
    577         const is_const = param_info.ty.qual.@"const";
    578 
    579         const mangled_param_name = try block_scope.makeMangledName(c, param_name);
    580         param.name = mangled_param_name;
    581 
    582         if (!is_const) {
    583             const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{s}", .{mangled_param_name});
    584             const arg_name = try block_scope.makeMangledName(c, bare_arg_name);
    585             param.name = arg_name;
    586 
    587             const redecl_node = try ZigTag.arg_redecl.create(c.arena, .{ .actual = mangled_param_name, .mangled = arg_name });
    588             try block_scope.statements.append(redecl_node);
    589         }
    590         try block_scope.discardVariable(c, mangled_param_name);
    591 
    592         param_id += 1;
    593     }
    594 
    595     transCompoundStmtInline(c, body_stmt, &block_scope) catch |err| switch (err) {
    596         error.OutOfMemory => |e| return e,
    597         error.UnsupportedTranslation,
    598         error.UnsupportedType,
    599         => {
    600             proto_payload.data.is_extern = true;
    601             proto_payload.data.is_export = false;
    602             proto_payload.data.is_inline = false;
    603             try warn(c, &c.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{});
    604             return addTopLevelDecl(c, fn_name, proto_node);
    605         },
    606     };
    607 
    608     proto_payload.data.body = try block_scope.complete(c);
    609     return addTopLevelDecl(c, fn_name, proto_node);
    610 }
    611 
    612 fn transVarDecl(c: *Context, node: NodeIndex) Error!void {
    613     const data = c.tree.nodes.items(.data)[@intFromEnum(node)];
    614     const name = c.tree.tokSlice(data.decl.name);
    615     return failDecl(c, data.decl.name, name, "unable to translate variable declaration", .{});
    616 }
    617 
    618 fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const Type.Enum, field_nodes: []const NodeIndex) Error!void {
    619     if (c.decl_table.get(@intFromPtr(enum_decl))) |_|
    620         return; // Avoid processing this decl twice
    621     const toplevel = scope.id == .root;
    622     const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined;
    623 
    624     var is_unnamed = false;
    625     var bare_name: []const u8 = c.mapper.lookup(enum_decl.name);
    626     var name = bare_name;
    627     if (c.unnamed_typedefs.get(@intFromPtr(enum_decl))) |typedef_name| {
    628         bare_name = typedef_name;
    629         name = typedef_name;
    630     } else {
    631         if (bare_name.len == 0) {
    632             bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()});
    633             is_unnamed = true;
    634         }
    635         name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name});
    636     }
    637     if (!toplevel) name = try bs.makeMangledName(c, name);
    638     try c.decl_table.putNoClobber(c.gpa, @intFromPtr(enum_decl), name);
    639 
    640     const enum_type_node = if (!enum_decl.isIncomplete()) blk: {
    641         for (enum_decl.fields, field_nodes) |field, field_node| {
    642             var enum_val_name: []const u8 = c.mapper.lookup(field.name);
    643             if (!toplevel) {
    644                 enum_val_name = try bs.makeMangledName(c, enum_val_name);
    645             }
    646 
    647             const enum_const_type_node: ?ZigNode = transType(c, scope, field.ty, .standard, field.name_tok) catch |err| switch (err) {
    648                 error.UnsupportedType => null,
    649                 else => |e| return e,
    650             };
    651 
    652             const val = c.tree.value_map.get(field_node).?;
    653             const enum_const_def = try ZigTag.enum_constant.create(c.arena, .{
    654                 .name = enum_val_name,
    655                 .is_public = toplevel,
    656                 .type = enum_const_type_node,
    657                 .value = try transCreateNodeAPInt(c, val),
    658             });
    659             if (toplevel)
    660                 try addTopLevelDecl(c, enum_val_name, enum_const_def)
    661             else {
    662                 try scope.appendNode(enum_const_def);
    663                 try bs.discardVariable(c, enum_val_name);
    664             }
    665         }
    666 
    667         break :blk transType(c, scope, enum_decl.tag_ty, .standard, 0) catch |err| switch (err) {
    668             error.UnsupportedType => {
    669                 return failDecl(c, 0, name, "unable to translate enum integer type", .{});
    670             },
    671             else => |e| return e,
    672         };
    673     } else blk: {
    674         try c.opaque_demotes.put(c.gpa, @intFromPtr(enum_decl), {});
    675         break :blk ZigTag.opaque_literal.init();
    676     };
    677 
    678     const is_pub = toplevel and !is_unnamed;
    679     const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
    680     payload.* = .{
    681         .base = .{ .tag = ([2]ZigTag{ .var_simple, .pub_var_simple })[@intFromBool(is_pub)] },
    682         .data = .{
    683             .init = enum_type_node,
    684             .name = name,
    685         },
    686     };
    687     const node = ZigNode.initPayload(&payload.base);
    688     if (toplevel) {
    689         try addTopLevelDecl(c, name, node);
    690         if (!is_unnamed)
    691             try c.alias_list.append(.{ .alias = bare_name, .name = name });
    692     } else {
    693         try scope.appendNode(node);
    694         if (node.tag() != .pub_var_simple) {
    695             try bs.discardVariable(c, name);
    696         }
    697     }
    698 }
    699 
    700 fn getTypeStr(c: *Context, ty: Type) ![]const u8 {
    701     var buf: std.ArrayListUnmanaged(u8) = .empty;
    702     defer buf.deinit(c.gpa);
    703     const w = buf.writer(c.gpa);
    704     try ty.print(c.mapper, c.comp.langopts, w);
    705     return c.arena.dupe(u8, buf.items);
    706 }
    707 
    708 fn transType(c: *Context, scope: *Scope, raw_ty: Type, qual_handling: Type.QualHandling, source_loc: TokenIndex) TypeError!ZigNode {
    709     const ty = raw_ty.canonicalize(qual_handling);
    710     if (ty.qual.atomic) {
    711         const type_name = try getTypeStr(c, ty);
    712         return fail(c, error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{type_name});
    713     }
    714 
    715     switch (ty.specifier) {
    716         .void => return ZigTag.type.create(c.arena, "anyopaque"),
    717         .bool => return ZigTag.type.create(c.arena, "bool"),
    718         .char => return ZigTag.type.create(c.arena, "c_char"),
    719         .schar => return ZigTag.type.create(c.arena, "i8"),
    720         .uchar => return ZigTag.type.create(c.arena, "u8"),
    721         .short => return ZigTag.type.create(c.arena, "c_short"),
    722         .ushort => return ZigTag.type.create(c.arena, "c_ushort"),
    723         .int => return ZigTag.type.create(c.arena, "c_int"),
    724         .uint => return ZigTag.type.create(c.arena, "c_uint"),
    725         .long => return ZigTag.type.create(c.arena, "c_long"),
    726         .ulong => return ZigTag.type.create(c.arena, "c_ulong"),
    727         .long_long => return ZigTag.type.create(c.arena, "c_longlong"),
    728         .ulong_long => return ZigTag.type.create(c.arena, "c_ulonglong"),
    729         .int128 => return ZigTag.type.create(c.arena, "i128"),
    730         .uint128 => return ZigTag.type.create(c.arena, "u128"),
    731         .fp16, .float16 => return ZigTag.type.create(c.arena, "f16"),
    732         .float => return ZigTag.type.create(c.arena, "f32"),
    733         .double => return ZigTag.type.create(c.arena, "f64"),
    734         .long_double => return ZigTag.type.create(c.arena, "c_longdouble"),
    735         .float128 => return ZigTag.type.create(c.arena, "f128"),
    736         .@"enum" => {
    737             const enum_decl = ty.data.@"enum";
    738             var trans_scope = scope;
    739             if (enum_decl.name != .empty) {
    740                 const decl_name = c.mapper.lookup(enum_decl.name);
    741                 if (c.weak_global_names.contains(decl_name)) trans_scope = &c.global_scope.base;
    742             }
    743             try transEnumDecl(c, trans_scope, enum_decl, &.{});
    744             return ZigTag.identifier.create(c.arena, c.decl_table.get(@intFromPtr(enum_decl)).?);
    745         },
    746         .pointer => {
    747             const child_type = ty.elemType();
    748 
    749             const is_fn_proto = child_type.isFunc();
    750             const is_const = is_fn_proto or child_type.isConst();
    751             const is_volatile = child_type.qual.@"volatile";
    752             const elem_type = try transType(c, scope, child_type, qual_handling, source_loc);
    753             const ptr_info: @FieldType(ast.Payload.Pointer, "data") = .{
    754                 .is_const = is_const,
    755                 .is_volatile = is_volatile,
    756                 .elem_type = elem_type,
    757             };
    758             if (is_fn_proto or
    759                 typeIsOpaque(c, child_type) or
    760                 typeWasDemotedToOpaque(c, child_type))
    761             {
    762                 const ptr = try ZigTag.single_pointer.create(c.arena, ptr_info);
    763                 return ZigTag.optional_type.create(c.arena, ptr);
    764             }
    765 
    766             return ZigTag.c_pointer.create(c.arena, ptr_info);
    767         },
    768         .unspecified_variable_len_array, .incomplete_array => {
    769             const child_type = ty.elemType();
    770             const is_const = child_type.qual.@"const";
    771             const is_volatile = child_type.qual.@"volatile";
    772             const elem_type = try transType(c, scope, child_type, qual_handling, source_loc);
    773 
    774             return ZigTag.c_pointer.create(c.arena, .{ .is_const = is_const, .is_volatile = is_volatile, .elem_type = elem_type });
    775         },
    776         .array,
    777         .static_array,
    778         => {
    779             const size = ty.arrayLen().?;
    780             const elem_type = try transType(c, scope, ty.elemType(), qual_handling, source_loc);
    781             return ZigTag.array_type.create(c.arena, .{ .len = size, .elem_type = elem_type });
    782         },
    783         .func,
    784         .var_args_func,
    785         .old_style_func,
    786         => return transFnType(c, scope, ty, ty, source_loc, .{}),
    787         .@"struct",
    788         .@"union",
    789         => {
    790             var trans_scope = scope;
    791             if (ty.isAnonymousRecord(c.comp)) {
    792                 const record_decl = ty.data.record;
    793                 const name_id = c.mapper.lookup(record_decl.name);
    794                 if (c.weak_global_names.contains(name_id)) trans_scope = &c.global_scope.base;
    795             }
    796             try transRecordDecl(c, trans_scope, ty);
    797             const name = c.decl_table.get(@intFromPtr(ty.data.record)).?;
    798             return ZigTag.identifier.create(c.arena, name);
    799         },
    800         .attributed,
    801         .typeof_type,
    802         .typeof_expr,
    803         => unreachable,
    804         else => return error.UnsupportedType,
    805     }
    806 }
    807 
    808 /// Look ahead through the fields of the record to determine what the alignment of the record
    809 /// would be without any align/packed/etc. attributes. This helps us determine whether or not
    810 /// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just
    811 /// pedantically assign those fields the same alignment as the parent's pointer alignment,
    812 /// but this helps the generated code to be a little less verbose.
    813 fn headFieldAlignment(record_decl: *const Type.Record) ?c_uint {
    814     const bits_per_byte = 8;
    815     const parent_ptr_alignment_bits = record_decl.type_layout.pointer_alignment_bits;
    816     const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte;
    817     var max_field_alignment_bits: u64 = 0;
    818     for (record_decl.fields) |field| {
    819         if (field.ty.getRecord()) |field_record_decl| {
    820             const child_record_alignment = field_record_decl.type_layout.field_alignment_bits;
    821             if (child_record_alignment > max_field_alignment_bits)
    822                 max_field_alignment_bits = child_record_alignment;
    823         } else {
    824             const field_size = field.layout.size_bits;
    825             if (field_size > max_field_alignment_bits)
    826                 max_field_alignment_bits = field_size;
    827         }
    828     }
    829     if (max_field_alignment_bits != parent_ptr_alignment_bits) {
    830         return parent_ptr_alignment;
    831     } else {
    832         return null;
    833     }
    834 }
    835 
    836 /// This function inspects the generated layout of a record to determine the alignment for a
    837 /// particular field. This approach is necessary because unlike Zig, a C compiler is not
    838 /// required to fulfill the requested alignment, which means we'd risk generating different code
    839 /// if we only look at the user-requested alignment.
    840 ///
    841 /// Returns a ?c_uint to match Clang's behaviour of using c_uint. The return type can be changed
    842 /// after the Clang frontend for translate-c is removed. A null value indicates that a field is
    843 /// 'naturally aligned'.
    844 fn alignmentForField(
    845     record_decl: *const Type.Record,
    846     head_field_alignment: ?c_uint,
    847     field_index: usize,
    848 ) ?c_uint {
    849     const fields = record_decl.fields;
    850     assert(fields.len != 0);
    851     const field = fields[field_index];
    852 
    853     const bits_per_byte = 8;
    854     const parent_ptr_alignment_bits = record_decl.type_layout.pointer_alignment_bits;
    855     const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte;
    856 
    857     // bitfields aren't supported yet. Until support is added, records with bitfields
    858     // should be demoted to opaque, and this function shouldn't be called for them.
    859     if (!field.isRegularField()) {
    860         @panic("TODO: add bitfield support for records");
    861     }
    862 
    863     const field_offset_bits: u64 = field.layout.offset_bits;
    864     const field_size_bits: u64 = field.layout.size_bits;
    865 
    866     // Fields with zero width always have an alignment of 1
    867     if (field_size_bits == 0) {
    868         return 1;
    869     }
    870 
    871     // Fields with 0 offset inherit the parent's pointer alignment.
    872     if (field_offset_bits == 0) {
    873         return head_field_alignment;
    874     }
    875 
    876     // Records have a natural alignment when used as a field, and their size is
    877     // a multiple of this alignment value. For all other types, the natural alignment
    878     // is their size.
    879     const field_natural_alignment_bits: u64 = if (field.ty.getRecord()) |record| record.type_layout.field_alignment_bits else field_size_bits;
    880     const rem_bits = field_offset_bits % field_natural_alignment_bits;
    881 
    882     // If there's a remainder, then the alignment is smaller than the field's
    883     // natural alignment
    884     if (rem_bits > 0) {
    885         const rem_alignment = rem_bits / bits_per_byte;
    886         if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) {
    887             const actual_alignment = @min(rem_alignment, parent_ptr_alignment);
    888             return @as(c_uint, @truncate(actual_alignment));
    889         } else {
    890             return 1;
    891         }
    892     }
    893 
    894     // A field may have an offset which positions it to be naturally aligned, but the
    895     // parent's pointer alignment determines if this is actually true, so we take the minimum
    896     // value.
    897     // For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural
    898     // alignment, but if the parent pointer alignment is 2, then the actual alignment of the
    899     // float is 2.
    900     const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte;
    901     const offset_alignment = field_offset_bits / bits_per_byte;
    902     const possible_alignment = @min(parent_ptr_alignment, offset_alignment);
    903     if (possible_alignment == field_natural_alignment) {
    904         return null;
    905     } else if (possible_alignment < field_natural_alignment) {
    906         if (std.math.isPowerOfTwo(possible_alignment)) {
    907             return possible_alignment;
    908         } else {
    909             return 1;
    910         }
    911     } else { // possible_alignment > field_natural_alignment
    912         // Here, the field is positioned be at a higher alignment than it's natural alignment. This means we
    913         // need to determine whether it's a specified alignment. We can determine that from the padding preceding
    914         // the field.
    915         const padding_from_prev_field: u64 = blk: {
    916             if (field_offset_bits != 0) {
    917                 const previous_field = fields[field_index - 1];
    918                 break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits;
    919             } else {
    920                 break :blk 0;
    921             }
    922         };
    923         if (padding_from_prev_field < field_natural_alignment_bits) {
    924             return null;
    925         } else {
    926             return possible_alignment;
    927         }
    928     }
    929 }
    930 
    931 const FnProtoContext = struct {
    932     is_pub: bool = false,
    933     is_export: bool = false,
    934     is_extern: bool = false,
    935     is_inline: bool = false,
    936     fn_name: ?[]const u8 = null,
    937 };
    938 
    939 fn transFnType(
    940     c: *Context,
    941     scope: *Scope,
    942     raw_ty: Type,
    943     fn_ty: Type,
    944     source_loc: TokenIndex,
    945     ctx: FnProtoContext,
    946 ) !ZigNode {
    947     const param_count: usize = fn_ty.data.func.params.len;
    948     const fn_params = try c.arena.alloc(ast.Payload.Param, param_count);
    949 
    950     for (fn_ty.data.func.params, fn_params) |param_info, *param_node| {
    951         const param_ty = param_info.ty;
    952         const is_noalias = param_ty.qual.restrict;
    953 
    954         const param_name: ?[]const u8 = if (param_info.name == .empty)
    955             null
    956         else
    957             c.mapper.lookup(param_info.name);
    958 
    959         const type_node = try transType(c, scope, param_ty, .standard, param_info.name_tok);
    960         param_node.* = .{
    961             .is_noalias = is_noalias,
    962             .name = param_name,
    963             .type = type_node,
    964         };
    965     }
    966 
    967     const linksection_string = blk: {
    968         if (raw_ty.getAttribute(.section)) |section| {
    969             break :blk c.comp.interner.get(section.name.ref()).bytes;
    970         }
    971         break :blk null;
    972     };
    973 
    974     const alignment: ?c_uint = raw_ty.requestedAlignment(c.comp) orelse null;
    975 
    976     const explicit_callconv = null;
    977     // const explicit_callconv = if ((ctx.is_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .C) null else ctx.cc;
    978 
    979     const return_type_node = blk: {
    980         if (raw_ty.getAttribute(.noreturn) != null) {
    981             break :blk ZigTag.noreturn_type.init();
    982         } else {
    983             const return_ty = fn_ty.data.func.return_type;
    984             if (return_ty.is(.void)) {
    985                 // convert primitive anyopaque to actual void (only for return type)
    986                 break :blk ZigTag.void_type.init();
    987             } else {
    988                 break :blk transType(c, scope, return_ty, .standard, source_loc) catch |err| switch (err) {
    989                     error.UnsupportedType => {
    990                         try warn(c, scope, source_loc, "unsupported function proto return type", .{});
    991                         return err;
    992                     },
    993                     error.OutOfMemory => |e| return e,
    994                 };
    995             }
    996         }
    997     };
    998 
    999     const payload = try c.arena.create(ast.Payload.Func);
   1000     payload.* = .{
   1001         .base = .{ .tag = .func },
   1002         .data = .{
   1003             .is_pub = ctx.is_pub,
   1004             .is_extern = ctx.is_extern,
   1005             .is_export = ctx.is_export,
   1006             .is_inline = ctx.is_inline,
   1007             .is_var_args = switch (fn_ty.specifier) {
   1008                 .func => false,
   1009                 .var_args_func => true,
   1010                 .old_style_func => !ctx.is_export and !ctx.is_inline,
   1011                 else => unreachable,
   1012             },
   1013             .name = ctx.fn_name,
   1014             .linksection_string = linksection_string,
   1015             .explicit_callconv = explicit_callconv,
   1016             .params = fn_params,
   1017             .return_type = return_type_node,
   1018             .body = null,
   1019             .alignment = alignment,
   1020         },
   1021     };
   1022     return ZigNode.initPayload(&payload.base);
   1023 }
   1024 
   1025 fn transStmt(c: *Context, node: NodeIndex) TransError!ZigNode {
   1026     _ = c;
   1027     _ = node;
   1028     return error.UnsupportedTranslation;
   1029 }
   1030 
   1031 fn transCompoundStmtInline(c: *Context, compound: NodeIndex, block: *Scope.Block) TransError!void {
   1032     const data = c.tree.nodes.items(.data)[@intFromEnum(compound)];
   1033     var buf: [2]NodeIndex = undefined;
   1034     // TODO move these helpers to Aro
   1035     const stmts = switch (c.tree.nodes.items(.tag)[@intFromEnum(compound)]) {
   1036         .compound_stmt_two => blk: {
   1037             if (data.bin.lhs != .none) buf[0] = data.bin.lhs;
   1038             if (data.bin.rhs != .none) buf[1] = data.bin.rhs;
   1039             break :blk buf[0 .. @as(u32, @intFromBool(data.bin.lhs != .none)) + @intFromBool(data.bin.rhs != .none)];
   1040         },
   1041         .compound_stmt => c.tree.data[data.range.start..data.range.end],
   1042         else => unreachable,
   1043     };
   1044     for (stmts) |stmt| {
   1045         const result = try transStmt(c, stmt);
   1046         switch (result.tag()) {
   1047             .declaration, .empty_block => {},
   1048             else => try block.statements.append(result),
   1049         }
   1050     }
   1051 }
   1052 
   1053 fn recordHasBitfield(record: *const Type.Record) bool {
   1054     if (record.isIncomplete()) return false;
   1055     for (record.fields) |field| {
   1056         if (!field.isRegularField()) return true;
   1057     }
   1058     return false;
   1059 }
   1060 
   1061 fn typeIsOpaque(c: *Context, ty: Type) bool {
   1062     return switch (ty.specifier) {
   1063         .void => true,
   1064         .@"struct", .@"union" => recordHasBitfield(ty.getRecord().?),
   1065         .typeof_type => typeIsOpaque(c, ty.data.sub_type.*),
   1066         .typeof_expr => typeIsOpaque(c, ty.data.expr.ty),
   1067         .attributed => typeIsOpaque(c, ty.data.attributed.base),
   1068         else => false,
   1069     };
   1070 }
   1071 
   1072 fn typeWasDemotedToOpaque(c: *Context, ty: Type) bool {
   1073     switch (ty.specifier) {
   1074         .@"struct", .@"union" => {
   1075             const record = ty.getRecord().?;
   1076             if (c.opaque_demotes.contains(@intFromPtr(record))) return true;
   1077             for (record.fields) |field| {
   1078                 if (typeWasDemotedToOpaque(c, field.ty)) return true;
   1079             }
   1080             return false;
   1081         },
   1082 
   1083         .@"enum" => return c.opaque_demotes.contains(@intFromPtr(ty.data.@"enum")),
   1084 
   1085         .typeof_type => return typeWasDemotedToOpaque(c, ty.data.sub_type.*),
   1086         .typeof_expr => return typeWasDemotedToOpaque(c, ty.data.expr.ty),
   1087         .attributed => return typeWasDemotedToOpaque(c, ty.data.attributed.base),
   1088         else => return false,
   1089     }
   1090 }
   1091 
   1092 fn transCompoundStmt(c: *Context, scope: *Scope, compound: NodeIndex) TransError!ZigNode {
   1093     var block_scope = try Scope.Block.init(c, scope, false);
   1094     defer block_scope.deinit();
   1095     try transCompoundStmtInline(c, compound, &block_scope);
   1096     return try block_scope.complete(c);
   1097 }
   1098 
   1099 fn transExpr(c: *Context, node: NodeIndex, result_used: ResultUsed) TransError!ZigNode {
   1100     std.debug.assert(node != .none);
   1101     const ty = c.tree.nodes.items(.ty)[@intFromEnum(node)];
   1102     if (c.tree.value_map.get(node)) |val| {
   1103         // TODO handle other values
   1104         const int = try transCreateNodeAPInt(c, val);
   1105         const as_node = try ZigTag.as.create(c.arena, .{
   1106             .lhs = try transType(c, undefined, ty, .standard, undefined),
   1107             .rhs = int,
   1108         });
   1109         return maybeSuppressResult(c, result_used, as_node);
   1110     }
   1111     const node_tags = c.tree.nodes.items(.tag);
   1112     switch (node_tags[@intFromEnum(node)]) {
   1113         else => unreachable, // Not an expression.
   1114     }
   1115     return .none;
   1116 }
   1117 
   1118 fn transCreateNodeAPInt(c: *Context, int: aro.Value) !ZigNode {
   1119     var space: aro.Interner.Tag.Int.BigIntSpace = undefined;
   1120     var big = int.toBigInt(&space, c.comp);
   1121     const is_negative = !big.positive;
   1122     big.positive = true;
   1123 
   1124     const str = big.toStringAlloc(c.arena, 10, .lower) catch |err| switch (err) {
   1125         error.OutOfMemory => return error.OutOfMemory,
   1126     };
   1127     const res = try ZigTag.integer_literal.create(c.arena, str);
   1128     if (is_negative) return ZigTag.negate.create(c.arena, res);
   1129     return res;
   1130 }
   1131 
   1132 pub const PatternList = struct {
   1133     patterns: []Pattern,
   1134 
   1135     /// Templates must be function-like macros
   1136     /// first element is macro source, second element is the name of the function
   1137     /// in std.lib.zig.c_translation.Macros which implements it
   1138     const templates = [_][2][]const u8{
   1139         [2][]const u8{ "f_SUFFIX(X) (X ## f)", "F_SUFFIX" },
   1140         [2][]const u8{ "F_SUFFIX(X) (X ## F)", "F_SUFFIX" },
   1141 
   1142         [2][]const u8{ "u_SUFFIX(X) (X ## u)", "U_SUFFIX" },
   1143         [2][]const u8{ "U_SUFFIX(X) (X ## U)", "U_SUFFIX" },
   1144 
   1145         [2][]const u8{ "l_SUFFIX(X) (X ## l)", "L_SUFFIX" },
   1146         [2][]const u8{ "L_SUFFIX(X) (X ## L)", "L_SUFFIX" },
   1147 
   1148         [2][]const u8{ "ul_SUFFIX(X) (X ## ul)", "UL_SUFFIX" },
   1149         [2][]const u8{ "uL_SUFFIX(X) (X ## uL)", "UL_SUFFIX" },
   1150         [2][]const u8{ "Ul_SUFFIX(X) (X ## Ul)", "UL_SUFFIX" },
   1151         [2][]const u8{ "UL_SUFFIX(X) (X ## UL)", "UL_SUFFIX" },
   1152 
   1153         [2][]const u8{ "ll_SUFFIX(X) (X ## ll)", "LL_SUFFIX" },
   1154         [2][]const u8{ "LL_SUFFIX(X) (X ## LL)", "LL_SUFFIX" },
   1155 
   1156         [2][]const u8{ "ull_SUFFIX(X) (X ## ull)", "ULL_SUFFIX" },
   1157         [2][]const u8{ "uLL_SUFFIX(X) (X ## uLL)", "ULL_SUFFIX" },
   1158         [2][]const u8{ "Ull_SUFFIX(X) (X ## Ull)", "ULL_SUFFIX" },
   1159         [2][]const u8{ "ULL_SUFFIX(X) (X ## ULL)", "ULL_SUFFIX" },
   1160 
   1161         [2][]const u8{ "f_SUFFIX(X) X ## f", "F_SUFFIX" },
   1162         [2][]const u8{ "F_SUFFIX(X) X ## F", "F_SUFFIX" },
   1163 
   1164         [2][]const u8{ "u_SUFFIX(X) X ## u", "U_SUFFIX" },
   1165         [2][]const u8{ "U_SUFFIX(X) X ## U", "U_SUFFIX" },
   1166 
   1167         [2][]const u8{ "l_SUFFIX(X) X ## l", "L_SUFFIX" },
   1168         [2][]const u8{ "L_SUFFIX(X) X ## L", "L_SUFFIX" },
   1169 
   1170         [2][]const u8{ "ul_SUFFIX(X) X ## ul", "UL_SUFFIX" },
   1171         [2][]const u8{ "uL_SUFFIX(X) X ## uL", "UL_SUFFIX" },
   1172         [2][]const u8{ "Ul_SUFFIX(X) X ## Ul", "UL_SUFFIX" },
   1173         [2][]const u8{ "UL_SUFFIX(X) X ## UL", "UL_SUFFIX" },
   1174 
   1175         [2][]const u8{ "ll_SUFFIX(X) X ## ll", "LL_SUFFIX" },
   1176         [2][]const u8{ "LL_SUFFIX(X) X ## LL", "LL_SUFFIX" },
   1177 
   1178         [2][]const u8{ "ull_SUFFIX(X) X ## ull", "ULL_SUFFIX" },
   1179         [2][]const u8{ "uLL_SUFFIX(X) X ## uLL", "ULL_SUFFIX" },
   1180         [2][]const u8{ "Ull_SUFFIX(X) X ## Ull", "ULL_SUFFIX" },
   1181         [2][]const u8{ "ULL_SUFFIX(X) X ## ULL", "ULL_SUFFIX" },
   1182 
   1183         [2][]const u8{ "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL" },
   1184         [2][]const u8{ "CAST_OR_CALL(X, Y) ((X)(Y))", "CAST_OR_CALL" },
   1185 
   1186         [2][]const u8{
   1187             \\wl_container_of(ptr, sample, member)                     \
   1188             \\(__typeof__(sample))((char *)(ptr) -                     \
   1189             \\     offsetof(__typeof__(*sample), member))
   1190             ,
   1191             "WL_CONTAINER_OF",
   1192         },
   1193 
   1194         [2][]const u8{ "IGNORE_ME(X) ((void)(X))", "DISCARD" },
   1195         [2][]const u8{ "IGNORE_ME(X) (void)(X)", "DISCARD" },
   1196         [2][]const u8{ "IGNORE_ME(X) ((const void)(X))", "DISCARD" },
   1197         [2][]const u8{ "IGNORE_ME(X) (const void)(X)", "DISCARD" },
   1198         [2][]const u8{ "IGNORE_ME(X) ((volatile void)(X))", "DISCARD" },
   1199         [2][]const u8{ "IGNORE_ME(X) (volatile void)(X)", "DISCARD" },
   1200         [2][]const u8{ "IGNORE_ME(X) ((const volatile void)(X))", "DISCARD" },
   1201         [2][]const u8{ "IGNORE_ME(X) (const volatile void)(X)", "DISCARD" },
   1202         [2][]const u8{ "IGNORE_ME(X) ((volatile const void)(X))", "DISCARD" },
   1203         [2][]const u8{ "IGNORE_ME(X) (volatile const void)(X)", "DISCARD" },
   1204     };
   1205 
   1206     /// Assumes that `ms` represents a tokenized function-like macro.
   1207     fn buildArgsHash(allocator: mem.Allocator, ms: MacroSlicer, hash: *ArgsPositionMap) MacroProcessingError!void {
   1208         assert(ms.tokens.len > 2);
   1209         assert(ms.tokens[0].id.isMacroIdentifier());
   1210         assert(ms.tokens[1].id == .l_paren);
   1211 
   1212         var i: usize = 2;
   1213         while (true) : (i += 1) {
   1214             const token = ms.tokens[i];
   1215             switch (token.id) {
   1216                 .r_paren => break,
   1217                 .comma => continue,
   1218                 .identifier, .extended_identifier => {
   1219                     const identifier = ms.slice(token);
   1220                     try hash.put(allocator, identifier, i);
   1221                 },
   1222                 else => return error.UnexpectedMacroToken,
   1223             }
   1224         }
   1225     }
   1226 
   1227     const Pattern = struct {
   1228         tokens: []const CToken,
   1229         source: []const u8,
   1230         impl: []const u8,
   1231         args_hash: ArgsPositionMap,
   1232 
   1233         fn init(self: *Pattern, allocator: mem.Allocator, template: [2][]const u8) Error!void {
   1234             const source = template[0];
   1235             const impl = template[1];
   1236 
   1237             var tok_list = std.array_list.Managed(CToken).init(allocator);
   1238             defer tok_list.deinit();
   1239             try tokenizeMacro(source, &tok_list);
   1240             const tokens = try allocator.dupe(CToken, tok_list.items);
   1241 
   1242             self.* = .{
   1243                 .tokens = tokens,
   1244                 .source = source,
   1245                 .impl = impl,
   1246                 .args_hash = .{},
   1247             };
   1248             const ms = MacroSlicer{ .source = source, .tokens = tokens };
   1249             buildArgsHash(allocator, ms, &self.args_hash) catch |err| switch (err) {
   1250                 error.UnexpectedMacroToken => unreachable,
   1251                 else => |e| return e,
   1252             };
   1253         }
   1254 
   1255         fn deinit(self: *Pattern, allocator: mem.Allocator) void {
   1256             self.args_hash.deinit(allocator);
   1257             allocator.free(self.tokens);
   1258         }
   1259 
   1260         /// This function assumes that `ms` has already been validated to contain a function-like
   1261         /// macro, and that the parsed template macro in `self` also contains a function-like
   1262         /// macro. Please review this logic carefully if changing that assumption. Two
   1263         /// function-like macros are considered equivalent if and only if they contain the same
   1264         /// list of tokens, modulo parameter names.
   1265         pub fn isEquivalent(self: Pattern, ms: MacroSlicer, args_hash: ArgsPositionMap) bool {
   1266             if (self.tokens.len != ms.tokens.len) return false;
   1267             if (args_hash.count() != self.args_hash.count()) return false;
   1268 
   1269             var i: usize = 2;
   1270             while (self.tokens[i].id != .r_paren) : (i += 1) {}
   1271 
   1272             const pattern_slicer = MacroSlicer{ .source = self.source, .tokens = self.tokens };
   1273             while (i < self.tokens.len) : (i += 1) {
   1274                 const pattern_token = self.tokens[i];
   1275                 const macro_token = ms.tokens[i];
   1276                 if (pattern_token.id != macro_token.id) return false;
   1277 
   1278                 const pattern_bytes = pattern_slicer.slice(pattern_token);
   1279                 const macro_bytes = ms.slice(macro_token);
   1280                 switch (pattern_token.id) {
   1281                     .identifier, .extended_identifier => {
   1282                         const pattern_arg_index = self.args_hash.get(pattern_bytes);
   1283                         const macro_arg_index = args_hash.get(macro_bytes);
   1284 
   1285                         if (pattern_arg_index == null and macro_arg_index == null) {
   1286                             if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false;
   1287                         } else if (pattern_arg_index != null and macro_arg_index != null) {
   1288                             if (pattern_arg_index.? != macro_arg_index.?) return false;
   1289                         } else {
   1290                             return false;
   1291                         }
   1292                     },
   1293                     .string_literal, .char_literal, .pp_num => {
   1294                         if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false;
   1295                     },
   1296                     else => {
   1297                         // other tags correspond to keywords and operators that do not contain a "payload"
   1298                         // that can vary
   1299                     },
   1300                 }
   1301             }
   1302             return true;
   1303         }
   1304     };
   1305 
   1306     pub fn init(allocator: mem.Allocator) Error!PatternList {
   1307         const patterns = try allocator.alloc(Pattern, templates.len);
   1308         for (templates, 0..) |template, i| {
   1309             try patterns[i].init(allocator, template);
   1310         }
   1311         return PatternList{ .patterns = patterns };
   1312     }
   1313 
   1314     pub fn deinit(self: *PatternList, allocator: mem.Allocator) void {
   1315         for (self.patterns) |*pattern| pattern.deinit(allocator);
   1316         allocator.free(self.patterns);
   1317     }
   1318 
   1319     pub fn match(self: PatternList, allocator: mem.Allocator, ms: MacroSlicer) Error!?Pattern {
   1320         var args_hash: ArgsPositionMap = .{};
   1321         defer args_hash.deinit(allocator);
   1322 
   1323         buildArgsHash(allocator, ms, &args_hash) catch |err| switch (err) {
   1324             error.UnexpectedMacroToken => return null,
   1325             else => |e| return e,
   1326         };
   1327 
   1328         for (self.patterns) |pattern| if (pattern.isEquivalent(ms, args_hash)) return pattern;
   1329         return null;
   1330     }
   1331 };
   1332 
   1333 pub const MacroSlicer = struct {
   1334     source: []const u8,
   1335     tokens: []const CToken,
   1336 
   1337     pub fn slice(self: MacroSlicer, token: CToken) []const u8 {
   1338         return self.source[token.start..token.end];
   1339     }
   1340 };
   1341 
   1342 // Maps macro parameter names to token position, for determining if different
   1343 // identifiers refer to the same positional argument in different macros.
   1344 pub const ArgsPositionMap = std.StringArrayHashMapUnmanaged(usize);
   1345 
   1346 pub const Error = std.mem.Allocator.Error;
   1347 pub const MacroProcessingError = Error || error{UnexpectedMacroToken};
   1348 pub const TypeError = Error || error{UnsupportedType};
   1349 pub const TransError = TypeError || error{UnsupportedTranslation};
   1350 
   1351 pub const SymbolTable = std.StringArrayHashMap(ast.Node);
   1352 pub const AliasList = std.array_list.Managed(struct {
   1353     alias: []const u8,
   1354     name: []const u8,
   1355 });
   1356 
   1357 pub const ResultUsed = enum {
   1358     used,
   1359     unused,
   1360 };
   1361 
   1362 pub fn ScopeExtra(comptime ScopeExtraContext: type, comptime ScopeExtraType: type) type {
   1363     return struct {
   1364         id: Id,
   1365         parent: ?*ScopeExtraScope,
   1366 
   1367         const ScopeExtraScope = @This();
   1368 
   1369         pub const Id = enum {
   1370             block,
   1371             root,
   1372             condition,
   1373             loop,
   1374             do_loop,
   1375         };
   1376 
   1377         /// Used for the scope of condition expressions, for example `if (cond)`.
   1378         /// The block is lazily initialised because it is only needed for rare
   1379         /// cases of comma operators being used.
   1380         pub const Condition = struct {
   1381             base: ScopeExtraScope,
   1382             block: ?Block = null,
   1383 
   1384             pub fn getBlockScope(self: *Condition, c: *ScopeExtraContext) !*Block {
   1385                 if (self.block) |*b| return b;
   1386                 self.block = try Block.init(c, &self.base, true);
   1387                 return &self.block.?;
   1388             }
   1389 
   1390             pub fn deinit(self: *Condition) void {
   1391                 if (self.block) |*b| b.deinit();
   1392             }
   1393         };
   1394 
   1395         /// Represents an in-progress Node.Block. This struct is stack-allocated.
   1396         /// When it is deinitialized, it produces an Node.Block which is allocated
   1397         /// into the main arena.
   1398         pub const Block = struct {
   1399             base: ScopeExtraScope,
   1400             statements: std.array_list.Managed(ast.Node),
   1401             variables: AliasList,
   1402             mangle_count: u32 = 0,
   1403             label: ?[]const u8 = null,
   1404 
   1405             /// By default all variables are discarded, since we do not know in advance if they
   1406             /// will be used. This maps the variable's name to the Discard payload, so that if
   1407             /// the variable is subsequently referenced we can indicate that the discard should
   1408             /// be skipped during the intermediate AST -> Zig AST render step.
   1409             variable_discards: std.StringArrayHashMap(*ast.Payload.Discard),
   1410 
   1411             /// When the block corresponds to a function, keep track of the return type
   1412             /// so that the return expression can be cast, if necessary
   1413             return_type: ?ScopeExtraType = null,
   1414 
   1415             /// C static local variables are wrapped in a block-local struct. The struct
   1416             /// is named after the (mangled) variable name, the Zig variable within the
   1417             /// struct itself is given this name.
   1418             pub const static_inner_name = "static";
   1419 
   1420             /// C extern variables declared within a block are wrapped in a block-local
   1421             /// struct. The struct is named ExternLocal_[variable_name], the Zig variable
   1422             /// within the struct itself is [variable_name] by neccessity since it's an
   1423             /// extern reference to an existing symbol.
   1424             pub const extern_inner_prepend = "ExternLocal";
   1425 
   1426             pub fn init(c: *ScopeExtraContext, parent: *ScopeExtraScope, labeled: bool) !Block {
   1427                 var blk = Block{
   1428                     .base = .{
   1429                         .id = .block,
   1430                         .parent = parent,
   1431                     },
   1432                     .statements = std.array_list.Managed(ast.Node).init(c.gpa),
   1433                     .variables = AliasList.init(c.gpa),
   1434                     .variable_discards = std.StringArrayHashMap(*ast.Payload.Discard).init(c.gpa),
   1435                 };
   1436                 if (labeled) {
   1437                     blk.label = try blk.makeMangledName(c, "blk");
   1438                 }
   1439                 return blk;
   1440             }
   1441 
   1442             pub fn deinit(self: *Block) void {
   1443                 self.statements.deinit();
   1444                 self.variables.deinit();
   1445                 self.variable_discards.deinit();
   1446                 self.* = undefined;
   1447             }
   1448 
   1449             pub fn complete(self: *Block, c: *ScopeExtraContext) !ast.Node {
   1450                 if (self.base.parent.?.id == .do_loop) {
   1451                     // We reserve 1 extra statement if the parent is a do_loop. This is in case of
   1452                     // do while, we want to put `if (cond) break;` at the end.
   1453                     const alloc_len = self.statements.items.len + @intFromBool(self.base.parent.?.id == .do_loop);
   1454                     var stmts = try c.arena.alloc(ast.Node, alloc_len);
   1455                     stmts.len = self.statements.items.len;
   1456                     @memcpy(stmts[0..self.statements.items.len], self.statements.items);
   1457                     return ast.Node.Tag.block.create(c.arena, .{
   1458                         .label = self.label,
   1459                         .stmts = stmts,
   1460                     });
   1461                 }
   1462                 if (self.statements.items.len == 0) return ast.Node.Tag.empty_block.init();
   1463                 return ast.Node.Tag.block.create(c.arena, .{
   1464                     .label = self.label,
   1465                     .stmts = try c.arena.dupe(ast.Node, self.statements.items),
   1466                 });
   1467             }
   1468 
   1469             /// Given the desired name, return a name that does not shadow anything from outer scopes.
   1470             /// Inserts the returned name into the scope.
   1471             /// The name will not be visible to callers of getAlias.
   1472             pub fn reserveMangledName(scope: *Block, c: *ScopeExtraContext, name: []const u8) ![]const u8 {
   1473                 return scope.createMangledName(c, name, true);
   1474             }
   1475 
   1476             /// Same as reserveMangledName, but enables the alias immediately.
   1477             pub fn makeMangledName(scope: *Block, c: *ScopeExtraContext, name: []const u8) ![]const u8 {
   1478                 return scope.createMangledName(c, name, false);
   1479             }
   1480 
   1481             pub fn createMangledName(scope: *Block, c: *ScopeExtraContext, name: []const u8, reservation: bool) ![]const u8 {
   1482                 const name_copy = try c.arena.dupe(u8, name);
   1483                 var proposed_name = name_copy;
   1484                 while (scope.contains(proposed_name)) {
   1485                     scope.mangle_count += 1;
   1486                     proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, scope.mangle_count });
   1487                 }
   1488                 const new_mangle = try scope.variables.addOne();
   1489                 if (reservation) {
   1490                     new_mangle.* = .{ .name = name_copy, .alias = name_copy };
   1491                 } else {
   1492                     new_mangle.* = .{ .name = name_copy, .alias = proposed_name };
   1493                 }
   1494                 return proposed_name;
   1495             }
   1496 
   1497             pub fn getAlias(scope: *Block, name: []const u8) []const u8 {
   1498                 for (scope.variables.items) |p| {
   1499                     if (std.mem.eql(u8, p.name, name))
   1500                         return p.alias;
   1501                 }
   1502                 return scope.base.parent.?.getAlias(name);
   1503             }
   1504 
   1505             /// Finds the (potentially) mangled struct name for a locally scoped extern variable or function given the original declaration name.
   1506             ///
   1507             /// Block scoped extern declarations translate to:
   1508             ///     const MangledStructName = struct {extern [qualifiers] original_extern_variable_name: [type]};
   1509             /// This finds MangledStructName given original_extern_variable_name for referencing correctly in transDeclRefExpr()
   1510             pub fn getLocalExternAlias(scope: *Block, name: []const u8) ?[]const u8 {
   1511                 for (scope.statements.items) |node| {
   1512                     switch (node.tag()) {
   1513                         .extern_local_var => {
   1514                             const parent_node = node.castTag(.extern_local_var).?;
   1515                             const init_node = parent_node.data.init.castTag(.var_decl).?;
   1516                             if (std.mem.eql(u8, init_node.data.name, name)) {
   1517                                 return parent_node.data.name;
   1518                             }
   1519                         },
   1520                         .extern_local_fn => {
   1521                             const parent_node = node.castTag(.extern_local_fn).?;
   1522                             const init_node = parent_node.data.init.castTag(.func).?;
   1523                             if (std.mem.eql(u8, init_node.data.name.?, name)) {
   1524                                 return parent_node.data.name;
   1525                             }
   1526                         },
   1527                         else => {},
   1528                     }
   1529                 }
   1530                 return null;
   1531             }
   1532 
   1533             pub fn localContains(scope: *Block, name: []const u8) bool {
   1534                 for (scope.variables.items) |p| {
   1535                     if (std.mem.eql(u8, p.alias, name))
   1536                         return true;
   1537                 }
   1538                 return false;
   1539             }
   1540 
   1541             pub fn contains(scope: *Block, name: []const u8) bool {
   1542                 if (scope.localContains(name))
   1543                     return true;
   1544                 return scope.base.parent.?.contains(name);
   1545             }
   1546 
   1547             pub fn discardVariable(scope: *Block, c: *ScopeExtraContext, name: []const u8) Error!void {
   1548                 const name_node = try ast.Node.Tag.identifier.create(c.arena, name);
   1549                 const discard = try ast.Node.Tag.discard.create(c.arena, .{ .should_skip = false, .value = name_node });
   1550                 try scope.statements.append(discard);
   1551                 try scope.variable_discards.putNoClobber(name, discard.castTag(.discard).?);
   1552             }
   1553         };
   1554 
   1555         pub const Root = struct {
   1556             base: ScopeExtraScope,
   1557             sym_table: SymbolTable,
   1558             blank_macros: std.StringArrayHashMap(void),
   1559             context: *ScopeExtraContext,
   1560             nodes: std.array_list.Managed(ast.Node),
   1561 
   1562             pub fn init(c: *ScopeExtraContext) Root {
   1563                 return .{
   1564                     .base = .{
   1565                         .id = .root,
   1566                         .parent = null,
   1567                     },
   1568                     .sym_table = SymbolTable.init(c.gpa),
   1569                     .blank_macros = std.StringArrayHashMap(void).init(c.gpa),
   1570                     .context = c,
   1571                     .nodes = std.array_list.Managed(ast.Node).init(c.gpa),
   1572                 };
   1573             }
   1574 
   1575             pub fn deinit(scope: *Root) void {
   1576                 scope.sym_table.deinit();
   1577                 scope.blank_macros.deinit();
   1578                 scope.nodes.deinit();
   1579             }
   1580 
   1581             /// Check if the global scope contains this name, without looking into the "future", e.g.
   1582             /// ignore the preprocessed decl and macro names.
   1583             pub fn containsNow(scope: *Root, name: []const u8) bool {
   1584                 return scope.sym_table.contains(name);
   1585             }
   1586 
   1587             /// Check if the global scope contains the name, includes all decls that haven't been translated yet.
   1588             pub fn contains(scope: *Root, name: []const u8) bool {
   1589                 return scope.containsNow(name) or scope.context.global_names.contains(name) or scope.context.weak_global_names.contains(name);
   1590             }
   1591         };
   1592 
   1593         pub fn findBlockScope(inner: *ScopeExtraScope, c: *ScopeExtraContext) !*Block {
   1594             var scope = inner;
   1595             while (true) {
   1596                 switch (scope.id) {
   1597                     .root => unreachable,
   1598                     .block => return @fieldParentPtr("base", scope),
   1599                     .condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(c),
   1600                     else => scope = scope.parent.?,
   1601                 }
   1602             }
   1603         }
   1604 
   1605         pub fn findBlockReturnType(inner: *ScopeExtraScope) ScopeExtraType {
   1606             var scope = inner;
   1607             while (true) {
   1608                 switch (scope.id) {
   1609                     .root => unreachable,
   1610                     .block => {
   1611                         const block: *Block = @fieldParentPtr("base", scope);
   1612                         if (block.return_type) |ty| return ty;
   1613                         scope = scope.parent.?;
   1614                     },
   1615                     else => scope = scope.parent.?,
   1616                 }
   1617             }
   1618         }
   1619 
   1620         pub fn getAlias(scope: *ScopeExtraScope, name: []const u8) []const u8 {
   1621             return switch (scope.id) {
   1622                 .root => name,
   1623                 .block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name),
   1624                 .loop, .do_loop, .condition => scope.parent.?.getAlias(name),
   1625             };
   1626         }
   1627 
   1628         pub fn getLocalExternAlias(scope: *ScopeExtraScope, name: []const u8) ?[]const u8 {
   1629             return switch (scope.id) {
   1630                 .root => null,
   1631                 .block => ret: {
   1632                     const block = @as(*Block, @fieldParentPtr("base", scope));
   1633                     const alias_name = block.getLocalExternAlias(name);
   1634                     if (alias_name) |_alias_name| {
   1635                         break :ret _alias_name;
   1636                     }
   1637                     break :ret scope.parent.?.getLocalExternAlias(name);
   1638                 },
   1639                 .loop, .do_loop, .condition => scope.parent.?.getLocalExternAlias(name),
   1640             };
   1641         }
   1642 
   1643         pub fn contains(scope: *ScopeExtraScope, name: []const u8) bool {
   1644             return switch (scope.id) {
   1645                 .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name),
   1646                 .block => @as(*Block, @fieldParentPtr("base", scope)).contains(name),
   1647                 .loop, .do_loop, .condition => scope.parent.?.contains(name),
   1648             };
   1649         }
   1650 
   1651         pub fn getBreakableScope(inner: *ScopeExtraScope) *ScopeExtraScope {
   1652             var scope = inner;
   1653             while (true) {
   1654                 switch (scope.id) {
   1655                     .root => unreachable,
   1656                     .loop, .do_loop => return scope,
   1657                     else => scope = scope.parent.?,
   1658                 }
   1659             }
   1660         }
   1661 
   1662         /// Appends a node to the first block scope if inside a function, or to the root tree if not.
   1663         pub fn appendNode(inner: *ScopeExtraScope, node: ast.Node) !void {
   1664             var scope = inner;
   1665             while (true) {
   1666                 switch (scope.id) {
   1667                     .root => {
   1668                         const root: *Root = @fieldParentPtr("base", scope);
   1669                         return root.nodes.append(node);
   1670                     },
   1671                     .block => {
   1672                         const block: *Block = @fieldParentPtr("base", scope);
   1673                         return block.statements.append(node);
   1674                     },
   1675                     else => scope = scope.parent.?,
   1676                 }
   1677             }
   1678         }
   1679 
   1680         pub fn skipVariableDiscard(inner: *ScopeExtraScope, name: []const u8) void {
   1681             if (true) {
   1682                 // TODO: due to 'local variable is never mutated' errors, we can
   1683                 // only skip discards if a variable is used as an lvalue, which
   1684                 // we don't currently have detection for in translate-c.
   1685                 // Once #17584 is completed, perhaps we can do away with this
   1686                 // logic entirely, and instead rely on render to fixup code.
   1687                 return;
   1688             }
   1689             var scope = inner;
   1690             while (true) {
   1691                 switch (scope.id) {
   1692                     .root => return,
   1693                     .block => {
   1694                         const block: *Block = @fieldParentPtr("base", scope);
   1695                         if (block.variable_discards.get(name)) |discard| {
   1696                             discard.data.should_skip = true;
   1697                             return;
   1698                         }
   1699                     },
   1700                     else => {},
   1701                 }
   1702                 scope = scope.parent.?;
   1703             }
   1704         }
   1705     };
   1706 }
   1707 
   1708 pub fn tokenizeMacro(source: []const u8, tok_list: *std.array_list.Managed(CToken)) Error!void {
   1709     var tokenizer: aro.Tokenizer = .{
   1710         .buf = source,
   1711         .source = .unused,
   1712         .langopts = .{},
   1713     };
   1714     while (true) {
   1715         const tok = tokenizer.next();
   1716         switch (tok.id) {
   1717             .whitespace => continue,
   1718             .nl, .eof => {
   1719                 try tok_list.append(tok);
   1720                 break;
   1721             },
   1722             else => {},
   1723         }
   1724         try tok_list.append(tok);
   1725     }
   1726 }
   1727 
   1728 // Testing here instead of test/translate_c.zig allows us to also test that the
   1729 // mapped function exists in `std.zig.c_translation.Macros`
   1730 test "Macro matching" {
   1731     const testing = std.testing;
   1732     const helper = struct {
   1733         const MacroFunctions = std.zig.c_translation.Macros;
   1734         fn checkMacro(allocator: mem.Allocator, pattern_list: PatternList, source: []const u8, comptime expected_match: ?[]const u8) !void {
   1735             var tok_list = std.array_list.Managed(CToken).init(allocator);
   1736             defer tok_list.deinit();
   1737             try tokenizeMacro(source, &tok_list);
   1738             const macro_slicer: MacroSlicer = .{ .source = source, .tokens = tok_list.items };
   1739             const matched = try pattern_list.match(allocator, macro_slicer);
   1740             if (expected_match) |expected| {
   1741                 try testing.expectEqualStrings(expected, matched.?.impl);
   1742                 try testing.expect(@hasDecl(MacroFunctions, expected));
   1743             } else {
   1744                 try testing.expectEqual(@as(@TypeOf(matched), null), matched);
   1745             }
   1746         }
   1747     };
   1748     const allocator = std.testing.allocator;
   1749     var pattern_list = try PatternList.init(allocator);
   1750     defer pattern_list.deinit(allocator);
   1751 
   1752     try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", "F_SUFFIX");
   1753     try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", "U_SUFFIX");
   1754     try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", "L_SUFFIX");
   1755     try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", "LL_SUFFIX");
   1756     try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", "UL_SUFFIX");
   1757     try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", "ULL_SUFFIX");
   1758     try helper.checkMacro(allocator, pattern_list,
   1759         \\container_of(a, b, c)                             \
   1760         \\(__typeof__(b))((char *)(a) -                     \
   1761         \\     offsetof(__typeof__(*b), c))
   1762     , "WL_CONTAINER_OF");
   1763 
   1764     try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null);
   1765     try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL");
   1766     try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", "CAST_OR_CALL");
   1767     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", "DISCARD");
   1768     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", "DISCARD");
   1769     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", "DISCARD");
   1770     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", "DISCARD");
   1771     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", "DISCARD");
   1772     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", "DISCARD");
   1773     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", "DISCARD");
   1774     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", "DISCARD");
   1775     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", "DISCARD");
   1776     try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", "DISCARD");
   1777 }
   1778 
   1779 /// Renders errors and fatal errors + associated notes (e.g. "expanded from here"); does not render warnings or associated notes
   1780 /// Terminates with exit code 1
   1781 fn renderErrorsAndExit(comp: *aro.Compilation) noreturn {
   1782     defer std.process.exit(1);
   1783 
   1784     var buffer: [1000]u8 = undefined;
   1785     var writer = aro.Diagnostics.defaultMsgWriter(std.io.tty.detectConfig(std.fs.File.stderr()), &buffer);
   1786     defer writer.deinit(); // writer deinit must run *before* exit so that stderr is flushed
   1787 
   1788     var saw_error = false;
   1789     for (comp.diagnostics.list.items) |msg| {
   1790         switch (msg.kind) {
   1791             .@"error", .@"fatal error" => {
   1792                 saw_error = true;
   1793                 aro.Diagnostics.renderMessage(comp, &writer, msg);
   1794             },
   1795             .warning => saw_error = false,
   1796             .note => {
   1797                 if (saw_error) {
   1798                     aro.Diagnostics.renderMessage(comp, &writer, msg);
   1799                 }
   1800             },
   1801             .off => {},
   1802             .default => unreachable,
   1803         }
   1804     }
   1805 }
   1806 
   1807 pub fn main() !void {
   1808     var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
   1809     defer arena_instance.deinit();
   1810     const arena = arena_instance.allocator();
   1811 
   1812     var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
   1813     const gpa = general_purpose_allocator.allocator();
   1814 
   1815     const args = try std.process.argsAlloc(arena);
   1816 
   1817     var aro_comp = aro.Compilation.init(gpa, std.fs.cwd());
   1818     defer aro_comp.deinit();
   1819 
   1820     var tree = translate(gpa, &aro_comp, args) catch |err| switch (err) {
   1821         error.ParsingFailed, error.FatalError => renderErrorsAndExit(&aro_comp),
   1822         error.OutOfMemory => return error.OutOfMemory,
   1823         error.StreamTooLong => std.process.fatal("An input file was larger than 4GiB", .{}),
   1824     };
   1825     defer tree.deinit(gpa);
   1826 
   1827     const formatted = try tree.renderAlloc(arena);
   1828     try std.fs.File.stdout().writeAll(formatted);
   1829     return std.process.cleanExit();
   1830 }