Similar to the previous commit, this commit untangles LLD integration from the self-hosted linkers. Despite the big network of functions which were involved, it turns out what was going on here is quite simple. The LLD linking logic is actually very self-contained; it requires a few flags from the `link.File.OpenOptions`, but that's really about it. We don't need any of the mutable state on `Elf`/`Coff`/`Wasm`, for instance. There was some legacy code trying to handle support for using self-hosted codegen with LLD, but that's not a supported use case, so I've just stripped it out. For now, I've just pasted the logic for linking the 3 targets we currently support using LLD for into this new linker implementation, `link.Lld`; however, it's almost certainly possible to combine some of the logic and simplify this file a bit. But to be honest, it's not actually that bad right now. This commit ends up eliminating the distinction between `flush` and `flushZcu` (formerly `flushModule`) in linkers, where the latter previously meant something along the lines of "flush, but if you're going to be linking with LLD, just flush the ZCU object file, don't actually link"?. The distinction here doesn't seem like it was properly defined, and most linkers seem to treat them as essentially identical anyway. Regardless, all calls to `flushZcu` are gone now, so it's deleted -- one `flush` to rule them all! The end result of this commit and the preceding one is that LLVM and LLD fit into the pipeline much more sanely: * If we're using LLVM for the ZCU, that state is on `zcu.llvm_object` * If we're using LLD to link, then the `link.File` is a `link.Lld` * Calls to "ZCU link functions" (e.g. `updateNav`) lower to calls to the LLVM object if it's available, or otherwise to the `link.File` if it's available (neither is available under `-fno-emit-bin`) * After everything is done, linking is finalized by calling `flush` on the `link.File`; for `link.Lld` this invokes LLD, for other linkers it flushes self-hosted linker state There's one messy thing remaining, and that's how self-hosted function codegen in a ZCU works; right now, we process AIR with a call sequence something like this: * `link.doTask` * `Zcu.PerThread.linkerUpdateFunc` * `link.File.updateFunc` * `link.Elf.updateFunc` * `link.Elf.ZigObject.updateFunc` * `codegen.generateFunction` * `arch.x86_64.CodeGen.generate` So, we start in the linker, take a scenic detour through `Zcu`, go back to the linker, into its implementation, and then... right back out, into code which is generic over the linker implementation, and then dispatch on the *backend* instead! Of course, within `arch.x86_64.CodeGen`, there are some more places which switch on the `link` implementation being used. This is all pretty silly... so it shall be my next target.
894 lines
30 KiB
Zig
894 lines
30 KiB
Zig
const std = @import("std");
|
|
const mem = std.mem;
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const fs = std.fs;
|
|
const Path = std.Build.Cache.Path;
|
|
|
|
const C = @This();
|
|
const build_options = @import("build_options");
|
|
const Zcu = @import("../Zcu.zig");
|
|
const Module = @import("../Package/Module.zig");
|
|
const InternPool = @import("../InternPool.zig");
|
|
const Alignment = InternPool.Alignment;
|
|
const Compilation = @import("../Compilation.zig");
|
|
const codegen = @import("../codegen/c.zig");
|
|
const link = @import("../link.zig");
|
|
const trace = @import("../tracy.zig").trace;
|
|
const Type = @import("../Type.zig");
|
|
const Value = @import("../Value.zig");
|
|
const Air = @import("../Air.zig");
|
|
|
|
pub const zig_h = "#include \"zig.h\"\n";
|
|
|
|
base: link.File,
|
|
/// This linker backend does not try to incrementally link output C source code.
|
|
/// Instead, it tracks all declarations in this table, and iterates over it
|
|
/// in the flush function, stitching pre-rendered pieces of C code together.
|
|
navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, AvBlock) = .empty,
|
|
/// All the string bytes of rendered C code, all squished into one array.
|
|
/// While in progress, a separate buffer is used, and then when finished, the
|
|
/// buffer is copied into this one.
|
|
string_bytes: std.ArrayListUnmanaged(u8) = .empty,
|
|
/// Tracks all the anonymous decls that are used by all the decls so they can
|
|
/// be rendered during flush().
|
|
uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, AvBlock) = .empty,
|
|
/// Sparse set of uavs that are overaligned. Underaligned anon decls are
|
|
/// lowered the same as ABI-aligned anon decls. The keys here are a subset of
|
|
/// the keys of `uavs`.
|
|
aligned_uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, Alignment) = .empty,
|
|
|
|
exported_navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, ExportedBlock) = .empty,
|
|
exported_uavs: std.AutoArrayHashMapUnmanaged(InternPool.Index, ExportedBlock) = .empty,
|
|
|
|
/// Optimization, `updateDecl` reuses this buffer rather than creating a new
|
|
/// one with every call.
|
|
fwd_decl_buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
/// Optimization, `updateDecl` reuses this buffer rather than creating a new
|
|
/// one with every call.
|
|
code_buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
/// Optimization, `flush` reuses this buffer rather than creating a new
|
|
/// one with every call.
|
|
lazy_fwd_decl_buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
/// Optimization, `flush` reuses this buffer rather than creating a new
|
|
/// one with every call.
|
|
lazy_code_buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
|
|
/// A reference into `string_bytes`.
|
|
const String = extern struct {
|
|
start: u32,
|
|
len: u32,
|
|
|
|
const empty: String = .{
|
|
.start = 0,
|
|
.len = 0,
|
|
};
|
|
};
|
|
|
|
/// Per-declaration data.
|
|
pub const AvBlock = struct {
|
|
code: String = String.empty,
|
|
fwd_decl: String = String.empty,
|
|
/// Each `Decl` stores a set of used `CType`s. In `flush()`, we iterate
|
|
/// over each `Decl` and generate the definition for each used `CType` once.
|
|
ctype_pool: codegen.CType.Pool = codegen.CType.Pool.empty,
|
|
/// May contain string references to ctype_pool
|
|
lazy_fns: codegen.LazyFnMap = .{},
|
|
|
|
fn deinit(ab: *AvBlock, gpa: Allocator) void {
|
|
ab.lazy_fns.deinit(gpa);
|
|
ab.ctype_pool.deinit(gpa);
|
|
ab.* = undefined;
|
|
}
|
|
};
|
|
|
|
/// Per-exported-symbol data.
|
|
pub const ExportedBlock = struct {
|
|
fwd_decl: String = String.empty,
|
|
};
|
|
|
|
pub fn getString(this: C, s: String) []const u8 {
|
|
return this.string_bytes.items[s.start..][0..s.len];
|
|
}
|
|
|
|
pub fn addString(this: *C, s: []const u8) Allocator.Error!String {
|
|
const comp = this.base.comp;
|
|
const gpa = comp.gpa;
|
|
try this.string_bytes.appendSlice(gpa, s);
|
|
return .{
|
|
.start = @intCast(this.string_bytes.items.len - s.len),
|
|
.len = @intCast(s.len),
|
|
};
|
|
}
|
|
|
|
pub fn open(
|
|
arena: Allocator,
|
|
comp: *Compilation,
|
|
emit: Path,
|
|
options: link.File.OpenOptions,
|
|
) !*C {
|
|
return createEmpty(arena, comp, emit, options);
|
|
}
|
|
|
|
pub fn createEmpty(
|
|
arena: Allocator,
|
|
comp: *Compilation,
|
|
emit: Path,
|
|
options: link.File.OpenOptions,
|
|
) !*C {
|
|
const target = comp.root_mod.resolved_target.result;
|
|
assert(target.ofmt == .c);
|
|
const optimize_mode = comp.root_mod.optimize_mode;
|
|
const use_lld = build_options.have_llvm and comp.config.use_lld;
|
|
const use_llvm = comp.config.use_llvm;
|
|
const output_mode = comp.config.output_mode;
|
|
|
|
// These are caught by `Compilation.Config.resolve`.
|
|
assert(!use_lld);
|
|
assert(!use_llvm);
|
|
|
|
const file = try emit.root_dir.handle.createFile(emit.sub_path, .{
|
|
// Truncation is done on `flush`.
|
|
.truncate = false,
|
|
});
|
|
errdefer file.close();
|
|
|
|
const c_file = try arena.create(C);
|
|
|
|
c_file.* = .{
|
|
.base = .{
|
|
.tag = .c,
|
|
.comp = comp,
|
|
.emit = emit,
|
|
.gc_sections = options.gc_sections orelse (optimize_mode != .Debug and output_mode != .Obj),
|
|
.print_gc_sections = options.print_gc_sections,
|
|
.stack_size = options.stack_size orelse 16777216,
|
|
.allow_shlib_undefined = options.allow_shlib_undefined orelse false,
|
|
.file = file,
|
|
.build_id = options.build_id,
|
|
},
|
|
};
|
|
|
|
return c_file;
|
|
}
|
|
|
|
pub fn deinit(self: *C) void {
|
|
const gpa = self.base.comp.gpa;
|
|
|
|
for (self.navs.values()) |*db| {
|
|
db.deinit(gpa);
|
|
}
|
|
self.navs.deinit(gpa);
|
|
|
|
for (self.uavs.values()) |*db| {
|
|
db.deinit(gpa);
|
|
}
|
|
self.uavs.deinit(gpa);
|
|
self.aligned_uavs.deinit(gpa);
|
|
|
|
self.string_bytes.deinit(gpa);
|
|
self.fwd_decl_buf.deinit(gpa);
|
|
self.code_buf.deinit(gpa);
|
|
self.lazy_fwd_decl_buf.deinit(gpa);
|
|
self.lazy_code_buf.deinit(gpa);
|
|
}
|
|
|
|
pub fn updateFunc(
|
|
self: *C,
|
|
pt: Zcu.PerThread,
|
|
func_index: InternPool.Index,
|
|
air: Air,
|
|
liveness: Air.Liveness,
|
|
) link.File.UpdateNavError!void {
|
|
const zcu = pt.zcu;
|
|
const gpa = zcu.gpa;
|
|
const func = zcu.funcInfo(func_index);
|
|
const gop = try self.navs.getOrPut(gpa, func.owner_nav);
|
|
if (!gop.found_existing) gop.value_ptr.* = .{};
|
|
const ctype_pool = &gop.value_ptr.ctype_pool;
|
|
const lazy_fns = &gop.value_ptr.lazy_fns;
|
|
const fwd_decl = &self.fwd_decl_buf;
|
|
const code = &self.code_buf;
|
|
try ctype_pool.init(gpa);
|
|
ctype_pool.clearRetainingCapacity();
|
|
lazy_fns.clearRetainingCapacity();
|
|
fwd_decl.clearRetainingCapacity();
|
|
code.clearRetainingCapacity();
|
|
|
|
var function: codegen.Function = .{
|
|
.value_map = codegen.CValueMap.init(gpa),
|
|
.air = air,
|
|
.liveness = liveness,
|
|
.func_index = func_index,
|
|
.object = .{
|
|
.dg = .{
|
|
.gpa = gpa,
|
|
.pt = pt,
|
|
.mod = zcu.navFileScope(func.owner_nav).mod.?,
|
|
.error_msg = null,
|
|
.pass = .{ .nav = func.owner_nav },
|
|
.is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked,
|
|
.expected_block = null,
|
|
.fwd_decl = fwd_decl.toManaged(gpa),
|
|
.ctype_pool = ctype_pool.*,
|
|
.scratch = .{},
|
|
.uav_deps = self.uavs,
|
|
.aligned_uavs = self.aligned_uavs,
|
|
},
|
|
.code = code.toManaged(gpa),
|
|
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
|
},
|
|
.lazy_fns = lazy_fns.*,
|
|
};
|
|
function.object.indent_writer = .{ .underlying_writer = function.object.code.writer() };
|
|
defer {
|
|
self.uavs = function.object.dg.uav_deps;
|
|
self.aligned_uavs = function.object.dg.aligned_uavs;
|
|
fwd_decl.* = function.object.dg.fwd_decl.moveToUnmanaged();
|
|
ctype_pool.* = function.object.dg.ctype_pool.move();
|
|
ctype_pool.freeUnusedCapacity(gpa);
|
|
function.object.dg.scratch.deinit(gpa);
|
|
lazy_fns.* = function.lazy_fns.move();
|
|
lazy_fns.shrinkAndFree(gpa, lazy_fns.count());
|
|
code.* = function.object.code.moveToUnmanaged();
|
|
function.deinit();
|
|
}
|
|
|
|
try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1);
|
|
codegen.genFunc(&function) catch |err| switch (err) {
|
|
error.AnalysisFail => {
|
|
zcu.failed_codegen.putAssumeCapacityNoClobber(func.owner_nav, function.object.dg.error_msg.?);
|
|
return;
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
gop.value_ptr.fwd_decl = try self.addString(function.object.dg.fwd_decl.items);
|
|
gop.value_ptr.code = try self.addString(function.object.code.items);
|
|
}
|
|
|
|
fn updateUav(self: *C, pt: Zcu.PerThread, i: usize) !void {
|
|
const gpa = self.base.comp.gpa;
|
|
const uav = self.uavs.keys()[i];
|
|
|
|
const fwd_decl = &self.fwd_decl_buf;
|
|
const code = &self.code_buf;
|
|
fwd_decl.clearRetainingCapacity();
|
|
code.clearRetainingCapacity();
|
|
|
|
var object: codegen.Object = .{
|
|
.dg = .{
|
|
.gpa = gpa,
|
|
.pt = pt,
|
|
.mod = pt.zcu.root_mod,
|
|
.error_msg = null,
|
|
.pass = .{ .uav = uav },
|
|
.is_naked_fn = false,
|
|
.expected_block = null,
|
|
.fwd_decl = fwd_decl.toManaged(gpa),
|
|
.ctype_pool = codegen.CType.Pool.empty,
|
|
.scratch = .{},
|
|
.uav_deps = self.uavs,
|
|
.aligned_uavs = self.aligned_uavs,
|
|
},
|
|
.code = code.toManaged(gpa),
|
|
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
|
};
|
|
object.indent_writer = .{ .underlying_writer = object.code.writer() };
|
|
defer {
|
|
self.uavs = object.dg.uav_deps;
|
|
self.aligned_uavs = object.dg.aligned_uavs;
|
|
fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
|
|
object.dg.ctype_pool.deinit(object.dg.gpa);
|
|
object.dg.scratch.deinit(gpa);
|
|
code.* = object.code.moveToUnmanaged();
|
|
}
|
|
try object.dg.ctype_pool.init(gpa);
|
|
|
|
const c_value: codegen.CValue = .{ .constant = Value.fromInterned(uav) };
|
|
const alignment: Alignment = self.aligned_uavs.get(uav) orelse .none;
|
|
codegen.genDeclValue(&object, c_value.constant, c_value, alignment, .none) catch |err| switch (err) {
|
|
error.AnalysisFail => {
|
|
@panic("TODO: C backend AnalysisFail on anonymous decl");
|
|
//try zcu.failed_decls.put(gpa, decl_index, object.dg.error_msg.?);
|
|
//return;
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
object.dg.ctype_pool.freeUnusedCapacity(gpa);
|
|
object.dg.uav_deps.values()[i] = .{
|
|
.code = try self.addString(object.code.items),
|
|
.fwd_decl = try self.addString(object.dg.fwd_decl.items),
|
|
.ctype_pool = object.dg.ctype_pool.move(),
|
|
};
|
|
}
|
|
|
|
pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) link.File.UpdateNavError!void {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
const gpa = self.base.comp.gpa;
|
|
const zcu = pt.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
|
|
const nav = ip.getNav(nav_index);
|
|
const nav_init = switch (ip.indexToKey(nav.status.fully_resolved.val)) {
|
|
.func => return,
|
|
.@"extern" => .none,
|
|
.variable => |variable| variable.init,
|
|
else => nav.status.fully_resolved.val,
|
|
};
|
|
if (nav_init != .none and !Value.fromInterned(nav_init).typeOf(zcu).hasRuntimeBits(zcu)) return;
|
|
|
|
const gop = try self.navs.getOrPut(gpa, nav_index);
|
|
errdefer _ = self.navs.pop();
|
|
if (!gop.found_existing) gop.value_ptr.* = .{};
|
|
const ctype_pool = &gop.value_ptr.ctype_pool;
|
|
const fwd_decl = &self.fwd_decl_buf;
|
|
const code = &self.code_buf;
|
|
try ctype_pool.init(gpa);
|
|
ctype_pool.clearRetainingCapacity();
|
|
fwd_decl.clearRetainingCapacity();
|
|
code.clearRetainingCapacity();
|
|
|
|
var object: codegen.Object = .{
|
|
.dg = .{
|
|
.gpa = gpa,
|
|
.pt = pt,
|
|
.mod = zcu.navFileScope(nav_index).mod.?,
|
|
.error_msg = null,
|
|
.pass = .{ .nav = nav_index },
|
|
.is_naked_fn = false,
|
|
.expected_block = null,
|
|
.fwd_decl = fwd_decl.toManaged(gpa),
|
|
.ctype_pool = ctype_pool.*,
|
|
.scratch = .{},
|
|
.uav_deps = self.uavs,
|
|
.aligned_uavs = self.aligned_uavs,
|
|
},
|
|
.code = code.toManaged(gpa),
|
|
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
|
};
|
|
object.indent_writer = .{ .underlying_writer = object.code.writer() };
|
|
defer {
|
|
self.uavs = object.dg.uav_deps;
|
|
self.aligned_uavs = object.dg.aligned_uavs;
|
|
fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
|
|
ctype_pool.* = object.dg.ctype_pool.move();
|
|
ctype_pool.freeUnusedCapacity(gpa);
|
|
object.dg.scratch.deinit(gpa);
|
|
code.* = object.code.moveToUnmanaged();
|
|
}
|
|
|
|
try zcu.failed_codegen.ensureUnusedCapacity(gpa, 1);
|
|
codegen.genDecl(&object) catch |err| switch (err) {
|
|
error.AnalysisFail => {
|
|
zcu.failed_codegen.putAssumeCapacityNoClobber(nav_index, object.dg.error_msg.?);
|
|
return;
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
gop.value_ptr.code = try self.addString(object.code.items);
|
|
gop.value_ptr.fwd_decl = try self.addString(object.dg.fwd_decl.items);
|
|
}
|
|
|
|
pub fn updateLineNumber(self: *C, pt: Zcu.PerThread, ti_id: InternPool.TrackedInst.Index) !void {
|
|
// The C backend does not have the ability to fix line numbers without re-generating
|
|
// the entire Decl.
|
|
_ = self;
|
|
_ = pt;
|
|
_ = ti_id;
|
|
}
|
|
|
|
fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) {
|
|
const gpa = self.base.comp.gpa;
|
|
var defines = std.ArrayList(u8).init(gpa);
|
|
errdefer defines.deinit();
|
|
const writer = defines.writer();
|
|
switch (target.abi) {
|
|
.msvc, .itanium => try writer.writeAll("#define ZIG_TARGET_ABI_MSVC\n"),
|
|
else => {},
|
|
}
|
|
try writer.print("#define ZIG_TARGET_MAX_INT_ALIGNMENT {d}\n", .{
|
|
target.cMaxIntAlignment(),
|
|
});
|
|
return defines;
|
|
}
|
|
|
|
pub fn flush(self: *C, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node) link.File.FlushError!void {
|
|
_ = arena; // Has the same lifetime as the call to Compilation.update.
|
|
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
const sub_prog_node = prog_node.start("Flush Module", 0);
|
|
defer sub_prog_node.end();
|
|
|
|
const comp = self.base.comp;
|
|
const diags = &comp.link_diags;
|
|
const gpa = comp.gpa;
|
|
const zcu = self.base.comp.zcu.?;
|
|
const ip = &zcu.intern_pool;
|
|
const pt: Zcu.PerThread = .activate(zcu, tid);
|
|
defer pt.deactivate();
|
|
|
|
{
|
|
var i: usize = 0;
|
|
while (i < self.uavs.count()) : (i += 1) {
|
|
try self.updateUav(pt, i);
|
|
}
|
|
}
|
|
|
|
// This code path happens exclusively with -ofmt=c. The flush logic for
|
|
// emit-h is in `flushEmitH` below.
|
|
|
|
var f: Flush = .{
|
|
.ctype_pool = codegen.CType.Pool.empty,
|
|
.lazy_ctype_pool = codegen.CType.Pool.empty,
|
|
};
|
|
defer f.deinit(gpa);
|
|
|
|
const abi_defines = try self.abiDefines(zcu.getTarget());
|
|
defer abi_defines.deinit();
|
|
|
|
// Covers defines, zig.h, ctypes, asm, lazy fwd.
|
|
try f.all_buffers.ensureUnusedCapacity(gpa, 5);
|
|
|
|
f.appendBufAssumeCapacity(abi_defines.items);
|
|
f.appendBufAssumeCapacity(zig_h);
|
|
|
|
const ctypes_index = f.all_buffers.items.len;
|
|
f.all_buffers.items.len += 1;
|
|
|
|
{
|
|
var asm_buf = f.asm_buf.toManaged(gpa);
|
|
defer f.asm_buf = asm_buf.moveToUnmanaged();
|
|
try codegen.genGlobalAsm(zcu, asm_buf.writer());
|
|
f.appendBufAssumeCapacity(asm_buf.items);
|
|
}
|
|
|
|
const lazy_index = f.all_buffers.items.len;
|
|
f.all_buffers.items.len += 1;
|
|
|
|
self.lazy_fwd_decl_buf.clearRetainingCapacity();
|
|
self.lazy_code_buf.clearRetainingCapacity();
|
|
try f.lazy_ctype_pool.init(gpa);
|
|
try self.flushErrDecls(pt, &f.lazy_ctype_pool);
|
|
|
|
// Unlike other backends, the .c code we are emitting has order-dependent decls.
|
|
// `CType`s, forward decls, and non-functions first.
|
|
|
|
{
|
|
var export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty;
|
|
defer export_names.deinit(gpa);
|
|
try export_names.ensureTotalCapacity(gpa, @intCast(zcu.single_exports.count()));
|
|
for (zcu.single_exports.values()) |export_index| {
|
|
export_names.putAssumeCapacity(export_index.ptr(zcu).opts.name, {});
|
|
}
|
|
for (zcu.multi_exports.values()) |info| {
|
|
try export_names.ensureUnusedCapacity(gpa, info.len);
|
|
for (zcu.all_exports.items[info.index..][0..info.len]) |@"export"| {
|
|
export_names.putAssumeCapacity(@"export".opts.name, {});
|
|
}
|
|
}
|
|
|
|
for (self.uavs.keys(), self.uavs.values()) |uav, *av_block| try self.flushAvBlock(
|
|
pt,
|
|
zcu.root_mod,
|
|
&f,
|
|
av_block,
|
|
self.exported_uavs.getPtr(uav),
|
|
export_names,
|
|
.none,
|
|
);
|
|
|
|
for (self.navs.keys(), self.navs.values()) |nav, *av_block| try self.flushAvBlock(
|
|
pt,
|
|
zcu.navFileScope(nav).mod.?,
|
|
&f,
|
|
av_block,
|
|
self.exported_navs.getPtr(nav),
|
|
export_names,
|
|
if (ip.getNav(nav).getExtern(ip) != null)
|
|
ip.getNav(nav).name.toOptional()
|
|
else
|
|
.none,
|
|
);
|
|
}
|
|
|
|
{
|
|
// We need to flush lazy ctypes after flushing all decls but before flushing any decl ctypes.
|
|
// This ensures that every lazy CType.Index exactly matches the global CType.Index.
|
|
try f.ctype_pool.init(gpa);
|
|
try self.flushCTypes(zcu, &f, .flush, &f.lazy_ctype_pool);
|
|
|
|
for (self.uavs.keys(), self.uavs.values()) |uav, av_block| {
|
|
try self.flushCTypes(zcu, &f, .{ .uav = uav }, &av_block.ctype_pool);
|
|
}
|
|
|
|
for (self.navs.keys(), self.navs.values()) |nav, av_block| {
|
|
try self.flushCTypes(zcu, &f, .{ .nav = nav }, &av_block.ctype_pool);
|
|
}
|
|
}
|
|
|
|
f.all_buffers.items[ctypes_index] = .{
|
|
.base = if (f.ctypes_buf.items.len > 0) f.ctypes_buf.items.ptr else "",
|
|
.len = f.ctypes_buf.items.len,
|
|
};
|
|
f.file_size += f.ctypes_buf.items.len;
|
|
|
|
const lazy_fwd_decl_len = self.lazy_fwd_decl_buf.items.len;
|
|
f.all_buffers.items[lazy_index] = .{
|
|
.base = if (lazy_fwd_decl_len > 0) self.lazy_fwd_decl_buf.items.ptr else "",
|
|
.len = lazy_fwd_decl_len,
|
|
};
|
|
f.file_size += lazy_fwd_decl_len;
|
|
|
|
// Now the code.
|
|
try f.all_buffers.ensureUnusedCapacity(gpa, 1 + (self.uavs.count() + self.navs.count()) * 2);
|
|
f.appendBufAssumeCapacity(self.lazy_code_buf.items);
|
|
for (self.uavs.keys(), self.uavs.values()) |uav, av_block| f.appendCodeAssumeCapacity(
|
|
if (self.exported_uavs.contains(uav)) .default else switch (ip.indexToKey(uav)) {
|
|
.@"extern" => .zig_extern,
|
|
else => .static,
|
|
},
|
|
self.getString(av_block.code),
|
|
);
|
|
for (self.navs.keys(), self.navs.values()) |nav, av_block| f.appendCodeAssumeCapacity(storage: {
|
|
if (self.exported_navs.contains(nav)) break :storage .default;
|
|
if (ip.getNav(nav).getExtern(ip) != null) break :storage .zig_extern;
|
|
break :storage .static;
|
|
}, self.getString(av_block.code));
|
|
|
|
const file = self.base.file.?;
|
|
file.setEndPos(f.file_size) catch |err| return diags.fail("failed to allocate file: {s}", .{@errorName(err)});
|
|
file.pwritevAll(f.all_buffers.items, 0) catch |err| return diags.fail("failed to write to '{'}': {s}", .{
|
|
self.base.emit, @errorName(err),
|
|
});
|
|
}
|
|
|
|
const Flush = struct {
|
|
ctype_pool: codegen.CType.Pool,
|
|
ctype_global_from_decl_map: std.ArrayListUnmanaged(codegen.CType) = .empty,
|
|
ctypes_buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
|
|
lazy_ctype_pool: codegen.CType.Pool,
|
|
lazy_fns: LazyFns = .{},
|
|
|
|
asm_buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
|
|
/// We collect a list of buffers to write, and write them all at once with pwritev 😎
|
|
all_buffers: std.ArrayListUnmanaged(std.posix.iovec_const) = .empty,
|
|
/// Keeps track of the total bytes of `all_buffers`.
|
|
file_size: u64 = 0,
|
|
|
|
const LazyFns = std.AutoHashMapUnmanaged(codegen.LazyFnKey, void);
|
|
|
|
fn appendBufAssumeCapacity(f: *Flush, buf: []const u8) void {
|
|
if (buf.len == 0) return;
|
|
f.all_buffers.appendAssumeCapacity(.{ .base = buf.ptr, .len = buf.len });
|
|
f.file_size += buf.len;
|
|
}
|
|
|
|
fn appendCodeAssumeCapacity(f: *Flush, storage: enum { default, zig_extern, static }, code: []const u8) void {
|
|
if (code.len == 0) return;
|
|
f.appendBufAssumeCapacity(switch (storage) {
|
|
.default => "\n",
|
|
.zig_extern => "\nzig_extern ",
|
|
.static => "\nstatic ",
|
|
});
|
|
f.appendBufAssumeCapacity(code);
|
|
}
|
|
|
|
fn deinit(f: *Flush, gpa: Allocator) void {
|
|
f.all_buffers.deinit(gpa);
|
|
f.asm_buf.deinit(gpa);
|
|
f.lazy_fns.deinit(gpa);
|
|
f.lazy_ctype_pool.deinit(gpa);
|
|
f.ctypes_buf.deinit(gpa);
|
|
assert(f.ctype_global_from_decl_map.items.len == 0);
|
|
f.ctype_global_from_decl_map.deinit(gpa);
|
|
f.ctype_pool.deinit(gpa);
|
|
}
|
|
};
|
|
|
|
const FlushDeclError = error{
|
|
OutOfMemory,
|
|
};
|
|
|
|
fn flushCTypes(
|
|
self: *C,
|
|
zcu: *Zcu,
|
|
f: *Flush,
|
|
pass: codegen.DeclGen.Pass,
|
|
decl_ctype_pool: *const codegen.CType.Pool,
|
|
) FlushDeclError!void {
|
|
const gpa = self.base.comp.gpa;
|
|
const global_ctype_pool = &f.ctype_pool;
|
|
|
|
const global_from_decl_map = &f.ctype_global_from_decl_map;
|
|
assert(global_from_decl_map.items.len == 0);
|
|
try global_from_decl_map.ensureTotalCapacity(gpa, decl_ctype_pool.items.len);
|
|
defer global_from_decl_map.clearRetainingCapacity();
|
|
|
|
var ctypes_buf = f.ctypes_buf.toManaged(gpa);
|
|
defer f.ctypes_buf = ctypes_buf.moveToUnmanaged();
|
|
const writer = ctypes_buf.writer();
|
|
|
|
for (0..decl_ctype_pool.items.len) |decl_ctype_pool_index| {
|
|
const PoolAdapter = struct {
|
|
global_from_decl_map: []const codegen.CType,
|
|
pub fn eql(pool_adapter: @This(), decl_ctype: codegen.CType, global_ctype: codegen.CType) bool {
|
|
return if (decl_ctype.toPoolIndex()) |decl_pool_index|
|
|
decl_pool_index < pool_adapter.global_from_decl_map.len and
|
|
pool_adapter.global_from_decl_map[decl_pool_index].eql(global_ctype)
|
|
else
|
|
decl_ctype.index == global_ctype.index;
|
|
}
|
|
pub fn copy(pool_adapter: @This(), decl_ctype: codegen.CType) codegen.CType {
|
|
return if (decl_ctype.toPoolIndex()) |decl_pool_index|
|
|
pool_adapter.global_from_decl_map[decl_pool_index]
|
|
else
|
|
decl_ctype;
|
|
}
|
|
};
|
|
const decl_ctype = codegen.CType.fromPoolIndex(decl_ctype_pool_index);
|
|
const global_ctype, const found_existing = try global_ctype_pool.getOrPutAdapted(
|
|
gpa,
|
|
decl_ctype_pool,
|
|
decl_ctype,
|
|
PoolAdapter{ .global_from_decl_map = global_from_decl_map.items },
|
|
);
|
|
global_from_decl_map.appendAssumeCapacity(global_ctype);
|
|
try codegen.genTypeDecl(
|
|
zcu,
|
|
writer,
|
|
global_ctype_pool,
|
|
global_ctype,
|
|
pass,
|
|
decl_ctype_pool,
|
|
decl_ctype,
|
|
found_existing,
|
|
);
|
|
}
|
|
}
|
|
|
|
fn flushErrDecls(self: *C, pt: Zcu.PerThread, ctype_pool: *codegen.CType.Pool) FlushDeclError!void {
|
|
const gpa = self.base.comp.gpa;
|
|
|
|
const fwd_decl = &self.lazy_fwd_decl_buf;
|
|
const code = &self.lazy_code_buf;
|
|
|
|
var object = codegen.Object{
|
|
.dg = .{
|
|
.gpa = gpa,
|
|
.pt = pt,
|
|
.mod = pt.zcu.root_mod,
|
|
.error_msg = null,
|
|
.pass = .flush,
|
|
.is_naked_fn = false,
|
|
.expected_block = null,
|
|
.fwd_decl = fwd_decl.toManaged(gpa),
|
|
.ctype_pool = ctype_pool.*,
|
|
.scratch = .{},
|
|
.uav_deps = self.uavs,
|
|
.aligned_uavs = self.aligned_uavs,
|
|
},
|
|
.code = code.toManaged(gpa),
|
|
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
|
};
|
|
object.indent_writer = .{ .underlying_writer = object.code.writer() };
|
|
defer {
|
|
self.uavs = object.dg.uav_deps;
|
|
self.aligned_uavs = object.dg.aligned_uavs;
|
|
fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
|
|
ctype_pool.* = object.dg.ctype_pool.move();
|
|
ctype_pool.freeUnusedCapacity(gpa);
|
|
object.dg.scratch.deinit(gpa);
|
|
code.* = object.code.moveToUnmanaged();
|
|
}
|
|
|
|
codegen.genErrDecls(&object) catch |err| switch (err) {
|
|
error.AnalysisFail => unreachable,
|
|
else => |e| return e,
|
|
};
|
|
}
|
|
|
|
fn flushLazyFn(
|
|
self: *C,
|
|
pt: Zcu.PerThread,
|
|
mod: *Module,
|
|
ctype_pool: *codegen.CType.Pool,
|
|
lazy_ctype_pool: *const codegen.CType.Pool,
|
|
lazy_fn: codegen.LazyFnMap.Entry,
|
|
) FlushDeclError!void {
|
|
const gpa = self.base.comp.gpa;
|
|
|
|
const fwd_decl = &self.lazy_fwd_decl_buf;
|
|
const code = &self.lazy_code_buf;
|
|
|
|
var object = codegen.Object{
|
|
.dg = .{
|
|
.gpa = gpa,
|
|
.pt = pt,
|
|
.mod = mod,
|
|
.error_msg = null,
|
|
.pass = .flush,
|
|
.is_naked_fn = false,
|
|
.expected_block = null,
|
|
.fwd_decl = fwd_decl.toManaged(gpa),
|
|
.ctype_pool = ctype_pool.*,
|
|
.scratch = .{},
|
|
.uav_deps = .{},
|
|
.aligned_uavs = .{},
|
|
},
|
|
.code = code.toManaged(gpa),
|
|
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
|
};
|
|
object.indent_writer = .{ .underlying_writer = object.code.writer() };
|
|
defer {
|
|
// If this assert trips just handle the anon_decl_deps the same as
|
|
// `updateFunc()` does.
|
|
assert(object.dg.uav_deps.count() == 0);
|
|
assert(object.dg.aligned_uavs.count() == 0);
|
|
fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
|
|
ctype_pool.* = object.dg.ctype_pool.move();
|
|
ctype_pool.freeUnusedCapacity(gpa);
|
|
object.dg.scratch.deinit(gpa);
|
|
code.* = object.code.moveToUnmanaged();
|
|
}
|
|
|
|
codegen.genLazyFn(&object, lazy_ctype_pool, lazy_fn) catch |err| switch (err) {
|
|
error.AnalysisFail => unreachable,
|
|
else => |e| return e,
|
|
};
|
|
}
|
|
|
|
fn flushLazyFns(
|
|
self: *C,
|
|
pt: Zcu.PerThread,
|
|
mod: *Module,
|
|
f: *Flush,
|
|
lazy_ctype_pool: *const codegen.CType.Pool,
|
|
lazy_fns: codegen.LazyFnMap,
|
|
) FlushDeclError!void {
|
|
const gpa = self.base.comp.gpa;
|
|
try f.lazy_fns.ensureUnusedCapacity(gpa, @intCast(lazy_fns.count()));
|
|
|
|
var it = lazy_fns.iterator();
|
|
while (it.next()) |entry| {
|
|
const gop = f.lazy_fns.getOrPutAssumeCapacity(entry.key_ptr.*);
|
|
if (gop.found_existing) continue;
|
|
gop.value_ptr.* = {};
|
|
try self.flushLazyFn(pt, mod, &f.lazy_ctype_pool, lazy_ctype_pool, entry);
|
|
}
|
|
}
|
|
|
|
fn flushAvBlock(
|
|
self: *C,
|
|
pt: Zcu.PerThread,
|
|
mod: *Module,
|
|
f: *Flush,
|
|
av_block: *const AvBlock,
|
|
exported_block: ?*const ExportedBlock,
|
|
export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void),
|
|
extern_name: InternPool.OptionalNullTerminatedString,
|
|
) FlushDeclError!void {
|
|
const gpa = self.base.comp.gpa;
|
|
try self.flushLazyFns(pt, mod, f, &av_block.ctype_pool, av_block.lazy_fns);
|
|
try f.all_buffers.ensureUnusedCapacity(gpa, 1);
|
|
// avoid emitting extern decls that are already exported
|
|
if (extern_name.unwrap()) |name| if (export_names.contains(name)) return;
|
|
f.appendBufAssumeCapacity(self.getString(if (exported_block) |exported|
|
|
exported.fwd_decl
|
|
else
|
|
av_block.fwd_decl));
|
|
}
|
|
|
|
pub fn flushEmitH(zcu: *Zcu) !void {
|
|
const tracy = trace(@src());
|
|
defer tracy.end();
|
|
|
|
if (true) return; // emit-h is regressed
|
|
|
|
const emit_h = zcu.emit_h orelse return;
|
|
|
|
// We collect a list of buffers to write, and write them all at once with pwritev 😎
|
|
const num_buffers = emit_h.decl_table.count() + 1;
|
|
var all_buffers = try std.ArrayList(std.posix.iovec_const).initCapacity(zcu.gpa, num_buffers);
|
|
defer all_buffers.deinit();
|
|
|
|
var file_size: u64 = zig_h.len;
|
|
if (zig_h.len != 0) {
|
|
all_buffers.appendAssumeCapacity(.{
|
|
.base = zig_h,
|
|
.len = zig_h.len,
|
|
});
|
|
}
|
|
|
|
for (emit_h.decl_table.keys()) |decl_index| {
|
|
const decl_emit_h = emit_h.declPtr(decl_index);
|
|
const buf = decl_emit_h.fwd_decl.items;
|
|
if (buf.len != 0) {
|
|
all_buffers.appendAssumeCapacity(.{
|
|
.base = buf.ptr,
|
|
.len = buf.len,
|
|
});
|
|
file_size += buf.len;
|
|
}
|
|
}
|
|
|
|
const directory = emit_h.loc.directory orelse zcu.comp.local_cache_directory;
|
|
const file = try directory.handle.createFile(emit_h.loc.basename, .{
|
|
// We set the end position explicitly below; by not truncating the file, we possibly
|
|
// make it easier on the file system by doing 1 reallocation instead of two.
|
|
.truncate = false,
|
|
});
|
|
defer file.close();
|
|
|
|
try file.setEndPos(file_size);
|
|
try file.pwritevAll(all_buffers.items, 0);
|
|
}
|
|
|
|
pub fn updateExports(
|
|
self: *C,
|
|
pt: Zcu.PerThread,
|
|
exported: Zcu.Exported,
|
|
export_indices: []const Zcu.Export.Index,
|
|
) !void {
|
|
const zcu = pt.zcu;
|
|
const gpa = zcu.gpa;
|
|
const mod, const pass: codegen.DeclGen.Pass, const decl_block, const exported_block = switch (exported) {
|
|
.nav => |nav| .{
|
|
zcu.navFileScope(nav).mod.?,
|
|
.{ .nav = nav },
|
|
self.navs.getPtr(nav).?,
|
|
(try self.exported_navs.getOrPut(gpa, nav)).value_ptr,
|
|
},
|
|
.uav => |uav| .{
|
|
zcu.root_mod,
|
|
.{ .uav = uav },
|
|
self.uavs.getPtr(uav).?,
|
|
(try self.exported_uavs.getOrPut(gpa, uav)).value_ptr,
|
|
},
|
|
};
|
|
const ctype_pool = &decl_block.ctype_pool;
|
|
const fwd_decl = &self.fwd_decl_buf;
|
|
fwd_decl.clearRetainingCapacity();
|
|
var dg: codegen.DeclGen = .{
|
|
.gpa = gpa,
|
|
.pt = pt,
|
|
.mod = mod,
|
|
.error_msg = null,
|
|
.pass = pass,
|
|
.is_naked_fn = false,
|
|
.expected_block = null,
|
|
.fwd_decl = fwd_decl.toManaged(gpa),
|
|
.ctype_pool = decl_block.ctype_pool,
|
|
.scratch = .{},
|
|
.uav_deps = .{},
|
|
.aligned_uavs = .{},
|
|
};
|
|
defer {
|
|
assert(dg.uav_deps.count() == 0);
|
|
assert(dg.aligned_uavs.count() == 0);
|
|
fwd_decl.* = dg.fwd_decl.moveToUnmanaged();
|
|
ctype_pool.* = dg.ctype_pool.move();
|
|
ctype_pool.freeUnusedCapacity(gpa);
|
|
dg.scratch.deinit(gpa);
|
|
}
|
|
try codegen.genExports(&dg, exported, export_indices);
|
|
exported_block.* = .{ .fwd_decl = try self.addString(dg.fwd_decl.items) };
|
|
}
|
|
|
|
pub fn deleteExport(
|
|
self: *C,
|
|
exported: Zcu.Exported,
|
|
_: InternPool.NullTerminatedString,
|
|
) void {
|
|
switch (exported) {
|
|
.nav => |nav| _ = self.exported_navs.swapRemove(nav),
|
|
.uav => |uav| _ = self.exported_uavs.swapRemove(uav),
|
|
}
|
|
}
|