Skip to content

link: support exporting constant values without a Decl #17735

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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