Skip to content

Commit 1c85b0a

Browse files
authored
Merge pull request #17735 from ziglang/export-anon
link: support exporting constant values without a Decl
2 parents 772636e + 4bc88dd commit 1c85b0a

File tree

13 files changed

+425
-233
lines changed

13 files changed

+425
-233
lines changed

src/Module.zig

Lines changed: 110 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ local_zir_cache: Compilation.Directory,
7070
/// The Export memory is owned by the `export_owners` table; the slice itself
7171
/// is owned by this table. The slice is guaranteed to not be empty.
7272
decl_exports: std.AutoArrayHashMapUnmanaged(Decl.Index, ArrayListUnmanaged(*Export)) = .{},
73+
/// Same as `decl_exports` but for exported constant values.
74+
value_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, ArrayListUnmanaged(*Export)) = .{},
7375
/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
7476
/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
7577
/// is performing the export of another Decl.
@@ -244,6 +246,13 @@ pub const GlobalEmitH = struct {
244246

245247
pub const ErrorInt = u32;
246248

249+
pub const Exported = union(enum) {
250+
/// The Decl being exported. Note this is *not* the Decl performing the export.
251+
decl_index: Decl.Index,
252+
/// Constant value being exported.
253+
value: InternPool.Index,
254+
};
255+
247256
pub const Export = struct {
248257
opts: Options,
249258
src: LazySrcLoc,
@@ -252,8 +261,7 @@ pub const Export = struct {
252261
/// The Decl containing the export statement. Inline function calls
253262
/// may cause this to be different from the owner_decl.
254263
src_decl: Decl.Index,
255-
/// The Decl being exported. Note this is *not* the Decl performing the export.
256-
exported_decl: Decl.Index,
264+
exported: Exported,
257265
status: enum {
258266
in_progress,
259267
failed,
@@ -2575,6 +2583,11 @@ pub fn deinit(mod: *Module) void {
25752583
}
25762584
mod.decl_exports.deinit(gpa);
25772585

2586+
for (mod.value_exports.values()) |*export_list| {
2587+
export_list.deinit(gpa);
2588+
}
2589+
mod.value_exports.deinit(gpa);
2590+
25782591
for (mod.export_owners.values()) |*value| {
25792592
freeExportList(gpa, value);
25802593
}
@@ -4620,36 +4633,49 @@ fn deleteDeclExports(mod: *Module, decl_index: Decl.Index) Allocator.Error!void
46204633
var export_owners = (mod.export_owners.fetchSwapRemove(decl_index) orelse return).value;
46214634

46224635
for (export_owners.items) |exp| {
4623-
if (mod.decl_exports.getPtr(exp.exported_decl)) |value_ptr| {
4624-
// Remove exports with owner_decl matching the regenerating decl.
4625-
const list = value_ptr.items;
4626-
var i: usize = 0;
4627-
var new_len = list.len;
4628-
while (i < new_len) {
4629-
if (list[i].owner_decl == decl_index) {
4630-
mem.copyBackwards(*Export, list[i..], list[i + 1 .. new_len]);
4631-
new_len -= 1;
4632-
} else {
4633-
i += 1;
4636+
switch (exp.exported) {
4637+
.decl_index => |exported_decl_index| {
4638+
if (mod.decl_exports.getPtr(exported_decl_index)) |export_list| {
4639+
// Remove exports with owner_decl matching the regenerating decl.
4640+
const list = export_list.items;
4641+
var i: usize = 0;
4642+
var new_len = list.len;
4643+
while (i < new_len) {
4644+
if (list[i].owner_decl == decl_index) {
4645+
mem.copyBackwards(*Export, list[i..], list[i + 1 .. new_len]);
4646+
new_len -= 1;
4647+
} else {
4648+
i += 1;
4649+
}
4650+
}
4651+
export_list.shrinkAndFree(mod.gpa, new_len);
4652+
if (new_len == 0) {
4653+
assert(mod.decl_exports.swapRemove(exported_decl_index));
4654+
}
46344655
}
4635-
}
4636-
value_ptr.shrinkAndFree(mod.gpa, new_len);
4637-
if (new_len == 0) {
4638-
assert(mod.decl_exports.swapRemove(exp.exported_decl));
4639-
}
4640-
}
4641-
if (mod.comp.bin_file.cast(link.File.Elf)) |elf| {
4642-
elf.deleteDeclExport(decl_index, exp.opts.name);
4643-
}
4644-
if (mod.comp.bin_file.cast(link.File.MachO)) |macho| {
4645-
try macho.deleteDeclExport(decl_index, exp.opts.name);
4646-
}
4647-
if (mod.comp.bin_file.cast(link.File.Wasm)) |wasm| {
4648-
wasm.deleteDeclExport(decl_index);
4649-
}
4650-
if (mod.comp.bin_file.cast(link.File.Coff)) |coff| {
4651-
coff.deleteDeclExport(decl_index, exp.opts.name);
4656+
},
4657+
.value => |value| {
4658+
if (mod.value_exports.getPtr(value)) |export_list| {
4659+
// Remove exports with owner_decl matching the regenerating decl.
4660+
const list = export_list.items;
4661+
var i: usize = 0;
4662+
var new_len = list.len;
4663+
while (i < new_len) {
4664+
if (list[i].owner_decl == decl_index) {
4665+
mem.copyBackwards(*Export, list[i..], list[i + 1 .. new_len]);
4666+
new_len -= 1;
4667+
} else {
4668+
i += 1;
4669+
}
4670+
}
4671+
export_list.shrinkAndFree(mod.gpa, new_len);
4672+
if (new_len == 0) {
4673+
assert(mod.value_exports.swapRemove(value));
4674+
}
4675+
}
4676+
},
46524677
}
4678+
try mod.comp.bin_file.deleteDeclExport(decl_index, exp.opts.name);
46534679
if (mod.failed_exports.fetchSwapRemove(exp)) |failed_kv| {
46544680
failed_kv.value.destroy(mod.gpa);
46554681
}
@@ -5503,48 +5529,63 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void {
55035529
/// reporting compile errors. In this function we emit exported symbol collision
55045530
/// errors and communicate exported symbols to the linker backend.
55055531
pub fn processExports(mod: *Module) !void {
5506-
const gpa = mod.gpa;
55075532
// Map symbol names to `Export` for name collision detection.
5508-
var symbol_exports: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, *Export) = .{};
5509-
defer symbol_exports.deinit(gpa);
5510-
5511-
var it = mod.decl_exports.iterator();
5512-
while (it.next()) |entry| {
5513-
const exported_decl = entry.key_ptr.*;
5514-
const exports = entry.value_ptr.items;
5515-
for (exports) |new_export| {
5516-
const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name);
5517-
if (gop.found_existing) {
5518-
new_export.status = .failed_retryable;
5519-
try mod.failed_exports.ensureUnusedCapacity(gpa, 1);
5520-
const src_loc = new_export.getSrcLoc(mod);
5521-
const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {}", .{
5522-
new_export.opts.name.fmt(&mod.intern_pool),
5523-
});
5524-
errdefer msg.destroy(gpa);
5525-
const other_export = gop.value_ptr.*;
5526-
const other_src_loc = other_export.getSrcLoc(mod);
5527-
try mod.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{});
5528-
mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
5529-
new_export.status = .failed;
5530-
} else {
5531-
gop.value_ptr.* = new_export;
5532-
}
5533+
var symbol_exports: SymbolExports = .{};
5534+
defer symbol_exports.deinit(mod.gpa);
5535+
5536+
for (mod.decl_exports.keys(), mod.decl_exports.values()) |exported_decl, exports_list| {
5537+
const exported: Exported = .{ .decl_index = exported_decl };
5538+
try processExportsInner(mod, &symbol_exports, exported, exports_list.items);
5539+
}
5540+
5541+
for (mod.value_exports.keys(), mod.value_exports.values()) |exported_value, exports_list| {
5542+
const exported: Exported = .{ .value = exported_value };
5543+
try processExportsInner(mod, &symbol_exports, exported, exports_list.items);
5544+
}
5545+
}
5546+
5547+
const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, *Export);
5548+
5549+
fn processExportsInner(
5550+
mod: *Module,
5551+
symbol_exports: *SymbolExports,
5552+
exported: Exported,
5553+
exports: []const *Export,
5554+
) error{OutOfMemory}!void {
5555+
const gpa = mod.gpa;
5556+
5557+
for (exports) |new_export| {
5558+
const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name);
5559+
if (gop.found_existing) {
5560+
new_export.status = .failed_retryable;
5561+
try mod.failed_exports.ensureUnusedCapacity(gpa, 1);
5562+
const src_loc = new_export.getSrcLoc(mod);
5563+
const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {}", .{
5564+
new_export.opts.name.fmt(&mod.intern_pool),
5565+
});
5566+
errdefer msg.destroy(gpa);
5567+
const other_export = gop.value_ptr.*;
5568+
const other_src_loc = other_export.getSrcLoc(mod);
5569+
try mod.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{});
5570+
mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
5571+
new_export.status = .failed;
5572+
} else {
5573+
gop.value_ptr.* = new_export;
55335574
}
5534-
mod.comp.bin_file.updateDeclExports(mod, exported_decl, exports) catch |err| switch (err) {
5535-
error.OutOfMemory => return error.OutOfMemory,
5536-
else => {
5537-
const new_export = exports[0];
5538-
new_export.status = .failed_retryable;
5539-
try mod.failed_exports.ensureUnusedCapacity(gpa, 1);
5540-
const src_loc = new_export.getSrcLoc(mod);
5541-
const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{
5542-
@errorName(err),
5543-
});
5544-
mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
5545-
},
5546-
};
55475575
}
5576+
mod.comp.bin_file.updateExports(mod, exported, exports) catch |err| switch (err) {
5577+
error.OutOfMemory => return error.OutOfMemory,
5578+
else => {
5579+
const new_export = exports[0];
5580+
new_export.status = .failed_retryable;
5581+
try mod.failed_exports.ensureUnusedCapacity(gpa, 1);
5582+
const src_loc = new_export.getSrcLoc(mod);
5583+
const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{
5584+
@errorName(err),
5585+
});
5586+
mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg);
5587+
},
5588+
};
55485589
}
55495590

