Commit 4bc88dd116
Changed files (13)
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;
-
- 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.
- }
+ 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.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);
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;
}
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);
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);
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);
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 {
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;
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);
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();
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]`.
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 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;
- }
+ var symbol_exports: SymbolExports = .{};
+ defer symbol_exports.deinit(mod.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);
+ }
+
+ 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.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);
- },
- };
}
+ 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(
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();
@@ -6035,12 +6036,21 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
.needed_comptime_reason = "export target must be comptime-known",
});
const options = try sema.resolveExportOptions(block, options_src, extra.options);
- 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);
+ 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(
@@ -6050,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);
@@ -6063,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);
@@ -6083,38 +6092,45 @@ pub fn analyzeExport(
try mod.markDeclAlive(exported_decl);
try sema.maybeQueueFuncBodyAnalysis(exported_decl_index);
- const gpa = sema.gpa;
+ try addExport(mod, .{
+ .opts = options,
+ .src = src,
+ .owner_decl = sema.owner_decl_index,
+ .src_decl = block.src_decl,
+ .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(Export);
+ const new_export = try gpa.create(Module.Export);
errdefer gpa.destroy(new_export);
- new_export.* = .{
- .opts = options,
- .src = src,
- .owner_decl = sema.owner_decl_index,
- .src_decl = block.src_decl,
- .exported_decl = exported_decl_index,
- .status = .in_progress,
- };
+ new_export.* = export_init;
- // 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.* = .{};
- }
+ 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 {
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