From 525f341f33af9b8aad53931fd5511f00a82cb090 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 15 Jun 2024 16:10:53 -0400 Subject: Zcu: introduce `PerThread` and pass to all the functions --- src/Zcu.zig | 2264 +++-------------------------------------------------------- 1 file changed, 103 insertions(+), 2161 deletions(-) (limited to 'src/Zcu.zig') diff --git a/src/Zcu.zig b/src/Zcu.zig index 203238c633..bfc70815df 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -6,7 +6,6 @@ const std = @import("std"); const builtin = @import("builtin"); const mem = std.mem; const Allocator = std.mem.Allocator; -const ArrayListUnmanaged = std.ArrayListUnmanaged; const assert = std.debug.assert; const log = std.log.scoped(.module); const BigIntConst = std.math.big.int.Const; @@ -75,10 +74,10 @@ local_zir_cache: Compilation.Directory, /// This is where all `Export` values are stored. Not all values here are necessarily valid exports; /// to enumerate all exports, `single_exports` and `multi_exports` must be consulted. -all_exports: ArrayListUnmanaged(Export) = .{}, +all_exports: std.ArrayListUnmanaged(Export) = .{}, /// This is a list of free indices in `all_exports`. These indices may be reused by exports from /// future semantic analysis. -free_exports: ArrayListUnmanaged(u32) = .{}, +free_exports: std.ArrayListUnmanaged(u32) = .{}, /// Maps from an `AnalUnit` which performs a single export, to the index into `all_exports` of /// the export it performs. Note that the key is not the `Decl` being exported, but the `AnalUnit` /// whose analysis triggered the export. @@ -179,7 +178,7 @@ stage1_flags: packed struct { reserved: u2 = 0, } = .{}, -compile_log_text: ArrayListUnmanaged(u8) = .{}, +compile_log_text: std.ArrayListUnmanaged(u8) = .{}, emit_h: ?*GlobalEmitH, @@ -203,6 +202,8 @@ panic_messages: [PanicId.len]Decl.OptionalIndex = .{.none} ** PanicId.len, panic_func_index: InternPool.Index = .none, null_stack_trace: InternPool.Index = .none, +pub const PerThread = @import("Zcu/PerThread.zig"); + pub const PanicId = enum { unreach, unwrap_null, @@ -519,24 +520,24 @@ pub const Decl = struct { return decl.getExternDecl(zcu) != .none; } - pub fn getAlignment(decl: Decl, zcu: *Zcu) Alignment { + pub fn getAlignment(decl: Decl, pt: Zcu.PerThread) Alignment { assert(decl.has_tv); if (decl.alignment != .none) return decl.alignment; - return decl.typeOf(zcu).abiAlignment(zcu); + return decl.typeOf(pt.zcu).abiAlignment(pt); } - pub fn declPtrType(decl: Decl, zcu: *Zcu) !Type { + pub fn declPtrType(decl: Decl, pt: Zcu.PerThread) !Type { assert(decl.has_tv); - const decl_ty = decl.typeOf(zcu); - return zcu.ptrType(.{ + const decl_ty = decl.typeOf(pt.zcu); + return pt.ptrType(.{ .child = decl_ty.toIntern(), .flags = .{ - .alignment = if (decl.alignment == decl_ty.abiAlignment(zcu)) + .alignment = if (decl.alignment == decl_ty.abiAlignment(pt)) .none else decl.alignment, .address_space = decl.@"addrspace", - .is_const = decl.getOwnedVariable(zcu) == null, + .is_const = decl.getOwnedVariable(pt.zcu) == null, }, }); } @@ -589,7 +590,7 @@ pub const Decl = struct { /// This state is attached to every Decl when Module emit_h is non-null. pub const EmitH = struct { - fwd_decl: ArrayListUnmanaged(u8) = .{}, + fwd_decl: std.ArrayListUnmanaged(u8) = .{}, }; pub const DeclAdapter = struct { @@ -622,8 +623,8 @@ pub const Namespace = struct { /// Value is whether the usingnamespace decl is marked `pub`. usingnamespace_set: std.AutoHashMapUnmanaged(Decl.Index, bool) = .{}, - const Index = InternPool.NamespaceIndex; - const OptionalIndex = InternPool.OptionalNamespaceIndex; + pub const Index = InternPool.NamespaceIndex; + pub const OptionalIndex = InternPool.OptionalNamespaceIndex; const DeclContext = struct { zcu: *Zcu, @@ -3079,7 +3080,7 @@ pub fn markDependeeOutdated(zcu: *Zcu, dependee: InternPool.Dependee) !void { } } -fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { +pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void { var it = zcu.intern_pool.dependencyIterator(dependee); while (it.next()) |depender| { if (zcu.outdated.getPtr(depender)) |po_dep_count| { @@ -3279,7 +3280,7 @@ pub fn mapOldZirToNew( old_inst: Zir.Inst.Index, new_inst: Zir.Inst.Index, }; - var match_stack: ArrayListUnmanaged(MatchedZirDecl) = .{}; + var match_stack: std.ArrayListUnmanaged(MatchedZirDecl) = .{}; defer match_stack.deinit(gpa); // Main struct inst is always matched @@ -3394,357 +3395,6 @@ pub fn mapOldZirToNew( } } -/// Like `ensureDeclAnalyzed`, but the Decl is a file's root Decl. -pub fn ensureFileAnalyzed(zcu: *Zcu, file_index: File.Index) SemaError!void { - if (zcu.fileRootDecl(file_index).unwrap()) |existing_root| { - return zcu.ensureDeclAnalyzed(existing_root); - } else { - return zcu.semaFile(file_index); - } -} - -/// This ensures that the Decl will have an up-to-date Type and Value populated. -/// However the resolution status of the Type may not be fully resolved. -/// For example an inferred error set is not resolved until after `analyzeFnBody`. -/// is called. -pub fn ensureDeclAnalyzed(mod: *Module, decl_index: Decl.Index) SemaError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const ip = &mod.intern_pool; - const decl = mod.declPtr(decl_index); - - log.debug("ensureDeclAnalyzed '{d}' (name '{}')", .{ - @intFromEnum(decl_index), - decl.name.fmt(ip), - }); - - // Determine whether or not this Decl is outdated, i.e. requires re-analysis - // even if `complete`. If a Decl is PO, we pessismistically assume that it - // *does* require re-analysis, to ensure that the Decl is definitely - // up-to-date when this function returns. - - // If analysis occurs in a poor order, this could result in over-analysis. - // We do our best to avoid this by the other dependency logic in this file - // which tries to limit re-analysis to Decls whose previously listed - // dependencies are all up-to-date. - - const decl_as_depender = AnalUnit.wrap(.{ .decl = decl_index }); - const decl_was_outdated = mod.outdated.swapRemove(decl_as_depender) or - mod.potentially_outdated.swapRemove(decl_as_depender); - - if (decl_was_outdated) { - _ = mod.outdated_ready.swapRemove(decl_as_depender); - } - - const was_outdated = mod.outdated_file_root.swapRemove(decl_index) or decl_was_outdated; - - switch (decl.analysis) { - .in_progress => unreachable, - - .file_failure => return error.AnalysisFail, - - .sema_failure, - .dependency_failure, - .codegen_failure, - => if (!was_outdated) return error.AnalysisFail, - - .complete => if (!was_outdated) return, - - .unreferenced => {}, - } - - if (was_outdated) { - // The exports this Decl performs will be re-discovered, so we remove them here - // prior to re-analysis. - if (build_options.only_c) unreachable; - mod.deleteUnitExports(decl_as_depender); - mod.deleteUnitReferences(decl_as_depender); - } - - const sema_result: SemaDeclResult = blk: { - if (decl.zir_decl_index == .none and !mod.declIsRoot(decl_index)) { - // Anonymous decl. We don't semantically analyze these. - break :blk .{ - .invalidate_decl_val = false, - .invalidate_decl_ref = false, - }; - } - - if (mod.declIsRoot(decl_index)) { - const changed = try mod.semaFileUpdate(decl.getFileScopeIndex(mod), decl_was_outdated); - break :blk .{ - .invalidate_decl_val = changed, - .invalidate_decl_ref = changed, - }; - } - - const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(mod)).toSlice(ip), 0); - defer decl_prog_node.end(); - - break :blk mod.semaDecl(decl_index) catch |err| switch (err) { - 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; - }, - error.GenericPoison => unreachable, - else => |e| { - decl.analysis = .sema_failure; - try mod.failed_analysis.ensureUnusedCapacity(mod.gpa, 1); - try mod.retryable_failures.append(mod.gpa, AnalUnit.wrap(.{ .decl = decl_index })); - mod.failed_analysis.putAssumeCapacityNoClobber(AnalUnit.wrap(.{ .decl = decl_index }), try ErrorMsg.create( - mod.gpa, - decl.navSrcLoc(mod), - "unable to analyze: {s}", - .{@errorName(e)}, - )); - return error.AnalysisFail; - }, - }; - }; - - // TODO: we do not yet have separate dependencies for decl values vs types. - if (decl_was_outdated) { - if (sema_result.invalidate_decl_val or sema_result.invalidate_decl_ref) { - log.debug("Decl tv invalidated ('{d}')", .{@intFromEnum(decl_index)}); - // This dependency was marked as PO, meaning dependees were waiting - // on its analysis result, and it has turned out to be outdated. - // Update dependees accordingly. - try mod.markDependeeOutdated(.{ .decl_val = decl_index }); - } else { - log.debug("Decl tv up-to-date ('{d}')", .{@intFromEnum(decl_index)}); - // This dependency was previously PO, but turned out to be up-to-date. - // We do not need to queue successive analysis. - try mod.markPoDependeeUpToDate(.{ .decl_val = decl_index }); - } - } -} - -pub fn ensureFuncBodyAnalyzed(zcu: *Zcu, maybe_coerced_func_index: InternPool.Index) SemaError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - - // We only care about the uncoerced function. - // We need to do this for the "orphaned function" check below to be valid. - const func_index = ip.unwrapCoercedFunc(maybe_coerced_func_index); - - const func = zcu.funcInfo(maybe_coerced_func_index); - const decl_index = func.owner_decl; - const decl = zcu.declPtr(decl_index); - - log.debug("ensureFuncBodyAnalyzed '{d}' (instance of '{}')", .{ - @intFromEnum(func_index), - decl.name.fmt(ip), - }); - - // First, our owner decl must be up-to-date. This will always be the case - // during the first update, but may not on successive updates if we happen - // to get analyzed before our parent decl. - try zcu.ensureDeclAnalyzed(decl_index); - - // On an update, it's possible this function changed such that our owner - // decl now refers to a different function, making this one orphaned. If - // that's the case, we should remove this function from the binary. - if (decl.val.ip_index != func_index) { - try zcu.markDependeeOutdated(.{ .func_ies = func_index }); - ip.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .func = func_index })); - ip.remove(func_index); - @panic("TODO: remove orphaned function from binary"); - } - - // We'll want to remember what the IES used to be before the update for - // dependency invalidation purposes. - const old_resolved_ies = if (func.analysis(ip).inferred_error_set) - func.resolvedErrorSet(ip).* - else - .none; - - switch (decl.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - - .codegen_failure => unreachable, // functions do not perform constant value generation - - .file_failure, - .sema_failure, - .dependency_failure, - => return error.AnalysisFail, - - .complete => {}, - } - - const func_as_depender = AnalUnit.wrap(.{ .func = func_index }); - const was_outdated = zcu.outdated.swapRemove(func_as_depender) or - zcu.potentially_outdated.swapRemove(func_as_depender); - - if (was_outdated) { - if (build_options.only_c) unreachable; - _ = zcu.outdated_ready.swapRemove(func_as_depender); - zcu.deleteUnitExports(func_as_depender); - zcu.deleteUnitReferences(func_as_depender); - } - - switch (func.analysis(ip).state) { - .success => if (!was_outdated) return, - .sema_failure, - .dependency_failure, - .codegen_failure, - => if (!was_outdated) return error.AnalysisFail, - .none, .queued => {}, - .in_progress => unreachable, - .inline_only => unreachable, // don't queue work for this - } - - log.debug("analyze and generate fn body '{d}'; reason='{s}'", .{ - @intFromEnum(func_index), - if (was_outdated) "outdated" else "never analyzed", - }); - - var tmp_arena = std.heap.ArenaAllocator.init(gpa); - defer tmp_arena.deinit(); - const sema_arena = tmp_arena.allocator(); - - var air = zcu.analyzeFnBody(func_index, sema_arena) catch |err| switch (err) { - error.AnalysisFail => { - if (func.analysis(ip).state == .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. - func.analysis(ip).state = .dependency_failure; - } - return error.AnalysisFail; - }, - error.OutOfMemory => return error.OutOfMemory, - }; - errdefer air.deinit(gpa); - - const invalidate_ies_deps = i: { - if (!was_outdated) break :i false; - if (!func.analysis(ip).inferred_error_set) break :i true; - const new_resolved_ies = func.resolvedErrorSet(ip).*; - break :i new_resolved_ies != old_resolved_ies; - }; - if (invalidate_ies_deps) { - log.debug("func IES invalidated ('{d}')", .{@intFromEnum(func_index)}); - try zcu.markDependeeOutdated(.{ .func_ies = func_index }); - } else if (was_outdated) { - log.debug("func IES up-to-date ('{d}')", .{@intFromEnum(func_index)}); - try zcu.markPoDependeeUpToDate(.{ .func_ies = func_index }); - } - - const comp = zcu.comp; - - const dump_air = build_options.enable_debug_extensions and comp.verbose_air; - const dump_llvm_ir = build_options.enable_debug_extensions and (comp.verbose_llvm_ir != null or comp.verbose_llvm_bc != null); - - if (comp.bin_file == null and zcu.llvm_object == null and !dump_air and !dump_llvm_ir) { - air.deinit(gpa); - return; - } - - try comp.work_queue.writeItem(.{ .codegen_func = .{ - .func = func_index, - .air = air, - } }); -} - -/// Takes ownership of `air`, even on error. -/// If any types referenced by `air` are unresolved, marks the codegen as failed. -pub fn linkerUpdateFunc(zcu: *Zcu, func_index: InternPool.Index, air: Air) Allocator.Error!void { - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const comp = zcu.comp; - - defer { - var air_mut = air; - air_mut.deinit(gpa); - } - - const func = zcu.funcInfo(func_index); - const decl_index = func.owner_decl; - const decl = zcu.declPtr(decl_index); - - var liveness = try Liveness.analyze(gpa, air, ip); - defer liveness.deinit(gpa); - - if (build_options.enable_debug_extensions and comp.verbose_air) { - const fqn = try decl.fullyQualifiedName(zcu); - std.debug.print("# Begin Function AIR: {}:\n", .{fqn.fmt(ip)}); - @import("print_air.zig").dump(zcu, air, liveness); - std.debug.print("# End Function AIR: {}\n\n", .{fqn.fmt(ip)}); - } - - if (std.debug.runtime_safety) { - var verify: Liveness.Verify = .{ - .gpa = gpa, - .air = air, - .liveness = liveness, - .intern_pool = ip, - }; - defer verify.deinit(); - - verify.verify() catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - else => { - try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - zcu.failed_analysis.putAssumeCapacityNoClobber( - AnalUnit.wrap(.{ .func = func_index }), - try Module.ErrorMsg.create( - gpa, - decl.navSrcLoc(zcu), - "invalid liveness: {s}", - .{@errorName(err)}, - ), - ); - func.analysis(ip).state = .codegen_failure; - return; - }, - }; - } - - const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(zcu)).toSlice(ip), 0); - defer codegen_prog_node.end(); - - if (!air.typesFullyResolved(zcu)) { - // A type we depend on failed to resolve. This is a transitive failure. - // Correcting this failure will involve changing a type this function - // depends on, hence triggering re-analysis of this function, so this - // interacts correctly with incremental compilation. - func.analysis(ip).state = .codegen_failure; - } else if (comp.bin_file) |lf| { - lf.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - func.analysis(ip).state = .codegen_failure; - }, - else => { - try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - zcu.failed_analysis.putAssumeCapacityNoClobber(AnalUnit.wrap(.{ .func = func_index }), try Module.ErrorMsg.create( - gpa, - decl.navSrcLoc(zcu), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - func.analysis(ip).state = .codegen_failure; - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .func = func_index })); - }, - }; - } else if (zcu.llvm_object) |llvm_object| { - if (build_options.only_c) unreachable; - llvm_object.updateFunc(zcu, func_index, air, liveness) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - }; - } -} - /// Ensure this function's body is or will be analyzed and emitted. This should /// be called whenever a potential runtime call of a function is seen. /// @@ -3804,608 +3454,105 @@ pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func_index: InternPool.Index) func.analysis(ip).state = .queued; } -pub fn semaPkg(zcu: *Zcu, pkg: *Package.Module) !void { - const import_file_result = try zcu.importPkg(pkg); - const root_decl_index = zcu.fileRootDecl(import_file_result.file_index); - if (root_decl_index == .none) { - return zcu.semaFile(import_file_result.file_index); - } -} - -fn getFileRootStruct( - zcu: *Zcu, - decl_index: Decl.Index, - namespace_index: Namespace.Index, - file_index: File.Index, -) Allocator.Error!InternPool.Index { - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const file = zcu.fileByIndex(file_index); - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - assert(extended.opcode == .struct_decl); - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - assert(!small.has_captures_len); - assert(!small.has_backing_int); - assert(small.layout == .auto); - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; - const fields_len = if (small.has_fields_len) blk: { - const fields_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk fields_len; - } else 0; - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - const decls = file.zir.bodySlice(extra_index, decls_len); - extra_index += decls_len; - - const tracked_inst = try ip.trackZir(gpa, file_index, .main_struct_inst); - const wip_ty = switch (try ip.getStructType(gpa, .{ - .layout = .auto, - .fields_len = fields_len, - .known_non_opv = small.known_non_opv, - .requires_comptime = if (small.known_comptime_only) .yes else .unknown, - .is_tuple = small.is_tuple, - .any_comptime_fields = small.any_comptime_fields, - .any_default_inits = small.any_default_inits, - .inits_resolved = false, - .any_aligned_fields = small.any_aligned_fields, - .has_namespace = true, - .key = .{ .declared = .{ - .zir_index = tracked_inst, - .captures = &.{}, - } }, - })) { - .existing => unreachable, // we wouldn't be analysing the file root if this type existed - .wip => |wip| wip, - }; - errdefer wip_ty.cancel(ip); - - if (zcu.comp.debug_incremental) { - try ip.addDependency( - gpa, - AnalUnit.wrap(.{ .decl = decl_index }), - .{ .src_hash = tracked_inst }, - ); - } +pub const SemaDeclResult = packed struct { + /// Whether the value of a `decl_val` of this Decl changed. + invalidate_decl_val: bool, + /// Whether the type of a `decl_ref` of this Decl changed. + invalidate_decl_ref: bool, +}; +pub fn semaAnonOwnerDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult { const decl = zcu.declPtr(decl_index); - decl.val = Value.fromInterned(wip_ty.index); - decl.has_tv = true; - decl.owns_tv = true; - decl.analysis = .complete; - - try zcu.scanNamespace(namespace_index, decls, decl); - try zcu.comp.work_queue.writeItem(.{ .resolve_type_fully = wip_ty.index }); - return wip_ty.finish(ip, decl_index, namespace_index.toOptional()); -} - -/// Re-analyze the root Decl of a file on an incremental update. -/// If `type_outdated`, the struct type itself is considered outdated and is -/// reconstructed at a new InternPool index. Otherwise, the namespace is just -/// re-analyzed. Returns whether the decl's tyval was invalidated. -fn semaFileUpdate(zcu: *Zcu, file_index: File.Index, type_outdated: bool) SemaError!bool { - const file = zcu.fileByIndex(file_index); - const decl = zcu.declPtr(zcu.fileRootDecl(file_index).unwrap().?); - - log.debug("semaFileUpdate mod={s} sub_file_path={s} type_outdated={}", .{ - file.mod.fully_qualified_name, - file.sub_file_path, - type_outdated, - }); - - if (file.status != .success_zir) { - if (decl.analysis == .file_failure) { - return false; - } else { - decl.analysis = .file_failure; - return true; - } - } - - if (decl.analysis == .file_failure) { - // No struct type currently exists. Create one! - const root_decl = zcu.fileRootDecl(file_index); - _ = try zcu.getFileRootStruct(root_decl.unwrap().?, decl.src_namespace, file_index); - return true; - } assert(decl.has_tv); assert(decl.owns_tv); - if (type_outdated) { - // Invalidate the existing type, reusing the decl and namespace. - const file_root_decl = zcu.fileRootDecl(file_index).unwrap().?; - zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{ - .decl = file_root_decl, - })); - zcu.intern_pool.remove(decl.val.toIntern()); - decl.val = undefined; - _ = try zcu.getFileRootStruct(file_root_decl, decl.src_namespace, file_index); - return true; - } - - // Only the struct's namespace is outdated. - // Preserve the type - just scan the namespace again. - - const extended = file.zir.instructions.items(.data)[@intFromEnum(Zir.Inst.Index.main_struct_inst)].extended; - const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small); - - var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).Struct.fields.len; - extra_index += @intFromBool(small.has_fields_len); - const decls_len = if (small.has_decls_len) blk: { - const decls_len = file.zir.extra[extra_index]; - extra_index += 1; - break :blk decls_len; - } else 0; - const decls = file.zir.bodySlice(extra_index, decls_len); + log.debug("semaAnonOwnerDecl '{d}'", .{@intFromEnum(decl_index)}); - if (!type_outdated) { - try zcu.scanNamespace(decl.src_namespace, decls, decl); + switch (decl.typeOf(zcu).zigTypeTag(zcu)) { + .Fn => @panic("TODO: update fn instance"), + .Type => {}, + else => unreachable, } - return false; + // We are the owner Decl of a type, and we were marked as outdated. That means the *structure* + // of this type changed; not just its namespace. Therefore, we need a new InternPool index. + // + // However, as soon as we make that, the context that created us will require re-analysis anyway + // (as it depends on this Decl's value), meaning the `struct_decl` (or equivalent) instruction + // will be analyzed again. Since Sema already needs to be able to reconstruct types like this, + // why should we bother implementing it here too when the Sema logic will be hit right after? + // + // So instead, let's just mark this Decl as failed - so that any remaining Decls which genuinely + // reference it (via `@This`) end up silently erroring too - and we'll let Sema make a new type + // with a new Decl. + // + // Yes, this does mean that any type owner Decl has a constant value for its entire lifetime. + zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{ .decl = decl_index })); + zcu.intern_pool.remove(decl.val.toIntern()); + decl.analysis = .dependency_failure; + return .{ + .invalidate_decl_val = true, + .invalidate_decl_ref = true, + }; } -/// Regardless of the file status, will create a `Decl` if none exists so that we can track -/// dependencies and re-analyze when the file becomes outdated. -fn semaFile(zcu: *Zcu, file_index: File.Index) SemaError!void { - const tracy = trace(@src()); - defer tracy.end(); - - const file = zcu.fileByIndex(file_index); - assert(zcu.fileRootDecl(file_index) == .none); +pub const ImportFileResult = struct { + file: *File, + file_index: File.Index, + is_new: bool, + is_pkg: bool, +}; +pub fn importPkg(zcu: *Zcu, mod: *Package.Module) !ImportFileResult { const gpa = zcu.gpa; - log.debug("semaFile zcu={s} sub_file_path={s}", .{ - file.mod.fully_qualified_name, file.sub_file_path, - }); - // Because these three things each reference each other, `undefined` - // placeholders are used before being set after the struct type gains an - // InternPool index. - const new_namespace_index = try zcu.createNamespace(.{ - .parent = .none, - .decl_index = undefined, - .file_scope = file_index, + // The resolved path is used as the key in the import table, to detect if + // an import refers to the same as another, despite different relative paths + // or differently mapped package names. + const resolved_path = try std.fs.path.resolve(gpa, &.{ + mod.root.root_dir.path orelse ".", + mod.root.sub_path, + mod.root_src_path, }); - errdefer zcu.destroyNamespace(new_namespace_index); + var keep_resolved_path = false; + defer if (!keep_resolved_path) gpa.free(resolved_path); - const new_decl_index = try zcu.allocateNewDecl(new_namespace_index); - const new_decl = zcu.declPtr(new_decl_index); - errdefer @panic("TODO error handling"); + const gop = try zcu.import_table.getOrPut(gpa, resolved_path); + errdefer _ = zcu.import_table.pop(); + if (gop.found_existing) { + try gop.value_ptr.*.addReference(zcu.*, .{ .root = mod }); + return .{ + .file = gop.value_ptr.*, + .file_index = @enumFromInt(gop.index), + .is_new = false, + .is_pkg = true, + }; + } - zcu.setFileRootDecl(file_index, new_decl_index.toOptional()); - zcu.namespacePtr(new_namespace_index).decl_index = new_decl_index; + const ip = &zcu.intern_pool; - new_decl.name = try file.fullyQualifiedName(zcu); - new_decl.name_fully_qualified = true; - new_decl.is_pub = true; - new_decl.is_exported = false; - new_decl.alignment = .none; - new_decl.@"linksection" = .none; - new_decl.analysis = .in_progress; + try ip.files.ensureUnusedCapacity(gpa, 1); - if (file.status != .success_zir) { - new_decl.analysis = .file_failure; - return; + if (mod.builtin_file) |builtin_file| { + keep_resolved_path = true; // It's now owned by import_table. + gop.value_ptr.* = builtin_file; + try builtin_file.addReference(zcu.*, .{ .root = mod }); + const path_digest = computePathDigest(zcu, mod, builtin_file.sub_file_path); + ip.files.putAssumeCapacityNoClobber(path_digest, .none); + return .{ + .file = builtin_file, + .file_index = @enumFromInt(ip.files.entries.len - 1), + .is_new = false, + .is_pkg = true, + }; } - assert(file.zir_loaded); - const struct_ty = try zcu.getFileRootStruct(new_decl_index, new_namespace_index, file_index); - errdefer zcu.intern_pool.remove(struct_ty); + const sub_file_path = try gpa.dupe(u8, mod.root_src_path); + errdefer gpa.free(sub_file_path); - switch (zcu.comp.cache_use) { - .whole => |whole| if (whole.cache_manifest) |man| { - const source = file.getSource(gpa) catch |err| { - try reportRetryableFileError(zcu, file_index, "unable to load source: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }; - - const resolved_path = std.fs.path.resolve(gpa, &.{ - file.mod.root.root_dir.path orelse ".", - file.mod.root.sub_path, - file.sub_file_path, - }) catch |err| { - try reportRetryableFileError(zcu, file_index, "unable to resolve path: {s}", .{@errorName(err)}); - return error.AnalysisFail; - }; - errdefer gpa.free(resolved_path); - - whole.cache_manifest_mutex.lock(); - defer whole.cache_manifest_mutex.unlock(); - try man.addFilePostContents(resolved_path, source.bytes, source.stat); - }, - .incremental => {}, - } -} - -const SemaDeclResult = packed struct { - /// Whether the value of a `decl_val` of this Decl changed. - invalidate_decl_val: bool, - /// Whether the type of a `decl_ref` of this Decl changed. - invalidate_decl_ref: bool, -}; - -fn semaDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult { - const tracy = trace(@src()); - defer tracy.end(); - - const decl = zcu.declPtr(decl_index); - const ip = &zcu.intern_pool; - - if (decl.getFileScope(zcu).status != .success_zir) { - return error.AnalysisFail; - } - - assert(!zcu.declIsRoot(decl_index)); - - if (decl.zir_decl_index == .none and decl.owns_tv) { - // We are re-analyzing an anonymous owner Decl (for a function or a namespace type). - return zcu.semaAnonOwnerDecl(decl_index); - } - - log.debug("semaDecl '{d}'", .{@intFromEnum(decl_index)}); - log.debug("decl name '{}'", .{(try decl.fullyQualifiedName(zcu)).fmt(ip)}); - defer blk: { - log.debug("finish decl name '{}'", .{(decl.fullyQualifiedName(zcu) catch break :blk).fmt(ip)}); - } - - const old_has_tv = decl.has_tv; - // The following values are ignored if `!old_has_tv` - const old_ty = if (old_has_tv) decl.typeOf(zcu) else undefined; - const old_val = decl.val; - const old_align = decl.alignment; - const old_linksection = decl.@"linksection"; - const old_addrspace = decl.@"addrspace"; - const old_is_inline = if (decl.getOwnedFunction(zcu)) |prev_func| - prev_func.analysis(ip).state == .inline_only - else - false; - - const decl_inst = decl.zir_decl_index.unwrap().?.resolve(ip); - - const gpa = zcu.gpa; - const zir = decl.getFileScope(zcu).zir; - - const builtin_type_target_index: InternPool.Index = ip_index: { - const std_mod = zcu.std_mod; - if (decl.getFileScope(zcu).mod != std_mod) break :ip_index .none; - // We're in the std module. - const std_file_imported = try zcu.importPkg(std_mod); - const std_file_root_decl_index = zcu.fileRootDecl(std_file_imported.file_index); - const std_decl = zcu.declPtr(std_file_root_decl_index.unwrap().?); - const std_namespace = std_decl.getInnerNamespace(zcu).?; - const builtin_str = try ip.getOrPutString(gpa, "builtin", .no_embedded_nulls); - const builtin_decl = zcu.declPtr(std_namespace.decls.getKeyAdapted(builtin_str, DeclAdapter{ .zcu = zcu }) orelse break :ip_index .none); - const builtin_namespace = builtin_decl.getInnerNamespaceIndex(zcu).unwrap() orelse break :ip_index .none; - if (decl.src_namespace != builtin_namespace) break :ip_index .none; - // We're in builtin.zig. This could be a builtin we need to add to a specific InternPool index. - for ([_][]const u8{ - "AtomicOrder", - "AtomicRmwOp", - "CallingConvention", - "AddressSpace", - "FloatMode", - "ReduceOp", - "CallModifier", - "PrefetchOptions", - "ExportOptions", - "ExternOptions", - "Type", - }, [_]InternPool.Index{ - .atomic_order_type, - .atomic_rmw_op_type, - .calling_convention_type, - .address_space_type, - .float_mode_type, - .reduce_op_type, - .call_modifier_type, - .prefetch_options_type, - .export_options_type, - .extern_options_type, - .type_info_type, - }) |type_name, type_ip| { - if (decl.name.eqlSlice(type_name, ip)) break :ip_index type_ip; - } - break :ip_index .none; - }; - - zcu.intern_pool.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .decl = decl_index })); - - decl.analysis = .in_progress; - - var analysis_arena = std.heap.ArenaAllocator.init(gpa); - defer analysis_arena.deinit(); - - var comptime_err_ret_trace = std.ArrayList(LazySrcLoc).init(gpa); - defer comptime_err_ret_trace.deinit(); - - var sema: Sema = .{ - .mod = zcu, - .gpa = gpa, - .arena = analysis_arena.allocator(), - .code = zir, - .owner_decl = decl, - .owner_decl_index = decl_index, - .func_index = .none, - .func_is_naked = false, - .fn_ret_ty = Type.void, - .fn_ret_ty_ies = null, - .owner_func_index = .none, - .comptime_err_ret_trace = &comptime_err_ret_trace, - .builtin_type_target_index = builtin_type_target_index, - }; - defer sema.deinit(); - - // Every Decl (other than file root Decls, which do not have a ZIR index) has a dependency on its own source. - try sema.declareDependency(.{ .src_hash = try ip.trackZir( - gpa, - decl.getFileScopeIndex(zcu), - decl_inst, - ) }); - - var block_scope: Sema.Block = .{ - .parent = null, - .sema = &sema, - .namespace = decl.src_namespace, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - .src_base_inst = decl.zir_decl_index.unwrap().?, - .type_name_ctx = decl.name, - }; - defer block_scope.instructions.deinit(gpa); - - const decl_bodies = decl.zirBodies(zcu); - - const result_ref = try sema.resolveInlineBody(&block_scope, decl_bodies.value_body, decl_inst); - // We'll do some other bits with the Sema. Clear the type target index just - // in case they analyze any type. - sema.builtin_type_target_index = .none; - const align_src: LazySrcLoc = block_scope.src(.{ .node_offset_var_decl_align = 0 }); - const section_src: LazySrcLoc = block_scope.src(.{ .node_offset_var_decl_section = 0 }); - const address_space_src: LazySrcLoc = block_scope.src(.{ .node_offset_var_decl_addrspace = 0 }); - const ty_src: LazySrcLoc = block_scope.src(.{ .node_offset_var_decl_ty = 0 }); - const init_src: LazySrcLoc = block_scope.src(.{ .node_offset_var_decl_init = 0 }); - const decl_val = try sema.resolveFinalDeclValue(&block_scope, init_src, result_ref); - const decl_ty = decl_val.typeOf(zcu); - - // Note this resolves the type of the Decl, not the value; if this Decl - // is a struct, for example, this resolves `type` (which needs no resolution), - // not the struct itself. - try decl_ty.resolveLayout(zcu); - - if (decl.kind == .@"usingnamespace") { - if (!decl_ty.eql(Type.type, zcu)) { - return sema.fail(&block_scope, ty_src, "expected type, found {}", .{ - decl_ty.fmt(zcu), - }); - } - const ty = decl_val.toType(); - if (ty.getNamespace(zcu) == null) { - return sema.fail(&block_scope, ty_src, "type {} has no namespace", .{ty.fmt(zcu)}); - } - - decl.val = ty.toValue(); - decl.alignment = .none; - decl.@"linksection" = .none; - decl.has_tv = true; - decl.owns_tv = false; - decl.analysis = .complete; - - // TODO: usingnamespace cannot currently participate in incremental compilation - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; - } - - var queue_linker_work = true; - var is_func = false; - var is_inline = false; - switch (decl_val.toIntern()) { - .generic_poison => unreachable, - .unreachable_value => unreachable, - else => switch (ip.indexToKey(decl_val.toIntern())) { - .variable => |variable| { - decl.owns_tv = variable.decl == decl_index; - queue_linker_work = decl.owns_tv; - }, - - .extern_func => |extern_func| { - decl.owns_tv = extern_func.decl == decl_index; - queue_linker_work = decl.owns_tv; - is_func = decl.owns_tv; - }, - - .func => |func| { - decl.owns_tv = func.owner_decl == decl_index; - queue_linker_work = false; - is_inline = decl.owns_tv and decl_ty.fnCallingConvention(zcu) == .Inline; - is_func = decl.owns_tv; - }, - - else => {}, - }, - } - - decl.val = decl_val; - // Function linksection, align, and addrspace were already set by Sema - if (!is_func) { - decl.alignment = blk: { - const align_body = decl_bodies.align_body orelse break :blk .none; - const align_ref = try sema.resolveInlineBody(&block_scope, align_body, decl_inst); - break :blk try sema.analyzeAsAlign(&block_scope, align_src, align_ref); - }; - decl.@"linksection" = blk: { - const linksection_body = decl_bodies.linksection_body orelse break :blk .none; - const linksection_ref = try sema.resolveInlineBody(&block_scope, linksection_body, decl_inst); - const bytes = try sema.toConstString(&block_scope, section_src, linksection_ref, .{ - .needed_comptime_reason = "linksection must be comptime-known", - }); - if (mem.indexOfScalar(u8, bytes, 0) != null) { - return sema.fail(&block_scope, section_src, "linksection cannot contain null bytes", .{}); - } else if (bytes.len == 0) { - return sema.fail(&block_scope, section_src, "linksection cannot be empty", .{}); - } - break :blk try ip.getOrPutStringOpt(gpa, bytes, .no_embedded_nulls); - }; - decl.@"addrspace" = blk: { - const addrspace_ctx: Sema.AddressSpaceContext = switch (ip.indexToKey(decl_val.toIntern())) { - .variable => .variable, - .extern_func, .func => .function, - else => .constant, - }; - - const target = sema.mod.getTarget(); - - const addrspace_body = decl_bodies.addrspace_body orelse break :blk switch (addrspace_ctx) { - .function => target_util.defaultAddressSpace(target, .function), - .variable => target_util.defaultAddressSpace(target, .global_mutable), - .constant => target_util.defaultAddressSpace(target, .global_constant), - else => unreachable, - }; - const addrspace_ref = try sema.resolveInlineBody(&block_scope, addrspace_body, decl_inst); - break :blk try sema.analyzeAsAddressSpace(&block_scope, address_space_src, addrspace_ref, addrspace_ctx); - }; - } - decl.has_tv = true; - decl.analysis = .complete; - - const result: SemaDeclResult = if (old_has_tv) .{ - .invalidate_decl_val = !decl_ty.eql(old_ty, zcu) or - !decl.val.eql(old_val, decl_ty, zcu) or - is_inline != old_is_inline, - .invalidate_decl_ref = !decl_ty.eql(old_ty, zcu) or - decl.alignment != old_align or - decl.@"linksection" != old_linksection or - decl.@"addrspace" != old_addrspace or - is_inline != old_is_inline, - } else .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; - - const has_runtime_bits = queue_linker_work and (is_func or try sema.typeHasRuntimeBits(decl_ty)); - if (has_runtime_bits) { - // Needed for codegen_decl which will call updateDecl and then the - // codegen backend wants full access to the Decl Type. - try decl_ty.resolveFully(zcu); - - try zcu.comp.work_queue.writeItem(.{ .codegen_decl = decl_index }); - - if (result.invalidate_decl_ref and zcu.emit_h != null) { - try zcu.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index }); - } - } - - if (decl.is_exported) { - const export_src: LazySrcLoc = block_scope.src(.{ .token_offset = @intFromBool(decl.is_pub) }); - if (is_inline) return sema.fail(&block_scope, export_src, "export of inline function", .{}); - // The scope needs to have the decl in it. - try sema.analyzeExport(&block_scope, export_src, .{ .name = decl.name }, decl_index); - } - - try sema.flushExports(); - - return result; -} - -fn semaAnonOwnerDecl(zcu: *Zcu, decl_index: Decl.Index) !SemaDeclResult { - const decl = zcu.declPtr(decl_index); - - assert(decl.has_tv); - assert(decl.owns_tv); - - log.debug("semaAnonOwnerDecl '{d}'", .{@intFromEnum(decl_index)}); - - switch (decl.typeOf(zcu).zigTypeTag(zcu)) { - .Fn => @panic("TODO: update fn instance"), - .Type => {}, - else => unreachable, - } - - // We are the owner Decl of a type, and we were marked as outdated. That means the *structure* - // of this type changed; not just its namespace. Therefore, we need a new InternPool index. - // - // However, as soon as we make that, the context that created us will require re-analysis anyway - // (as it depends on this Decl's value), meaning the `struct_decl` (or equivalent) instruction - // will be analyzed again. Since Sema already needs to be able to reconstruct types like this, - // why should we bother implementing it here too when the Sema logic will be hit right after? - // - // So instead, let's just mark this Decl as failed - so that any remaining Decls which genuinely - // reference it (via `@This`) end up silently erroring too - and we'll let Sema make a new type - // with a new Decl. - // - // Yes, this does mean that any type owner Decl has a constant value for its entire lifetime. - zcu.intern_pool.removeDependenciesForDepender(zcu.gpa, AnalUnit.wrap(.{ .decl = decl_index })); - zcu.intern_pool.remove(decl.val.toIntern()); - decl.analysis = .dependency_failure; - return .{ - .invalidate_decl_val = true, - .invalidate_decl_ref = true, - }; -} - -pub const ImportFileResult = struct { - file: *File, - file_index: File.Index, - is_new: bool, - is_pkg: bool, -}; - -pub fn importPkg(zcu: *Zcu, mod: *Package.Module) !ImportFileResult { - const gpa = zcu.gpa; - - // The resolved path is used as the key in the import table, to detect if - // an import refers to the same as another, despite different relative paths - // or differently mapped package names. - const resolved_path = try std.fs.path.resolve(gpa, &.{ - mod.root.root_dir.path orelse ".", - mod.root.sub_path, - mod.root_src_path, - }); - var keep_resolved_path = false; - defer if (!keep_resolved_path) gpa.free(resolved_path); - - const gop = try zcu.import_table.getOrPut(gpa, resolved_path); - errdefer _ = zcu.import_table.pop(); - if (gop.found_existing) { - try gop.value_ptr.*.addReference(zcu.*, .{ .root = mod }); - return .{ - .file = gop.value_ptr.*, - .file_index = @enumFromInt(gop.index), - .is_new = false, - .is_pkg = true, - }; - } - - const ip = &zcu.intern_pool; - - try ip.files.ensureUnusedCapacity(gpa, 1); - - if (mod.builtin_file) |builtin_file| { - keep_resolved_path = true; // It's now owned by import_table. - gop.value_ptr.* = builtin_file; - try builtin_file.addReference(zcu.*, .{ .root = mod }); - const path_digest = computePathDigest(zcu, mod, builtin_file.sub_file_path); - ip.files.putAssumeCapacityNoClobber(path_digest, .none); - return .{ - .file = builtin_file, - .file_index = @enumFromInt(ip.files.entries.len - 1), - .is_new = false, - .is_pkg = true, - }; - } - - const sub_file_path = try gpa.dupe(u8, mod.root_src_path); - errdefer gpa.free(sub_file_path); - - const new_file = try gpa.create(File); - errdefer gpa.destroy(new_file); + const new_file = try gpa.create(File); + errdefer gpa.destroy(new_file); keep_resolved_path = true; // It's now owned by import_table. gop.value_ptr.* = new_file; @@ -4533,78 +3680,6 @@ pub fn importFile( }; } -pub fn embedFile( - mod: *Module, - cur_file: *File, - import_string: []const u8, - src_loc: LazySrcLoc, -) !InternPool.Index { - const gpa = mod.gpa; - - if (cur_file.mod.deps.get(import_string)) |pkg| { - const resolved_path = try std.fs.path.resolve(gpa, &.{ - pkg.root.root_dir.path orelse ".", - pkg.root.sub_path, - pkg.root_src_path, - }); - var keep_resolved_path = false; - defer if (!keep_resolved_path) gpa.free(resolved_path); - - const gop = try mod.embed_table.getOrPut(gpa, resolved_path); - errdefer { - assert(std.mem.eql(u8, mod.embed_table.pop().key, resolved_path)); - keep_resolved_path = false; - } - if (gop.found_existing) return gop.value_ptr.*.val; - keep_resolved_path = true; - - const sub_file_path = try gpa.dupe(u8, pkg.root_src_path); - errdefer gpa.free(sub_file_path); - - return newEmbedFile(mod, pkg, sub_file_path, resolved_path, gop.value_ptr, src_loc); - } - - // The resolved path is used as the key in the table, to detect if a file - // refers to the same as another, despite different relative paths. - const resolved_path = try std.fs.path.resolve(gpa, &.{ - cur_file.mod.root.root_dir.path orelse ".", - cur_file.mod.root.sub_path, - cur_file.sub_file_path, - "..", - import_string, - }); - - var keep_resolved_path = false; - defer if (!keep_resolved_path) gpa.free(resolved_path); - - const gop = try mod.embed_table.getOrPut(gpa, resolved_path); - errdefer { - assert(std.mem.eql(u8, mod.embed_table.pop().key, resolved_path)); - keep_resolved_path = false; - } - if (gop.found_existing) return gop.value_ptr.*.val; - keep_resolved_path = true; - - const resolved_root_path = try std.fs.path.resolve(gpa, &.{ - cur_file.mod.root.root_dir.path orelse ".", - cur_file.mod.root.sub_path, - }); - defer gpa.free(resolved_root_path); - - const sub_file_path = p: { - const relative = try std.fs.path.relative(gpa, resolved_root_path, resolved_path); - errdefer gpa.free(relative); - - if (!isUpDir(relative) and !std.fs.path.isAbsolute(relative)) { - break :p relative; - } - return error.ImportOutsideModulePath; - }; - defer gpa.free(sub_file_path); - - return newEmbedFile(mod, cur_file.mod, sub_file_path, resolved_path, gop.value_ptr, src_loc); -} - fn computePathDigest(zcu: *Zcu, mod: *Package.Module, sub_file_path: []const u8) Cache.BinDigest { const want_local_cache = mod == zcu.main_mod; var path_hash: Cache.HashHelper = .{}; @@ -4620,87 +3695,6 @@ fn computePathDigest(zcu: *Zcu, mod: *Package.Module, sub_file_path: []const u8) return bin; } -/// https://github.com/ziglang/zig/issues/14307 -fn newEmbedFile( - mod: *Module, - pkg: *Package.Module, - sub_file_path: []const u8, - resolved_path: []const u8, - result: **EmbedFile, - src_loc: LazySrcLoc, -) !InternPool.Index { - const gpa = mod.gpa; - const ip = &mod.intern_pool; - - const new_file = try gpa.create(EmbedFile); - errdefer gpa.destroy(new_file); - - var file = try pkg.root.openFile(sub_file_path, .{}); - defer file.close(); - - const actual_stat = try file.stat(); - const stat: Cache.File.Stat = .{ - .size = actual_stat.size, - .inode = actual_stat.inode, - .mtime = actual_stat.mtime, - }; - const size = std.math.cast(usize, actual_stat.size) orelse return error.Overflow; - - const bytes = try ip.string_bytes.addManyAsSlice(gpa, try std.math.add(usize, size, 1)); - const actual_read = try file.readAll(bytes[0..size]); - if (actual_read != size) return error.UnexpectedEndOfFile; - bytes[size] = 0; - - const comp = mod.comp; - switch (comp.cache_use) { - .whole => |whole| if (whole.cache_manifest) |man| { - const copied_resolved_path = try gpa.dupe(u8, resolved_path); - errdefer gpa.free(copied_resolved_path); - whole.cache_manifest_mutex.lock(); - defer whole.cache_manifest_mutex.unlock(); - try man.addFilePostContents(copied_resolved_path, bytes[0..size], stat); - }, - .incremental => {}, - } - - const array_ty = try ip.get(gpa, .{ .array_type = .{ - .len = size, - .sentinel = .zero_u8, - .child = .u8_type, - } }); - const array_val = try ip.get(gpa, .{ .aggregate = .{ - .ty = array_ty, - .storage = .{ .bytes = try ip.getOrPutTrailingString(gpa, bytes.len, .maybe_embedded_nulls) }, - } }); - - const ptr_ty = (try mod.ptrType(.{ - .child = array_ty, - .flags = .{ - .alignment = .none, - .is_const = true, - .address_space = .generic, - }, - })).toIntern(); - const ptr_val = try ip.get(gpa, .{ .ptr = .{ - .ty = ptr_ty, - .base_addr = .{ .anon_decl = .{ - .val = array_val, - .orig_ty = ptr_ty, - } }, - .byte_offset = 0, - } }); - - result.* = new_file; - new_file.* = .{ - .sub_file_path = try ip.getOrPutString(gpa, sub_file_path, .no_embedded_nulls), - .owner = pkg, - .stat = stat, - .val = ptr_val, - .src_loc = src_loc, - }; - return ptr_val; -} - pub fn scanNamespace( zcu: *Zcu, namespace_index: Namespace.Index, @@ -4970,13 +3964,6 @@ pub fn abortAnonDecl(mod: *Module, decl_index: Decl.Index) void { mod.destroyDecl(decl_index); } -/// Finalize the creation of an anon decl. -pub fn finalizeAnonDecl(mod: *Module, decl_index: Decl.Index) Allocator.Error!void { - if (mod.declPtr(decl_index).typeOf(mod).isFnOrHasRuntimeBits(mod)) { - try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl_index }); - } -} - /// Delete all the Export objects that are caused by this `AnalUnit`. Re-analysis of /// this `AnalUnit` will cause them to be re-created (or not). pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void { @@ -5019,7 +4006,7 @@ pub fn deleteUnitExports(zcu: *Zcu, anal_unit: AnalUnit) void { /// Delete all references in `reference_table` which are caused by this `AnalUnit`. /// Re-analysis of the `AnalUnit` will cause appropriate references to be recreated. -fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void { +pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void { const gpa = zcu.gpa; const kv = zcu.reference_table.fetchSwapRemove(anal_unit) orelse return; @@ -5058,258 +4045,13 @@ pub fn addUnitReference(zcu: *Zcu, src_unit: AnalUnit, referenced_unit: AnalUnit gop.value_ptr.* = @intCast(ref_idx); } -pub fn analyzeFnBody(mod: *Module, func_index: InternPool.Index, arena: Allocator) SemaError!Air { - const tracy = trace(@src()); - defer tracy.end(); - - const gpa = mod.gpa; - const ip = &mod.intern_pool; - const func = mod.funcInfo(func_index); - const decl_index = func.owner_decl; - const decl = mod.declPtr(decl_index); - - log.debug("func name '{}'", .{(try decl.fullyQualifiedName(mod)).fmt(ip)}); - defer blk: { - log.debug("finish func name '{}'", .{(decl.fullyQualifiedName(mod) catch break :blk).fmt(ip)}); - } - - const decl_prog_node = mod.sema_prog_node.start((try decl.fullyQualifiedName(mod)).toSlice(ip), 0); - defer decl_prog_node.end(); - - mod.intern_pool.removeDependenciesForDepender(gpa, AnalUnit.wrap(.{ .func = func_index })); - - var comptime_err_ret_trace = std.ArrayList(LazySrcLoc).init(gpa); - defer comptime_err_ret_trace.deinit(); +pub fn createNamespace(mod: *Module, initialization: Namespace) !Namespace.Index { + return mod.intern_pool.createNamespace(mod.gpa, initialization); +} - // In the case of a generic function instance, this is the type of the - // instance, which has comptime parameters elided. In other words, it is - // the runtime-known parameters only, not to be confused with the - // generic_owner function type, which potentially has more parameters, - // including comptime parameters. - const fn_ty = decl.typeOf(mod); - const fn_ty_info = mod.typeToFunc(fn_ty).?; - - var sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = arena, - .code = decl.getFileScope(mod).zir, - .owner_decl = decl, - .owner_decl_index = decl_index, - .func_index = func_index, - .func_is_naked = fn_ty_info.cc == .Naked, - .fn_ret_ty = Type.fromInterned(fn_ty_info.return_type), - .fn_ret_ty_ies = null, - .owner_func_index = func_index, - .branch_quota = @max(func.branchQuota(ip).*, Sema.default_branch_quota), - .comptime_err_ret_trace = &comptime_err_ret_trace, - }; - defer sema.deinit(); - - // Every runtime function has a dependency on the source of the Decl it originates from. - // It also depends on the value of its owner Decl. - try sema.declareDependency(.{ .src_hash = decl.zir_decl_index.unwrap().? }); - try sema.declareDependency(.{ .decl_val = decl_index }); - - if (func.analysis(ip).inferred_error_set) { - const ies = try arena.create(Sema.InferredErrorSet); - ies.* = .{ .func = func_index }; - sema.fn_ret_ty_ies = ies; - } - - // reset in case calls to errorable functions are removed. - func.analysis(ip).calls_or_awaits_errorable_fn = false; - - // First few indexes of extra are reserved and set at the end. - const reserved_count = @typeInfo(Air.ExtraIndex).Enum.fields.len; - try sema.air_extra.ensureTotalCapacity(gpa, reserved_count); - sema.air_extra.items.len += reserved_count; - - var inner_block: Sema.Block = .{ - .parent = null, - .sema = &sema, - .namespace = decl.src_namespace, - .instructions = .{}, - .inlining = null, - .is_comptime = false, - .src_base_inst = inst: { - const owner_info = if (func.generic_owner == .none) - func - else - mod.funcInfo(func.generic_owner); - const orig_decl = mod.declPtr(owner_info.owner_decl); - break :inst orig_decl.zir_decl_index.unwrap().?; - }, - .type_name_ctx = decl.name, - }; - defer inner_block.instructions.deinit(gpa); - - const fn_info = sema.code.getFnInfo(func.zirBodyInst(ip).resolve(ip)); - - // Here we are performing "runtime semantic analysis" for a function body, which means - // we must map the parameter ZIR instructions to `arg` AIR instructions. - // AIR requires the `arg` parameters to be the first N instructions. - // This could be a generic function instantiation, however, in which case we need to - // map the comptime parameters to constant values and only emit arg AIR instructions - // for the runtime ones. - const runtime_params_len = fn_ty_info.param_types.len; - try inner_block.instructions.ensureTotalCapacityPrecise(gpa, runtime_params_len); - try sema.air_instructions.ensureUnusedCapacity(gpa, fn_info.total_params_len); - try sema.inst_map.ensureSpaceForInstructions(gpa, fn_info.param_body); - - // In the case of a generic function instance, pre-populate all the comptime args. - if (func.comptime_args.len != 0) { - for ( - fn_info.param_body[0..func.comptime_args.len], - func.comptime_args.get(ip), - ) |inst, comptime_arg| { - if (comptime_arg == .none) continue; - sema.inst_map.putAssumeCapacityNoClobber(inst, Air.internedToRef(comptime_arg)); - } - } - - const src_params_len = if (func.comptime_args.len != 0) - func.comptime_args.len - else - runtime_params_len; - - var runtime_param_index: usize = 0; - for (fn_info.param_body[0..src_params_len], 0..) |inst, src_param_index| { - const gop = sema.inst_map.getOrPutAssumeCapacity(inst); - if (gop.found_existing) continue; // provided above by comptime arg - - const param_ty = fn_ty_info.param_types.get(ip)[runtime_param_index]; - runtime_param_index += 1; - - const opt_opv = sema.typeHasOnePossibleValue(Type.fromInterned(param_ty)) catch |err| switch (err) { - error.GenericPoison => unreachable, - error.ComptimeReturn => unreachable, - error.ComptimeBreak => unreachable, - else => |e| return e, - }; - if (opt_opv) |opv| { - gop.value_ptr.* = Air.internedToRef(opv.toIntern()); - continue; - } - const arg_index: Air.Inst.Index = @enumFromInt(sema.air_instructions.len); - gop.value_ptr.* = arg_index.toRef(); - inner_block.instructions.appendAssumeCapacity(arg_index); - sema.air_instructions.appendAssumeCapacity(.{ - .tag = .arg, - .data = .{ .arg = .{ - .ty = Air.internedToRef(param_ty), - .src_index = @intCast(src_param_index), - } }, - }); - } - - func.analysis(ip).state = .in_progress; - - const last_arg_index = inner_block.instructions.items.len; - - // Save the error trace as our first action in the function. - // If this is unnecessary after all, Liveness will clean it up for us. - const error_return_trace_index = try sema.analyzeSaveErrRetIndex(&inner_block); - sema.error_return_trace_index_on_fn_entry = error_return_trace_index; - inner_block.error_return_trace_index = error_return_trace_index; - - sema.analyzeFnBody(&inner_block, fn_info.body) catch |err| switch (err) { - // TODO make these unreachable instead of @panic - error.GenericPoison => @panic("zig compiler bug: GenericPoison"), - error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"), - else => |e| return e, - }; - - for (sema.unresolved_inferred_allocs.keys()) |ptr_inst| { - // The lack of a resolve_inferred_alloc means that this instruction - // is unused so it just has to be a no-op. - sema.air_instructions.set(@intFromEnum(ptr_inst), .{ - .tag = .alloc, - .data = .{ .ty = Type.single_const_pointer_to_comptime_int }, - }); - } - - // If we don't get an error return trace from a caller, create our own. - if (func.analysis(ip).calls_or_awaits_errorable_fn and - mod.comp.config.any_error_tracing and - !sema.fn_ret_ty.isError(mod)) - { - sema.setupErrorReturnTrace(&inner_block, last_arg_index) catch |err| switch (err) { - // TODO make these unreachable instead of @panic - error.GenericPoison => @panic("zig compiler bug: GenericPoison"), - error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"), - error.ComptimeBreak => @panic("zig compiler bug: ComptimeBreak"), - else => |e| return e, - }; - } - - // Copy the block into place and mark that as the main block. - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + - inner_block.instructions.items.len); - const main_block_index = sema.addExtraAssumeCapacity(Air.Block{ - .body_len = @intCast(inner_block.instructions.items.len), - }); - sema.air_extra.appendSliceAssumeCapacity(@ptrCast(inner_block.instructions.items)); - sema.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)] = main_block_index; - - // Resolving inferred error sets is done *before* setting the function - // state to success, so that "unable to resolve inferred error set" errors - // can be emitted here. - if (sema.fn_ret_ty_ies) |ies| { - sema.resolveInferredErrorSetPtr(&inner_block, .{ - .base_node_inst = inner_block.src_base_inst, - .offset = LazySrcLoc.Offset.nodeOffset(0), - }, ies) catch |err| switch (err) { - error.GenericPoison => unreachable, - error.ComptimeReturn => unreachable, - error.ComptimeBreak => unreachable, - error.AnalysisFail => { - // In this case our function depends on a type that had a compile error. - // We should not try to lower this function. - decl.analysis = .dependency_failure; - return error.AnalysisFail; - }, - else => |e| return e, - }; - assert(ies.resolved != .none); - ip.funcIesResolved(func_index).* = ies.resolved; - } - - func.analysis(ip).state = .success; - - // Finally we must resolve the return type and parameter types so that backends - // have full access to type information. - // Crucially, this happens *after* we set the function state to success above, - // so that dependencies on the function body will now be satisfied rather than - // result in circular dependency errors. - sema.resolveFnTypes(fn_ty) catch |err| switch (err) { - error.GenericPoison => unreachable, - error.ComptimeReturn => unreachable, - error.ComptimeBreak => unreachable, - error.AnalysisFail => { - // In this case our function depends on a type that had a compile error. - // We should not try to lower this function. - decl.analysis = .dependency_failure; - return error.AnalysisFail; - }, - else => |e| return e, - }; - - try sema.flushExports(); - - return .{ - .instructions = sema.air_instructions.toOwnedSlice(), - .extra = try sema.air_extra.toOwnedSlice(gpa), - }; -} - -pub fn createNamespace(mod: *Module, initialization: Namespace) !Namespace.Index { - return mod.intern_pool.createNamespace(mod.gpa, initialization); -} - -pub fn destroyNamespace(mod: *Module, index: Namespace.Index) void { - return mod.intern_pool.destroyNamespace(mod.gpa, index); -} +pub fn destroyNamespace(mod: *Module, index: Namespace.Index) void { + return mod.intern_pool.destroyNamespace(mod.gpa, index); +} pub fn allocateNewDecl(zcu: *Zcu, namespace: Namespace.Index) !Decl.Index { const gpa = zcu.gpa; @@ -5420,117 +4162,7 @@ fn lockAndClearFileCompileError(mod: *Module, file: *File) void { } } -/// 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(zcu: *Zcu) !void { - const gpa = zcu.gpa; - - // First, construct a mapping of every exported value and Decl to the indices of all its different exports. - var decl_exports: std.AutoArrayHashMapUnmanaged(Decl.Index, ArrayListUnmanaged(u32)) = .{}; - var value_exports: std.AutoArrayHashMapUnmanaged(InternPool.Index, ArrayListUnmanaged(u32)) = .{}; - defer { - for (decl_exports.values()) |*exports| { - exports.deinit(gpa); - } - decl_exports.deinit(gpa); - for (value_exports.values()) |*exports| { - exports.deinit(gpa); - } - value_exports.deinit(gpa); - } - - // We note as a heuristic: - // * It is rare to export a value. - // * It is rare for one Decl to be exported multiple times. - // So, this ensureTotalCapacity serves as a reasonable (albeit very approximate) optimization. - try decl_exports.ensureTotalCapacity(gpa, zcu.single_exports.count() + zcu.multi_exports.count()); - - for (zcu.single_exports.values()) |export_idx| { - const exp = zcu.all_exports.items[export_idx]; - const value_ptr, const found_existing = switch (exp.exported) { - .decl_index => |i| gop: { - const gop = try decl_exports.getOrPut(gpa, i); - break :gop .{ gop.value_ptr, gop.found_existing }; - }, - .value => |i| gop: { - const gop = try value_exports.getOrPut(gpa, i); - break :gop .{ gop.value_ptr, gop.found_existing }; - }, - }; - if (!found_existing) value_ptr.* = .{}; - try value_ptr.append(gpa, export_idx); - } - - for (zcu.multi_exports.values()) |info| { - for (zcu.all_exports.items[info.index..][0..info.len], info.index..) |exp, export_idx| { - const value_ptr, const found_existing = switch (exp.exported) { - .decl_index => |i| gop: { - const gop = try decl_exports.getOrPut(gpa, i); - break :gop .{ gop.value_ptr, gop.found_existing }; - }, - .value => |i| gop: { - const gop = try value_exports.getOrPut(gpa, i); - break :gop .{ gop.value_ptr, gop.found_existing }; - }, - }; - if (!found_existing) value_ptr.* = .{}; - try value_ptr.append(gpa, @intCast(export_idx)); - } - } - - // Map symbol names to `Export` for name collision detection. - var symbol_exports: SymbolExports = .{}; - defer symbol_exports.deinit(gpa); - - for (decl_exports.keys(), decl_exports.values()) |exported_decl, exports_list| { - const exported: Exported = .{ .decl_index = exported_decl }; - try processExportsInner(zcu, &symbol_exports, exported, exports_list.items); - } - - for (value_exports.keys(), value_exports.values()) |exported_value, exports_list| { - const exported: Exported = .{ .value = exported_value }; - try processExportsInner(zcu, &symbol_exports, exported, exports_list.items); - } -} - -const SymbolExports = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, u32); - -fn processExportsInner( - zcu: *Zcu, - symbol_exports: *SymbolExports, - exported: Exported, - export_indices: []const u32, -) error{OutOfMemory}!void { - const gpa = zcu.gpa; - - for (export_indices) |export_idx| { - const new_export = &zcu.all_exports.items[export_idx]; - const gop = try symbol_exports.getOrPut(gpa, new_export.opts.name); - if (gop.found_existing) { - new_export.status = .failed_retryable; - try zcu.failed_exports.ensureUnusedCapacity(gpa, 1); - const msg = try ErrorMsg.create(gpa, new_export.src, "exported symbol collision: {}", .{ - new_export.opts.name.fmt(&zcu.intern_pool), - }); - errdefer msg.destroy(gpa); - const other_export = zcu.all_exports.items[gop.value_ptr.*]; - try zcu.errNote(other_export.src, msg, "other symbol here", .{}); - zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg); - new_export.status = .failed; - } else { - gop.value_ptr.* = export_idx; - } - } - if (zcu.comp.bin_file) |lf| { - try handleUpdateExports(zcu, export_indices, lf.updateExports(zcu, exported, export_indices)); - } else if (zcu.llvm_object) |llvm_object| { - if (build_options.only_c) unreachable; - try handleUpdateExports(zcu, export_indices, llvm_object.updateExports(zcu, exported, export_indices)); - } -} - -fn handleUpdateExports( +pub fn handleUpdateExports( zcu: *Zcu, export_indices: []const u32, result: link.File.UpdateExportsError!void, @@ -5551,180 +4183,7 @@ fn handleUpdateExports( }; } -pub fn populateTestFunctions( - zcu: *Zcu, - main_progress_node: std.Progress.Node, -) !void { - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const builtin_mod = zcu.root_mod.getBuiltinDependency(); - const builtin_file_index = (zcu.importPkg(builtin_mod) catch unreachable).file_index; - const root_decl_index = zcu.fileRootDecl(builtin_file_index); - const root_decl = zcu.declPtr(root_decl_index.unwrap().?); - const builtin_namespace = zcu.namespacePtr(root_decl.src_namespace); - const test_functions_str = try ip.getOrPutString(gpa, "test_functions", .no_embedded_nulls); - const decl_index = builtin_namespace.decls.getKeyAdapted( - test_functions_str, - DeclAdapter{ .zcu = zcu }, - ).?; - { - // We have to call `ensureDeclAnalyzed` here in case `builtin.test_functions` - // was not referenced by start code. - zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); - defer { - zcu.sema_prog_node.end(); - zcu.sema_prog_node = undefined; - } - try zcu.ensureDeclAnalyzed(decl_index); - } - - const decl = zcu.declPtr(decl_index); - const test_fn_ty = decl.typeOf(zcu).slicePtrFieldType(zcu).childType(zcu); - - const array_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = array: { - // Add zcu.test_functions to an array decl then make the test_functions - // decl reference it as a slice. - const test_fn_vals = try gpa.alloc(InternPool.Index, zcu.test_functions.count()); - defer gpa.free(test_fn_vals); - - for (test_fn_vals, zcu.test_functions.keys()) |*test_fn_val, test_decl_index| { - const test_decl = zcu.declPtr(test_decl_index); - const test_decl_name = try test_decl.fullyQualifiedName(zcu); - const test_decl_name_len = test_decl_name.length(ip); - const test_name_anon_decl: InternPool.Key.Ptr.BaseAddr.AnonDecl = n: { - const test_name_ty = try zcu.arrayType(.{ - .len = test_decl_name_len, - .child = .u8_type, - }); - const test_name_val = try zcu.intern(.{ .aggregate = .{ - .ty = test_name_ty.toIntern(), - .storage = .{ .bytes = test_decl_name.toString() }, - } }); - break :n .{ - .orig_ty = (try zcu.singleConstPtrType(test_name_ty)).toIntern(), - .val = test_name_val, - }; - }; - - const test_fn_fields = .{ - // name - try zcu.intern(.{ .slice = .{ - .ty = .slice_const_u8_type, - .ptr = try zcu.intern(.{ .ptr = .{ - .ty = .manyptr_const_u8_type, - .base_addr = .{ .anon_decl = test_name_anon_decl }, - .byte_offset = 0, - } }), - .len = try zcu.intern(.{ .int = .{ - .ty = .usize_type, - .storage = .{ .u64 = test_decl_name_len }, - } }), - } }), - // func - try zcu.intern(.{ .ptr = .{ - .ty = try zcu.intern(.{ .ptr_type = .{ - .child = test_decl.typeOf(zcu).toIntern(), - .flags = .{ - .is_const = true, - }, - } }), - .base_addr = .{ .decl = test_decl_index }, - .byte_offset = 0, - } }), - }; - test_fn_val.* = try zcu.intern(.{ .aggregate = .{ - .ty = test_fn_ty.toIntern(), - .storage = .{ .elems = &test_fn_fields }, - } }); - } - - const array_ty = try zcu.arrayType(.{ - .len = test_fn_vals.len, - .child = test_fn_ty.toIntern(), - .sentinel = .none, - }); - const array_val = try zcu.intern(.{ .aggregate = .{ - .ty = array_ty.toIntern(), - .storage = .{ .elems = test_fn_vals }, - } }); - break :array .{ - .orig_ty = (try zcu.singleConstPtrType(array_ty)).toIntern(), - .val = array_val, - }; - }; - - { - const new_ty = try zcu.ptrType(.{ - .child = test_fn_ty.toIntern(), - .flags = .{ - .is_const = true, - .size = .Slice, - }, - }); - const new_val = decl.val; - const new_init = try zcu.intern(.{ .slice = .{ - .ty = new_ty.toIntern(), - .ptr = try zcu.intern(.{ .ptr = .{ - .ty = new_ty.slicePtrFieldType(zcu).toIntern(), - .base_addr = .{ .anon_decl = array_anon_decl }, - .byte_offset = 0, - } }), - .len = (try zcu.intValue(Type.usize, zcu.test_functions.count())).toIntern(), - } }); - ip.mutateVarInit(decl.val.toIntern(), new_init); - - // Since we are replacing the Decl's value we must perform cleanup on the - // previous value. - decl.val = new_val; - decl.has_tv = true; - } - { - zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); - defer { - zcu.codegen_prog_node.end(); - zcu.codegen_prog_node = undefined; - } - - try zcu.linkerUpdateDecl(decl_index); - } -} - -pub fn linkerUpdateDecl(zcu: *Zcu, decl_index: Decl.Index) !void { - const comp = zcu.comp; - - const decl = zcu.declPtr(decl_index); - - const codegen_prog_node = zcu.codegen_prog_node.start((try decl.fullyQualifiedName(zcu)).toSlice(&zcu.intern_pool), 0); - defer codegen_prog_node.end(); - - if (comp.bin_file) |lf| { - lf.updateDecl(zcu, decl_index) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.AnalysisFail => { - decl.analysis = .codegen_failure; - }, - else => { - const gpa = zcu.gpa; - try zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - zcu.failed_analysis.putAssumeCapacityNoClobber(AnalUnit.wrap(.{ .decl = decl_index }), try ErrorMsg.create( - gpa, - decl.navSrcLoc(zcu), - "unable to codegen: {s}", - .{@errorName(err)}, - )); - decl.analysis = .codegen_failure; - try zcu.retryable_failures.append(zcu.gpa, AnalUnit.wrap(.{ .decl = decl_index })); - }, - }; - } else if (zcu.llvm_object) |llvm_object| { - if (build_options.only_c) unreachable; - llvm_object.updateDecl(zcu, decl_index) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - }; - } -} - -fn reportRetryableFileError( +pub fn reportRetryableFileError( zcu: *Zcu, file_index: File.Index, comptime format: []const u8, @@ -5795,344 +4254,6 @@ pub fn backendSupportsFeature(zcu: Module, feature: Feature) bool { return target_util.backendSupportsFeature(cpu_arch, ofmt, use_llvm, feature); } -/// Shortcut for calling `intern_pool.get`. -pub fn intern(mod: *Module, key: InternPool.Key) Allocator.Error!InternPool.Index { - return mod.intern_pool.get(mod.gpa, key); -} - -/// Shortcut for calling `intern_pool.getCoerced`. -pub fn getCoerced(mod: *Module, val: Value, new_ty: Type) Allocator.Error!Value { - return Value.fromInterned((try mod.intern_pool.getCoerced(mod.gpa, val.toIntern(), new_ty.toIntern()))); -} - -pub fn intType(mod: *Module, signedness: std.builtin.Signedness, bits: u16) Allocator.Error!Type { - return Type.fromInterned((try intern(mod, .{ .int_type = .{ - .signedness = signedness, - .bits = bits, - } }))); -} - -pub fn errorIntType(mod: *Module) std.mem.Allocator.Error!Type { - return mod.intType(.unsigned, mod.errorSetBits()); -} - -pub fn arrayType(mod: *Module, info: InternPool.Key.ArrayType) Allocator.Error!Type { - const i = try intern(mod, .{ .array_type = info }); - return Type.fromInterned(i); -} - -pub fn vectorType(mod: *Module, info: InternPool.Key.VectorType) Allocator.Error!Type { - const i = try intern(mod, .{ .vector_type = info }); - return Type.fromInterned(i); -} - -pub fn optionalType(mod: *Module, child_type: InternPool.Index) Allocator.Error!Type { - const i = try intern(mod, .{ .opt_type = child_type }); - return Type.fromInterned(i); -} - -pub fn ptrType(mod: *Module, info: InternPool.Key.PtrType) Allocator.Error!Type { - var canon_info = info; - - if (info.flags.size == .C) canon_info.flags.is_allowzero = true; - - // Canonicalize non-zero alignment. If it matches the ABI alignment of the pointee - // type, we change it to 0 here. If this causes an assertion trip because the - // pointee type needs to be resolved more, that needs to be done before calling - // this ptr() function. - if (info.flags.alignment != .none and - info.flags.alignment == Type.fromInterned(info.child).abiAlignment(mod)) - { - canon_info.flags.alignment = .none; - } - - switch (info.flags.vector_index) { - // Canonicalize host_size. If it matches the bit size of the pointee type, - // we change it to 0 here. If this causes an assertion trip, the pointee type - // needs to be resolved before calling this ptr() function. - .none => if (info.packed_offset.host_size != 0) { - const elem_bit_size = Type.fromInterned(info.child).bitSize(mod); - assert(info.packed_offset.bit_offset + elem_bit_size <= info.packed_offset.host_size * 8); - if (info.packed_offset.host_size * 8 == elem_bit_size) { - canon_info.packed_offset.host_size = 0; - } - }, - .runtime => {}, - _ => assert(@intFromEnum(info.flags.vector_index) < info.packed_offset.host_size), - } - - return Type.fromInterned((try intern(mod, .{ .ptr_type = canon_info }))); -} - -/// Like `ptrType`, but if `info` specifies an `alignment`, first ensures the pointer -/// child type's alignment is resolved so that an invalid alignment is not used. -/// In general, prefer this function during semantic analysis. -pub fn ptrTypeSema(zcu: *Zcu, info: InternPool.Key.PtrType) SemaError!Type { - if (info.flags.alignment != .none) { - _ = try Type.fromInterned(info.child).abiAlignmentAdvanced(zcu, .sema); - } - return zcu.ptrType(info); -} - -pub fn singleMutPtrType(mod: *Module, child_type: Type) Allocator.Error!Type { - return ptrType(mod, .{ .child = child_type.toIntern() }); -} - -pub fn singleConstPtrType(mod: *Module, child_type: Type) Allocator.Error!Type { - return ptrType(mod, .{ - .child = child_type.toIntern(), - .flags = .{ - .is_const = true, - }, - }); -} - -pub fn manyConstPtrType(mod: *Module, child_type: Type) Allocator.Error!Type { - return ptrType(mod, .{ - .child = child_type.toIntern(), - .flags = .{ - .size = .Many, - .is_const = true, - }, - }); -} - -pub fn adjustPtrTypeChild(mod: *Module, ptr_ty: Type, new_child: Type) Allocator.Error!Type { - var info = ptr_ty.ptrInfo(mod); - info.child = new_child.toIntern(); - return mod.ptrType(info); -} - -pub fn funcType(mod: *Module, key: InternPool.GetFuncTypeKey) Allocator.Error!Type { - return Type.fromInterned((try mod.intern_pool.getFuncType(mod.gpa, key))); -} - -/// Use this for `anyframe->T` only. -/// For `anyframe`, use the `InternPool.Index.anyframe` tag directly. -pub fn anyframeType(mod: *Module, payload_ty: Type) Allocator.Error!Type { - return Type.fromInterned((try intern(mod, .{ .anyframe_type = payload_ty.toIntern() }))); -} - -pub fn errorUnionType(mod: *Module, error_set_ty: Type, payload_ty: Type) Allocator.Error!Type { - return Type.fromInterned((try intern(mod, .{ .error_union_type = .{ - .error_set_type = error_set_ty.toIntern(), - .payload_type = payload_ty.toIntern(), - } }))); -} - -pub fn singleErrorSetType(mod: *Module, name: InternPool.NullTerminatedString) Allocator.Error!Type { - const names: *const [1]InternPool.NullTerminatedString = &name; - const new_ty = try mod.intern_pool.getErrorSetType(mod.gpa, names); - return Type.fromInterned(new_ty); -} - -/// Sorts `names` in place. -pub fn errorSetFromUnsortedNames( - mod: *Module, - names: []InternPool.NullTerminatedString, -) Allocator.Error!Type { - std.mem.sort( - InternPool.NullTerminatedString, - names, - {}, - InternPool.NullTerminatedString.indexLessThan, - ); - const new_ty = try mod.intern_pool.getErrorSetType(mod.gpa, names); - return Type.fromInterned(new_ty); -} - -/// Supports only pointers, not pointer-like optionals. -pub fn ptrIntValue(mod: *Module, ty: Type, x: u64) Allocator.Error!Value { - assert(ty.zigTypeTag(mod) == .Pointer and !ty.isSlice(mod)); - assert(x != 0 or ty.isAllowzeroPtr(mod)); - const i = try intern(mod, .{ .ptr = .{ - .ty = ty.toIntern(), - .base_addr = .int, - .byte_offset = x, - } }); - return Value.fromInterned(i); -} - -/// Creates an enum tag value based on the integer tag value. -pub fn enumValue(mod: *Module, ty: Type, tag_int: InternPool.Index) Allocator.Error!Value { - if (std.debug.runtime_safety) { - const tag = ty.zigTypeTag(mod); - assert(tag == .Enum); - } - const i = try intern(mod, .{ .enum_tag = .{ - .ty = ty.toIntern(), - .int = tag_int, - } }); - return Value.fromInterned(i); -} - -/// Creates an enum tag value based on the field index according to source code -/// declaration order. -pub fn enumValueFieldIndex(mod: *Module, ty: Type, field_index: u32) Allocator.Error!Value { - const ip = &mod.intern_pool; - const gpa = mod.gpa; - const enum_type = ip.loadEnumType(ty.toIntern()); - - if (enum_type.values.len == 0) { - // Auto-numbered fields. - return Value.fromInterned((try ip.get(gpa, .{ .enum_tag = .{ - .ty = ty.toIntern(), - .int = try ip.get(gpa, .{ .int = .{ - .ty = enum_type.tag_ty, - .storage = .{ .u64 = field_index }, - } }), - } }))); - } - - return Value.fromInterned((try ip.get(gpa, .{ .enum_tag = .{ - .ty = ty.toIntern(), - .int = enum_type.values.get(ip)[field_index], - } }))); -} - -pub fn undefValue(mod: *Module, ty: Type) Allocator.Error!Value { - return Value.fromInterned((try mod.intern(.{ .undef = ty.toIntern() }))); -} - -pub fn undefRef(mod: *Module, ty: Type) Allocator.Error!Air.Inst.Ref { - return Air.internedToRef((try mod.undefValue(ty)).toIntern()); -} - -pub fn intValue(mod: *Module, ty: Type, x: anytype) Allocator.Error!Value { - if (std.math.cast(u64, x)) |casted| return intValue_u64(mod, ty, casted); - if (std.math.cast(i64, x)) |casted| return intValue_i64(mod, ty, casted); - var limbs_buffer: [4]usize = undefined; - var big_int = BigIntMutable.init(&limbs_buffer, x); - return intValue_big(mod, ty, big_int.toConst()); -} - -pub fn intRef(mod: *Module, ty: Type, x: anytype) Allocator.Error!Air.Inst.Ref { - return Air.internedToRef((try mod.intValue(ty, x)).toIntern()); -} - -pub fn intValue_big(mod: *Module, ty: Type, x: BigIntConst) Allocator.Error!Value { - const i = try intern(mod, .{ .int = .{ - .ty = ty.toIntern(), - .storage = .{ .big_int = x }, - } }); - return Value.fromInterned(i); -} - -pub fn intValue_u64(mod: *Module, ty: Type, x: u64) Allocator.Error!Value { - const i = try intern(mod, .{ .int = .{ - .ty = ty.toIntern(), - .storage = .{ .u64 = x }, - } }); - return Value.fromInterned(i); -} - -pub fn intValue_i64(mod: *Module, ty: Type, x: i64) Allocator.Error!Value { - const i = try intern(mod, .{ .int = .{ - .ty = ty.toIntern(), - .storage = .{ .i64 = x }, - } }); - return Value.fromInterned(i); -} - -pub fn unionValue(mod: *Module, union_ty: Type, tag: Value, val: Value) Allocator.Error!Value { - const i = try intern(mod, .{ .un = .{ - .ty = union_ty.toIntern(), - .tag = tag.toIntern(), - .val = val.toIntern(), - } }); - return Value.fromInterned(i); -} - -/// This function casts the float representation down to the representation of the type, potentially -/// losing data if the representation wasn't correct. -pub fn floatValue(mod: *Module, ty: Type, x: anytype) Allocator.Error!Value { - const storage: InternPool.Key.Float.Storage = switch (ty.floatBits(mod.getTarget())) { - 16 => .{ .f16 = @as(f16, @floatCast(x)) }, - 32 => .{ .f32 = @as(f32, @floatCast(x)) }, - 64 => .{ .f64 = @as(f64, @floatCast(x)) }, - 80 => .{ .f80 = @as(f80, @floatCast(x)) }, - 128 => .{ .f128 = @as(f128, @floatCast(x)) }, - else => unreachable, - }; - const i = try intern(mod, .{ .float = .{ - .ty = ty.toIntern(), - .storage = storage, - } }); - return Value.fromInterned(i); -} - -pub fn nullValue(mod: *Module, opt_ty: Type) Allocator.Error!Value { - const ip = &mod.intern_pool; - assert(ip.isOptionalType(opt_ty.toIntern())); - const result = try ip.get(mod.gpa, .{ .opt = .{ - .ty = opt_ty.toIntern(), - .val = .none, - } }); - return Value.fromInterned(result); -} - -pub fn smallestUnsignedInt(mod: *Module, max: u64) Allocator.Error!Type { - return intType(mod, .unsigned, Type.smallestUnsignedBits(max)); -} - -/// Returns the smallest possible integer type containing both `min` and -/// `max`. Asserts that neither value is undef. -/// TODO: if #3806 is implemented, this becomes trivial -pub fn intFittingRange(mod: *Module, min: Value, max: Value) !Type { - assert(!min.isUndef(mod)); - assert(!max.isUndef(mod)); - - if (std.debug.runtime_safety) { - assert(Value.order(min, max, mod).compare(.lte)); - } - - const sign = min.orderAgainstZero(mod) == .lt; - - const min_val_bits = intBitsForValue(mod, min, sign); - const max_val_bits = intBitsForValue(mod, max, sign); - - return mod.intType( - if (sign) .signed else .unsigned, - @max(min_val_bits, max_val_bits), - ); -} - -/// Given a value representing an integer, returns the number of bits necessary to represent -/// this value in an integer. If `sign` is true, returns the number of bits necessary in a -/// twos-complement integer; otherwise in an unsigned integer. -/// Asserts that `val` is not undef. If `val` is negative, asserts that `sign` is true. -pub fn intBitsForValue(mod: *Module, val: Value, sign: bool) u16 { - assert(!val.isUndef(mod)); - - const key = mod.intern_pool.indexToKey(val.toIntern()); - switch (key.int.storage) { - .i64 => |x| { - if (std.math.cast(u64, x)) |casted| return Type.smallestUnsignedBits(casted) + @intFromBool(sign); - assert(sign); - // Protect against overflow in the following negation. - if (x == std.math.minInt(i64)) return 64; - return Type.smallestUnsignedBits(@as(u64, @intCast(-(x + 1)))) + 1; - }, - .u64 => |x| { - return Type.smallestUnsignedBits(x) + @intFromBool(sign); - }, - .big_int => |big| { - if (big.positive) return @as(u16, @intCast(big.bitCountAbs() + @intFromBool(sign))); - - // Zero is still a possibility, in which case unsigned is fine - if (big.eqlZero()) return 0; - - return @as(u16, @intCast(big.bitCountTwosComp())); - }, - .lazy_align => |lazy_ty| { - return Type.smallestUnsignedBits(Type.fromInterned(lazy_ty).abiAlignment(mod).toByteUnits() orelse 0) + @intFromBool(sign); - }, - .lazy_size => |lazy_ty| { - return Type.smallestUnsignedBits(Type.fromInterned(lazy_ty).abiSize(mod)) + @intFromBool(sign); - }, - } -} - pub const AtomicPtrAlignmentError = error{ FloatTooBig, IntTooBig, @@ -6371,101 +4492,6 @@ pub const UnionLayout = struct { padding: u32, }; -pub fn getUnionLayout(mod: *Module, loaded_union: InternPool.LoadedUnionType) UnionLayout { - const ip = &mod.intern_pool; - assert(loaded_union.haveLayout(ip)); - var most_aligned_field: u32 = undefined; - var most_aligned_field_size: u64 = undefined; - var biggest_field: u32 = undefined; - var payload_size: u64 = 0; - var payload_align: Alignment = .@"1"; - for (loaded_union.field_types.get(ip), 0..) |field_ty, field_index| { - if (!Type.fromInterned(field_ty).hasRuntimeBitsIgnoreComptime(mod)) continue; - - const explicit_align = loaded_union.fieldAlign(ip, field_index); - const field_align = if (explicit_align != .none) - explicit_align - else - Type.fromInterned(field_ty).abiAlignment(mod); - const field_size = Type.fromInterned(field_ty).abiSize(mod); - if (field_size > payload_size) { - payload_size = field_size; - biggest_field = @intCast(field_index); - } - if (field_align.compare(.gte, payload_align)) { - payload_align = field_align; - most_aligned_field = @intCast(field_index); - most_aligned_field_size = field_size; - } - } - const have_tag = loaded_union.flagsPtr(ip).runtime_tag.hasTag(); - if (!have_tag or !Type.fromInterned(loaded_union.enum_tag_ty).hasRuntimeBits(mod)) { - return .{ - .abi_size = payload_align.forward(payload_size), - .abi_align = payload_align, - .most_aligned_field = most_aligned_field, - .most_aligned_field_size = most_aligned_field_size, - .biggest_field = biggest_field, - .payload_size = payload_size, - .payload_align = payload_align, - .tag_align = .none, - .tag_size = 0, - .padding = 0, - }; - } - - const tag_size = Type.fromInterned(loaded_union.enum_tag_ty).abiSize(mod); - const tag_align = Type.fromInterned(loaded_union.enum_tag_ty).abiAlignment(mod).max(.@"1"); - return .{ - .abi_size = loaded_union.size(ip).*, - .abi_align = tag_align.max(payload_align), - .most_aligned_field = most_aligned_field, - .most_aligned_field_size = most_aligned_field_size, - .biggest_field = biggest_field, - .payload_size = payload_size, - .payload_align = payload_align, - .tag_align = tag_align, - .tag_size = tag_size, - .padding = loaded_union.padding(ip).*, - }; -} - -pub fn unionAbiSize(mod: *Module, loaded_union: InternPool.LoadedUnionType) u64 { - return mod.getUnionLayout(loaded_union).abi_size; -} - -/// Returns 0 if the union is represented with 0 bits at runtime. -pub fn unionAbiAlignment(mod: *Module, loaded_union: InternPool.LoadedUnionType) Alignment { - const ip = &mod.intern_pool; - const have_tag = loaded_union.flagsPtr(ip).runtime_tag.hasTag(); - var max_align: Alignment = .none; - if (have_tag) max_align = Type.fromInterned(loaded_union.enum_tag_ty).abiAlignment(mod); - for (loaded_union.field_types.get(ip), 0..) |field_ty, field_index| { - if (!Type.fromInterned(field_ty).hasRuntimeBits(mod)) continue; - - const field_align = mod.unionFieldNormalAlignment(loaded_union, @intCast(field_index)); - max_align = max_align.max(field_align); - } - return max_align; -} - -/// Returns the field alignment of a non-packed union. Asserts the layout is not packed. -pub fn unionFieldNormalAlignment(zcu: *Zcu, loaded_union: InternPool.LoadedUnionType, field_index: u32) Alignment { - return zcu.unionFieldNormalAlignmentAdvanced(loaded_union, field_index, .normal) catch unreachable; -} - -/// Returns the field alignment of a non-packed union. Asserts the layout is not packed. -/// If `strat` is `.sema`, may perform type resolution. -pub fn unionFieldNormalAlignmentAdvanced(zcu: *Zcu, loaded_union: InternPool.LoadedUnionType, field_index: u32, strat: Type.ResolveStrat) SemaError!Alignment { - const ip = &zcu.intern_pool; - assert(loaded_union.flagsPtr(ip).layout != .@"packed"); - const field_align = loaded_union.fieldAlign(ip, field_index); - if (field_align != .none) return field_align; - const field_ty = Type.fromInterned(loaded_union.field_types.get(ip)[field_index]); - if (field_ty.isNoReturn(zcu)) return .none; - return (try field_ty.abiAlignmentAdvanced(zcu, strat.toLazy())).scalar; -} - /// Returns the index of the active field, given the current tag value pub fn unionTagFieldIndex(mod: *Module, loaded_union: InternPool.LoadedUnionType, enum_tag: Value) ?u32 { const ip = &mod.intern_pool; @@ -6474,63 +4500,6 @@ pub fn unionTagFieldIndex(mod: *Module, loaded_union: InternPool.LoadedUnionType return loaded_union.loadTagType(ip).tagValueIndex(ip, enum_tag.toIntern()); } -/// Returns the field alignment of a non-packed struct. Asserts the layout is not packed. -pub fn structFieldAlignment( - zcu: *Zcu, - explicit_alignment: InternPool.Alignment, - field_ty: Type, - layout: std.builtin.Type.ContainerLayout, -) Alignment { - return zcu.structFieldAlignmentAdvanced(explicit_alignment, field_ty, layout, .normal) catch unreachable; -} - -/// Returns the field alignment of a non-packed struct. Asserts the layout is not packed. -/// If `strat` is `.sema`, may perform type resolution. -pub fn structFieldAlignmentAdvanced( - zcu: *Zcu, - explicit_alignment: InternPool.Alignment, - field_ty: Type, - layout: std.builtin.Type.ContainerLayout, - strat: Type.ResolveStrat, -) SemaError!Alignment { - assert(layout != .@"packed"); - if (explicit_alignment != .none) return explicit_alignment; - const ty_abi_align = (try field_ty.abiAlignmentAdvanced(zcu, strat.toLazy())).scalar; - switch (layout) { - .@"packed" => unreachable, - .auto => if (zcu.getTarget().ofmt != .c) return ty_abi_align, - .@"extern" => {}, - } - // extern - if (field_ty.isAbiInt(zcu) and field_ty.intInfo(zcu).bits >= 128) { - return ty_abi_align.maxStrict(.@"16"); - } - return ty_abi_align; -} - -/// https://github.com/ziglang/zig/issues/17178 explored storing these bit offsets -/// into the packed struct InternPool data rather than computing this on the -/// fly, however it was found to perform worse when measured on real world -/// projects. -pub fn structPackedFieldBitOffset( - mod: *Module, - struct_type: InternPool.LoadedStructType, - field_index: u32, -) u16 { - const ip = &mod.intern_pool; - assert(struct_type.layout == .@"packed"); - assert(struct_type.haveLayout(ip)); - var bit_sum: u64 = 0; - for (0..struct_type.field_types.len) |i| { - if (i == field_index) { - return @intCast(bit_sum); - } - const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[i]); - bit_sum += field_ty.bitSize(mod); - } - unreachable; // index out of bounds -} - pub const ResolvedReference = struct { referencer: AnalUnit, src: LazySrcLoc, @@ -6564,33 +4533,6 @@ pub fn resolveReferences(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, Resolved return result; } -pub fn getBuiltin(zcu: *Zcu, name: []const u8) Allocator.Error!Air.Inst.Ref { - const decl_index = try zcu.getBuiltinDecl(name); - zcu.ensureDeclAnalyzed(decl_index) catch @panic("std.builtin is corrupt"); - return Air.internedToRef(zcu.declPtr(decl_index).val.toIntern()); -} - -pub fn getBuiltinDecl(zcu: *Zcu, name: []const u8) Allocator.Error!InternPool.DeclIndex { - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - const std_file_imported = zcu.importPkg(zcu.std_mod) catch @panic("failed to import lib/std.zig"); - const std_file_root_decl = zcu.fileRootDecl(std_file_imported.file_index).unwrap().?; - const std_namespace = zcu.declPtr(std_file_root_decl).getOwnedInnerNamespace(zcu).?; - const builtin_str = try ip.getOrPutString(gpa, "builtin", .no_embedded_nulls); - const builtin_decl = std_namespace.decls.getKeyAdapted(builtin_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse @panic("lib/std.zig is corrupt and missing 'builtin'"); - zcu.ensureDeclAnalyzed(builtin_decl) catch @panic("std.builtin is corrupt"); - const builtin_namespace = zcu.declPtr(builtin_decl).getInnerNamespace(zcu) orelse @panic("std.builtin is corrupt"); - const name_str = try ip.getOrPutString(gpa, name, .no_embedded_nulls); - return builtin_namespace.decls.getKeyAdapted(name_str, Zcu.DeclAdapter{ .zcu = zcu }) orelse @panic("lib/std/builtin.zig is corrupt"); -} - -pub fn getBuiltinType(zcu: *Zcu, name: []const u8) Allocator.Error!Type { - const ty_inst = try zcu.getBuiltin(name); - const ty = Type.fromInterned(ty_inst.toInterned() orelse @panic("std.builtin is corrupt")); - ty.resolveFully(zcu) catch @panic("std.builtin is corrupt"); - return ty; -} - pub fn fileByIndex(zcu: *const Zcu, i: File.Index) *File { return zcu.import_table.values()[@intFromEnum(i)]; } -- cgit v1.2.3