master
  1const std = @import("std");
  2
  3const aro = @import("aro");
  4
  5const ast = @import("ast.zig");
  6const Translator = @import("Translator.zig");
  7
  8const Scope = @This();
  9
 10pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node);
 11pub const AliasList = std.ArrayList(struct {
 12    alias: []const u8,
 13    name: []const u8,
 14});
 15
 16/// Associates a container (structure or union) with its relevant member functions.
 17pub const ContainerMemberFns = struct {
 18    container_decl_ptr: *ast.Node,
 19    member_fns: std.ArrayList(*ast.Payload.Func) = .empty,
 20};
 21pub const ContainerMemberFnsHashMap = std.AutoArrayHashMapUnmanaged(aro.QualType, ContainerMemberFns);
 22
 23id: Id,
 24parent: ?*Scope,
 25
 26pub const Id = enum {
 27    block,
 28    root,
 29    condition,
 30    loop,
 31    do_loop,
 32};
 33
 34/// Used for the scope of condition expressions, for example `if (cond)`.
 35/// The block is lazily initialized because it is only needed for rare
 36/// cases of comma operators being used.
 37pub const Condition = struct {
 38    base: Scope,
 39    block: ?Block = null,
 40
 41    fn getBlockScope(cond: *Condition, t: *Translator) !*Block {
 42        if (cond.block) |*b| return b;
 43        cond.block = try Block.init(t, &cond.base, true);
 44        return &cond.block.?;
 45    }
 46
 47    pub fn deinit(cond: *Condition) void {
 48        if (cond.block) |*b| b.deinit();
 49    }
 50};
 51
 52/// Represents an in-progress Node.Block. This struct is stack-allocated.
 53/// When it is deinitialized, it produces an Node.Block which is allocated
 54/// into the main arena.
 55pub const Block = struct {
 56    base: Scope,
 57    translator: *Translator,
 58    statements: std.ArrayList(ast.Node),
 59    variables: AliasList,
 60    mangle_count: u32 = 0,
 61    label: ?[]const u8 = null,
 62
 63    /// By default all variables are discarded, since we do not know in advance if they
 64    /// will be used. This maps the variable's name to the Discard payload, so that if
 65    /// the variable is subsequently referenced we can indicate that the discard should
 66    /// be skipped during the intermediate AST -> Zig AST render step.
 67    variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard),
 68
 69    /// When the block corresponds to a function, keep track of the return type
 70    /// so that the return expression can be cast, if necessary
 71    return_type: ?aro.QualType = null,
 72
 73    /// C static local variables are wrapped in a block-local struct. The struct
 74    /// is named `mangle(static_local_ + name)` and the Zig variable within the
 75    /// struct keeps the name of the C variable.
 76    pub const static_local_prefix = "static_local";
 77
 78    /// C extern local variables are wrapped in a block-local struct. The struct
 79    /// is named `mangle(extern_local + name)` and the Zig variable within the
 80    /// struct keeps the name of the C variable.
 81    pub const extern_local_prefix = "extern_local";
 82
 83    pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block {
 84        var blk: Block = .{
 85            .base = .{
 86                .id = .block,
 87                .parent = parent,
 88            },
 89            .translator = t,
 90            .statements = .empty,
 91            .variables = .empty,
 92            .variable_discards = .empty,
 93        };
 94        if (labeled) {
 95            blk.label = try blk.makeMangledName("blk");
 96        }
 97        return blk;
 98    }
 99