55505591
pub fn populateTestFunctions(

src/Sema.zig

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -6026,6 +6026,7 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
60266026
const tracy = trace(@src());
60276027
defer tracy.end();
60286028

6029+
const mod = sema.mod;
60296030
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
60306031
const extra = sema.code.extraData(Zir.Inst.ExportValue, inst_data.payload_index).data;
60316032
const src = inst_data.src();
@@ -6034,19 +6035,22 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError
60346035
const operand = try sema.resolveInstConst(block, operand_src, extra.operand, .{
60356036
.needed_comptime_reason = "export target must be comptime-known",
60366037
});
6037-
const options = sema.resolveExportOptions(block, .unneeded, extra.options) catch |err| switch (err) {
6038-
error.NeededSourceLocation => {
6039-
_ = try sema.resolveExportOptions(block, options_src, extra.options);
6040-
unreachable;
6041-
},
6042-
else => |e| return e,
6043-
};
6044-
const decl_index = if (operand.val.getFunction(sema.mod)) |function| function.owner_decl else blk: {
6045-
var anon_decl = try block.startAnonDecl(); // TODO: export value without Decl
6046-
defer anon_decl.deinit();
6047-
break :blk try anon_decl.finish(operand.ty, operand.val, .none);
6048-
};
6049-
try sema.analyzeExport(block, src, options, decl_index);
6038+
const options = try sema.resolveExportOptions(block, options_src, extra.options);
6039+
if (options.linkage == .Internal)
6040+
return;
6041+
if (operand.val.getFunction(mod)) |function| {
6042+
const decl_index = function.owner_decl;
6043+
return sema.analyzeExport(block, src, options, decl_index);
6044+
}
6045+
6046+
try addExport(mod, .{
6047+
.opts = options,
6048+
.src = src,
6049+
.owner_decl = sema.owner_decl_index,
6050+
.src_decl = block.src_decl,
6051+
.exported = .{ .value = operand.val.toIntern() },
6052+
.status = .in_progress,
6053+
});
60506054
}
60516055

