diff --git a/src/Sema.zig b/src/Sema.zig index df7ad3cd64..fafde99f47 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -117,6 +117,10 @@ maybe_comptime_allocs: std.AutoHashMapUnmanaged(Air.Inst.Index, MaybeComptimeAll /// Backed by gpa. comptime_allocs: std.ArrayListUnmanaged(ComptimeAlloc) = .{}, +/// A list of exports performed by this analysis. After this `Sema` terminates, +/// these are flushed to `Zcu.single_exports` or `Zcu.multi_exports`. +exports: std.ArrayListUnmanaged(Zcu.Export) = .{}, + const MaybeComptimeAlloc = struct { /// The runtime index of the `alloc` instruction. runtime_index: Value.RuntimeIndex, @@ -186,6 +190,7 @@ const build_options = @import("build_options"); const Compilation = @import("Compilation.zig"); const InternPool = @import("InternPool.zig"); const Alignment = InternPool.Alignment; +const AnalUnit = InternPool.AnalUnit; const ComptimeAllocIndex = InternPool.ComptimeAllocIndex; pub const default_branch_quota = 1000; @@ -875,6 +880,7 @@ pub fn deinit(sema: *Sema) void { sema.base_allocs.deinit(gpa); sema.maybe_comptime_allocs.deinit(gpa); sema.comptime_allocs.deinit(gpa); + sema.exports.deinit(gpa); sema.* = undefined; } @@ -2735,12 +2741,12 @@ fn maybeRemoveOutdatedType(sema: *Sema, ty: InternPool.Index) !bool { if (!zcu.comp.debug_incremental) return false; const decl_index = Type.fromInterned(ty).getOwnerDecl(zcu); - const decl_as_depender = InternPool.AnalUnit.wrap(.{ .decl = decl_index }); + const decl_as_depender = AnalUnit.wrap(.{ .decl = decl_index }); const was_outdated = zcu.outdated.swapRemove(decl_as_depender) or zcu.potentially_outdated.swapRemove(decl_as_depender); if (!was_outdated) return false; _ = zcu.outdated_ready.swapRemove(decl_as_depender); - zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index })); + zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{ .decl = decl_index })); zcu.intern_pool.remove(ty); zcu.declPtr(decl_index).analysis = .dependency_failure; try zcu.markDependeeOutdated(.{ .decl_val = decl_index }); @@ -2834,7 +2840,7 @@ fn zirStructDecl( if (sema.mod.comp.debug_incremental) { try ip.addDependency( sema.gpa, - InternPool.AnalUnit.wrap(.{ .decl = new_decl_index }), + AnalUnit.wrap(.{ .decl = new_decl_index }), .{ .src_hash = try ip.trackZir(sema.gpa, block.getFileScope(mod), inst) }, ); } @@ -3068,7 +3074,7 @@ fn zirEnumDecl( if (sema.mod.comp.debug_incremental) { try mod.intern_pool.addDependency( sema.gpa, - InternPool.AnalUnit.wrap(.{ .decl = new_decl_index }), + AnalUnit.wrap(.{ .decl = new_decl_index }), .{ .src_hash = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst) }, ); } @@ -3334,7 +3340,7 @@ fn zirUnionDecl( if (sema.mod.comp.debug_incremental) { try mod.intern_pool.addDependency( sema.gpa, - InternPool.AnalUnit.wrap(.{ .decl = new_decl_index }), + AnalUnit.wrap(.{ .decl = new_decl_index }), .{ .src_hash = try mod.intern_pool.trackZir(sema.gpa, block.getFileScope(mod), inst) }, ); } @@ -3422,7 +3428,7 @@ fn zirOpaqueDecl( if (sema.mod.comp.debug_incremental) { try ip.addDependency( gpa, - InternPool.AnalUnit.wrap(.{ .decl = new_decl_index }), + AnalUnit.wrap(.{ .decl = new_decl_index }), .{ .src_hash = try ip.trackZir(gpa, block.getFileScope(mod), inst) }, ); } @@ -6423,10 +6429,9 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError return sema.analyzeExport(block, src, options, decl_index); } - try addExport(mod, .{ + try sema.exports.append(mod.gpa, .{ .opts = options, .src = src, - .owner_decl = sema.owner_decl_index, .exported = .{ .value = operand.toIntern() }, .status = .in_progress, }); @@ -6469,46 +6474,14 @@ pub fn analyzeExport( try sema.maybeQueueFuncBodyAnalysis(exported_decl_index); - try addExport(mod, .{ + try sema.exports.append(gpa, .{ .opts = options, .src = src, - .owner_decl = sema.owner_decl_index, .exported = .{ .decl_index = exported_decl_index }, .status = .in_progress, }); } -fn addExport(mod: *Module, export_init: Module.Export) error{OutOfMemory}!void { - const gpa = mod.gpa; - - try mod.decl_exports.ensureUnusedCapacity(gpa, 1); - try mod.value_exports.ensureUnusedCapacity(gpa, 1); - try mod.export_owners.ensureUnusedCapacity(gpa, 1); - - const new_export = try gpa.create(Module.Export); - errdefer gpa.destroy(new_export); - - new_export.* = export_init; - - const eo_gop = mod.export_owners.getOrPutAssumeCapacity(export_init.owner_decl); - if (!eo_gop.found_existing) eo_gop.value_ptr.* = .{}; - try eo_gop.value_ptr.append(gpa, new_export); - errdefer _ = eo_gop.value_ptr.pop(); - - switch (export_init.exported) { - .decl_index => |decl_index| { - const de_gop = mod.decl_exports.getOrPutAssumeCapacity(decl_index); - if (!de_gop.found_existing) de_gop.value_ptr.* = .{}; - try de_gop.value_ptr.append(gpa, new_export); - }, - .value => |value| { - const ve_gop = mod.value_exports.getOrPutAssumeCapacity(value); - if (!ve_gop.found_existing) ve_gop.value_ptr.* = .{}; - try ve_gop.value_ptr.append(gpa, new_export); - }, - } -} - fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { const mod = sema.mod; const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; @@ -8411,6 +8384,9 @@ fn instantiateGenericCall( }); sema.appendRefsAssumeCapacity(runtime_args.items); + // `child_sema` is owned by us, so just take its exports. + try sema.exports.appendSlice(sema.gpa, child_sema.exports.items); + if (ensure_result_used) { try sema.ensureResultUsed(block, sema.typeOf(result), call_src); } @@ -35263,6 +35239,8 @@ fn semaBackingIntType(mod: *Module, struct_type: InternPool.LoadedStructType) Co const backing_int_ty = try mod.intType(.unsigned, @intCast(fields_bit_sum)); struct_type.backingIntType(ip).* = backing_int_ty.toIntern(); } + + try sema.flushExports(); } fn checkBackingIntType(sema: *Sema, block: *Block, src: LazySrcLoc, backing_int_ty: Type, fields_bit_sum: u64) CompileError!void { @@ -36225,6 +36203,8 @@ fn semaStructFields( struct_type.clearTypesWip(ip); if (!any_inits) struct_type.setHaveFieldInits(ip); + + try sema.flushExports(); } // This logic must be kept in sync with `semaStructFields` @@ -36365,6 +36345,8 @@ fn semaStructFieldInits( struct_type.field_inits.get(ip)[field_i] = default_val.toIntern(); } } + + try sema.flushExports(); } fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.LoadedUnionType) CompileError!void { @@ -36738,6 +36720,8 @@ fn semaUnionFields(mod: *Module, arena: Allocator, union_type: InternPool.Loaded const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, mod.declPtr(union_type.decl)); union_type.tagTypePtr(ip).* = enum_ty; } + + try sema.flushExports(); } fn semaUnionFieldVal(sema: *Sema, block: *Block, src: LazySrcLoc, int_tag_ty: Type, tag_ref: Air.Inst.Ref) CompileError!Value { @@ -38362,7 +38346,7 @@ pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void { return; } - const depender = InternPool.AnalUnit.wrap( + const depender = AnalUnit.wrap( if (sema.owner_func_index != .none) .{ .func = sema.owner_func_index } else @@ -38494,6 +38478,52 @@ fn analyzeUnreachable(sema: *Sema, block: *Block, src: LazySrcLoc, safety_check: } } +/// This should be called exactly once, at the end of a `Sema`'s lifetime. +/// It takes the exports stored in `sema.export` and flushes them to the `Zcu` +/// to be processed by the linker after the update. +pub fn flushExports(sema: *Sema) !void { + if (sema.exports.items.len == 0) return; + + const zcu = sema.mod; + const gpa = zcu.gpa; + + const unit: AnalUnit = if (sema.owner_func_index != .none) + AnalUnit.wrap(.{ .func = sema.owner_func_index }) + else + AnalUnit.wrap(.{ .decl = sema.owner_decl_index }); + + // There may be existing exports. For instance, a struct may export + // things during both field type resolution and field default resolution. + // + // So, pick up and delete any existing exports. This strategy performs + // redundant work, but that's okay, because this case is exceedingly rare. + if (zcu.single_exports.get(unit)) |export_idx| { + try sema.exports.append(gpa, zcu.all_exports.items[export_idx]); + } else if (zcu.multi_exports.get(unit)) |info| { + try sema.exports.appendSlice(gpa, zcu.all_exports.items[info.index..][0..info.len]); + } + zcu.deleteUnitExports(unit); + + // `sema.exports` is completed; store the data into the `Zcu`. + if (sema.exports.items.len == 1) { + try zcu.single_exports.ensureUnusedCapacity(gpa, 1); + const export_idx = zcu.free_exports.popOrNull() orelse idx: { + _ = try zcu.all_exports.addOne(gpa); + break :idx zcu.all_exports.items.len - 1; + }; + zcu.all_exports.items[export_idx] = sema.exports.items[0]; + zcu.single_exports.putAssumeCapacityNoClobber(unit, @intCast(export_idx)); + } else { + try zcu.multi_exports.ensureUnusedCapacity(gpa, 1); + const exports_base = zcu.all_exports.items.len; + try zcu.all_exports.appendSlice(gpa, sema.exports.items); + zcu.multi_exports.putAssumeCapacityNoClobber(unit, .{ + .index = @intCast(exports_base), + .len = @intCast(sema.exports.items.len), + }); + } +} + pub const bitCastVal = @import("Sema/bitcast.zig").bitCast; pub const bitCastSpliceVal = @import("Sema/bitcast.zig").bitCastSplice; diff --git a/src/Zcu.zig b/src/Zcu.zig index f776879df3..3a329f0b03 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -35,6 +35,7 @@ const isUpDir = @import("introspect.zig").isUpDir; const clang = @import("clang.zig"); const InternPool = @import("InternPool.zig"); const Alignment = InternPool.Alignment; +const AnalUnit = InternPool.AnalUnit; const BuiltinFn = std.zig.BuiltinFn; const LlvmObject = @import("codegen/llvm.zig").Object; @@ -71,18 +72,22 @@ codegen_prog_node: std.Progress.Node = undefined, global_zir_cache: Compilation.Directory, /// Used by AstGen worker to load and store ZIR cache. local_zir_cache: Compilation.Directory, -/// It's rare for a decl to be exported, so we save memory by having a sparse -/// map of Decl indexes to details about them being exported. -/// The Export memory is owned by the `export_owners` table; the slice itself -/// is owned by this table. The slice is guaranteed to not be empty. -decl_exports: std.AutoArrayHashMapUnmanaged(Decl.Index, ArrayListUnmanaged(*Export)) = .{}, -/// Same as `decl_exports` but for exported constant values. -value_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, ArrayListUnmanaged(*Export)) = .{}, -/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl -/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that -/// is performing the export of another Decl. -/// This table owns the Export memory. -export_owners: std.AutoArrayHashMapUnmanaged(Decl.Index, ArrayListUnmanaged(*Export)) = .{}, +/// This is where all `Export` values are stored. Not all values here are necessarily valid exports; +/// to enumerate all exports, `single_exports` and `multi_exports` must be consulted. +all_exports: ArrayListUnmanaged(Export) = .{}, +/// This is a list of free indices in `all_exports`. These indices may be reused by exports from +/// future semantic analysis. +free_exports: ArrayListUnmanaged(u32) = .{}, +/// Maps from an `AnalUnit` which performs a single export, to the index into `all_exports` of +/// the export it performs. Note that the key is not the `Decl` being exported, but the `AnalUnit` +/// whose analysis triggered the export. +single_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, +/// Like `single_exports`, but for `AnalUnit`s which perform multiple exports. +/// The exports are `all_exports.items[index..][0..len]`. +multi_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct { + index: u32, + len: u32, +}) = .{}, /// The set of all the Zig source files in the Module. We keep track of this in order /// to iterate over it and check which source files have been modified on the file system when /// an update is requested, as well as to cache `@import` results. @@ -126,9 +131,8 @@ compile_log_decls: std.AutoArrayHashMapUnmanaged(Decl.Index, extern struct { failed_files: std.AutoArrayHashMapUnmanaged(*File, ?*ErrorMsg) = .{}, /// The ErrorMsg memory is owned by the `EmbedFile`, using Module's general purpose allocator. failed_embed_files: std.AutoArrayHashMapUnmanaged(*EmbedFile, *ErrorMsg) = .{}, -/// Using a map here for consistency with the other fields here. -/// The ErrorMsg memory is owned by the `Export`, using Module's general purpose allocator. -failed_exports: std.AutoArrayHashMapUnmanaged(*Export, *ErrorMsg) = .{}, +/// Key is index into `all_exports`. +failed_exports: std.AutoArrayHashMapUnmanaged(u32, *ErrorMsg) = .{}, /// If a decl failed due to a cimport error, the corresponding Clang errors /// are stored here. cimport_errors: std.AutoArrayHashMapUnmanaged(Decl.Index, std.zig.ErrorBundle) = .{}, @@ -140,14 +144,14 @@ global_error_set: GlobalErrorSet = .{}, error_limit: ErrorInt, /// Value is the number of PO or outdated Decls which this AnalUnit depends on. -potentially_outdated: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, u32) = .{}, +potentially_outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, /// Value is the number of PO or outdated Decls which this AnalUnit depends on. /// Once this value drops to 0, the AnalUnit is a candidate for re-analysis. -outdated: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, u32) = .{}, +outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .{}, /// This contains all `AnalUnit`s in `outdated` whose PO dependency count is 0. /// Such `AnalUnit`s are ready for immediate re-analysis. /// See `findOutdatedToAnalyze` for details. -outdated_ready: std.AutoArrayHashMapUnmanaged(InternPool.AnalUnit, void) = .{}, +outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .{}, /// This contains a set of Decls which may not be in `outdated`, but are the /// root Decls of files which have updated source and thus must be re-analyzed. /// If such a Decl is only in this set, the struct type index may be preserved @@ -158,7 +162,7 @@ outdated_file_root: std.AutoArrayHashMapUnmanaged(Decl.Index, void) = .{}, /// failure was something like running out of disk space, and trying again may /// succeed. On the next update, we will flush this list, marking all members of /// it as outdated. -retryable_failures: std.ArrayListUnmanaged(InternPool.AnalUnit) = .{}, +retryable_failures: std.ArrayListUnmanaged(AnalUnit) = .{}, stage1_flags: packed struct { have_winmain: bool = false, @@ -267,8 +271,6 @@ pub const Exported = union(enum) { pub const Export = struct { opts: Options, src: LazySrcLoc, - /// The Decl that performs the export. Note that this is *not* the Decl being exported. - owner_decl: Decl.Index, exported: Exported, status: enum { in_progress, @@ -2507,20 +2509,10 @@ pub fn deinit(zcu: *Zcu) void { zcu.compile_log_decls.deinit(gpa); - for (zcu.decl_exports.values()) |*export_list| { - export_list.deinit(gpa); - } - zcu.decl_exports.deinit(gpa); - - for (zcu.value_exports.values()) |*export_list| { - export_list.deinit(gpa); - } - zcu.value_exports.deinit(gpa); - - for (zcu.export_owners.values()) |*value| { - freeExportList(gpa, value); - } - zcu.export_owners.deinit(gpa); + zcu.all_exports.deinit(gpa); + zcu.free_exports.deinit(gpa); + zcu.single_exports.deinit(gpa); + zcu.multi_exports.deinit(gpa); zcu.global_error_set.deinit(gpa); @@ -2590,11 +2582,6 @@ pub fn declIsRoot(mod: *Module, decl_index: Decl.Index) bool { return decl_index == namespace.decl_index; } -fn freeExportList(gpa: Allocator, export_list: *ArrayListUnmanaged(*Export)) void { - for (export_list.items) |exp| gpa.destroy(exp); - export_list.deinit(gpa); -} - // TODO https://github.com/ziglang/zig/issues/8643 const data_has_safety_tag = @sizeOf(Zir.Inst.Data) != 8; const HackDataLayout = extern struct { @@ -3139,7 +3126,7 @@ fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { /// Given a AnalUnit which is newly outdated or PO, mark all AnalUnits which may /// in turn be PO, due to a dependency on the original AnalUnit's tyval or IES. -fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: InternPool.AnalUnit) !void { +fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) !void { var it = zcu.intern_pool.dependencyIterator(switch (maybe_outdated.unwrap()) { .decl => |decl_index| .{ .decl_val = decl_index }, // TODO: also `decl_ref` deps when introduced .func => |func_index| .{ .func_ies = func_index }, @@ -3166,7 +3153,7 @@ fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: InternP } } -pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?InternPool.AnalUnit { +pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit { if (!zcu.comp.debug_incremental) return null; if (zcu.outdated.count() == 0 and zcu.potentially_outdated.count() == 0) { @@ -3197,7 +3184,7 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?InternPool.AnalUnit { // `outdated`. This set will be small (number of files changed in this // update), so it's alright for us to just iterate here. for (zcu.outdated_file_root.keys()) |file_decl| { - const decl_depender = InternPool.AnalUnit.wrap(.{ .decl = file_decl }); + const decl_depender = AnalUnit.wrap(.{ .decl = file_decl }); if (zcu.outdated.contains(decl_depender)) { // Since we didn't hit this in the first loop, this Decl must have // pending dependencies, so is ineligible. @@ -3271,7 +3258,7 @@ pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?InternPool.AnalUnit { chosen_decl_dependers, }); - return InternPool.AnalUnit.wrap(.{ .decl = chosen_decl_idx.? }); + return AnalUnit.wrap(.{ .decl = chosen_decl_idx.? }); } /// During an incremental update, before semantic analysis, call this to flush all values from @@ -3456,7 +3443,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { // which tries to limit re-analysis to Decls whose previously listed // dependencies are all up-to-date. - const decl_as_depender = InternPool.AnalUnit.wrap(.{ .decl = decl_index }); + const decl_as_depender = AnalUnit.wrap(.{ .decl = decl_index }); const decl_was_outdated = mod.outdated.swapRemove(decl_as_depender) or mod.potentially_outdated.swapRemove(decl_as_depender); @@ -3485,7 +3472,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { // The exports this Decl performs will be re-discovered, so we remove them here // prior to re-analysis. if (build_options.only_c) unreachable; - try mod.deleteDeclExports(decl_index); + mod.deleteUnitExports(AnalUnit.wrap(.{ .decl = decl_index })); } const sema_result: SemaDeclResult = blk: { @@ -3522,7 +3509,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { else => |e| { decl.analysis = .sema_failure; try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1); - try mod.retryable_failures.append(mod.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index })); + try mod.retryable_failures.append(mod.gpa, AnalUnit.wrap(.{ .decl = decl_index })); mod.failed_decls.putAssumeCapacityNoClobber(decl_index, try ErrorMsg.create( mod.gpa, decl.navSrcLoc(mod).upgrade(mod), @@ -3581,7 +3568,7 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.In // that's the case, we should remove this function from the binary. if (decl.val.ip_index != func_index) { try zcu.markDependeeOutdated(.{ .func_ies = func_index }); - ip.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .func = func_index })); + ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .func = func_index })); ip.remove(func_index); @panic("TODO: remove orphaned function from binary"); } @@ -3607,12 +3594,14 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.In .complete => {}, } - const func_as_depender = InternPool.AnalUnit.wrap(.{ .func = func_index }); + const func_as_depender = AnalUnit.wrap(.{ .func = func_index }); const was_outdated = zcu.outdated.swapRemove(func_as_depender) or zcu.potentially_outdated.swapRemove(func_as_depender); if (was_outdated) { + if (build_options.only_c) unreachable; _ = zcu.outdated_ready.swapRemove(func_as_depender); + zcu.deleteUnitExports(AnalUnit.wrap(.{ .func = func_index })); } switch (func.analysis(ip).state) { @@ -3728,16 +3717,13 @@ pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.In .{@errorName(err)}, )); func.analysis(ip).state = .codegen_failure; - try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .func = func_index })); + try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); }, }; } else if (zcu.llvm_object) |llvm_object| { if (build_options.only_c) unreachable; llvm_object.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - func.analysis(ip).state = .codegen_failure; - }, }; } } @@ -3773,7 +3759,7 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index) assert(decl.has_tv); - const func_as_depender = InternPool.AnalUnit.wrap(.{ .func = func_index }); + const func_as_depender = AnalUnit.wrap(.{ .func = func_index }); const is_outdated = mod.outdated.contains(func_as_depender) or mod.potentially_outdated.contains(func_as_depender); @@ -3857,7 +3843,7 @@ fn getFileRootStruct(zcu: *Zcu, decl_index: Decl.Index, namespace_index: Namespa if (zcu.comp.debug_incremental) { try ip.addDependency( gpa, - InternPool.AnalUnit.wrap(.{ .decl = decl_index }), + AnalUnit.wrap(.{ .decl = decl_index }), .{ .src_hash = tracked_inst }, ); } @@ -3906,7 +3892,7 @@ fn semaFileUpdate(zcu: *Zcu, file: *File, type_outdated: bool) SemaError!bool { if (type_outdated) { // Invalidate the existing type, reusing the decl and namespace. - zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.AnalUnit.wrap(.{ .decl = file.root_decl.unwrap().? })); + zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{ .decl = file.root_decl.unwrap().? })); zcu.intern_pool.remove(decl.val.toIntern()); decl.val = undefined; _ = try zcu.getFileRootStruct(file.root_decl.unwrap().?, decl.src_namespace, file); @@ -4097,7 +4083,7 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult { break :ip_index .none; }; - mod.intern_pool.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index })); + mod.intern_pool.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .decl = decl_index })); decl.analysis = .in_progress; @@ -4293,6 +4279,8 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !SemaDeclResult { try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index); } + try sema.flushExports(); + return result; } @@ -4323,7 +4311,7 @@ fn semaAnonOwnerDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult { // with a new Decl. // // Yes, this does mean that any type owner Decl has a constant value for its entire lifetime. - zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index })); + zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{ .decl = decl_index })); zcu.intern_pool.remove(decl.val.toIntern()); decl.analysis = .dependency_failure; return .{ @@ -4949,63 +4937,44 @@ pub fn finalizeAnonDecl(mod: *Module, decl_index: Decl.Index) Allocator.Error!vo } } -/// Delete all the Export objects that are caused by this Decl. Re-analysis of -/// this Decl will cause them to be re-created (or not). -fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) Allocator.Error!void { - var export_owners = (mod.export_owners.fetchSwapRemove(decl_index) orelse return).value; +/// Delete all the Export objects that are caused by this `AnalUnit`. Re-analysis of +/// this `AnalUnit` will cause them to be re-created (or not). +pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void { + const gpa = zcu.gpa; - for (export_owners.items) |exp| { - switch (exp.exported) { - .decl_index => |exported_decl_index| { - if (mod.decl_exports.getPtr(exported_decl_index)) |export_list| { - // Remove exports with owner_decl matching the regenerating decl. - const list = export_list.items; - var i: usize = 0; - var new_len = list.len; - while (i < new_len) { - if (list[i].owner_decl == decl_index) { - mem.copyBackwards(*Export, list[i..], list[i + 1 .. new_len]); - new_len -= 1; - } else { - i += 1; - } - } - export_list.shrinkAndFree(mod.gpa, new_len); - if (new_len == 0) { - assert(mod.decl_exports.swapRemove(exported_decl_index)); - } - } - }, - .value => |value| { - if (mod.value_exports.getPtr(value)) |export_list| { - // Remove exports with owner_decl matching the regenerating decl. - const list = export_list.items; - var i: usize = 0; - var new_len = list.len; - while (i < new_len) { - if (list[i].owner_decl == decl_index) { - mem.copyBackwards(*Export, list[i..], list[i + 1 .. new_len]); - new_len -= 1; - } else { - i += 1; - } - } - export_list.shrinkAndFree(mod.gpa, new_len); - if (new_len == 0) { - assert(mod.value_exports.swapRemove(value)); - } - } - }, + const exports_base, const exports_len = if (zcu.single_exports.fetchSwapRemove(anal_unit)) |kv| + .{ kv.value, 1 } + else if (zcu.multi_exports.fetchSwapRemove(anal_unit)) |info| + .{ info.value.index, info.value.len } + else + return; + + const exports = zcu.all_exports.items[exports_base..][0..exports_len]; + + // In an only-c build, we're guaranteed to never use incremental compilation, so there are + // guaranteed not to be any exports in the output file that need deleting (since we only call + // `updateExports` on flush). + // This case is needed because in some rare edge cases, `Sema` wants to add and delete exports + // within a single update. + if (!build_options.only_c) { + for (exports, exports_base..) |exp, export_idx| { + if (zcu.comp.bin_file) |lf| { + lf.deleteExport(exp.exported, exp.opts.name); + } + if (zcu.failed_exports.fetchSwapRemove(@intCast(export_idx))) |failed_kv| { + failed_kv.value.destroy(gpa); + } } - if (mod.comp.bin_file) |lf| { - try lf.deleteDeclExport(decl_index, exp.opts.name); - } - if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| { - failed_kv.value.destroy(mod.gpa); - } - mod.gpa.destroy(exp); } - export_owners.deinit(mod.gpa); + + zcu.free_exports.ensureUnusedCapacity(gpa, exports_len) catch { + // This space will be reused eventually, so we need not propagate this error. + // Just leak it for now, and let GC reclaim it later on. + return; + }; + for (exports_base..exports_base + exports_len) |export_idx| { + zcu.free_exports.appendAssumeCapacity(@intCast(export_idx)); + } } pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocator) SemaError!Air { @@ -5026,7 +4995,7 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(mod)).toSlice(ip), 0); defer decl_prog_node.end(); - mod.intern_pool.removeDependenciesForDepender(gpa, InternPool.AnalUnit.wrap(.{ .func = func_index })); + mod.intern_pool.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .func = func_index })); var comptime_err_ret_trace = std.ArrayList(LazySrcLoc).init(gpa); defer comptime_err_ret_trace.deinit(); @@ -5262,6 +5231,8 @@ pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocato }; } + try sema.flushExports(); + return .{ .instructions = sema.air_instructions.toOwnedSlice(), .extra = try sema.air_extra.toOwnedSlice(gpa), @@ -5392,33 +5363,89 @@ fn lockAndClearFileCompileError(mod: *Module, file: *File) void { /// Called from `Compilation.update`, after everything is done, just before /// reporting compile errors. In this function we emit exported symbol collision /// errors and communicate exported symbols to the linker backend. -pub fn processExports(mod: *Module) !void { - // Map symbol names to `Export` for name collision detection. - var symbol_exports: SymbolExports = .{}; - defer symbol_exports.deinit(mod.gpa); +pub fn processExports(zcu: *Zcu) !void { + const gpa = zcu.gpa; - for (mod.decl_exports.keys(), mod.decl_exports.values()) |exported_decl, exports_list| { - const exported: Exported = .{ .decl_index = exported_decl }; - try processExportsInner(mod, &symbol_exports, exported, exports_list.items); + // First, construct a mapping of every exported value and Decl to the indices of all its different exports. + var decl_exports: std.AutoArrayHashMapUnmanaged(Decl.Index, ArrayListUnmanaged(u32)) = .{}; + var value_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, ArrayListUnmanaged(u32)) = .{}; + defer { + for (decl_exports.values()) |*exports| { + exports.deinit(gpa); + } + decl_exports.deinit(gpa); + for (value_exports.values()) |*exports| { + exports.deinit(gpa); + } + value_exports.deinit(gpa); } - for (mod.value_exports.keys(), mod.value_exports.values()) |exported_value, exports_list| { + // We note as a heuristic: + // * It is rare to export a value. + // * It is rare for one Decl to be exported multiple times. + // So, this ensureTotalCapacity serves as a reasonable (albeit very approximate) optimization. + try decl_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count()); + + for (zcu.single_exports.values()) |export_idx| { + const exp = zcu.all_exports.items[export_idx]; + const value_ptr, const found_existing = switch (exp.exported) { + .decl_index => |i| gop: { + const gop = try decl_exports.getOrPut(gpa, i); + break :gop .{ gop.value_ptr, gop.found_existing }; + }, + .value => |i| gop: { + const gop = try value_exports.getOrPut(gpa, i); + break :gop .{ gop.value_ptr, gop.found_existing }; + }, + }; + if (!found_existing) value_ptr.* = .{}; + try value_ptr.append(gpa, export_idx); + } + + for (zcu.multi_exports.values()) |info| { + for (zcu.all_exports.items[info.index..][0..info.len], info.index..) |exp, export_idx| { + const value_ptr, const found_existing = switch (exp.exported) { + .decl_index => |i| gop: { + const gop = try decl_exports.getOrPut(gpa, i); + break :gop .{ gop.value_ptr, gop.found_existing }; + }, + .value => |i| gop: { + const gop = try value_exports.getOrPut(gpa, i); + break :gop .{ gop.value_ptr, gop.found_existing }; + }, + }; + if (!found_existing) value_ptr.* = .{}; + try value_ptr.append(gpa, @intCast(export_idx)); + } + } + + // Map symbol names to `Export` for name collision detection. + var symbol_exports: SymbolExports = .{}; + defer symbol_exports.deinit(gpa); + + for (decl_exports.keys(), decl_exports.values()) |exported_decl, exports_list| { + const exported: Exported = .{ .decl_index = exported_decl }; + try processExportsInner(zcu, &symbol_exports, exported, exports_list.items); + } + + for (value_exports.keys(), value_exports.values()) |exported_value, exports_list| { const exported: Exported = .{ .value = exported_value }; - try processExportsInner(mod, &symbol_exports, exported, exports_list.items); + try processExportsInner(zcu, &symbol_exports, exported, exports_list.items); } } -const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, *Export); +const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, u32); fn processExportsInner( zcu: *Zcu, symbol_exports: *SymbolExports, exported: Exported, - exports: []const *Export, + export_indices: []const u32, ) error{OutOfMemory}!void { const gpa = zcu.gpa; - for (exports) |new_export| { + for (export_indices) |export_idx| { + const new_export = &zcu.all_exports.items[export_idx]; const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name); if (gop.found_existing) { new_export.status = .failed_retryable; @@ -5428,40 +5455,41 @@ fn processExportsInner( new_export.opts.name.fmt(&zcu.intern_pool), }); errdefer msg.destroy(gpa); - const other_export = gop.value_ptr.*; + const other_export = zcu.all_exports.items[gop.value_ptr.*]; const other_src_loc = other_export.getSrcLoc(zcu); try zcu.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{}); - zcu.failed_exports.putAssumeCapacityNoClobber(new_export, msg); + zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg); new_export.status = .failed; } else { - gop.value_ptr.* = new_export; + gop.value_ptr.* = export_idx; } } if (zcu.comp.bin_file) |lf| { - try handleUpdateExports(zcu, exports, lf.updateExports(zcu, exported, exports)); + try handleUpdateExports(zcu, export_indices, lf.updateExports(zcu, exported, export_indices)); } else if (zcu.llvm_object) |llvm_object| { if (build_options.only_c) unreachable; - try handleUpdateExports(zcu, exports, llvm_object.updateExports(zcu, exported, exports)); + try handleUpdateExports(zcu, export_indices, llvm_object.updateExports(zcu, exported, export_indices)); } } fn handleUpdateExports( zcu: *Zcu, - exports: []const *Export, + export_indices: []const u32, result: link.File.UpdateExportsError!void, ) Allocator.Error!void { const gpa = zcu.gpa; result catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { - const new_export = exports[0]; + const export_idx = export_indices[0]; + const new_export = &zcu.all_exports.items[export_idx]; new_export.status = .failed_retryable; try zcu.failed_exports.ensureUnusedCapacity(gpa, 1); const src_loc = new_export.getSrcLoc(zcu); const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{ @errorName(err), }); - zcu.failed_exports.putAssumeCapacityNoClobber(new_export, msg); + zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg); }, }; } @@ -5627,16 +5655,13 @@ pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void { .{@errorName(err)}, )); decl.analysis = .codegen_failure; - try zcu.retryable_failures.append(zcu.gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index })); + try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .decl = decl_index })); }, }; } else if (zcu.llvm_object) |llvm_object| { if (build_options.only_c) unreachable; llvm_object.updateDecl(zcu, decl_index) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - decl.analysis = .codegen_failure; - }, }; } } @@ -5684,14 +5709,6 @@ pub fn addGlobalAssembly(mod: *Module, decl_index: Decl.Index, source: []const u } } -pub fn getDeclExports(mod: Module, decl_index: Decl.Index) []const *Export { - if (mod.decl_exports.get(decl_index)) |l| { - return l.items; - } else { - return &[0]*Export{}; - } -} - pub const Feature = enum { panic_fn, panic_unwrap_error, diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 94f8faa441..a8e58a1055 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -3081,6 +3081,8 @@ pub fn genDeclValue( } pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { + if (true) @panic("TODO jacobly"); + const tracy = trace(@src()); defer tracy.end(); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 00cfd4404a..dd6606ece7 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -848,10 +848,6 @@ pub const Object = struct { /// Note that the values are not added until `emit`, when all errors in /// the compilation are known. error_name_table: Builder.Variable.Index, - /// This map is usually very close to empty. It tracks only the cases when a - /// second extern Decl could not be emitted with the correct name due to a - /// name collision. - extern_collisions: std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, void), /// Memoizes a null `?usize` value. null_opt_usize: Builder.Constant, @@ -1011,7 +1007,6 @@ pub const Object = struct { .named_enum_map = .{}, .type_map = .{}, .error_name_table = .none, - .extern_collisions = .{}, .null_opt_usize = .no_init, .struct_field_map = .{}, }; @@ -1029,7 +1024,6 @@ pub const Object = struct { self.anon_decl_map.deinit(gpa); self.named_enum_map.deinit(gpa); self.type_map.deinit(gpa); - self.extern_collisions.deinit(gpa); self.builder.deinit(); self.struct_field_map.deinit(gpa); self.* = undefined; @@ -1121,61 +1115,6 @@ pub const Object = struct { try object.builder.finishModuleAsm(); } - fn resolveExportExternCollisions(object: *Object) !void { - const mod = object.module; - - // This map has externs with incorrect symbol names. - for (object.extern_collisions.keys()) |decl_index| { - const global = object.decl_map.get(decl_index) orelse continue; - // Same logic as below but for externs instead of exports. - const decl_name = object.builder.strtabStringIfExists(mod.declPtr(decl_index).name.toSlice(&mod.intern_pool)) orelse continue; - const other_global = object.builder.getGlobal(decl_name) orelse continue; - if (other_global.toConst().getBase(&object.builder) == - global.toConst().getBase(&object.builder)) continue; - - try global.replace(other_global, &object.builder); - } - object.extern_collisions.clearRetainingCapacity(); - - for (mod.decl_exports.keys(), mod.decl_exports.values()) |decl_index, export_list| { - const global = object.decl_map.get(decl_index) orelse continue; - try resolveGlobalCollisions(object, global, export_list.items); - } - - for (mod.value_exports.keys(), mod.value_exports.values()) |val, export_list| { - const global = object.anon_decl_map.get(val) orelse continue; - try resolveGlobalCollisions(object, global, export_list.items); - } - } - - fn resolveGlobalCollisions( - object: *Object, - global: Builder.Global.Index, - export_list: []const *Module.Export, - ) !void { - const mod = object.module; - const global_base = global.toConst().getBase(&object.builder); - for (export_list) |exp| { - // Detect if the LLVM global has already been created as an extern. In such - // case, we need to replace all uses of it with this exported global. - const exp_name = object.builder.strtabStringIfExists(exp.opts.name.toSlice(&mod.intern_pool)) orelse continue; - - const other_global = object.builder.getGlobal(exp_name) orelse continue; - if (other_global.toConst().getBase(&object.builder) == global_base) continue; - - try global.takeName(other_global, &object.builder); - try other_global.replace(global, &object.builder); - // Problem: now we need to replace in the decl_map that - // the extern decl index points to this new global. However we don't - // know the decl index. - // Even if we did, a future incremental update to the extern would then - // treat the LLVM global as an extern rather than an export, so it would - // need a way to check that. - // This is a TODO that needs to be solved when making - // the LLVM backend support incremental compilation. - } - } - pub const EmitOptions = struct { pre_ir_path: ?[]const u8, pre_bc_path: ?[]const u8, @@ -1193,7 +1132,6 @@ pub const Object = struct { pub fn emit(self: *Object, options: EmitOptions) !void { { - try self.resolveExportExternCollisions(); try self.genErrorNameTable(); try self.genCmpLtErrorsLenFunction(); try self.genModuleLevelAssembly(); @@ -1698,8 +1636,7 @@ pub const Object = struct { const file = try o.getDebugFile(namespace.file_scope); const line_number = decl.navSrcLine(zcu) + 1; - const is_internal_linkage = decl.val.getExternFunc(zcu) == null and - !zcu.decl_exports.contains(decl_index); + const is_internal_linkage = decl.val.getExternFunc(zcu) == null; const debug_decl_type = try o.lowerDebugType(decl.typeOf(zcu)); const subprogram = try o.builder.debugSubprogram( @@ -1760,8 +1697,6 @@ pub const Object = struct { }; try fg.wip.finish(); - - try o.updateExports(zcu, .{ .decl_index = decl_index }, zcu.getDeclExports(decl_index)); } pub fn updateDecl(self: *Object, module: *Module, decl_index: InternPool.DeclIndex) !void { @@ -1781,66 +1716,25 @@ pub const Object = struct { }, else => |e| return e, }; - try self.updateExports(module, .{ .decl_index = decl_index }, module.getDeclExports(decl_index)); } pub fn updateExports( self: *Object, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { const decl_index = switch (exported) { .decl_index => |i| i, - .value => |val| return updateExportedValue(self, mod, val, exports), + .value => |val| return updateExportedValue(self, mod, val, export_indices), }; - const gpa = mod.gpa; const ip = &mod.intern_pool; - // If the module does not already have the function, we ignore this function call - // because we call `updateExports` at the end of `updateFunc` and `updateDecl`. - const global_index = self.decl_map.get(decl_index) orelse return; + const global_index = self.decl_map.get(decl_index).?; const decl = mod.declPtr(decl_index); const comp = mod.comp; - if (decl.isExtern(mod)) { - const decl_name = decl_name: { - if (mod.getTarget().isWasm() and decl.val.typeOf(mod).zigTypeTag(mod) == .Fn) { - if (decl.getOwnedExternFunc(mod).?.lib_name.toSlice(ip)) |lib_name| { - if (!std.mem.eql(u8, lib_name, "c")) { - break :decl_name try self.builder.strtabStringFmt("{}|{s}", .{ decl.name.fmt(ip), lib_name }); - } - } - } - break :decl_name try self.builder.strtabString(decl.name.toSlice(ip)); - }; - if (self.builder.getGlobal(decl_name)) |other_global| { - if (other_global != global_index) { - try self.extern_collisions.put(gpa, decl_index, {}); - } - } - - try global_index.rename(decl_name, &self.builder); - global_index.setLinkage(.external, &self.builder); - global_index.setUnnamedAddr(.default, &self.builder); - if (comp.config.dll_export_fns) - global_index.setDllStorageClass(.default, &self.builder); - - if (decl.val.getVariable(mod)) |decl_var| { - global_index.ptrConst(&self.builder).kind.variable.setThreadLocal( - if (decl_var.is_threadlocal) .generaldynamic else .default, - &self.builder, - ); - if (decl_var.is_weak_linkage) global_index.setLinkage(.extern_weak, &self.builder); - } - } else if (exports.len != 0) { - const main_exp_name = try self.builder.strtabString(exports[0].opts.name.toSlice(ip)); - try global_index.rename(main_exp_name, &self.builder); - - if (decl.val.getVariable(mod)) |decl_var| if (decl_var.is_threadlocal) - global_index.ptrConst(&self.builder).kind - .variable.setThreadLocal(.generaldynamic, &self.builder); - - return updateExportedGlobal(self, mod, global_index, exports); + if (export_indices.len != 0) { + return updateExportedGlobal(self, mod, global_index, export_indices); } else { const fqn = try self.builder.strtabString((try decl.fullyQualifiedName(mod)).toSlice(ip)); try global_index.rename(fqn, &self.builder); @@ -1848,17 +1742,6 @@ pub const Object = struct { if (comp.config.dll_export_fns) global_index.setDllStorageClass(.default, &self.builder); global_index.setUnnamedAddr(.unnamed_addr, &self.builder); - if (decl.val.getVariable(mod)) |decl_var| { - const decl_namespace = mod.namespacePtr(decl.src_namespace); - const single_threaded = decl_namespace.file_scope.mod.single_threaded; - global_index.ptrConst(&self.builder).kind.variable.setThreadLocal( - if (decl_var.is_threadlocal and !single_threaded) - .generaldynamic - else - .default, - &self.builder, - ); - } } } @@ -1866,11 +1749,11 @@ pub const Object = struct { o: *Object, mod: *Module, exported_value: InternPool.Index, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { const gpa = mod.gpa; const ip = &mod.intern_pool; - const main_exp_name = try o.builder.strtabString(exports[0].opts.name.toSlice(ip)); + const main_exp_name = try o.builder.strtabString(mod.all_exports.items[export_indices[0]].opts.name.toSlice(ip)); const global_index = i: { const gop = try o.anon_decl_map.getOrPut(gpa, exported_value); if (gop.found_existing) { @@ -1894,32 +1777,57 @@ pub const Object = struct { try variable_index.setInitializer(init_val, &o.builder); break :i global_index; }; - return updateExportedGlobal(o, mod, global_index, exports); + return updateExportedGlobal(o, mod, global_index, export_indices); } fn updateExportedGlobal( o: *Object, mod: *Module, global_index: Builder.Global.Index, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { const comp = mod.comp; const ip = &mod.intern_pool; + const first_export = mod.all_exports.items[export_indices[0]]; + + // We will rename this global to have a name matching `first_export`. + // Successive exports become aliases. + // If the first export name already exists, then there is a corresponding + // extern global - we replace it with this global. + const first_exp_name = try o.builder.strtabString(first_export.opts.name.toSlice(ip)); + if (o.builder.getGlobal(first_exp_name)) |other_global| replace: { + if (other_global.toConst().getBase(&o.builder) == global_index.toConst().getBase(&o.builder)) { + break :replace; // this global already has the name we want + } + try global_index.takeName(other_global, &o.builder); + try other_global.replace(global_index, &o.builder); + // Problem: now we need to replace in the decl_map that + // the extern decl index points to this new global. However we don't + // know the decl index. + // Even if we did, a future incremental update to the extern would then + // treat the LLVM global as an extern rather than an export, so it would + // need a way to check that. + // This is a TODO that needs to be solved when making + // the LLVM backend support incremental compilation. + } else { + try global_index.rename(first_exp_name, &o.builder); + } + global_index.setUnnamedAddr(.default, &o.builder); if (comp.config.dll_export_fns) global_index.setDllStorageClass(.dllexport, &o.builder); - global_index.setLinkage(switch (exports[0].opts.linkage) { + global_index.setLinkage(switch (first_export.opts.linkage) { .internal => unreachable, .strong => .external, .weak => .weak_odr, .link_once => .linkonce_odr, }, &o.builder); - global_index.setVisibility(switch (exports[0].opts.visibility) { + global_index.setVisibility(switch (first_export.opts.visibility) { .default => .default, .hidden => .hidden, .protected => .protected, }, &o.builder); - if (exports[0].opts.section.toSlice(ip)) |section| + if (first_export.opts.section.toSlice(ip)) |section| switch (global_index.ptrConst(&o.builder).kind) { .variable => |impl_index| impl_index.setSection( try o.builder.string(section), @@ -1936,7 +1844,8 @@ pub const Object = struct { // The planned solution to this is https://github.com/ziglang/zig/issues/13265 // Until then we iterate over existing aliases and make them point // to the correct decl, or otherwise add a new alias. Old aliases are leaked. - for (exports[1..]) |exp| { + for (export_indices[1..]) |export_idx| { + const exp = mod.all_exports.items[export_idx]; const exp_name = try o.builder.strtabString(exp.opts.name.toSlice(ip)); if (o.builder.getGlobal(exp_name)) |global| { switch (global.ptrConst(&o.builder).kind) { @@ -1944,7 +1853,13 @@ pub const Object = struct { alias.setAliasee(global_index.toConst(), &o.builder); continue; }, - .variable, .function => {}, + .variable, .function => { + // This existing global is an `extern` corresponding to this export. + // Replace it with the global being exported. + // This existing global must be replaced with the alias. + try global.rename(.empty, &o.builder); + try global.replace(global_index, &o.builder); + }, .replaced => unreachable, } } @@ -4762,36 +4677,77 @@ pub const DeclGen = struct { else => try o.lowerValue(init_val), }, &o.builder); + if (decl.val.getVariable(zcu)) |decl_var| { + const decl_namespace = zcu.namespacePtr(decl.src_namespace); + const single_threaded = decl_namespace.file_scope.mod.single_threaded; + variable_index.setThreadLocal( + if (decl_var.is_threadlocal and !single_threaded) .generaldynamic else .default, + &o.builder, + ); + } + const line_number = decl.navSrcLine(zcu) + 1; - const is_internal_linkage = !o.module.decl_exports.contains(decl_index); const namespace = zcu.namespacePtr(decl.src_namespace); const owner_mod = namespace.file_scope.mod; - if (owner_mod.strip) return; + if (!owner_mod.strip) { + const debug_file = try o.getDebugFile(namespace.file_scope); - const debug_file = try o.getDebugFile(namespace.file_scope); + const debug_global_var = try o.builder.debugGlobalVar( + try o.builder.metadataString(decl.name.toSlice(ip)), // Name + try o.builder.metadataStringFromStrtabString(variable_index.name(&o.builder)), // Linkage name + debug_file, // File + debug_file, // Scope + line_number, + try o.lowerDebugType(decl.typeOf(zcu)), + variable_index, + .{ .local = !decl.isExtern(zcu) }, + ); - const debug_global_var = try o.builder.debugGlobalVar( - try o.builder.metadataString(decl.name.toSlice(ip)), // Name - try o.builder.metadataStringFromStrtabString(variable_index.name(&o.builder)), // Linkage name - debug_file, // File - debug_file, // Scope - line_number, - try o.lowerDebugType(decl.typeOf(zcu)), - variable_index, - .{ .local = is_internal_linkage }, - ); + const debug_expression = try o.builder.debugExpression(&.{}); - const debug_expression = try o.builder.debugExpression(&.{}); + const debug_global_var_expression = try o.builder.debugGlobalVarExpression( + debug_global_var, + debug_expression, + ); - const debug_global_var_expression = try o.builder.debugGlobalVarExpression( - debug_global_var, - debug_expression, - ); + variable_index.setGlobalVariableExpression(debug_global_var_expression, &o.builder); + try o.debug_globals.append(o.gpa, debug_global_var_expression); + } + } - variable_index.setGlobalVariableExpression(debug_global_var_expression, &o.builder); - try o.debug_globals.append(o.gpa, debug_global_var_expression); + if (decl.isExtern(zcu)) { + const global_index = o.decl_map.get(decl_index).?; + + const decl_name = decl_name: { + if (zcu.getTarget().isWasm() and decl.typeOf(zcu).zigTypeTag(zcu) == .Fn) { + if (decl.getOwnedExternFunc(zcu).?.lib_name.toSlice(ip)) |lib_name| { + if (!std.mem.eql(u8, lib_name, "c")) { + break :decl_name try o.builder.strtabStringFmt("{}|{s}", .{ decl.name.fmt(ip), lib_name }); + } + } + } + break :decl_name try o.builder.strtabString(decl.name.toSlice(ip)); + }; + + if (o.builder.getGlobal(decl_name)) |other_global| { + if (other_global != global_index) { + // Another global already has this name; just use it in place of this global. + try global_index.replace(other_global, &o.builder); + return; + } + } + + try global_index.rename(decl_name, &o.builder); + global_index.setLinkage(.external, &o.builder); + global_index.setUnnamedAddr(.default, &o.builder); + if (zcu.comp.config.dll_export_fns) + global_index.setDllStorageClass(.default, &o.builder); + + if (decl.val.getVariable(zcu)) |decl_var| { + if (decl_var.is_weak_linkage) global_index.setLinkage(.extern_weak, &o.builder); + } } } }; @@ -5193,7 +5149,6 @@ pub const FuncGen = struct { const fqn = try decl.fullyQualifiedName(zcu); - const is_internal_linkage = !zcu.decl_exports.contains(decl_index); const fn_ty = try zcu.funcType(.{ .param_types = &.{}, .return_type = .void_type, @@ -5211,7 +5166,7 @@ pub const FuncGen = struct { .sp_flags = .{ .Optimized = owner_mod.optimize_mode != .Debug, .Definition = true, - .LocalToUnit = is_internal_linkage, + .LocalToUnit = true, // TODO: we can't know this at this point, since the function could be exported later! }, }, o.debug_compile_unit, diff --git a/src/link.zig b/src/link.zig index 75a9723f1c..36a5cb8187 100644 --- a/src/link.zig +++ b/src/link.zig @@ -606,12 +606,12 @@ pub const File = struct { base: *File, module: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) UpdateExportsError!void { switch (base.tag) { inline else => |tag| { if (tag != .c and build_options.only_c) unreachable; - return @as(*tag.Type(), @fieldParentPtr("base", base)).updateExports(module, exported, exports); + return @as(*tag.Type(), @fieldParentPtr("base", base)).updateExports(module, exported, export_indices); }, } } @@ -671,11 +671,11 @@ pub const File = struct { } } - pub fn deleteDeclExport( + pub fn deleteExport( base: *File, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, - ) !void { + ) void { if (build_options.only_c) @compileError("unreachable"); switch (base.tag) { .plan9, @@ -685,7 +685,7 @@ pub const File = struct { => {}, inline else => |tag| { - return @as(*tag.Type(), @fieldParentPtr("base", base)).deleteDeclExport(decl_index, name); + return @as(*tag.Type(), @fieldParentPtr("base", base)).deleteExport(exported, name); }, } } diff --git a/src/link/C.zig b/src/link/C.zig index e6830eac8c..3a8d06b5ee 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -183,6 +183,8 @@ pub fn updateFunc( air: Air, liveness: Liveness, ) !void { + if (true) @panic("TODO jacobly"); + const gpa = self.base.comp.gpa; const func = zcu.funcInfo(func_index); @@ -250,6 +252,8 @@ pub fn updateFunc( } fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void { + if (true) @panic("TODO jacobly"); + const gpa = self.base.comp.gpa; const anon_decl = self.anon_decls.keys()[i]; @@ -306,6 +310,8 @@ fn updateAnonDecl(self: *C, zcu: *Zcu, i: usize) !void { } pub fn updateDecl(self: *C, zcu: *Zcu, decl_index: InternPool.DeclIndex) !void { + if (true) @panic("TODO jacobly"); + const tracy = trace(@src()); defer tracy.end(); @@ -390,6 +396,8 @@ fn abiDefines(self: *C, target: std.Target) !std.ArrayList(u8) { } pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !void { + if (true) @panic("TODO jacobly"); + _ = arena; // Has the same lifetime as the call to Compilation.update. const tracy = trace(@src()); @@ -451,9 +459,16 @@ pub fn flushModule(self: *C, arena: Allocator, prog_node: std.Progress.Node) !vo { var export_names: std.AutoHashMapUnmanaged(InternPool.NullTerminatedString, void) = .{}; defer export_names.deinit(gpa); - try export_names.ensureTotalCapacity(gpa, @intCast(zcu.decl_exports.entries.len)); - for (zcu.decl_exports.values()) |exports| for (exports.items) |@"export"| - try export_names.put(gpa, @"export".opts.name, {}); + try export_names.ensureTotalCapacity(gpa, @intCast(zcu.single_exports.count())); + for (zcu.single_exports.values()) |export_idx| { + export_names.putAssumeCapacity(gpa, zcu.all_exports.items[export_idx].opts.name, {}); + } + for (zcu.multi_exports.values()) |info| { + try export_names.ensureUnusedCapacity(info.len); + for (zcu.all_exports.items[info.index..][0..info.len]) |export_idx| { + export_names.putAssumeCapacity(gpa, zcu.all_exports.items[export_idx].opts.name, {}); + } + } for (self.anon_decls.values()) |*decl_block| { try self.flushDeclBlock(zcu, zcu.root_mod, &f, decl_block, export_names, .none); @@ -781,10 +796,10 @@ pub fn updateExports( self: *C, zcu: *Zcu, exported: Zcu.Exported, - exports: []const *Zcu.Export, + export_indices: []const u32, ) !void { - _ = exports; - _ = exported; - _ = zcu; _ = self; + _ = zcu; + _ = exported; + _ = export_indices; } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 4524441f3b..0244d085b8 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1162,9 +1162,7 @@ pub fn updateFunc(self: *Coff, mod: *Module, func_index: InternPool.Index, air: try self.updateDeclCode(decl_index, code, .FUNCTION); - // Since we updated the vaddr and the size, each corresponding export - // symbol also needs to be updated. - return self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); + // Exports will be updated by `Zcu.processExports` after the update. } pub fn lowerUnnamedConst(self: *Coff, val: Value, decl_index: InternPool.DeclIndex) !u32 { @@ -1286,9 +1284,7 @@ pub fn updateDecl( try self.updateDeclCode(decl_index, code, .NULL); - // Since we updated the vaddr and the size, each corresponding export - // symbol also needs to be updated. - return self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); + // Exports will be updated by `Zcu.processExports` after the update. } fn updateLazySymbolAtom( @@ -1509,7 +1505,7 @@ pub fn updateExports( self: *Coff, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { if (build_options.skip_non_native and builtin.object_format != .coff) { @panic("Attempted to compile for object format that was disabled by build configuration"); @@ -1522,7 +1518,8 @@ pub fn updateExports( if (comp.config.use_llvm) { // Even in the case of LLVM, we need to notice certain exported symbols in order to // detect the default subsystem. - for (exports) |exp| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; const exported_decl_index = switch (exp.exported) { .decl_index => |i| i, .value => continue, @@ -1552,7 +1549,7 @@ pub fn updateExports( } } - if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); + if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, export_indices); const gpa = comp.gpa; @@ -1562,7 +1559,7 @@ pub fn updateExports( break :blk self.decls.getPtr(decl_index).?; }, .value => |value| self.anon_decls.getPtr(value) orelse blk: { - const first_exp = exports[0]; + const first_exp = mod.all_exports.items[export_indices[0]]; const res = try self.lowerAnonDecl(value, .none, first_exp.getSrcLoc(mod)); switch (res) { .ok => {}, @@ -1570,7 +1567,7 @@ pub fn updateExports( // TODO maybe it's enough to return an error here and let Module.processExportsInner // handle the error? try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); - mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em); + mod.failed_exports.putAssumeCapacityNoClobber(export_indices[0], em); return; }, } @@ -1580,12 +1577,13 @@ pub fn updateExports( const atom_index = metadata.atom; const atom = self.getAtom(atom_index); - for (exports) |exp| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; log.debug("adding new export '{}'", .{exp.opts.name.fmt(&mod.intern_pool)}); if (exp.opts.section.toSlice(&mod.intern_pool)) |section_name| { if (!mem.eql(u8, section_name, ".text")) { - try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create( gpa, exp.getSrcLoc(mod), "Unimplemented: ExportOptions.section", @@ -1596,7 +1594,7 @@ pub fn updateExports( } if (exp.opts.linkage == .link_once) { - try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create( gpa, exp.getSrcLoc(mod), "Unimplemented: GlobalLinkage.link_once", @@ -1641,13 +1639,16 @@ pub fn updateExports( } } -pub fn deleteDeclExport( +pub fn deleteExport( self: *Coff, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { if (self.llvm_object) |_| return; - const metadata = self.decls.getPtr(decl_index) orelse return; + const metadata = switch (exported) { + .decl_index => |decl_index| self.decls.getPtr(decl_index) orelse return, + .value => |value| self.anon_decls.getPtr(value) orelse return, + }; const mod = self.base.comp.module.?; const name_slice = name.toSlice(&mod.intern_pool); const sym_index = metadata.getExportPtr(self, name_slice) orelse return; diff --git a/src/link/Elf.zig b/src/link/Elf.zig index 5a14a544e8..df8e6c0dd8 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -3011,13 +3011,13 @@ pub fn updateExports( self: *Elf, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { if (build_options.skip_non_native and builtin.object_format != .elf) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); - return self.zigObjectPtr().?.updateExports(self, mod, exported, exports); + if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, export_indices); + return self.zigObjectPtr().?.updateExports(self, mod, exported, export_indices); } pub fn updateDeclLineNumber(self: *Elf, mod: *Module, decl_index: InternPool.DeclIndex) !void { @@ -3025,13 +3025,13 @@ pub fn updateDeclLineNumber(self: *Elf, mod: *Module, decl_index: InternPool.Dec return self.zigObjectPtr().?.updateDeclLineNumber(mod, decl_index); } -pub fn deleteDeclExport( +pub fn deleteExport( self: *Elf, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { if (self.llvm_object) |_| return; - return self.zigObjectPtr().?.deleteDeclExport(self, decl_index, name); + return self.zigObjectPtr().?.deleteExport(self, exported, name); } fn addLinkerDefinedSymbols(self: *Elf) !void { diff --git a/src/link/Elf/ZigObject.zig b/src/link/Elf/ZigObject.zig index c2c5e879cb..14040767b1 100644 --- a/src/link/Elf/ZigObject.zig +++ b/src/link/Elf/ZigObject.zig @@ -1115,9 +1115,7 @@ pub fn updateFunc( ); } - // Since we updated the vaddr and the size, each corresponding export - // symbol also needs to be updated. - return self.updateExports(elf_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); + // Exports will be updated by `Zcu.processExports` after the update. } pub fn updateDecl( @@ -1194,9 +1192,7 @@ pub fn updateDecl( ); } - // Since we updated the vaddr and the size, each corresponding export - // symbol also needs to be updated. - return self.updateExports(elf_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); + // Exports will be updated by `Zcu.processExports` after the update. } fn updateLazySymbol( @@ -1386,7 +1382,7 @@ pub fn updateExports( elf_file: *Elf, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { const tracy = trace(@src()); defer tracy.end(); @@ -1398,7 +1394,7 @@ pub fn updateExports( break :blk self.decls.getPtr(decl_index).?; }, .value => |value| self.anon_decls.getPtr(value) orelse blk: { - const first_exp = exports[0]; + const first_exp = mod.all_exports.items[export_indices[0]]; const res = try self.lowerAnonDecl(elf_file, value, .none, first_exp.getSrcLoc(mod)); switch (res) { .ok => {}, @@ -1406,7 +1402,7 @@ pub fn updateExports( // TODO maybe it's enough to return an error here and let Module.processExportsInner // handle the error? try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); - mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em); + mod.failed_exports.putAssumeCapacityNoClobber(export_indices[0], em); return; }, } @@ -1418,11 +1414,12 @@ pub fn updateExports( const esym = self.local_esyms.items(.elf_sym)[esym_index]; const esym_shndx = self.local_esyms.items(.shndx)[esym_index]; - for (exports) |exp| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; if (exp.opts.section.unwrap()) |section_name| { if (!section_name.eqlSlice(".text", &mod.intern_pool)) { try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); - mod.failed_exports.putAssumeCapacityNoClobber(exp, try Module.ErrorMsg.create( + mod.failed_exports.putAssumeCapacityNoClobber(export_idx, try Module.ErrorMsg.create( gpa, exp.getSrcLoc(mod), "Unimplemented: ExportOptions.section", @@ -1437,7 +1434,7 @@ pub fn updateExports( .weak => elf.STB_WEAK, .link_once => { try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); - mod.failed_exports.putAssumeCapacityNoClobber(exp, try Module.ErrorMsg.create( + mod.failed_exports.putAssumeCapacityNoClobber(export_idx, try Module.ErrorMsg.create( gpa, exp.getSrcLoc(mod), "Unimplemented: GlobalLinkage.LinkOnce", @@ -1487,13 +1484,16 @@ pub fn updateDeclLineNumber( } } -pub fn deleteDeclExport( +pub fn deleteExport( self: *ZigObject, elf_file: *Elf, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { - const metadata = self.decls.getPtr(decl_index) orelse return; + const metadata = switch (exported) { + .decl_index => |decl_index| self.decls.getPtr(decl_index) orelse return, + .value => |value| self.anon_decls.getPtr(value) orelse return, + }; const mod = elf_file.base.comp.module.?; const exp_name = name.toSlice(&mod.intern_pool); const esym_index = metadata.@"export"(self, exp_name) orelse return; diff --git a/src/link/MachO.zig b/src/link/MachO.zig index dd185fcaec..3187ba528b 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -3196,22 +3196,22 @@ pub fn updateExports( self: *MachO, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { if (build_options.skip_non_native and builtin.object_format != .macho) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); - return self.getZigObject().?.updateExports(self, mod, exported, exports); + if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, export_indices); + return self.getZigObject().?.updateExports(self, mod, exported, export_indices); } -pub fn deleteDeclExport( +pub fn deleteExport( self: *MachO, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, -) Allocator.Error!void { +) void { if (self.llvm_object) |_| return; - return self.getZigObject().?.deleteDeclExport(self, decl_index, name); + return self.getZigObject().?.deleteExport(self, exported, name); } pub fn freeDecl(self: *MachO, decl_index: InternPool.DeclIndex) void { diff --git a/src/link/MachO/ZigObject.zig b/src/link/MachO/ZigObject.zig index e2202d11fc..1fce9e37dd 100644 --- a/src/link/MachO/ZigObject.zig +++ b/src/link/MachO/ZigObject.zig @@ -713,9 +713,7 @@ pub fn updateFunc( ); } - // Since we updated the vaddr and the size, each corresponding export - // symbol also needs to be updated. - return self.updateExports(macho_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); + // Exports will be updated by `Zcu.processExports` after the update. } pub fn updateDecl( @@ -790,9 +788,7 @@ pub fn updateDecl( ); } - // Since we updated the vaddr and the size, each corresponding export symbol also - // needs to be updated. - try self.updateExports(macho_file, mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); + // Exports will be updated by `Zcu.processExports` after the update. } fn updateDeclCode( @@ -1187,7 +1183,7 @@ pub fn updateExports( macho_file: *MachO, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) link.File.UpdateExportsError!void { const tracy = trace(@src()); defer tracy.end(); @@ -1199,7 +1195,7 @@ pub fn updateExports( break :blk self.decls.getPtr(decl_index).?; }, .value => |value| self.anon_decls.getPtr(value) orelse blk: { - const first_exp = exports[0]; + const first_exp = mod.all_exports.items[export_indices[0]]; const res = try self.lowerAnonDecl(macho_file, value, .none, first_exp.getSrcLoc(mod)); switch (res) { .ok => {}, @@ -1207,7 +1203,7 @@ pub fn updateExports( // TODO maybe it's enough to return an error here and let Module.processExportsInner // handle the error? try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); - mod.failed_exports.putAssumeCapacityNoClobber(first_exp, em); + mod.failed_exports.putAssumeCapacityNoClobber(export_indices[0], em); return; }, } @@ -1218,11 +1214,12 @@ pub fn updateExports( const nlist_idx = macho_file.getSymbol(sym_index).nlist_idx; const nlist = self.symtab.items(.nlist)[nlist_idx]; - for (exports) |exp| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; if (exp.opts.section.unwrap()) |section_name| { if (!section_name.eqlSlice("__text", &mod.intern_pool)) { try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); - mod.failed_exports.putAssumeCapacityNoClobber(exp, try Module.ErrorMsg.create( + mod.failed_exports.putAssumeCapacityNoClobber(export_idx, try Module.ErrorMsg.create( gpa, exp.getSrcLoc(mod), "Unimplemented: ExportOptions.section", @@ -1232,7 +1229,7 @@ pub fn updateExports( } } if (exp.opts.linkage == .link_once) { - try mod.failed_exports.putNoClobber(mod.gpa, exp, try Module.ErrorMsg.create( + try mod.failed_exports.putNoClobber(mod.gpa, export_idx, try Module.ErrorMsg.create( gpa, exp.getSrcLoc(mod), "Unimplemented: GlobalLinkage.link_once", @@ -1364,15 +1361,18 @@ pub fn updateDeclLineNumber(self: *ZigObject, mod: *Module, decl_index: InternPo } } -pub fn deleteDeclExport( +pub fn deleteExport( self: *ZigObject, macho_file: *MachO, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { const mod = macho_file.base.comp.module.?; - const metadata = self.decls.getPtr(decl_index) orelse return; + const metadata = switch (exported) { + .decl_index => |decl_index| self.decls.getPtr(decl_index) orelse return, + .value => |value| self.anon_decls.getPtr(value) orelse return, + }; const nlist_index = metadata.@"export"(self, name.toSlice(&mod.intern_pool)) orelse return; log.debug("deleting export '{}'", .{name.fmt(&mod.intern_pool)}); diff --git a/src/link/NvPtx.zig b/src/link/NvPtx.zig index 3d059acbb5..aa9ea1b5cd 100644 --- a/src/link/NvPtx.zig +++ b/src/link/NvPtx.zig @@ -96,12 +96,12 @@ pub fn updateExports( self: *NvPtx, module: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) !void { if (build_options.skip_non_native and builtin.object_format != .nvptx) @panic("Attempted to compile for object format that was disabled by build configuration"); - return self.llvm_object.updateExports(module, exported, exports); + return self.llvm_object.updateExports(module, exported, export_indices); } pub fn freeDecl(self: *NvPtx, decl_index: InternPool.DeclIndex) void { diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index b15ec87815..60775ac662 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -60,6 +60,9 @@ fn_decl_table: std.AutoArrayHashMapUnmanaged( ) = .{}, /// the code is modified when relocated, so that is why it is mutable data_decl_table: std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, []u8) = .{}, +/// When `updateExports` is called, we store the export indices here, to be used +/// during flush. +decl_exports: std.AutoArrayHashMapUnmanaged(InternPool.DeclIndex, []u32) = .{}, /// Table of unnamed constants associated with a parent `Decl`. /// We store them here so that we can free the constants whenever the `Decl` @@ -770,8 +773,8 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: std.Progress.Node) mem.writeInt(u64, got_table[atom.got_index.? * 8 ..][0..8], off, target.cpu.arch.endian()); } self.syms.items[atom.sym_index.?].value = off; - if (mod.decl_exports.get(decl_index)) |exports| { - try self.addDeclExports(mod, decl_index, exports.items); + if (self.decl_exports.get(decl_index)) |export_indices| { + try self.addDeclExports(mod, decl_index, export_indices); } } } @@ -836,8 +839,8 @@ pub fn flushModule(self: *Plan9, arena: Allocator, prog_node: std.Progress.Node) mem.writeInt(u64, got_table[atom.got_index.? * 8 ..][0..8], off, target.cpu.arch.endian()); } self.syms.items[atom.sym_index.?].value = off; - if (mod.decl_exports.get(decl_index)) |exports| { - try self.addDeclExports(mod, decl_index, exports.items); + if (self.decl_exports.get(decl_index)) |export_indices| { + try self.addDeclExports(mod, decl_index, export_indices); } } // write the unnamed constants after the other data decls @@ -1007,20 +1010,21 @@ fn addDeclExports( self: *Plan9, mod: *Module, decl_index: InternPool.DeclIndex, - exports: []const *Module.Export, + export_indices: []const u32, ) !void { const gpa = self.base.comp.gpa; const metadata = self.decls.getPtr(decl_index).?; const atom = self.getAtom(metadata.index); - for (exports) |exp| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; const exp_name = exp.opts.name.toSlice(&mod.intern_pool); // plan9 does not support custom sections if (exp.opts.section.unwrap()) |section_name| { if (!section_name.eqlSlice(".text", &mod.intern_pool) and !section_name.eqlSlice(".data", &mod.intern_pool)) { - try mod.failed_exports.put(mod.gpa, exp, try Module.ErrorMsg.create( + try mod.failed_exports.put(mod.gpa, export_idx, try Module.ErrorMsg.create( gpa, mod.declPtr(decl_index).navSrcLoc(mod).upgrade(mod), "plan9 does not support extra sections", @@ -1152,15 +1156,23 @@ pub fn updateExports( self: *Plan9, module: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) !void { + const gpa = self.base.comp.gpa; switch (exported) { .value => @panic("TODO: plan9 updateExports handling values"), - .decl_index => |decl_index| _ = try self.seeDecl(decl_index), + .decl_index => |decl_index| { + _ = try self.seeDecl(decl_index); + if (self.decl_exports.fetchSwapRemove(decl_index)) |kv| { + gpa.free(kv.value); + } + try self.decl_exports.ensureUnusedCapacity(gpa, 1); + const duped_indices = try gpa.dupe(u32, export_indices); + self.decl_exports.putAssumeCapacityNoClobber(decl_index, duped_indices); + }, } - // we do all the things in flush + // all proper work is done in flush _ = module; - _ = exports; } pub fn getOrCreateAtomForLazySymbol(self: *Plan9, sym: File.LazySymbol) !Atom.Index { @@ -1290,6 +1302,10 @@ pub fn deinit(self: *Plan9) void { gpa.free(self.syms.items[sym_index].name); } self.data_decl_table.deinit(gpa); + for (self.decl_exports.values()) |export_indices| { + gpa.free(export_indices); + } + self.decl_exports.deinit(gpa); self.syms.deinit(gpa); self.got_index_free_list.deinit(gpa); self.syms_index_free_list.deinit(gpa); @@ -1395,10 +1411,13 @@ pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void { const atom = self.getAtom(decl_metadata.index); const sym = self.syms.items[atom.sym_index.?]; try self.writeSym(writer, sym); - if (self.base.comp.module.?.decl_exports.get(decl_index)) |exports| { - for (exports.items) |e| if (decl_metadata.getExport(self, e.opts.name.toSlice(ip))) |exp_i| { - try self.writeSym(writer, self.syms.items[exp_i]); - }; + if (self.decl_exports.get(decl_index)) |export_indices| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; + if (decl_metadata.getExport(self, exp.opts.name.toSlice(ip))) |exp_i| { + try self.writeSym(writer, self.syms.items[exp_i]); + } + } } } } @@ -1442,13 +1461,16 @@ pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void { const atom = self.getAtom(decl_metadata.index); const sym = self.syms.items[atom.sym_index.?]; try self.writeSym(writer, sym); - if (self.base.comp.module.?.decl_exports.get(decl_index)) |exports| { - for (exports.items) |e| if (decl_metadata.getExport(self, e.opts.name.toSlice(ip))) |exp_i| { - const s = self.syms.items[exp_i]; - if (mem.eql(u8, s.name, "_start")) - self.entry_val = s.value; - try self.writeSym(writer, s); - }; + if (self.decl_exports.get(decl_index)) |export_indices| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; + if (decl_metadata.getExport(self, exp.opts.name.toSlice(ip))) |exp_i| { + const s = self.syms.items[exp_i]; + if (mem.eql(u8, s.name, "_start")) + self.entry_val = s.value; + try self.writeSym(writer, s); + } + } } } } diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 099d58bfa0..d1a8ff96c6 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -152,7 +152,7 @@ pub fn updateExports( self: *SpirV, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) !void { const decl_index = switch (exported) { .decl_index => |i| i, @@ -177,7 +177,8 @@ pub fn updateExports( if ((!is_vulkan and execution_model == .Kernel) or (is_vulkan and (execution_model == .Fragment or execution_model == .Vertex))) { - for (exports) |exp| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; try self.object.spv.declareEntryPoint( spv_decl_index, exp.opts.name.toSlice(&mod.intern_pool), diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 6476784a01..164ddbc118 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1542,26 +1542,26 @@ pub fn getAnonDeclVAddr(wasm: *Wasm, decl_val: InternPool.Index, reloc_info: lin return wasm.zigObjectPtr().?.getAnonDeclVAddr(wasm, decl_val, reloc_info); } -pub fn deleteDeclExport( +pub fn deleteExport( wasm: *Wasm, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { if (wasm.llvm_object) |_| return; - return wasm.zigObjectPtr().?.deleteDeclExport(wasm, decl_index, name); + return wasm.zigObjectPtr().?.deleteExport(wasm, exported, name); } pub fn updateExports( wasm: *Wasm, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) !void { if (build_options.skip_non_native and builtin.object_format != .wasm) { @panic("Attempted to compile for object format that was disabled by build configuration"); } - if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); - return wasm.zigObjectPtr().?.updateExports(wasm, mod, exported, exports); + if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, export_indices); + return wasm.zigObjectPtr().?.updateExports(wasm, mod, exported, export_indices); } pub fn freeDecl(wasm: *Wasm, decl_index: InternPool.DeclIndex) void { diff --git a/src/link/Wasm/ZigObject.zig b/src/link/Wasm/ZigObject.zig index 1accf81c02..a3b8eb4459 100644 --- a/src/link/Wasm/ZigObject.zig +++ b/src/link/Wasm/ZigObject.zig @@ -833,13 +833,17 @@ pub fn getAnonDeclVAddr( return target_symbol_index; } -pub fn deleteDeclExport( +pub fn deleteExport( zig_object: *ZigObject, wasm_file: *Wasm, - decl_index: InternPool.DeclIndex, + exported: Zcu.Exported, name: InternPool.NullTerminatedString, ) void { const mod = wasm_file.base.comp.module.?; + const decl_index = switch (exported) { + .decl_index => |decl_index| decl_index, + .value => @panic("TODO: implement Wasm linker code for exporting a constant value"), + }; const decl_info = zig_object.decls_map.getPtr(decl_index) orelse return; if (decl_info.@"export"(zig_object, name.toSlice(&mod.intern_pool))) |sym_index| { const sym = zig_object.symbol(sym_index); @@ -856,7 +860,7 @@ pub fn updateExports( wasm_file: *Wasm, mod: *Module, exported: Module.Exported, - exports: []const *Module.Export, + export_indices: []const u32, ) !void { const decl_index = switch (exported) { .decl_index => |i| i, @@ -873,9 +877,10 @@ pub fn updateExports( const gpa = mod.gpa; log.debug("Updating exports for decl '{}'", .{decl.name.fmt(&mod.intern_pool)}); - for (exports) |exp| { + for (export_indices) |export_idx| { + const exp = mod.all_exports.items[export_idx]; if (exp.opts.section.toSlice(&mod.intern_pool)) |section| { - try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create( gpa, decl.navSrcLoc(mod).upgrade(mod), "Unimplemented: ExportOptions.section '{s}'", @@ -908,7 +913,7 @@ pub fn updateExports( }, .strong => {}, // symbols are strong by default .link_once => { - try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + try mod.failed_exports.putNoClobber(gpa, export_idx, try Module.ErrorMsg.create( gpa, decl.navSrcLoc(mod).upgrade(mod), "Unimplemented: LinkOnce",