diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-05-14 17:41:22 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-05-14 17:41:22 -0700 |
| commit | b4692c9a7808caabdf474c2acc6d6d3754e5e2e4 (patch) | |
| tree | a824991e12daf2e96be411926b82a123ccb2672b /src/Module.zig | |
| parent | 9958652d92ee074fbd71a5d75a56341518cc0f99 (diff) | |
| download | zig-b4692c9a7808caabdf474c2acc6d6d3754e5e2e4.tar.gz zig-b4692c9a7808caabdf474c2acc6d6d3754e5e2e4.zip | |
stage2: improve Decl dependency management
* Do not report export collision errors until the very end, because it
is possible, during an update, for a new export to be added before an
old one is semantically analyzed to be deleted. In such a case there
should be no compile error.
- Likewise we defer emitting exports until the end when we know for
sure what will happen.
* Sema: Fix not adding a Decl dependency on imported files.
* Sema: Properly add Decl dependencies for all identifier and namespace
lookups.
* After semantic analysis for a Decl, if it is still marked as
`in_progress`, change it to `dependency_failure` because if the Decl
itself failed, it would have already been changed during the call to
add the compile error.
Diffstat (limited to 'src/Module.zig')
| -rw-r--r-- | src/Module.zig | 184 |
1 files changed, 82 insertions, 102 deletions
diff --git a/src/Module.zig b/src/Module.zig index 1b80afd928..b4865cf34d 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -41,14 +41,11 @@ root_pkg: *Package, global_zir_cache: Compilation.Directory, /// Used by AstGen worker to load and store ZIR cache. local_zir_cache: Compilation.Directory, -/// It's rare for a decl to be exported, so we save memory by having a sparse map of -/// Decl pointers to details about them being exported. -/// 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. +/// It's rare for a decl to be exported, so we save memory by having a sparse +/// map of Decl pointers to details about them being exported. +/// 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, []*Export) = .{}, -/// We track which export is associated with the given symbol name for quick -/// detection of symbol collisions. -symbol_exports: std.StringArrayHashMapUnmanaged(*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. @@ -144,6 +141,14 @@ pub const Export = struct { failed_retryable, complete, }, + + pub fn getSrcLoc(exp: Export) SrcLoc { + return .{ + .file_scope = exp.owner_decl.namespace.file_scope, + .parent_decl_node = exp.owner_decl.src_node, + .lazy = exp.src, + }; + } }; /// When Module emit_h field is non-null, each Decl is allocated via this struct, so that @@ -2184,8 +2189,6 @@ pub fn deinit(mod: *Module) void { } mod.export_owners.deinit(gpa); - mod.symbol_exports.deinit(gpa); - var it = mod.global_error_set.iterator(); while (it.next()) |entry| { gpa.free(entry.key); @@ -2779,7 +2782,10 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void { for (decl.dependencies.items()) |entry| { const dep = entry.key; dep.removeDependant(decl); - if (dep.dependants.items().len == 0 and !dep.deletion_flag) { + if (dep.dependants.count() == 0 and !dep.deletion_flag) { + log.debug("insert {*} ({s}) dependant {*} ({s}) into deletion set", .{ + decl, decl.name, dep, dep.name, + }); // We don't perform a deletion here, because this Decl or another one // may end up referencing it before the update is complete. dep.deletion_flag = true; @@ -2795,11 +2801,18 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void { }; const type_changed = mod.semaDecl(decl) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => return error.AnalysisFail, + error.AnalysisFail => { + if (decl.analysis == .in_progress) { + // If this decl caused the compile error, the analysis field would + // be changed to indicate it was this Decl's fault. Because this + // did not happen, we infer here that it was a dependency failure. + decl.analysis = .dependency_failure; + } + return error.AnalysisFail; + }, else => { decl.analysis = .sema_failure_retryable; - try mod.failed_decls.ensureCapacity(mod.gpa, mod.failed_decls.items().len + 1); + try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1); mod.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( mod.gpa, decl.srcLoc(), @@ -2818,7 +2831,7 @@ pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void { const dep = entry.key; switch (dep.analysis) { .unreferenced => unreachable, - .in_progress => unreachable, + .in_progress => continue, // already doing analysis, ok .outdated => continue, // already queued for update .file_failure, @@ -3115,8 +3128,14 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool { /// Returns the depender's index of the dependee. pub fn declareDeclDependency(mod: *Module, depender: *Decl, dependee: *Decl) !void { - try depender.dependencies.ensureCapacity(mod.gpa, depender.dependencies.count() + 1); - try dependee.dependants.ensureCapacity(mod.gpa, dependee.dependants.count() + 1); + if (depender == dependee) return; + + log.debug("{*} ({s}) depends on {*} ({s})", .{ + depender, depender.name, dependee, dependee.name, + }); + + try depender.dependencies.ensureUnusedCapacity(mod.gpa, 1); + try dependee.dependants.ensureUnusedCapacity(mod.gpa, 1); if (dependee.deletion_flag) { dependee.deletion_flag = false; @@ -3513,7 +3532,6 @@ fn deleteDeclExports(mod: *Module, decl: *Decl) void { if (mod.failed_exports.swapRemove(exp)) |entry| { entry.value.destroy(mod.gpa); } - _ = mod.symbol_exports.swapRemove(exp.options.name); mod.gpa.free(exp.options.name); mod.gpa.destroy(exp); } @@ -3726,38 +3744,6 @@ pub fn analyzeExport( de_gop.entry.value = try mod.gpa.realloc(de_gop.entry.value, de_gop.entry.value.len + 1); de_gop.entry.value[de_gop.entry.value.len - 1] = new_export; errdefer de_gop.entry.value = mod.gpa.shrink(de_gop.entry.value, de_gop.entry.value.len - 1); - - if (mod.symbol_exports.get(symbol_name)) |other_export| { - new_export.status = .failed_retryable; - try mod.failed_exports.ensureUnusedCapacity(mod.gpa, 1); - const msg = try mod.errMsg( - scope, - src, - "exported symbol collision: {s}", - .{symbol_name}, - ); - errdefer msg.destroy(mod.gpa); - const other_src_loc: SrcLoc = .{ - .file_scope = other_export.owner_decl.namespace.file_scope, - .parent_decl_node = other_export.owner_decl.src_node, - .lazy = other_export.src, - }; - try mod.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{}); - mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); - new_export.status = .failed; - return; - } - - try mod.symbol_exports.putNoClobber(mod.gpa, symbol_name, new_export); - mod.comp.bin_file.updateDeclExports(mod, exported_decl, de_gop.entry.value) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - new_export.status = .failed_retryable; - try mod.failed_exports.ensureCapacity(mod.gpa, mod.failed_exports.items().len + 1); - const msg = try mod.errMsg(scope, src, "unable to export: {s}", .{@errorName(err)}); - mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); - }, - }; } pub fn constInst(mod: *Module, arena: *Allocator, src: LazySrcLoc, typed_value: TypedValue) !*ir.Inst { const const_inst = try arena.create(ir.Inst.Constant); @@ -3903,59 +3889,6 @@ pub fn getNextAnonNameIndex(mod: *Module) usize { return @atomicRmw(usize, &mod.next_anon_name_index, .Add, 1, .Monotonic); } -/// This looks up a bare identifier in the given scope. This will walk up the tree of namespaces -/// in scope and check each one for the identifier. -/// TODO emit a compile error if more than one decl would be matched. -pub fn lookupIdentifier( - mod: *Module, - scope: *Scope, - ident_name: []const u8, -) error{AnalysisFail}!?*Decl { - var namespace = scope.namespace(); - while (true) { - if (try mod.lookupInNamespace(namespace, ident_name, false)) |decl| { - return decl; - } - namespace = namespace.parent orelse break; - } - return null; -} - -/// This looks up a member of a specific namespace. It is affected by `usingnamespace` but -/// only for ones in the specified namespace. -pub fn lookupInNamespace( - mod: *Module, - namespace: *Scope.Namespace, - ident_name: []const u8, - only_pub_usingnamespaces: bool, -) error{AnalysisFail}!?*Decl { - const owner_decl = namespace.getDecl(); - if (owner_decl.analysis == .file_failure) { - return error.AnalysisFail; - } - - // TODO the decl doing the looking up needs to create a decl dependency - // TODO implement usingnamespace - if (namespace.decls.get(ident_name)) |decl| { - return decl; - } - return null; - //// TODO handle decl collision with usingnamespace - //// on each usingnamespace decl here. - //{ - // var it = namespace.usingnamespace_set.iterator(); - // while (it.next()) |entry| { - // const other_ns = entry.key; - // const other_is_pub = entry.value; - // if (only_pub_usingnamespaces and !other_is_pub) continue; - // // TODO handle cycles - // if (mod.lookupInNamespace(other_ns, ident_name, true)) |decl| { - // return decl; - // } - // } - //} -} - pub fn makeIntType(arena: *Allocator, signedness: std.builtin.Signedness, bits: u16) !Type { const int_payload = try arena.create(Type.Payload.Bits); int_payload.* = .{ @@ -4922,3 +4855,50 @@ pub fn processOutdatedAndDeletedDecls(mod: *Module) !void { try mod.markOutdatedDecl(entry.key); } } + +/// Called from `Compilation.update`, after everything is done, just before +/// 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.StringArrayHashMapUnmanaged(*Export) = .{}; + defer symbol_exports.deinit(gpa); + + for (mod.decl_exports.items()) |entry| { + const exported_decl = entry.key; + const exports = entry.value; + for (exports) |new_export| { + const gop = try symbol_exports.getOrPut(gpa, new_export.options.name); + if (gop.found_existing) { + new_export.status = .failed_retryable; + try mod.failed_exports.ensureUnusedCapacity(gpa, 1); + const src_loc = new_export.getSrcLoc(); + const msg = try ErrorMsg.create(gpa, src_loc, "exported symbol collision: {s}", .{ + new_export.options.name, + }); + errdefer msg.destroy(gpa); + const other_export = gop.entry.value; + const other_src_loc = other_export.getSrcLoc(); + try mod.errNoteNonLazy(other_src_loc, msg, "other symbol here", .{}); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); + new_export.status = .failed; + } else { + gop.entry.value = 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(); + const msg = try ErrorMsg.create(gpa, src_loc, "unable to export: {s}", .{ + @errorName(err), + }); + mod.failed_exports.putAssumeCapacityNoClobber(new_export, msg); + }, + }; + } +} |
