|
| 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