mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
404 lines
15 KiB
Zig
Vendored
404 lines
15 KiB
Zig
Vendored
const std = @import("std");
|
|
|
|
const aro = @import("aro");
|
|
|
|
const ast = @import("ast.zig");
|
|
const Translator = @import("Translator.zig");
|
|
|
|
const Scope = @This();
|
|
|
|
pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node);
|
|
pub const AliasList = std.ArrayList(struct {
|
|
alias: []const u8,
|
|
name: []const u8,
|
|
});
|
|
|
|
/// Associates a container (structure or union) with its relevant member functions.
|
|
pub const ContainerMemberFns = struct {
|
|
container_decl_ptr: *ast.Node,
|
|
member_fns: std.ArrayList(*ast.Payload.Func) = .empty,
|
|
};
|
|
pub const ContainerMemberFnsHashMap = std.AutoArrayHashMapUnmanaged(aro.QualType, ContainerMemberFns);
|
|
|
|
id: Id,
|
|
parent: ?*Scope,
|
|
|
|
pub const Id = enum {
|
|
block,
|
|
root,
|
|
condition,
|
|
loop,
|
|
do_loop,
|
|
};
|
|
|
|
/// Used for the scope of condition expressions, for example `if (cond)`.
|
|
/// The block is lazily initialized because it is only needed for rare
|
|
/// cases of comma operators being used.
|
|
pub const Condition = struct {
|
|
base: Scope,
|
|
block: ?Block = null,
|
|
|
|
fn getBlockScope(cond: *Condition, t: *Translator) !*Block {
|
|
if (cond.block) |*b| return b;
|
|
cond.block = try Block.init(t, &cond.base, true);
|
|
return &cond.block.?;
|
|
}
|
|
|
|
pub fn deinit(cond: *Condition) void {
|
|
if (cond.block) |*b| b.deinit();
|
|
}
|
|
};
|
|
|
|
/// Represents an in-progress Node.Block. This struct is stack-allocated.
|
|
/// When it is deinitialized, it produces an Node.Block which is allocated
|
|
/// into the main arena.
|
|
pub const Block = struct {
|
|
base: Scope,
|
|
translator: *Translator,
|
|
statements: std.ArrayList(ast.Node),
|
|
variables: AliasList,
|
|
mangle_count: u32 = 0,
|
|
label: ?[]const u8 = null,
|
|
|
|
/// By default all variables are discarded, since we do not know in advance if they
|
|
/// will be used. This maps the variable's name to the Discard payload, so that if
|
|
/// the variable is subsequently referenced we can indicate that the discard should
|
|
/// be skipped during the intermediate AST -> Zig AST render step.
|
|
variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard),
|
|
|
|
/// When the block corresponds to a function, keep track of the return type
|
|
/// so that the return expression can be cast, if necessary
|
|
return_type: ?aro.QualType = null,
|
|
|
|
/// C static local variables are wrapped in a block-local struct. The struct
|
|
/// is named `mangle(static_local_ + name)` and the Zig variable within the
|
|
/// struct keeps the name of the C variable.
|
|
pub const static_local_prefix = "static_local";
|
|
|
|
/// C extern local variables are wrapped in a block-local struct. The struct
|
|
/// is named `mangle(extern_local + name)` and the Zig variable within the
|
|
/// struct keeps the name of the C variable.
|
|
pub const extern_local_prefix = "extern_local";
|
|
|
|
pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block {
|
|
var blk: Block = .{
|
|
.base = .{
|
|
.id = .block,
|
|
.parent = parent,
|
|
},
|
|
.translator = t,
|
|
.statements = .empty,
|
|
.variables = .empty,
|
|
.variable_discards = .empty,
|
|
};
|
|
if (labeled) {
|
|
blk.label = try blk.makeMangledName("blk");
|
|
}
|
|
return blk;
|
|
}
|
|
|
|
pub fn deinit(block: *Block) void {
|
|
block.statements.deinit(block.translator.gpa);
|
|
block.variables.deinit(block.translator.gpa);
|
|
block.variable_discards.deinit(block.translator.gpa);
|
|
block.* = undefined;
|
|
}
|
|
|
|
pub fn complete(block: *Block) !ast.Node {
|
|
const arena = block.translator.arena;
|
|
if (block.base.parent.?.id == .do_loop) {
|
|
// We reserve 1 extra statement if the parent is a do_loop. This is in case of
|
|
// do while, we want to put `if (cond) break;` at the end.
|
|
const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop);
|
|
var stmts = try arena.alloc(ast.Node, alloc_len);
|
|
stmts.len = block.statements.items.len;
|
|
@memcpy(stmts[0..block.statements.items.len], block.statements.items);
|
|
return ast.Node.Tag.block.create(arena, .{
|
|
.label = block.label,
|
|
.stmts = stmts,
|
|
});
|
|
}
|
|
if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init();
|
|
return ast.Node.Tag.block.create(arena, .{
|
|
.label = block.label,
|
|
.stmts = try arena.dupe(ast.Node, block.statements.items),
|
|
});
|
|
}
|
|
|
|
/// Given the desired name, return a name that does not shadow anything from outer scopes.
|
|
/// Inserts the returned name into the scope.
|
|
/// The name will not be visible to callers of getAlias.
|
|
pub fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 {
|
|
return block.createMangledName(name, true, null);
|
|
}
|
|
|
|
/// Same as reserveMangledName, but enables the alias immediately.
|
|
pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 {
|
|
return block.createMangledName(name, false, null);
|
|
}
|
|
|
|
pub fn createMangledName(block: *Block, name: []const u8, reservation: bool, prefix_opt: ?[]const u8) ![]const u8 {
|
|
const arena = block.translator.arena;
|
|
const name_copy = try arena.dupe(u8, name);
|
|
const alias_base = if (prefix_opt) |prefix|
|
|
try std.fmt.allocPrint(arena, "{s}_{s}", .{ prefix, name })
|
|
else
|
|
name;
|
|
var proposed_name = alias_base;
|
|
while (block.contains(proposed_name)) {
|
|
block.mangle_count += 1;
|
|
proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ alias_base, block.mangle_count });
|
|
}
|
|
const new_mangle = try block.variables.addOne(block.translator.gpa);
|
|
if (reservation) {
|
|
new_mangle.* = .{ .name = name_copy, .alias = name_copy };
|
|
} else {
|
|
new_mangle.* = .{ .name = name_copy, .alias = proposed_name };
|
|
}
|
|
return proposed_name;
|
|
}
|
|
|
|
fn getAlias(block: *Block, name: []const u8) ?[]const u8 {
|
|
for (block.variables.items) |p| {
|
|
if (std.mem.eql(u8, p.name, name))
|
|
return p.alias;
|
|
}
|
|
return block.base.parent.?.getAlias(name);
|
|
}
|
|
|
|
fn localContains(block: *Block, name: []const u8) bool {
|
|
for (block.variables.items) |p| {
|
|
if (std.mem.eql(u8, p.alias, name))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn contains(block: *Block, name: []const u8) bool {
|
|
if (block.localContains(name))
|
|
return true;
|
|
return block.base.parent.?.contains(name);
|
|
}
|
|
|
|
pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void {
|
|
const gpa = block.translator.gpa;
|
|
const arena = block.translator.arena;
|
|
const name_node = try ast.Node.Tag.identifier.create(arena, name);
|
|
const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node });
|
|
try block.statements.append(gpa, discard);
|
|
try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?);
|
|
}
|
|
};
|
|
|
|
pub const Root = struct {
|
|
base: Scope,
|
|
translator: *Translator,
|
|
sym_table: SymbolTable,
|
|
blank_macros: std.StringArrayHashMapUnmanaged(void),
|
|
nodes: std.ArrayList(ast.Node),
|
|
container_member_fns_map: ContainerMemberFnsHashMap,
|
|
|
|
pub fn init(t: *Translator) Root {
|
|
return .{
|
|
.base = .{
|
|
.id = .root,
|
|
.parent = null,
|
|
},
|
|
.translator = t,
|
|
.sym_table = .empty,
|
|
.blank_macros = .empty,
|
|
.nodes = .empty,
|
|
.container_member_fns_map = .empty,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(root: *Root) void {
|
|
root.sym_table.deinit(root.translator.gpa);
|
|
root.blank_macros.deinit(root.translator.gpa);
|
|
root.nodes.deinit(root.translator.gpa);
|
|
for (root.container_member_fns_map.values()) |*members| {
|
|
members.member_fns.deinit(root.translator.gpa);
|
|
}
|
|
root.container_member_fns_map.deinit(root.translator.gpa);
|
|
}
|
|
|
|
/// Check if the global scope contains this name, without looking into the "future", e.g.
|
|
/// ignore the preprocessed decl and macro names.
|
|
pub fn containsNow(root: *Root, name: []const u8) bool {
|
|
return root.sym_table.contains(name);
|
|
}
|
|
|
|
/// Check if the global scope contains the name, includes all decls that haven't been translated yet.
|
|
pub fn contains(root: *Root, name: []const u8) bool {
|
|
return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name);
|
|
}
|
|
|
|
pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void {
|
|
std.debug.assert(func.data.name != null);
|
|
if (func_ty.params.len == 0) return;
|
|
|
|
const param1_base = func_ty.params[0].qt.base(root.translator.comp);
|
|
const container_qt = if (param1_base.type == .pointer)
|
|
param1_base.type.pointer.child.base(root.translator.comp).qt
|
|
else
|
|
param1_base.qt;
|
|
|
|
if (root.container_member_fns_map.getPtr(container_qt)) |members| {
|
|
try members.member_fns.append(root.translator.gpa, func);
|
|
}
|
|
}
|
|
|
|
pub fn processContainerMemberFns(root: *Root) !void {
|
|
const gpa = root.translator.gpa;
|
|
const arena = root.translator.arena;
|
|
|
|
var member_names: std.StringArrayHashMapUnmanaged(void) = .empty;
|
|
defer member_names.deinit(gpa);
|
|
for (root.container_member_fns_map.values()) |members| {
|
|
member_names.clearRetainingCapacity();
|
|
const decls_ptr = switch (members.container_decl_ptr.tag()) {
|
|
.@"struct", .@"union" => blk_record: {
|
|
const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise));
|
|
// Avoid duplication with field names
|
|
for (payload.data.fields) |field| {
|
|
try member_names.put(gpa, field.name, {});
|
|
}
|
|
break :blk_record &payload.data.decls;
|
|
},
|
|
.opaque_literal => blk_opaque: {
|
|
const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{
|
|
.layout = .none,
|
|
.fields = &.{},
|
|
.decls = &.{},
|
|
});
|
|
members.container_decl_ptr.* = container_decl;
|
|
break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls;
|
|
},
|
|
else => return,
|
|
};
|
|
|
|
const old_decls = decls_ptr.*;
|
|
const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len * 2);
|
|
@memcpy(new_decls[0..old_decls.len], old_decls);
|
|
// Assume the allocator of payload.data.decls is arena,
|
|
// so don't add arena.free(old_variables).
|
|
const func_ref_vars = new_decls[old_decls.len..];
|
|
var count: u32 = 0;
|
|
|
|
// Add members without mangling them - only fields may cause name conflicts
|
|
for (members.member_fns.items) |func| {
|
|
const func_name = func.data.name.?;
|
|
const member_name_slot = try member_names.getOrPutValue(gpa, func_name, {});
|
|
if (member_name_slot.found_existing) continue;
|
|
func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{
|
|
.name = func_name,
|
|
.init = try ast.Node.Tag.root_ref.create(arena, func_name),
|
|
});
|
|
count += 1;
|
|
}
|
|
|
|
for (members.member_fns.items) |func| {
|
|
const func_name = func.data.name.?;
|
|
const func_name_trimmed = std.mem.trimEnd(u8, func_name, "_");
|
|
const last_idx = std.mem.findLast(u8, func_name_trimmed, "_") orelse continue;
|
|
const func_name_alias = func_name[last_idx + 1 ..];
|
|
const member_name_slot = try member_names.getOrPutValue(gpa, func_name_alias, {});
|
|
if (member_name_slot.found_existing) continue;
|
|
func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{
|
|
.name = func_name_alias,
|
|
.init = try ast.Node.Tag.root_ref.create(arena, func_name),
|
|
});
|
|
count += 1;
|
|
}
|
|
|
|
decls_ptr.* = new_decls[0 .. old_decls.len + count];
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block {
|
|
var scope = inner;
|
|
while (true) {
|
|
switch (scope.id) {
|
|
.root => unreachable,
|
|
.block => return @fieldParentPtr("base", scope),
|
|
.condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t),
|
|
else => scope = scope.parent.?,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn findBlockReturnType(inner: *Scope) aro.QualType {
|
|
var scope = inner;
|
|
while (true) {
|
|
switch (scope.id) {
|
|
.root => unreachable,
|
|
.block => {
|
|
const block: *Block = @fieldParentPtr("base", scope);
|
|
if (block.return_type) |qt| return qt;
|
|
scope = scope.parent.?;
|
|
},
|
|
else => scope = scope.parent.?,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 {
|
|
return switch (scope.id) {
|
|
.root => null,
|
|
.block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name),
|
|
.loop, .do_loop, .condition => scope.parent.?.getAlias(name),
|
|
};
|
|
}
|
|
|
|
fn contains(scope: *Scope, name: []const u8) bool {
|
|
return switch (scope.id) {
|
|
.root => @as(*Root, @fieldParentPtr("base", scope)).contains(name),
|
|
.block => @as(*Block, @fieldParentPtr("base", scope)).contains(name),
|
|
.loop, .do_loop, .condition => scope.parent.?.contains(name),
|
|
};
|
|
}
|
|
|
|
/// Appends a node to the first block scope if inside a function, or to the root tree if not.
|
|
pub fn appendNode(inner: *Scope, node: ast.Node) !void {
|
|
var scope = inner;
|
|
while (true) {
|
|
switch (scope.id) {
|
|
.root => {
|
|
const root: *Root = @fieldParentPtr("base", scope);
|
|
return root.nodes.append(root.translator.gpa, node);
|
|
},
|
|
.block => {
|
|
const block: *Block = @fieldParentPtr("base", scope);
|
|
return block.statements.append(block.translator.gpa, node);
|
|
},
|
|
else => scope = scope.parent.?,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void {
|
|
if (true) {
|
|
// TODO: due to 'local variable is never mutated' errors, we can
|
|
// only skip discards if a variable is used as an lvalue, which
|
|
// we don't currently have detection for in translate-c.
|
|
// Once #17584 is completed, perhaps we can do away with this
|
|
// logic entirely, and instead rely on render to fixup code.
|
|
return;
|
|
}
|
|
var scope = inner;
|
|
while (true) {
|
|
switch (scope.id) {
|
|
.root => return,
|
|
.block => {
|
|
const block: *Block = @fieldParentPtr("base", scope);
|
|
if (block.variable_discards.get(name)) |discard| {
|
|
discard.data.should_skip = true;
|
|
return;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
scope = scope.parent.?;
|
|
}
|
|
}
|