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