diff --git a/src/Module.zig b/src/Module.zig index 1f80669f2e..5467cdfc24 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -70,6 +70,8 @@ local_zir_cache: Compilation.Directory, /// 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. @@ -244,6 +246,13 @@ pub const GlobalEmitH = struct { pub const ErrorInt = u32; +pub const Exported = union(enum) { + /// The Decl being exported. Note this is *not* the Decl performing the export. + decl_index: Decl.Index, + /// Constant value being exported. + value: InternPool.Index, +}; + pub const Export = struct { opts: Options, src: LazySrcLoc, @@ -252,8 +261,7 @@ pub const Export = struct { /// The Decl containing the export statement. Inline function calls /// may cause this to be different from the owner_decl. src_decl: Decl.Index, - /// The Decl being exported. Note this is *not* the Decl performing the export. - exported_decl: Decl.Index, + exported: Exported, status: enum { in_progress, failed, @@ -2575,6 +2583,11 @@ pub fn deinit(mod: *Module) void { } mod.decl_exports.deinit(gpa); + for (mod.value_exports.values()) |*export_list| { + export_list.deinit(gpa); + } + mod.value_exports.deinit(gpa); + for (mod.export_owners.values()) |*value| { freeExportList(gpa, value); } @@ -4620,36 +4633,49 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) Allocator.Error!void var export_owners = (mod.export_owners.fetchSwapRemove(decl_index) orelse return).value; for (export_owners.items) |exp| { - if (mod.decl_exports.getPtr(exp.exported_decl)) |value_ptr| { - // Remove exports with owner_decl matching the regenerating decl. - const list = value_ptr.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; + 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_ptr.shrinkAndFree(mod.gpa, new_len); - if (new_len == 0) { - assert(mod.decl_exports.swapRemove(exp.exported_decl)); - } - } - if (mod.comp.bin_file.cast(link.File.Elf)) |elf| { - elf.deleteDeclExport(decl_index, exp.opts.name); - } - if (mod.comp.bin_file.cast(link.File.MachO)) |macho| { - try macho.deleteDeclExport(decl_index, exp.opts.name); - } - if (mod.comp.bin_file.cast(link.File.Wasm)) |wasm| { - wasm.deleteDeclExport(decl_index); - } - if (mod.comp.bin_file.cast(link.File.Coff)) |coff| { - coff.deleteDeclExport(decl_index, exp.opts.name); + }, + .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)); + } + } + }, } + try mod.comp.bin_file.deleteDeclExport(decl_index, exp.opts.name); if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| { failed_kv.value.destroy(mod.gpa); } @@ -5503,48 +5529,63 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void { /// 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 { - const gpa = mod.gpa; // Map symbol names to `Export` for name collision detection. - var symbol_exports: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, *Export) = .{}; - defer symbol_exports.deinit(gpa); + var symbol_exports: SymbolExports = .{}; + defer symbol_exports.deinit(mod.gpa); - var it = mod.decl_exports.iterator(); - while (it.next()) |entry| { - const exported_decl = entry.key_ptr.*; - const exports = entry.value_ptr.items; - for (exports) |new_export| { - const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name); - if (gop.found_existing) { - new_export.status = .failed_retryable; - try mod.failed_exports.ensureUnusedCapacity(gpa, 1); - const src_loc = new_export.getSrcLoc(mod); - const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {}", .{ - new_export.opts.name.fmt(&mod.intern_pool), - }); - errdefer msg.destroy(gpa); - const other_export = gop.value_ptr.*; - const other_src_loc = other_export.getSrcLoc(mod); - try mod.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{}); - mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); - new_export.status = .failed; - } else { - gop.value_ptr.* = new_export; - } - } - mod.comp.bin_file.updateDeclExports(mod, exported_decl, exports) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - const new_export = exports[0]; - new_export.status = .failed_retryable; - try mod.failed_exports.ensureUnusedCapacity(gpa, 1); - const src_loc = new_export.getSrcLoc(mod); - const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{ - @errorName(err), - }); - mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); - }, - }; + 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); } + + for (mod.value_exports.keys(), mod.value_exports.values()) |exported_value, exports_list| { + const exported: Exported = .{ .value = exported_value }; + try processExportsInner(mod, &symbol_exports, exported, exports_list.items); + } +} + +const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, *Export); + +fn processExportsInner( + mod: *Module, + symbol_exports: *SymbolExports, + exported: Exported, + exports: []const *Export, +) error{OutOfMemory}!void { + const gpa = mod.gpa; + + for (exports) |new_export| { + const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name); + if (gop.found_existing) { + new_export.status = .failed_retryable; + try mod.failed_exports.ensureUnusedCapacity(gpa, 1); + const src_loc = new_export.getSrcLoc(mod); + const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {}", .{ + new_export.opts.name.fmt(&mod.intern_pool), + }); + errdefer msg.destroy(gpa); + const other_export = gop.value_ptr.*; + const other_src_loc = other_export.getSrcLoc(mod); + try mod.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{}); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); + new_export.status = .failed; + } else { + gop.value_ptr.* = new_export; + } + } + mod.comp.bin_file.updateExports(mod, exported, exports) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + else => { + const new_export = exports[0]; + new_export.status = .failed_retryable; + try mod.failed_exports.ensureUnusedCapacity(gpa, 1); + const src_loc = new_export.getSrcLoc(mod); + const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{ + @errorName(err), + }); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); + }, + }; } pub fn populateTestFunctions( diff --git a/src/Sema.zig b/src/Sema.zig index 2970e1f77b..7476126f99 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -6026,6 +6026,7 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const tracy = trace(@src()); defer tracy.end(); + const mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data; const src = inst_data.src(); @@ -6034,19 +6035,22 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const operand = try sema.resolveInstConst(block, operand_src, extra.operand, .{ .needed_comptime_reason = "export target must be comptime-known", }); - const options = sema.resolveExportOptions(block, .unneeded, extra.options) catch |err| switch (err) { - error.NeededSourceLocation => { - _ = try sema.resolveExportOptions(block, options_src, extra.options); - unreachable; - }, - else => |e| return e, - }; - const decl_index = if (operand.val.getFunction(sema.mod)) |function| function.owner_decl else blk: { - var anon_decl = try block.startAnonDecl(); // TODO: export value without Decl - defer anon_decl.deinit(); - break :blk try anon_decl.finish(operand.ty, operand.val, .none); - }; - try sema.analyzeExport(block, src, options, decl_index); + const options = try sema.resolveExportOptions(block, options_src, extra.options); + if (options.linkage == .Internal) + return; + if (operand.val.getFunction(mod)) |function| { + const decl_index = function.owner_decl; + return sema.analyzeExport(block, src, options, decl_index); + } + + try addExport(mod, .{ + .opts = options, + .src = src, + .owner_decl = sema.owner_decl_index, + .src_decl = block.src_decl, + .exported = .{ .value = operand.val.toIntern() }, + .status = .in_progress, + }); } pub fn analyzeExport( @@ -6056,12 +6060,11 @@ pub fn analyzeExport( options: Module.Export.Options, exported_decl_index: Decl.Index, ) !void { - const Export = Module.Export; + const gpa = sema.gpa; const mod = sema.mod; - if (options.linkage == .Internal) { + if (options.linkage == .Internal) return; - } try mod.ensureDeclAnalyzed(exported_decl_index); const exported_decl = mod.declPtr(exported_decl_index); @@ -6069,7 +6072,7 @@ pub fn analyzeExport( if (!try sema.validateExternType(exported_decl.ty, .other)) { const msg = msg: { const msg = try sema.errMsg(block, src, "unable to export type '{}'", .{exported_decl.ty.fmt(mod)}); - errdefer msg.destroy(sema.gpa); + errdefer msg.destroy(gpa); const src_decl = mod.declPtr(block.src_decl); try sema.explainWhyTypeIsNotExtern(msg, src.toSrcLoc(src_decl, mod), exported_decl.ty, .other); @@ -6089,38 +6092,45 @@ pub fn analyzeExport( try mod.markDeclAlive(exported_decl); try sema.maybeQueueFuncBodyAnalysis(exported_decl_index); - const gpa = sema.gpa; - - try mod.decl_exports.ensureUnusedCapacity(gpa, 1); - try mod.export_owners.ensureUnusedCapacity(gpa, 1); - - const new_export = try gpa.create(Export); - errdefer gpa.destroy(new_export); - - new_export.* = .{ + try addExport(mod, .{ .opts = options, .src = src, .owner_decl = sema.owner_decl_index, .src_decl = block.src_decl, - .exported_decl = exported_decl_index, + .exported = .{ .decl_index = exported_decl_index }, .status = .in_progress, - }; + }); +} - // Add to export_owners table. - const eo_gop = mod.export_owners.getOrPutAssumeCapacity(sema.owner_decl_index); - if (!eo_gop.found_existing) { - eo_gop.value_ptr.* = .{}; - } +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(); - // Add to exported_decl table. - const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl_index); - if (!de_gop.found_existing) { - de_gop.value_ptr.* = .{}; + 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); + }, } - try de_gop.value_ptr.append(gpa, new_export); - errdefer _ = de_gop.value_ptr.pop(); } fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index b202320194..9edc456b35 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1144,26 +1144,40 @@ pub const Object = struct { for (mod.decl_exports.keys(), mod.decl_exports.values()) |decl_index, export_list| { const global = object.decl_map.get(decl_index) orelse continue; - const global_base = global.toConst().getBase(&object.builder); - for (export_list.items) |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.stringIfExists(mod.intern_pool.stringToSlice(exp.opts.name)) orelse continue; + try resolveGlobalCollisions(object, global, export_list.items); + } - const other_global = object.builder.getGlobal(exp_name) orelse continue; - if (other_global.toConst().getBase(&object.builder) == global_base) continue; + 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); + } + } - 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. - } + 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.stringIfExists(mod.intern_pool.stringToSlice(exp.opts.name)) 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. } } @@ -1642,7 +1656,7 @@ pub const Object = struct { try fg.wip.finish(); - try o.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); + try o.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); } pub fn updateDecl(self: *Object, module: *Module, decl_index: Module.Decl.Index) !void { @@ -1662,18 +1676,22 @@ pub const Object = struct { }, else => |e| return e, }; - try self.updateDeclExports(module, decl_index, module.getDeclExports(decl_index)); + try self.updateExports(module, .{ .decl_index = decl_index }, module.getDeclExports(decl_index)); } - pub fn updateDeclExports( + pub fn updateExports( self: *Object, mod: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, - ) !void { + ) link.File.UpdateExportsError!void { + const decl_index = switch (exported) { + .decl_index => |i| i, + .value => |val| return updateExportedValue(self, mod, val, exports), + }; const gpa = mod.gpa; // If the module does not already have the function, we ignore this function call - // because we call `updateDeclExports` at the end of `updateFunc` and `updateDecl`. + // because we call `updateExports` at the end of `updateFunc` and `updateDecl`. const global_index = self.decl_map.get(decl_index) orelse return; const decl = mod.declPtr(decl_index); if (decl.isExtern(mod)) { @@ -1733,8 +1751,7 @@ pub const Object = struct { mod.intern_pool.stringToSlice(exports[0].opts.name), ); try global_index.rename(main_exp_name, &self.builder); - global_index.setUnnamedAddr(.default, &self.builder); - if (mod.wantDllExports()) global_index.setDllStorageClass(.dllexport, &self.builder); + if (self.di_map.get(decl)) |di_node| { const main_exp_name_slice = main_exp_name.slice(&self.builder).?; if (try decl.isFunction(mod)) { @@ -1755,55 +1772,12 @@ pub const Object = struct { di_global.replaceLinkageName(linkage_name); } } - global_index.setLinkage(switch (exports[0].opts.linkage) { - .Internal => unreachable, - .Strong => .external, - .Weak => .weak_odr, - .LinkOnce => .linkonce_odr, - }, &self.builder); - global_index.setVisibility(switch (exports[0].opts.visibility) { - .default => .default, - .hidden => .hidden, - .protected => .protected, - }, &self.builder); - if (mod.intern_pool.stringToSliceUnwrap(exports[0].opts.section)) |section| - switch (global_index.ptrConst(&self.builder).kind) { - inline .variable, .function => |impl_index| impl_index.setSection( - try self.builder.string(section), - &self.builder, - ), - .alias, .replaced => unreachable, - }; + if (decl.val.getVariable(mod)) |decl_var| if (decl_var.is_threadlocal) global_index.ptrConst(&self.builder).kind .variable.setThreadLocal(.generaldynamic, &self.builder); - // If a Decl is exported more than one time (which is rare), - // we add aliases for all but the first export. - // TODO LLVM C API does not support deleting aliases. - // 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| { - const exp_name = try self.builder.string(mod.intern_pool.stringToSlice(exp.opts.name)); - if (self.builder.getGlobal(exp_name)) |global| { - switch (global.ptrConst(&self.builder).kind) { - .alias => |alias| { - alias.setAliasee(global_index.toConst(), &self.builder); - continue; - }, - .variable, .function => {}, - .replaced => unreachable, - } - } - const alias_index = try self.builder.addAlias( - .empty, - global_index.typeOf(&self.builder), - .default, - global_index.toConst(), - ); - try alias_index.rename(exp_name, &self.builder); - } + return updateExportedGlobal(self, mod, global_index, exports); } else { const fqn = try self.builder.string( mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod)), @@ -1824,6 +1798,100 @@ pub const Object = struct { } } + fn updateExportedValue( + o: *Object, + mod: *Module, + exported_value: InternPool.Index, + exports: []const *Module.Export, + ) link.File.UpdateExportsError!void { + const gpa = mod.gpa; + const main_exp_name = try o.builder.string( + mod.intern_pool.stringToSlice(exports[0].opts.name), + ); + const global_index = i: { + const gop = try o.anon_decl_map.getOrPut(gpa, exported_value); + if (gop.found_existing) { + const global_index = gop.value_ptr.*; + try global_index.rename(main_exp_name, &o.builder); + break :i global_index; + } + const llvm_addr_space = toLlvmAddressSpace(.generic, o.target); + const variable_index = try o.builder.addVariable( + main_exp_name, + try o.lowerType(mod.intern_pool.typeOf(exported_value).toType()), + llvm_addr_space, + ); + const global_index = variable_index.ptrConst(&o.builder).global; + gop.value_ptr.* = global_index; + // This line invalidates `gop`. + const init_val = o.lowerValue(exported_value) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.CodegenFail => return error.AnalysisFail, + }; + try variable_index.setInitializer(init_val, &o.builder); + break :i global_index; + }; + return updateExportedGlobal(o, mod, global_index, exports); + } + + fn updateExportedGlobal( + o: *Object, + mod: *Module, + global_index: Builder.Global.Index, + exports: []const *Module.Export, + ) link.File.UpdateExportsError!void { + global_index.setUnnamedAddr(.default, &o.builder); + if (mod.wantDllExports()) global_index.setDllStorageClass(.dllexport, &o.builder); + global_index.setLinkage(switch (exports[0].opts.linkage) { + .Internal => unreachable, + .Strong => .external, + .Weak => .weak_odr, + .LinkOnce => .linkonce_odr, + }, &o.builder); + global_index.setVisibility(switch (exports[0].opts.visibility) { + .default => .default, + .hidden => .hidden, + .protected => .protected, + }, &o.builder); + if (mod.intern_pool.stringToSliceUnwrap(exports[0].opts.section)) |section| + switch (global_index.ptrConst(&o.builder).kind) { + .variable => |impl_index| impl_index.setSection( + try o.builder.string(section), + &o.builder, + ), + .function => unreachable, + .alias => unreachable, + .replaced => unreachable, + }; + + // If a Decl is exported more than one time (which is rare), + // we add aliases for all but the first export. + // TODO LLVM C API does not support deleting aliases. + // 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| { + const exp_name = try o.builder.string(mod.intern_pool.stringToSlice(exp.opts.name)); + if (o.builder.getGlobal(exp_name)) |global| { + switch (global.ptrConst(&o.builder).kind) { + .alias => |alias| { + alias.setAliasee(global_index.toConst(), &o.builder); + continue; + }, + .variable, .function => {}, + .replaced => unreachable, + } + } + const alias_index = try o.builder.addAlias( + .empty, + global_index.typeOf(&o.builder), + .default, + global_index.toConst(), + ); + try alias_index.rename(exp_name, &o.builder); + } + } + pub fn freeDecl(self: *Object, decl_index: Module.Decl.Index) void { const global = self.decl_map.get(decl_index) orelse return; global.delete(&self.builder); diff --git a/src/link.zig b/src/link.zig index 6e5c809f62..1648d6a63e 100644 --- a/src/link.zig +++ b/src/link.zig @@ -587,7 +587,7 @@ pub const File = struct { } } - /// May be called before or after updateDeclExports for any given Decl. + /// May be called before or after updateExports for any given Decl. pub fn updateDecl(base: *File, module: *Module, decl_index: Module.Decl.Index) UpdateDeclError!void { const decl = module.declPtr(decl_index); assert(decl.has_tv); @@ -609,7 +609,7 @@ pub const File = struct { } } - /// May be called before or after updateDeclExports for any given Decl. + /// May be called before or after updateExports for any given Decl. pub fn updateFunc(base: *File, module: *Module, func_index: InternPool.Index, air: Air, liveness: Liveness) UpdateDeclError!void { if (build_options.only_c) { assert(base.tag == .c); @@ -882,33 +882,34 @@ pub const File = struct { } } - pub const UpdateDeclExportsError = error{ + pub const UpdateExportsError = error{ OutOfMemory, AnalysisFail, }; + /// This is called for every exported thing. `exports` is almost always + /// a list of size 1, meaning that `exported` is exported once. However, it is possible + /// to export the same thing with multiple different symbol names (aliases). /// May be called before or after updateDecl for any given Decl. - pub fn updateDeclExports( + pub fn updateExports( base: *File, module: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, - ) UpdateDeclExportsError!void { - const decl = module.declPtr(decl_index); - assert(decl.has_tv); + ) UpdateExportsError!void { if (build_options.only_c) { assert(base.tag == .c); - return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl_index, exports); + return @fieldParentPtr(C, "base", base).updateExports(module, exported, exports); } switch (base.tag) { - .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl_index, exports), - .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl_index, exports), - .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl_index, exports), - .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl_index, exports), - .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl_index, exports), - .spirv => return @fieldParentPtr(SpirV, "base", base).updateDeclExports(module, decl_index, exports), - .plan9 => return @fieldParentPtr(Plan9, "base", base).updateDeclExports(module, decl_index, exports), - .nvptx => return @fieldParentPtr(NvPtx, "base", base).updateDeclExports(module, decl_index, exports), + .coff => return @fieldParentPtr(Coff, "base", base).updateExports(module, exported, exports), + .elf => return @fieldParentPtr(Elf, "base", base).updateExports(module, exported, exports), + .macho => return @fieldParentPtr(MachO, "base", base).updateExports(module, exported, exports), + .c => return @fieldParentPtr(C, "base", base).updateExports(module, exported, exports), + .wasm => return @fieldParentPtr(Wasm, "base", base).updateExports(module, exported, exports), + .spirv => return @fieldParentPtr(SpirV, "base", base).updateExports(module, exported, exports), + .plan9 => return @fieldParentPtr(Plan9, "base", base).updateExports(module, exported, exports), + .nvptx => return @fieldParentPtr(NvPtx, "base", base).updateExports(module, exported, exports), } } @@ -968,6 +969,20 @@ pub const File = struct { } } + pub fn deleteDeclExport(base: *File, decl_index: Module.Decl.Index, name: InternPool.NullTerminatedString) !void { + if (build_options.only_c) unreachable; + switch (base.tag) { + .coff => return @fieldParentPtr(Coff, "base", base).deleteDeclExport(decl_index, name), + .elf => return @fieldParentPtr(Elf, "base", base).deleteDeclExport(decl_index, name), + .macho => return @fieldParentPtr(MachO, "base", base).deleteDeclExport(decl_index, name), + .plan9 => {}, + .c => {}, + .wasm => return @fieldParentPtr(Wasm, "base", base).deleteDeclExport(decl_index), + .spirv => {}, + .nvptx => {}, + } + } + /// This function is called by the frontend before flush(). It communicates that /// `options.bin_file.emit` directory needs to be renamed from /// `[zig-cache]/tmp/[random]` to `[zig-cache]/o/[digest]`. diff --git a/src/link/C.zig b/src/link/C.zig index ee1d437b00..40dfc0771d 100644 --- a/src/link/C.zig +++ b/src/link/C.zig @@ -753,14 +753,14 @@ pub fn flushEmitH(module: *Module) !void { try file.pwritevAll(all_buffers.items, 0); } -pub fn updateDeclExports( +pub fn updateExports( self: *C, module: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, ) !void { _ = exports; - _ = decl_index; + _ = exported; _ = module; _ = self; } diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 91fe043317..5d12e82478 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -1075,7 +1075,7 @@ pub fn updateFunc(self: *Coff, mod: *Module, func_index: InternPool.Index, air: // Since we updated the vaddr and the size, each corresponding export // symbol also needs to be updated. - return self.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); + return self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); } pub fn lowerUnnamedConst(self: *Coff, tv: TypedValue, decl_index: Module.Decl.Index) !u32 { @@ -1195,7 +1195,7 @@ pub fn updateDecl( // Since we updated the vaddr and the size, each corresponding export // symbol also needs to be updated. - return self.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); + return self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); } fn updateLazySymbolAtom( @@ -1409,12 +1409,12 @@ pub fn freeDecl(self: *Coff, decl_index: Module.Decl.Index) void { } } -pub fn updateDeclExports( +pub fn updateExports( self: *Coff, mod: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, -) link.File.UpdateDeclExportsError!void { +) 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"); } @@ -1425,7 +1425,11 @@ pub fn updateDeclExports( // Even in the case of LLVM, we need to notice certain exported symbols in order to // detect the default subsystem. for (exports) |exp| { - const exported_decl = mod.declPtr(exp.exported_decl); + const exported_decl_index = switch (exp.exported) { + .decl_index => |i| i, + .value => continue, + }; + const exported_decl = mod.declPtr(exported_decl_index); if (exported_decl.getOwnedFunction(mod) == null) continue; const winapi_cc = switch (self.base.options.target.cpu.arch) { .x86 => std.builtin.CallingConvention.Stdcall, @@ -1452,12 +1456,19 @@ pub fn updateDeclExports( } } - if (self.llvm_object) |llvm_object| return llvm_object.updateDeclExports(mod, decl_index, exports); + if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); if (self.base.options.emit == null) return; const gpa = self.base.allocator; + const decl_index = switch (exported) { + .decl_index => |i| i, + .value => |val| { + _ = val; + @panic("TODO: implement COFF linker code for exporting a constant value"); + }, + }; const decl = mod.declPtr(decl_index); const atom_index = try self.getOrCreateAtomForDecl(decl_index); const atom = self.getAtom(atom_index); diff --git a/src/link/Elf.zig b/src/link/Elf.zig index aa1df9365f..42bd23a2c8 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -3306,7 +3306,7 @@ pub fn updateFunc(self: *Elf, mod: *Module, func_index: InternPool.Index, air: A // Since we updated the vaddr and the size, each corresponding export // symbol also needs to be updated. - return self.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); + return self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); } pub fn updateDecl( @@ -3388,7 +3388,7 @@ pub fn updateDecl( // Since we updated the vaddr and the size, each corresponding export // symbol also needs to be updated. - return self.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); + return self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); } fn updateLazySymbol(self: *Elf, sym: link.File.LazySymbol, symbol_index: Symbol.Index) !void { @@ -3555,16 +3555,16 @@ fn lowerConst( return .{ .ok = sym_index }; } -pub fn updateDeclExports( +pub fn updateExports( self: *Elf, mod: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, -) link.File.UpdateDeclExportsError!void { +) 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.updateDeclExports(mod, decl_index, exports); + if (self.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); if (self.base.options.emit == null) return; @@ -3573,6 +3573,13 @@ pub fn updateDeclExports( const gpa = self.base.allocator; + const decl_index = switch (exported) { + .decl_index => |i| i, + .value => |val| { + _ = val; + @panic("TODO: implement ELF linker code for exporting a constant value"); + }, + }; const zig_module = self.file(self.zig_module_index.?).?.zig_module; const decl = mod.declPtr(decl_index); const decl_sym_index = try self.getOrCreateMetadataForDecl(decl_index); diff --git a/src/link/MachO.zig b/src/link/MachO.zig index b863e7a0ac..b88911b6ce 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -1670,7 +1670,7 @@ fn resolveGlobalSymbol(self: *MachO, current: SymbolWithLoc) !void { const global_is_weak = global_sym.sect() and (global_sym.weakDef() or global_sym.pext()); if (sym_is_strong and global_is_strong) { - // TODO redo this logic with corresponding logic in updateDeclExports to avoid this + // TODO redo this logic with corresponding logic in updateExports to avoid this // ugly check. if (self.mode == .zld) { try self.reportSymbolCollision(global, current); @@ -2180,7 +2180,7 @@ pub fn updateFunc(self: *MachO, mod: *Module, func_index: InternPool.Index, air: // Since we updated the vaddr and the size, each corresponding export symbol also // needs to be updated. - try self.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); + try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); } pub fn lowerUnnamedConst(self: *MachO, typed_value: TypedValue, decl_index: Module.Decl.Index) !u32 { @@ -2340,7 +2340,7 @@ pub fn updateDecl(self: *MachO, mod: *Module, decl_index: Module.Decl.Index) !vo // Since we updated the vaddr and the size, each corresponding export symbol also // needs to be updated. - try self.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); + try self.updateExports(mod, .{ .decl_index = decl_index }, mod.getDeclExports(decl_index)); } fn updateLazySymbolAtom( @@ -2529,7 +2529,7 @@ fn updateThreadlocalVariable(self: *MachO, module: *Module, decl_index: Module.D ); } - try self.updateDeclExports(module, decl_index, module.getDeclExports(decl_index)); + try self.updateExports(module, .{ .decl_index = decl_index }, module.getDeclExports(decl_index)); // 2. Create a TLV descriptor. const init_atom_sym_loc = init_atom.getSymbolWithLoc(); @@ -2670,17 +2670,17 @@ pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl_index: Module.De } } -pub fn updateDeclExports( +pub fn updateExports( self: *MachO, mod: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, -) File.UpdateDeclExportsError!void { +) 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.updateDeclExports(mod, decl_index, exports); + return llvm_object.updateExports(mod, exported, exports); if (self.base.options.emit == null) return; @@ -2689,6 +2689,13 @@ pub fn updateDeclExports( const gpa = self.base.allocator; + const decl_index = switch (exported) { + .decl_index => |i| i, + .value => |val| { + _ = val; + @panic("TODO: implement MachO linker code for exporting a constant value"); + }, + }; const decl = mod.declPtr(decl_index); const atom_index = try self.getOrCreateAtomForDecl(decl_index); const atom = self.getAtom(atom_index); diff --git a/src/link/NvPtx.zig b/src/link/NvPtx.zig index 2ccfd8a3a7..5ccdb7218a 100644 --- a/src/link/NvPtx.zig +++ b/src/link/NvPtx.zig @@ -74,16 +74,16 @@ pub fn updateDecl(self: *NvPtx, module: *Module, decl_index: Module.Decl.Index) return self.llvm_object.updateDecl(module, decl_index); } -pub fn updateDeclExports( +pub fn updateExports( self: *NvPtx, module: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, ) !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.updateDeclExports(module, decl_index, exports); + return self.llvm_object.updateExports(module, exported, exports); } pub fn freeDecl(self: *NvPtx, decl_index: Module.Decl.Index) void { diff --git a/src/link/Plan9.zig b/src/link/Plan9.zig index 3dcef859ae..96d2bdf7b4 100644 --- a/src/link/Plan9.zig +++ b/src/link/Plan9.zig @@ -1116,13 +1116,16 @@ pub fn seeDecl(self: *Plan9, decl_index: Module.Decl.Index) !Atom.Index { return atom_idx; } -pub fn updateDeclExports( +pub fn updateExports( self: *Plan9, module: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, ) !void { - _ = try self.seeDecl(decl_index); + switch (exported) { + .value => @panic("TODO: plan9 updateExports handling values"), + .decl_index => |decl_index| _ = try self.seeDecl(decl_index), + } // we do all the things in flush _ = module; _ = exports; diff --git a/src/link/SpirV.zig b/src/link/SpirV.zig index 325bb30fe0..d897029f24 100644 --- a/src/link/SpirV.zig +++ b/src/link/SpirV.zig @@ -120,12 +120,19 @@ pub fn updateDecl(self: *SpirV, module: *Module, decl_index: Module.Decl.Index) try self.object.updateDecl(module, decl_index); } -pub fn updateDeclExports( +pub fn updateExports( self: *SpirV, mod: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, ) !void { + const decl_index = switch (exported) { + .decl_index => |i| i, + .value => |val| { + _ = val; + @panic("TODO: implement SpirV linker code for exporting a constant value"); + }, + }; const decl = mod.declPtr(decl_index); if (decl.val.isFuncBody(mod) and decl.ty.fnCallingConvention(mod) == .Kernel) { const spv_decl_index = try self.object.resolveDecl(mod, decl_index); diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index c51736ff28..7755915a67 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -1786,19 +1786,26 @@ pub fn deleteDeclExport(wasm: *Wasm, decl_index: Module.Decl.Index) void { } } -pub fn updateDeclExports( +pub fn updateExports( wasm: *Wasm, mod: *Module, - decl_index: Module.Decl.Index, + exported: Module.Exported, exports: []const *Module.Export, ) !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.updateDeclExports(mod, decl_index, exports); + if (wasm.llvm_object) |llvm_object| return llvm_object.updateExports(mod, exported, exports); if (wasm.base.options.emit == null) return; + const decl_index = switch (exported) { + .decl_index => |i| i, + .value => |val| { + _ = val; + @panic("TODO: implement Wasm linker code for exporting a constant value"); + }, + }; const decl = mod.declPtr(decl_index); const atom_index = try wasm.getOrCreateAtomForDecl(decl_index); const atom = wasm.getAtom(atom_index); @@ -1816,7 +1823,19 @@ pub fn updateDeclExports( continue; } - const exported_atom_index = try wasm.getOrCreateAtomForDecl(exp.exported_decl); + const exported_decl_index = switch (exp.exported) { + .value => { + try mod.failed_exports.putNoClobber(gpa, exp, try Module.ErrorMsg.create( + gpa, + decl.srcLoc(mod), + "Unimplemented: exporting a named constant value", + .{}, + )); + continue; + }, + .decl_index => |i| i, + }; + const exported_atom_index = try wasm.getOrCreateAtomForDecl(exported_decl_index); const exported_atom = wasm.getAtom(exported_atom_index); const export_name = try wasm.string_table.put(wasm.base.allocator, mod.intern_pool.stringToSlice(exp.opts.name)); const sym_loc = exported_atom.symbolLoc(); diff --git a/test/behavior/export.zig b/test/behavior/export.zig index 4751ccafe5..d10743206d 100644 --- a/test/behavior/export.zig +++ b/test/behavior/export.zig @@ -72,6 +72,8 @@ test "exporting using field access" { } test "exporting comptime-known value" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + const x: u32 = 10; @export(x, .{ .name = "exporting_comptime_known_value_foo" }); const S = struct { @@ -81,6 +83,8 @@ test "exporting comptime-known value" { } test "exporting comptime var" { + if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; + comptime var x: u32 = 5; @export(x, .{ .name = "exporting_comptime_var_foo" }); x = 7; // modifying this now shouldn't change anything