diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Compilation.zig | 2 | ||||
| -rw-r--r-- | src/Module.zig | 184 | ||||
| -rw-r--r-- | src/Sema.zig | 57 |
3 files changed, 128 insertions, 115 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 70a61af57a..80c8361d8d 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1622,6 +1622,8 @@ pub fn update(self: *Compilation) !void { assert(decl.dependants.count() == 0); try module.deleteDecl(decl, null); } + + try module.processExports(); } } 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); + }, + }; + } +} diff --git a/src/Sema.zig b/src/Sema.zig index 1551b59861..7022d2fdd3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -2070,15 +2070,43 @@ fn zirDeclVal(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError } fn lookupIdentifier(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, name: []const u8) !*Decl { - const mod = sema.mod; - const decl = (try mod.lookupIdentifier(&sema.namespace.base, name)) orelse { - // TODO insert a "dependency on the non-existence of a decl" here to make this - // compile error go away when the decl is introduced. This data should be in a global - // sparse map since it is only relevant when a compile error occurs. - return mod.fail(&block.base, src, "use of undeclared identifier '{s}'", .{name}); - }; - _ = try mod.declareDeclDependency(sema.owner_decl, decl); - return decl; + // TODO emit a compile error if more than one decl would be matched. + var namespace = sema.namespace; + while (true) { + if (try sema.lookupInNamespace(namespace, name)) |decl| { + return decl; + } + namespace = namespace.parent orelse break; + } + return sema.mod.fail(&block.base, src, "use of undeclared identifier '{s}'", .{name}); +} + +/// This looks up a member of a specific namespace. It is affected by `usingnamespace` but +/// only for ones in the specified namespace. +fn lookupInNamespace( + sema: *Sema, + namespace: *Scope.Namespace, + ident_name: []const u8, +) InnerError!?*Decl { + const namespace_decl = namespace.getDecl(); + if (namespace_decl.analysis == .file_failure) { + try sema.mod.declareDeclDependency(sema.owner_decl, namespace_decl); + return error.AnalysisFail; + } + + // TODO implement usingnamespace + if (namespace.decls.get(ident_name)) |decl| { + try sema.mod.declareDeclDependency(sema.owner_decl, decl); + return decl; + } + log.debug("{*} ({s}) depends on non-existence of '{s}' in {*} ({s})", .{ + sema.owner_decl, sema.owner_decl.name, ident_name, namespace_decl, namespace_decl.name, + }); + // TODO This dependency is too strong. Really, it should only be a dependency + // on the non-existence of `ident_name` in the namespace. We can lessen the number of + // outdated declarations by making this dependency more sophisticated. + try sema.mod.declareDeclDependency(sema.owner_decl, namespace_decl); + return null; } fn zirCall( @@ -4395,7 +4423,7 @@ fn zirHasDecl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError "expected struct, enum, union, or opaque, found '{}'", .{container_type}, ); - if (try mod.lookupInNamespace(namespace, decl_name, true)) |decl| { + if (try sema.lookupInNamespace(namespace, decl_name)) |decl| { if (decl.is_pub or decl.namespace.file_scope == block.base.namespace().file_scope) { return mod.constBool(arena, src, true); } @@ -4423,7 +4451,9 @@ fn zirImport(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError! }, }; try mod.semaFile(result.file); - return mod.constType(sema.arena, src, result.file.root_decl.?.ty); + const file_root_decl = result.file.root_decl.?; + try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl); + return mod.constType(sema.arena, src, file_root_decl.ty); } fn zirShl(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) InnerError!*Inst { @@ -6327,7 +6357,7 @@ fn analyzeNamespaceLookup( ) InnerError!?*Inst { const mod = sema.mod; const gpa = sema.gpa; - if (try mod.lookupInNamespace(namespace, decl_name, true)) |decl| { + if (try sema.lookupInNamespace(namespace, decl_name)) |decl| { if (!decl.is_pub and decl.namespace.file_scope != block.getFileScope()) { const msg = msg: { const msg = try mod.errMsg(&block.base, src, "'{s}' is not marked 'pub'", .{ @@ -6752,7 +6782,7 @@ fn analyzeDeclVal(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl } fn analyzeDeclRef(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, decl: *Decl) InnerError!*Inst { - _ = try sema.mod.declareDeclDependency(sema.owner_decl, decl); + try sema.mod.declareDeclDependency(sema.owner_decl, decl); sema.mod.ensureDeclAnalyzed(decl) catch |err| { if (sema.func) |func| { func.state = .dependency_failure; @@ -7544,3 +7574,4 @@ fn enumFieldSrcLoc( } } else unreachable; } + |
