Skip to content

Commit 43f752e

Browse files
committed
extract Scope into a separate file
1 parent d80977b commit 43f752e

File tree

2 files changed

+376
-362
lines changed

2 files changed

+376
-362
lines changed

src/Scope.zig

+346
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,346 @@
1+
const std = @import("std");
2+
3+
const aro = @import("aro");
4+
5+
const ast = @import("ast.zig");
6+
const Translator = @import("Translator.zig");
7+
8+
const Scope = @This();
9+
10+
pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node);
11+
pub const AliasList = std.ArrayListUnmanaged(struct {
12+
alias: []const u8,
13+
name: []const u8,
14+
});
15+
16+
id: Id,
17+
parent: ?*Scope,
18+
19+
pub const Id = enum {
20+
block,
21+
root,
22+
condition,
23+
loop,
24+
do_loop,
25+
};
26+
27+
/// Used for the scope of condition expressions, for example `if (cond)`.
28+
/// The block is lazily initialized because it is only needed for rare
29+
/// cases of comma operators being used.
30+
pub const Condition = struct {
31+
base: Scope,
32+
block: ?Block = null,
33+
34+
fn getBlockScope(cond: *Condition, t: *Translator) !*Block {
35+
if (cond.block) |*b| return b;
36+
cond.block = try Block.init(t, &cond.base, true);
37+
return &cond.block.?;
38+
}
39+
40+
fn deinit(cond: *Condition) void {
41+
if (cond.block) |*b| b.deinit();
42+
}
43+
};
44+
45+
/// Represents an in-progress Node.Block. This struct is stack-allocated.
46+
/// When it is deinitialized, it produces an Node.Block which is allocated
47+
/// into the main arena.
48+
pub const Block = struct {
49+
base: Scope,
50+
translator: *Translator,
51+
statements: std.ArrayListUnmanaged(ast.Node),
52+
variables: AliasList,
53+
mangle_count: u32 = 0,
54+
label: ?[]const u8 = null,
55+
56+
/// By default all variables are discarded, since we do not know in advance if they
57+
/// will be used. This maps the variable's name to the Discard payload, so that if
58+
/// the variable is subsequently referenced we can indicate that the discard should
59+
/// be skipped during the intermediate AST -> Zig AST render step.
60+
variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard),
61+
62+
/// When the block corresponds to a function, keep track of the return type
63+
/// so that the return expression can be cast, if necessary
64+
return_type: ?aro.Type = null,
65+
66+
/// C static local variables are wrapped in a block-local struct. The struct
67+
/// is named after the (mangled) variable name, the Zig variable within the
68+
/// struct itself is given this name.
69+
const static_inner_name = "static";
70+
71+
/// C extern variables declared within a block are wrapped in a block-local
72+
/// struct. The struct is named ExternLocal_[variable_name], the Zig variable
73+
/// within the struct itself is [variable_name] by necessity since it's an
74+
/// extern reference to an existing symbol.
75+
const extern_inner_prepend = "ExternLocal";
76+
77+
pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block {
78+
var blk: Block = .{
79+
.base = .{
80+
.id = .block,
81+
.parent = parent,
82+
},
83+
.translator = t,
84+
.statements = .empty,
85+
.variables = .empty,
86+
.variable_discards = .empty,
87+
};
88+
if (labeled) {
89+
blk.label = try blk.makeMangledName("blk");
90+
}
91+
return blk;
92+
}
93+
94+
pub fn deinit(block: *Block) void {
95+
block.statements.deinit(block.translator.gpa);
96+
block.variables.deinit(block.translator.gpa);
97+
block.variable_discards.deinit(block.translator.gpa);
98+
block.* = undefined;
99+
}
100+
101+
pub fn complete(block: *Block) !ast.Node {
102+
const arena = block.translator.arena;
103+
if (block.base.parent.?.id == .do_loop) {
104+
// We reserve 1 extra statement if the parent is a do_loop. This is in case of
105+
// do while, we want to put `if (cond) break;` at the end.
106+
const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop);
107+
var stmts = try arena.alloc(ast.Node, alloc_len);
108+
stmts.len = block.statements.items.len;
109+
@memcpy(stmts[0..block.statements.items.len], block.statements.items);
110+
return ast.Node.Tag.block.create(arena, .{
111+
.label = block.label,
112+
.stmts = stmts,
113+
});
114+
}
115+
if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init();
116+
return ast.Node.Tag.block.create(arena, .{
117+
.label = block.label,
118+
.stmts = try arena.dupe(ast.Node, block.statements.items),
119+
});
120+
}
121+
122+
/// Given the desired name, return a name that does not shadow anything from outer scopes.
123+
/// Inserts the returned name into the scope.
124+
/// The name will not be visible to callers of getAlias.
125+
fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 {
126+
return block.createMangledName(name, true);
127+
}
128+
129+
/// Same as reserveMangledName, but enables the alias immediately.
130+
pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 {
131+
return block.createMangledName(name, false);
132+
}
133+
134+
fn createMangledName(block: *Block, name: []const u8, reservation: bool) ![]const u8 {
135+
const arena = block.translator.arena;
136+
const name_copy = try arena.dupe(u8, name);
137+
var proposed_name = name_copy;
138+
while (block.contains(proposed_name)) {
139+
block.mangle_count += 1;
140+
proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ name, block.mangle_count });
141+
}
142+
const new_mangle = try block.variables.addOne(block.translator.gpa);
143+
if (reservation) {
144+
new_mangle.* = .{ .name = name_copy, .alias = name_copy };
145+
} else {
146+
new_mangle.* = .{ .name = name_copy, .alias = proposed_name };
147+
}
148+
return proposed_name;
149+
}
150+
151+
fn getAlias(block: *Block, name: []const u8) []const u8 {
152+
for (block.variables.items) |p| {
153+
if (std.mem.eql(u8, p.name, name))
154+
return p.alias;
155+
}
156+
return block.base.parent.?.getAlias(name);
157+
}
158+
159+
/// Finds the (potentially) mangled struct name for a locally scoped extern variable given the original declaration name.
160+
///
161+
/// Block scoped extern declarations translate to:
162+
/// const MangledStructName = struct {extern [qualifiers] original_extern_variable_name: [type]};
163+
/// This finds MangledStructName given original_extern_variable_name for referencing correctly in transDeclRefExpr()
164+
fn getLocalExternAlias(block: *Block, name: []const u8) ?[]const u8 {
165+
for (block.statements.items) |node| {
166+
if (node.tag() == .extern_local_var) {
167+
const parent_node = node.castTag(.extern_local_var).?;
168+
const init_node = parent_node.data.init.castTag(.var_decl).?;
169+
if (std.mem.eql(u8, init_node.data.name, name)) {
170+
return parent_node.data.name;
171+
}
172+
}
173+
}
174+
return null;
175+
}
176+
177+
fn localContains(block: *Block, name: []const u8) bool {
178+
for (block.variables.items) |p| {
179+
if (std.mem.eql(u8, p.alias, name))
180+
return true;
181+
}
182+
return false;
183+
}
184+
185+
fn contains(block: *Block, name: []const u8) bool {
186+
if (block.localContains(name))
187+
return true;
188+
return block.base.parent.?.contains(name);
189+
}
190+
191+
pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void {
192+
const gpa = block.translator.gpa;
193+
const arena = block.translator.arena;
194+
const name_node = try ast.Node.Tag.identifier.create(arena, name);
195+
const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node });
196+
try block.statements.append(gpa, discard);
197+
try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?);
198+
}
199+
};
200+
201+
pub const Root = struct {
202+
base: Scope,
203+
translator: *Translator,
204+
sym_table: SymbolTable,
205+
blank_macros: std.StringArrayHashMapUnmanaged(void),
206+
nodes: std.ArrayListUnmanaged(ast.Node),
207+
208+
pub fn init(t: *Translator) Root {
209+
return .{
210+
.base = .{
211+
.id = .root,
212+
.parent = null,
213+
},
214+
.translator = t,
215+
.sym_table = .empty,
216+
.blank_macros = .empty,
217+
.nodes = .empty,
218+
};
219+
}
220+
221+
pub fn deinit(root: *Root) void {
222+
root.sym_table.deinit(root.translator.gpa);
223+
root.blank_macros.deinit(root.translator.gpa);
224+
root.nodes.deinit(root.translator.gpa);
225+
}
226+
227+
/// Check if the global scope contains this name, without looking into the "future", e.g.
228+
/// ignore the preprocessed decl and macro names.
229+
fn containsNow(root: *Root, name: []const u8) bool {
230+
return root.sym_table.contains(name);
231+
}
232+
233+
/// Check if the global scope contains the name, includes all decls that haven't been translated yet.
234+
fn contains(root: *Root, name: []const u8) bool {
235+
return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name);
236+
}
237+
};
238+
239+
pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block {
240+
var scope = inner;
241+
while (true) {
242+
switch (scope.id) {
243+
.root => unreachable,
244+
.block => return @fieldParentPtr("base", scope),
245+
.condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t),
246+
else => scope = scope.parent.?,
247+
}
248+
}
249+
}
250+
251+
pub fn findBlockReturnType(inner: *Scope) aro.Type {
252+
var scope = inner;
253+
while (true) {
254+
switch (scope.id) {
255+
.root => unreachable,
256+
.block => {
257+
const block: *Block = @fieldParentPtr("base", scope);
258+
if (block.return_type) |ty| return ty;
259+
scope = scope.parent.?;
260+
},
261+
else => scope = scope.parent.?,
262+
}
263+
}
264+
}
265+
266+
pub fn getAlias(scope: *Scope, name: []const u8) []const u8 {
267+
return switch (scope.id) {
268+
.root => name,
269+
.block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name),
270+
.loop, .do_loop, .condition => scope.parent.?.getAlias(name),
271+
};
272+
}
273+
274+
fn getLocalExternAlias(scope: *Scope, name: []const u8) ?[]const u8 {
275+
return switch (scope.id) {
276+
.root => null,
277+
.block => ret: {
278+
const block = @as(*Block, @fieldParentPtr("base", scope));
279+
break :ret block.getLocalExternAlias(name);
280+
},
281+
.loop, .do_loop, .condition => scope.parent.?.getLocalExternAlias(name),
282+
};
283+
}
284+
285+
fn contains(scope: *Scope, name: []const u8) bool {
286+
return switch (scope.id) {
287+
.root => @as(*Root, @fieldParentPtr("base", scope)).contains(name),
288+
.block => @as(*Block, @fieldParentPtr("base", scope)).contains(name),
289+
.loop, .do_loop, .condition => scope.parent.?.contains(name),
290+
};
291+
}
292+
293+
fn getBreakableScope(inner: *Scope) *Scope {
294+
var scope = inner;
295+
while (true) {
296+
switch (scope.id) {
297+
.root => unreachable,
298+
.loop, .do_loop => return scope,
299+
else => scope = scope.parent.?,
300+
}
301+
}
302+
}
303+
304+
/// Appends a node to the first block scope if inside a function, or to the root tree if not.
305+
pub fn appendNode(inner: *Scope, node: ast.Node) !void {
306+
var scope = inner;
307+
while (true) {
308+
switch (scope.id) {
309+
.root => {
310+
const root: *Root = @fieldParentPtr("base", scope);
311+
return root.nodes.append(root.translator.gpa, node);
312+
},
313+
.block => {
314+
const block: *Block = @fieldParentPtr("base", scope);
315+
return block.statements.append(block.translator.gpa, node);
316+
},
317+
else => scope = scope.parent.?,
318+
}
319+
}
320+
}
321+
322+
pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void {
323+
if (true) {
324+
// TODO: due to 'local variable is never mutated' errors, we can
325+
// only skip discards if a variable is used as an lvalue, which
326+
// we don't currently have detection for in translate-c.
327+
// Once #17584 is completed, perhaps we can do away with this
328+
// logic entirely, and instead rely on render to fixup code.
329+
return;
330+
}
331+
var scope = inner;
332+
while (true) {
333+
switch (scope.id) {
334+
.root => return,
335+
.block => {
336+
const block: *Block = @fieldParentPtr("base", scope);
337+
if (block.variable_discards.get(name)) |discard| {
338+
discard.data.should_skip = true;
339+
return;
340+
}
341+
},
342+
else => {},
343+
}
344+
scope = scope.parent.?;
345+
}
346+
}

0 commit comments

Comments
 (0)