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}