Merge pull request #17735 from ziglang/export-anon

link: support exporting constant values without a Decl
This commit is contained in:
Andrew Kelley 2023-10-27 15:20:13 -04:00 committed by GitHub
commit 1c85b0acbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 424 additions and 232 deletions

View File

@ -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(

View File

@ -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 {

View File

@ -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);

View File

@ -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]`.

View File

@ -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;
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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();

View File

@ -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