Skip to content

Commit

Permalink
Merge pull request #17735 from ziglang/export-anon
Browse files Browse the repository at this point in the history
link: support exporting constant values without a Decl
  • Loading branch information
andrewrk authored Oct 27, 2023
2 parents 772636e + 4bc88dd commit 1c85b0a
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 233 deletions.
179 changes: 110 additions & 69 deletions src/Module.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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(
Expand Down
86 changes: 48 additions & 38 deletions src/Sema.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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(
Expand All @@ -6056,20 +6060,19 @@ 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);

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);
Expand All @@ -6089,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 {
Expand Down
Loading

0 comments on commit 1c85b0a

Please sign in to comment.