100    pub fn deinit(block: *Block) void {
101        block.statements.deinit(block.translator.gpa);
102        block.variables.deinit(block.translator.gpa);
103        block.variable_discards.deinit(block.translator.gpa);
104        block.* = undefined;
105    }
106
107    pub fn complete(block: *Block) !ast.Node {
108        const arena = block.translator.arena;
109        if (block.base.parent.?.id == .do_loop) {
110            // We reserve 1 extra statement if the parent is a do_loop. This is in case of
111            // do while, we want to put `if (cond) break;` at the end.
112            const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop);
113            var stmts = try arena.alloc(ast.Node, alloc_len);
114            stmts.len = block.statements.items.len;
115            @memcpy(stmts[0..block.statements.items.len], block.statements.items);
116            return ast.Node.Tag.block.create(arena, .{
117                .label = block.label,
118                .stmts = stmts,
119            });
120        }
121        if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init();
122        return ast.Node.Tag.block.create(arena, .{
123            .label = block.label,
124            .stmts = try arena.dupe(ast.Node, block.statements.items),
125        });
126    }
127
128    /// Given the desired name, return a name that does not shadow anything from outer scopes.
129    /// Inserts the returned name into the scope.
130    /// The name will not be visible to callers of getAlias.
131    pub fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 {
132        return block.createMangledName(name, true, null);
133    }
134
135    /// Same as reserveMangledName, but enables the alias immediately.
136    pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 {
137        return block.createMangledName(name, false, null);
138    }
139
140    pub fn createMangledName(block: *Block, name: []const u8, reservation: bool, prefix_opt: ?[]const u8) ![]const u8 {
141        const arena = block.translator.arena;
142        const name_copy = try arena.dupe(u8, name);
143        const alias_base = if (prefix_opt) |prefix|
144            try std.fmt.allocPrint(arena, "{s}_{s}", .{ prefix, name })
145        else
146            name;
147        var proposed_name = alias_base;
148        while (block.contains(proposed_name)) {
149            block.mangle_count += 1;
150            proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ alias_base, block.mangle_count });
151        }
152        const new_mangle = try block.variables.addOne(block.translator.gpa);
153        if (reservation) {
154            new_mangle.* = .{ .name = name_copy, .alias = name_copy };
155        } else {
156            new_mangle.* = .{ .name = name_copy, .alias = proposed_name };
157        }
158        return proposed_name;
159    }
160
161    fn getAlias(block: *Block, name: []const u8) ?[]const u8 {
162        for (block.variables.items) |p| {
163            if (std.mem.eql(u8, p.name, name))
164                return p.alias;
165        }
166        return block.base.parent.?.getAlias(name);
167    }
168
169    fn localContains(block: *Block, name: []const u8) bool {
170        for (block.variables.items) |p| {
171            if (std.mem.eql(u8, p.alias, name))
172                return true;
173        }
174        return false;
175    }
176
177    fn contains(block: *Block, name: []const u8) bool {
178        if (block.localContains(name))
179            return true;
180        return block.base.parent.?.contains(name);
181    }
182
183    pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void {
184        const gpa = block.translator.gpa;
185        const arena = block.translator.arena;
186        const name_node = try ast.Node.Tag.identifier.create(arena, name);
187        const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node });
188        try block.statements.append(gpa, discard);
189        try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?);
190    }
191};
192
193pub const Root = struct {
194    base: Scope,
195    translator: *Translator,
196    sym_table: SymbolTable,
197    blank_macros: std.StringArrayHashMapUnmanaged(void),
198    nodes: std.ArrayList(ast.Node),
199    container_member_fns_map: ContainerMemberFnsHashMap,
200
201    pub fn init(t: *Translator) Root {
202        return .{
203            .base = .{
204                .id = .root,
205                .parent = null,
206            },
207            .translator = t,
208            .sym_table = .empty,
209            .blank_macros = .empty,
210            .nodes = .empty,
211            .container_member_fns_map = .empty,
212        };
213    }
214
215    pub fn deinit(root: *Root) void {
216        root.sym_table.deinit(root.translator.gpa);
217        root.blank_macros.deinit(root.translator.gpa);
218        root.nodes.deinit(root.translator.gpa);
219        for (root.container_member_fns_map.values()) |*members| {
220            members.member_fns.deinit(root.translator.gpa);
221        }
222        root.container_member_fns_map.deinit(root.translator.gpa);
223    }
224
225    /// Check if the global scope contains this name, without looking into the "future", e.g.
226    /// ignore the preprocessed decl and macro names.
227    pub fn containsNow(root: *Root, name: []const u8) bool {
228        return root.sym_table.contains(name);
229    }
230
231    /// Check if the global scope contains the name, includes all decls that haven't been translated yet.
232    pub fn contains(root: *Root, name: []const u8) bool {
233        return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name);
234    }
235
236    pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void {
237        std.debug.assert(func.data.name != null);
238        if (func_ty.params.len == 0) return;
239
240        const param1_base = func_ty.params[0].qt.base(root.translator.comp);
241        const container_qt = if (param1_base.type == .pointer)
242            param1_base.type.pointer.child.base(root.translator.comp).qt
243        else
244            param1_base.qt;
245
246        if (root.container_member_fns_map.getPtr(container_qt)) |members| {
247            try members.member_fns.append(root.translator.gpa, func);
248        }
249    }
250
251    pub fn processContainerMemberFns(root: *Root) !void {
252        const gpa = root.translator.gpa;
253        const arena = root.translator.arena;
254
255        var member_names: std.StringArrayHashMapUnmanaged(void) = .empty;
256        defer member_names.deinit(gpa);
257        for (root.container_member_fns_map.values()) |members| {
258            member_names.clearRetainingCapacity();
259            const decls_ptr = switch (members.container_decl_ptr.tag()) {
260                .@"struct", .@"union" => blk_record: {
261                    const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise));
262                    // Avoid duplication with field names
263                    for (payload.data.fields) |field| {
264                        try member_names.put(gpa, field.name, {});
265                    }
266                    break :blk_record &payload.data.decls;
267                },
268                .opaque_literal => blk_opaque: {
269                    const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{
270                        .layout = .none,
271                        .fields = &.{},
272                        .decls = &.{},
273                    });
274                    members.container_decl_ptr.* = container_decl;
275                    break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls;
276                },
277                else => return,
278            };
279
280            const old_decls = decls_ptr.*;
281            const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len * 2);
282            @memcpy(new_decls[0..old_decls.len], old_decls);
283            // Assume the allocator of payload.data.decls is arena,
284            // so don't add arena.free(old_variables).
285            const func_ref_vars = new_decls[old_decls.len..];
286            var count: u32 = 0;
287
288            // Add members without mangling them - only fields may cause name conflicts
289            for (members.member_fns.items) |func| {
290                const func_name = func.data.name.?;
291                const member_name_slot = try member_names.getOrPutValue(gpa, func_name, {});
292                if (member_name_slot.found_existing) continue;
293                func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{
294                    .name = func_name,
295                    .init = try ast.Node.Tag.root_ref.create(arena, func_name),
296                });
297                count += 1;
298            }
299
300            for (members.member_fns.items) |func| {
301                const func_name = func.data.name.?;
302                const func_name_trimmed = std.mem.trimEnd(u8, func_name, "_");
303                const last_idx = std.mem.findLast(u8, func_name_trimmed, "_") orelse continue;
304                const func_name_alias = func_name[last_idx + 1 ..];
305                const member_name_slot = try member_names.getOrPutValue(gpa, func_name_alias, {});
306                if (member_name_slot.found_existing) continue;
307                func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{
308                    .name = func_name_alias,
309                    .init = try ast.Node.Tag.root_ref.create(arena, func_name),
310                });
311                count += 1;
312            }
313
314            decls_ptr.* = new_decls[0 .. old_decls.len + count];
315        }
316    }
317};
318
319pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block {
320    var scope = inner;
321    while (true) {
322        switch (scope.id) {
323            .root => unreachable,
324            .block => return @fieldParentPtr("base", scope),
325            .condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t),
326            else => scope = scope.parent.?,
327        }
328    }
329}
330
331pub fn findBlockReturnType(inner: *Scope) aro.QualType {
332    var scope = inner;
333    while (true) {
334        switch (scope.id) {
335            .root => unreachable,
336            .block => {
337                const block: *Block = @fieldParentPtr("base", scope);
338                if (block.return_type) |qt| return qt;
339                scope = scope.parent.?;
340            },
341            else => scope = scope.parent.?,
342        }
343    }
344}
345
346pub fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 {
347    return switch (scope.id) {
348        .root => null,
349        .block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name),
350        .loop, .do_loop, .condition => scope.parent.?.getAlias(name),
351    };
352}
353
354fn contains(scope: *Scope, name: []const u8) bool {
355    return switch (scope.id) {
356        .root => @as(*Root, @fieldParentPtr("base", scope)).contains(name),
357        .block => @as(*Block, @fieldParentPtr("base", scope)).contains(name),
358        .loop, .do_loop, .condition => scope.parent.?.contains(name),
359    };
360}
361
362/// Appends a node to the first block scope if inside a function, or to the root tree if not.
363pub fn appendNode(inner: *Scope, node: ast.Node) !void {
364    var scope = inner;
365    while (true) {
366        switch (scope.id) {
367            .root => {
368                const root: *Root = @fieldParentPtr("base", scope);
369                return root.nodes.append(root.translator.gpa, node);
370            },
371            .block => {
372                const block: *Block = @fieldParentPtr("base", scope);
373                return block.statements.append(block.translator.gpa, node);
374            },
375            else => scope = scope.parent.?,
376        }
377    }
378}
379
380pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void {
381    if (true) {
382        // TODO: due to 'local variable is never mutated' errors, we can
383        // only skip discards if a variable is used as an lvalue, which
384        // we don't currently have detection for in translate-c.
385        // Once #17584 is completed, perhaps we can do away with this
386        // logic entirely, and instead rely on render to fixup code.
387        return;
388    }
389    var scope = inner;
390    while (true) {
391        switch (scope.id) {
392            .root => return,
393            .block => {
394                const block: *Block = @fieldParentPtr("base", scope);
395                if (block.variable_discards.get(name)) |discard| {
396                    discard.data.should_skip = true;
397                    return;
398                }
399            },
400            else => {},
401        }
402        scope = scope.parent.?;
403    }
404}