60526056
pub fn analyzeExport(
@@ -6056,20 +6060,19 @@ pub fn analyzeExport(
60566060
options: Module.Export.Options,
60576061
exported_decl_index: Decl.Index,
60586062
) !void {
6059-
const Export = Module.Export;
6063+
const gpa = sema.gpa;
60606064
const mod = sema.mod;
60616065

6062-
if (options.linkage == .Internal) {
6066+
if (options.linkage == .Internal)
60636067
return;
6064-
}
60656068

60666069
try mod.ensureDeclAnalyzed(exported_decl_index);
60676070
const exported_decl = mod.declPtr(exported_decl_index);
60686071

60696072
if (!try sema.validateExternType(exported_decl.ty, .other)) {
60706073
const msg = msg: {
60716074
const msg = try sema.errMsg(block, src, "unable to export type '{}'", .{exported_decl.ty.fmt(mod)});
6072-
errdefer msg.destroy(sema.gpa);
6075+
errdefer msg.destroy(gpa);
60736076

60746077
const src_decl = mod.declPtr(block.src_decl);
60756078
try sema.explainWhyTypeIsNotExtern(msg, src.toSrcLoc(src_decl, mod), exported_decl.ty, .other);
@@ -6089,38 +6092,45 @@ pub fn analyzeExport(
60896092
try mod.markDeclAlive(exported_decl);
60906093
try sema.maybeQueueFuncBodyAnalysis(exported_decl_index);
60916094

6092-
const gpa = sema.gpa;
6095+
try addExport(mod, .{
6096+
.opts = options,
6097+
.src = src,
6098+
.owner_decl = sema.owner_decl_index,
6099+
.src_decl = block.src_decl,
6100+
.exported = .{ .decl_index = exported_decl_index },
6101+
.status = .in_progress,
6102+
});
6103+
}
6104+
6105+
fn addExport(mod: *Module, export_init: Module.Export) error{OutOfMemory}!void {
6106+
const gpa = mod.gpa;
60936107

60946108
try mod.decl_exports.ensureUnusedCapacity(gpa, 1);
6109+
try mod.value_exports.ensureUnusedCapacity(gpa, 1);
60956110
try mod.export_owners.ensureUnusedCapacity(gpa, 1);
60966111

6097-
const new_export = try gpa.create(Export);
6112+
const new_export = try gpa.create(Module.Export);
60986113
errdefer gpa.destroy(new_export);
60996114

6100-
new_export.* = .{
6101-
.opts = options,
6102-
.src = src,
6103-
.owner_decl = sema.owner_decl_index,
6104-
.src_decl = block.src_decl,
6105-
.exported_decl = exported_decl_index,
6106-
.status = .in_progress,
6107-
};
6115+
new_export.* = export_init;
61086116

6109-
// Add to export_owners table.
6110-
const eo_gop = mod.export_owners.getOrPutAssumeCapacity(sema.owner_decl_index);
6111-
if (!eo_gop.found_existing) {
6112-
eo_gop.value_ptr.* = .{};
6113-
}
6117+
const eo_gop = mod.export_owners.getOrPutAssumeCapacity(export_init.owner_decl);
6118+
if (!eo_gop.found_existing) eo_gop.value_ptr.* = .{};
61146119
try eo_gop.value_ptr.append(gpa, new_export);
61156120
errdefer _ = eo_gop.value_ptr.pop();
61166121

6117-
// Add to exported_decl table.
6118-
const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl_index);
6119-
if (!de_gop.found_existing) {
6120-
de_gop.value_ptr.* = .{};
6122+
switch (export_init.exported) {
6123+
.decl_index => |decl_index| {
6124+
const de_gop = mod.decl_exports.getOrPutAssumeCapacity(decl_index);
6125+
if (!de_gop.found_existing) de_gop.value_ptr.* = .{};
6126+
try de_gop.value_ptr.append(gpa, new_export);
6127+
},
6128+
.value => |value| {
6129+
const ve_gop = mod.value_exports.getOrPutAssumeCapacity(value);
6130+
if (!ve_gop.found_existing) ve_gop.value_ptr.* = .{};
6131+
try ve_gop.value_ptr.append(gpa, new_export);
6132+
},
61216133
}
6122-
try de_gop.value_ptr.append(gpa, new_export);
6123-
errdefer _ = de_gop.value_ptr.pop();
61246134
}
61256135

61266136
fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {

0 commit comments

Comments
 (0)