zig/src/Zcu.zig
mlugg 8744865425 frontend: fix reference tracking through coerced function bodies
This bug was manifesting for user as a nasty link error because they
were calling their application's main entry point as a coerced function,
which essentially broke reference tracking for the entire ZCU, causing
exported symbols to silently not get exported.

I've been a little unsure about how coerced functions should interact
with the unit graph before, but the solution is actually really obvious
now: they shouldn't! `Sema` is now responsible for unwrapping
possibly-coerced functions *before* queuing analysis or marking unit
references. This makes the reference graph optimal (there are no
redundant edges representing coerced versions of the same function) and
simplifies logic elsewhere at the expense of just a few lines in Sema.
2025-09-15 11:29:31 +01:00

4796 lines
205 KiB
Zig

//! Zig Compilation Unit
//!
//! Compilation of all Zig source code is represented by one `Zcu`.
//!
//! Each `Compilation` has exactly one or zero `Zcu`, depending on whether
//! there is or is not any zig source code, respectively.
const std = @import("std");
const builtin = @import("builtin");
const mem = std.mem;
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const log = std.log.scoped(.zcu);
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
const Ast = std.zig.Ast;
const Writer = std.Io.Writer;
const Zcu = @This();
const Compilation = @import("Compilation.zig");
const Cache = std.Build.Cache;
pub const Value = @import("Value.zig");
pub const Type = @import("Type.zig");
const Package = @import("Package.zig");
const link = @import("link.zig");
const Air = @import("Air.zig");
const Zir = std.zig.Zir;
const trace = @import("tracy.zig").trace;
const AstGen = std.zig.AstGen;
const Sema = @import("Sema.zig");
const target_util = @import("target.zig");
const build_options = @import("build_options");
const isUpDir = @import("introspect.zig").isUpDir;
const clang = @import("clang.zig");
const InternPool = @import("InternPool.zig");
const Alignment = InternPool.Alignment;
const AnalUnit = InternPool.AnalUnit;
const BuiltinFn = std.zig.BuiltinFn;
const LlvmObject = @import("codegen/llvm.zig").Object;
const dev = @import("dev.zig");
const Zoir = std.zig.Zoir;
const ZonGen = std.zig.ZonGen;
comptime {
@setEvalBranchQuota(4000);
for (
@typeInfo(Zir.Inst.Ref).@"enum".fields,
@typeInfo(Air.Inst.Ref).@"enum".fields,
@typeInfo(InternPool.Index).@"enum".fields,
) |zir_field, air_field, ip_field| {
assert(mem.eql(u8, zir_field.name, ip_field.name));
assert(mem.eql(u8, air_field.name, ip_field.name));
}
}
/// General-purpose allocator. Used for both temporary and long-term storage.
gpa: Allocator,
comp: *Compilation,
/// If the ZCU is emitting an LLVM object (i.e. we are using the LLVM backend), then this is the
/// `LlvmObject` we are emitting to.
llvm_object: ?LlvmObject.Ptr,
/// Pointer to externally managed resource.
root_mod: *Package.Module,
/// Normally, `main_mod` and `root_mod` are the same. The exception is `zig test`, in which
/// `root_mod` is the test runner, and `main_mod` is the user's source file which has the tests.
main_mod: *Package.Module,
std_mod: *Package.Module,
sema_prog_node: std.Progress.Node = .none,
codegen_prog_node: std.Progress.Node = .none,
/// The number of codegen jobs which are pending or in-progress. Whichever thread drops this value
/// to 0 is responsible for ending `codegen_prog_node`. While semantic analysis is happening, this
/// value bottoms out at 1 instead of 0, to ensure that it can only drop to 0 after analysis is
/// completed (since semantic analysis could trigger more codegen work).
pending_codegen_jobs: std.atomic.Value(u32) = .init(0),
/// This is the progress node *under* `sema_prog_node` which is currently running.
/// When we have to pause to analyze something else, we just temporarily rename this node.
/// Eventually, when we thread semantic analysis, we will want one of these per thread.
cur_sema_prog_node: std.Progress.Node = .none,
/// Used by AstGen worker to load and store ZIR cache.
global_zir_cache: Cache.Directory,
/// Used by AstGen worker to load and store ZIR cache.
local_zir_cache: Cache.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: std.ArrayListUnmanaged(Export) = .empty,
/// This is a list of free indices in `all_exports`. These indices may be reused by exports from
/// future semantic analysis.
free_exports: std.ArrayListUnmanaged(Export.Index) = .empty,
/// 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.
single_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, Export.Index) = .empty,
/// Like `single_exports`, but for `AnalUnit`s which perform multiple exports.
/// The exports are `all_exports.items[index..][0..len]`.
multi_exports: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
index: u32,
len: u32,
}) = .{},
/// Key is the digest returned by `Builtin.hash`; value is the corresponding module.
builtin_modules: std.AutoArrayHashMapUnmanaged(Cache.BinDigest, *Package.Module) = .empty,
/// Populated as soon as the `Compilation` is created. Guaranteed to contain all modules, even builtin ones.
/// Modules whose root file is not a Zig or ZON file have the value `.none`.
module_roots: std.AutoArrayHashMapUnmanaged(*Package.Module, File.Index.Optional) = .empty,
/// The set of all the Zig source files in the Zig Compilation Unit. Tracked in
/// order to iterate over it and check which source files have been modified on
/// the file system when an update is requested, as well as to cache `@import`
/// results.
///
/// Always accessed through `ImportTableAdapter`, where keys are fully resolved
/// file paths in order to ensure files are properly deduplicated. This table owns
/// the keys and values.
///
/// Protected by Compilation's mutex.
///
/// Not serialized. This state is reconstructed during the first call to
/// `Compilation.update` of the process for a given `Compilation`.
import_table: std.ArrayHashMapUnmanaged(
File.Index,
void,
struct {
pub const hash = @compileError("all accesses should be through ImportTableAdapter");
pub const eql = @compileError("all accesses should be through ImportTableAdapter");
},
true, // This is necessary! Without it, the map tries to use its Context to rehash. #21918
) = .empty,
/// The set of all files in `import_table` which are "alive" this update, meaning
/// they are reachable by traversing imports starting from an analysis root. This
/// is usually all files in `import_table`, but some could be omitted if an incremental
/// update removes an import, or if a module specified on the CLI is never imported.
/// Reconstructed on every update, after AstGen and before Sema.
/// Value is why the file is alive.
alive_files: std.AutoArrayHashMapUnmanaged(File.Index, File.Reference) = .empty,
/// If this is populated, a "file exists in multiple modules" error should be emitted.
/// This causes file errors to not be shown, because we don't really know which files
/// should be alive (because the user has messed up their imports somewhere!).
/// Cleared and recomputed every update, after AstGen and before Sema.
multi_module_err: ?struct {
file: File.Index,
modules: [2]*Package.Module,
refs: [2]File.Reference,
} = null,
/// The set of all the files which have been loaded with `@embedFile` in the Module.
/// We keep track of this in order to iterate over it and check which files have been
/// modified on the file system when an update is requested, as well as to cache
/// `@embedFile` results.
///
/// Like `import_table`, this is accessed through `EmbedTableAdapter`, so that it is keyed
/// on the `Compilation.Path` of the `EmbedFile`.
///
/// This table owns all of the `*EmbedFile` memory, which is allocated into gpa.
embed_table: std.ArrayHashMapUnmanaged(
*EmbedFile,
void,
struct {
pub const hash = @compileError("all accesses should be through EmbedTableAdapter");
pub const eql = @compileError("all accesses should be through EmbedTableAdapter");
},
true, // This is necessary! Without it, the map tries to use its Context to rehash. #21918
) = .empty,
/// Stores all Type and Value objects.
/// The idea is that this will be periodically garbage-collected, but such logic
/// is not yet implemented.
intern_pool: InternPool = .empty,
analysis_in_progress: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty,
/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator.
failed_analysis: std.AutoArrayHashMapUnmanaged(AnalUnit, *ErrorMsg) = .empty,
/// This `AnalUnit` failed semantic analysis because it required analysis of another `AnalUnit` which itself failed.
transitive_failed_analysis: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty,
/// This `Nav` succeeded analysis, but failed codegen.
/// This may be a simple "value" `Nav`, or it may be a function.
/// The ErrorMsg memory is owned by the `AnalUnit`, using Module's general purpose allocator.
/// While multiple threads are active (most of the time!), this is guarded by `zcu.comp.mutex`, as
/// codegen and linking run on a separate thread.
failed_codegen: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, *ErrorMsg) = .empty,
failed_types: std.AutoArrayHashMapUnmanaged(InternPool.Index, *ErrorMsg) = .empty,
/// Keep track of `@compileLog`s per `AnalUnit`.
/// We track the source location of the first `@compileLog` call, and all logged lines as a linked list.
/// The list is singly linked, but we do track its tail for fast appends (optimizing many logs in one unit).
compile_logs: std.AutoArrayHashMapUnmanaged(AnalUnit, extern struct {
base_node_inst: InternPool.TrackedInst.Index,
node_offset: Ast.Node.Offset,
first_line: CompileLogLine.Index,
last_line: CompileLogLine.Index,
pub fn src(self: @This()) LazySrcLoc {
return .{
.base_node_inst = self.base_node_inst,
.offset = LazySrcLoc.Offset.nodeOffset(self.node_offset),
};
}
}) = .empty,
compile_log_lines: std.ArrayListUnmanaged(CompileLogLine) = .empty,
free_compile_log_lines: std.ArrayListUnmanaged(CompileLogLine.Index) = .empty,
/// This tracks files which triggered errors when generating AST/ZIR/ZOIR.
/// If not `null`, the value is a retryable error (the file status is guaranteed
/// to be `.retryable_failure`). Otherwise, the file status is `.astgen_failure`
/// or `.success`, and there are ZIR/ZOIR errors which should be printed.
/// We just store a `[]u8` instead of a full `*ErrorMsg`, because the source
/// location is always the entire file. The `[]u8` memory is owned by the map
/// and allocated into `gpa`.
failed_files: std.AutoArrayHashMapUnmanaged(File.Index, ?[]u8) = .empty,
/// AstGen is not aware of modules, and so cannot determine whether an import
/// string makes sense. That is the job of a traversal after AstGen.
///
/// There are several ways in which an import can fail:
///
/// * It is an import of a file which does not exist. This case is not handled
/// by this field, but with a `failed_files` entry on the *imported* file.
/// * It is an import of a module which does not exist in the current module's
/// dependency table. This happens at `Sema` time, so is not tracked by this
/// field.
/// * It is an import which reaches outside of the current module's root
/// directory. This is tracked by this field.
/// * It is an import which reaches into an "illegal import directory". Right now,
/// the only such directory is 'global_cache/b/', but in general, these are
/// directories the compiler treats specially. This is tracked by this field.
///
/// This is a flat array containing all of the relevant errors. It is cleared and
/// recomputed on every update. The errors here are fatal, i.e. they block any
/// semantic analysis this update.
///
/// Allocated into gpa.
failed_imports: std.ArrayListUnmanaged(struct {
file_index: File.Index,
import_string: Zir.NullTerminatedString,
import_token: Ast.TokenIndex,
kind: enum { file_outside_module_root, illegal_zig_import },
}) = .empty,
failed_exports: std.AutoArrayHashMapUnmanaged(Export.Index, *ErrorMsg) = .empty,
/// If analysis failed due to a cimport error, the corresponding Clang errors
/// are stored here.
cimport_errors: std.AutoArrayHashMapUnmanaged(AnalUnit, std.zig.ErrorBundle) = .empty,
/// Maximum amount of distinct error values, set by --error-limit
error_limit: ErrorInt,
/// Value is the number of PO dependencies of this AnalUnit.
/// This value will decrease as we perform semantic analysis to learn what is outdated.
/// If any of these PO deps is outdated, this value will be moved to `outdated`.
potentially_outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
/// Value is the number of PO dependencies of this AnalUnit.
/// Once this value drops to 0, the AnalUnit is a candidate for re-analysis.
outdated: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
/// This contains all `AnalUnit`s in `outdated` whose PO dependency count is 0.
/// Such `AnalUnit`s are ready for immediate re-analysis.
/// See `findOutdatedToAnalyze` for details.
outdated_ready: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty,
/// This contains a list of AnalUnit whose analysis or codegen failed, but the
/// failure was something like running out of disk space, and trying again may
/// succeed. On the next update, we will flush this list, marking all members of
/// it as outdated.
retryable_failures: std.ArrayListUnmanaged(AnalUnit) = .empty,
func_body_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .empty,
nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty,
/// These are the modules which we initially queue for analysis in `Compilation.update`.
/// `resolveReferences` will use these as the root of its reachability traversal.
analysis_roots_buffer: [4]*Package.Module,
analysis_roots_len: usize = 0,
/// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and
/// reset to `null` when any semantic analysis occurs (since this invalidates the data).
/// Allocated into `gpa`.
resolved_references: ?std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) = null,
/// If `true`, then semantic analysis must not occur on this update due to AstGen errors.
/// Essentially the entire pipeline after AstGen, including Sema, codegen, and link, is skipped.
/// Reset to `false` at the start of each update in `Compilation.update`.
skip_analysis_this_update: bool = false,
test_functions: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, void) = .empty,
global_assembly: std.AutoArrayHashMapUnmanaged(AnalUnit, []u8) = .empty,
/// Key is the `AnalUnit` *performing* the reference. This representation allows
/// incremental updates to quickly delete references caused by a specific `AnalUnit`.
/// Value is index into `all_references` of the first reference triggered by the unit.
/// The `next` field on the `Reference` forms a linked list of all references
/// triggered by the key `AnalUnit`.
reference_table: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
all_references: std.ArrayListUnmanaged(Reference) = .empty,
/// Freelist of indices in `all_references`.
free_references: std.ArrayListUnmanaged(u32) = .empty,
inline_reference_frames: std.ArrayListUnmanaged(InlineReferenceFrame) = .empty,
free_inline_reference_frames: std.ArrayListUnmanaged(InlineReferenceFrame.Index) = .empty,
/// Key is the `AnalUnit` *performing* the reference. This representation allows
/// incremental updates to quickly delete references caused by a specific `AnalUnit`.
/// Value is index into `all_type_reference` of the first reference triggered by the unit.
/// The `next` field on the `TypeReference` forms a linked list of all type references
/// triggered by the key `AnalUnit`.
type_reference_table: std.AutoArrayHashMapUnmanaged(AnalUnit, u32) = .empty,
all_type_references: std.ArrayListUnmanaged(TypeReference) = .empty,
/// Freelist of indices in `all_type_references`.
free_type_references: std.ArrayListUnmanaged(u32) = .empty,
/// Populated by analysis of `AnalUnit.wrap(.{ .memoized_state = s })`, where `s` depends on the element.
builtin_decl_values: BuiltinDecl.Memoized = .initFill(.none),
incremental_debug_state: if (build_options.enable_debug_extensions) IncrementalDebugState else void =
if (build_options.enable_debug_extensions) .init else {},
/// Times semantic analysis of the current `AnalUnit`. When we pause to analyze a different unit,
/// this timer must be temporarily paused and resumed later.
cur_analysis_timer: ?Compilation.Timer = null,
generation: u32 = 0,
pub const IncrementalDebugState = struct {
/// All container types in the ZCU, even dead ones.
/// Value is the generation the type was created on.
types: std.AutoArrayHashMapUnmanaged(InternPool.Index, u32),
/// All `Nav`s in the ZCU, even dead ones.
/// Value is the generation the `Nav` was created on.
navs: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, u32),
/// All `AnalUnit`s in the ZCU, even dead ones.
units: std.AutoArrayHashMapUnmanaged(AnalUnit, UnitInfo),
pub const init: IncrementalDebugState = .{
.types = .empty,
.navs = .empty,
.units = .empty,
};
pub fn deinit(ids: *IncrementalDebugState, gpa: Allocator) void {
for (ids.units.values()) |*unit_info| {
unit_info.deps.deinit(gpa);
}
ids.types.deinit(gpa);
ids.navs.deinit(gpa);
ids.units.deinit(gpa);
}
pub const UnitInfo = struct {
last_update_gen: u32,
/// This information isn't easily recoverable from `InternPool`'s dependency storage format.
deps: std.ArrayListUnmanaged(InternPool.Dependee),
};
pub fn getUnitInfo(ids: *IncrementalDebugState, gpa: Allocator, unit: AnalUnit) Allocator.Error!*UnitInfo {
const gop = try ids.units.getOrPut(gpa, unit);
if (!gop.found_existing) gop.value_ptr.* = .{
.last_update_gen = std.math.maxInt(u32),
.deps = .empty,
};
return gop.value_ptr;
}
pub fn newType(ids: *IncrementalDebugState, zcu: *Zcu, ty: InternPool.Index) Allocator.Error!void {
try ids.types.putNoClobber(zcu.gpa, ty, zcu.generation);
}
pub fn newNav(ids: *IncrementalDebugState, zcu: *Zcu, nav: InternPool.Nav.Index) Allocator.Error!void {
try ids.navs.putNoClobber(zcu.gpa, nav, zcu.generation);
}
};
pub const PerThread = @import("Zcu/PerThread.zig");
pub const ImportTableAdapter = struct {
zcu: *const Zcu,
pub fn hash(ctx: ImportTableAdapter, path: Compilation.Path) u32 {
_ = ctx;
return @truncate(std.hash.Wyhash.hash(@intFromEnum(path.root), path.sub_path));
}
pub fn eql(ctx: ImportTableAdapter, a_path: Compilation.Path, b_file: File.Index, b_index: usize) bool {
_ = b_index;
const b_path = ctx.zcu.fileByIndex(b_file).path;
return a_path.root == b_path.root and mem.eql(u8, a_path.sub_path, b_path.sub_path);
}
};
pub const EmbedTableAdapter = struct {
pub fn hash(ctx: EmbedTableAdapter, path: Compilation.Path) u32 {
_ = ctx;
return @truncate(std.hash.Wyhash.hash(@intFromEnum(path.root), path.sub_path));
}
pub fn eql(ctx: EmbedTableAdapter, a_path: Compilation.Path, b_file: *EmbedFile, b_index: usize) bool {
_ = ctx;
_ = b_index;
const b_path = b_file.path;
return a_path.root == b_path.root and mem.eql(u8, a_path.sub_path, b_path.sub_path);
}
};
/// Names of declarations in `std.builtin` whose values are memoized in a `BuiltinDecl.Memoized`.
/// The name must exactly match the declaration name, as comptime logic is used to compute the namespace accesses.
/// Parent namespaces must be before their children in this enum. For instance, `.Type` must be before `.@"Type.Fn"`.
/// Additionally, parent namespaces must be resolved in the same stage as their children; see `BuiltinDecl.stage`.
pub const BuiltinDecl = enum {
Signedness,
AddressSpace,
CallingConvention,
returnError,
StackTrace,
SourceLocation,
CallModifier,
AtomicOrder,
AtomicRmwOp,
ReduceOp,
FloatMode,
PrefetchOptions,
ExportOptions,
ExternOptions,
BranchHint,
Type,
@"Type.Fn",
@"Type.Fn.Param",
@"Type.Int",
@"Type.Float",
@"Type.Pointer",
@"Type.Pointer.Size",
@"Type.Array",
@"Type.Vector",
@"Type.Optional",
@"Type.Error",
@"Type.ErrorUnion",
@"Type.EnumField",
@"Type.Enum",
@"Type.Union",
@"Type.UnionField",
@"Type.Struct",
@"Type.StructField",
@"Type.ContainerLayout",
@"Type.Opaque",
@"Type.Declaration",
panic,
@"panic.call",
@"panic.sentinelMismatch",
@"panic.unwrapError",
@"panic.outOfBounds",
@"panic.startGreaterThanEnd",
@"panic.inactiveUnionField",
@"panic.sliceCastLenRemainder",
@"panic.reachedUnreachable",
@"panic.unwrapNull",
@"panic.castToNull",
@"panic.incorrectAlignment",
@"panic.invalidErrorCode",
@"panic.integerOutOfBounds",
@"panic.integerOverflow",
@"panic.shlOverflow",
@"panic.shrOverflow",
@"panic.divideByZero",
@"panic.exactDivisionRemainder",
@"panic.integerPartOutOfBounds",
@"panic.corruptSwitch",
@"panic.shiftRhsTooBig",
@"panic.invalidEnumValue",
@"panic.forLenMismatch",
@"panic.copyLenMismatch",
@"panic.memcpyAlias",
@"panic.noreturnReturned",
VaList,
assembly,
@"assembly.Clobbers",
/// Determines what kind of validation will be done to the decl's value.
pub fn kind(decl: BuiltinDecl) enum { type, func, string } {
return switch (decl) {
.returnError => .func,
.StackTrace,
.CallingConvention,
.SourceLocation,
.Signedness,
.AddressSpace,
.VaList,
.CallModifier,
.AtomicOrder,
.AtomicRmwOp,
.ReduceOp,
.FloatMode,
.PrefetchOptions,
.ExportOptions,
.ExternOptions,
.BranchHint,
.assembly,
.@"assembly.Clobbers",
=> .type,
.Type,
.@"Type.Fn",
.@"Type.Fn.Param",
.@"Type.Int",
.@"Type.Float",
.@"Type.Pointer",
.@"Type.Pointer.Size",
.@"Type.Array",
.@"Type.Vector",
.@"Type.Optional",
.@"Type.Error",
.@"Type.ErrorUnion",
.@"Type.EnumField",
.@"Type.Enum",
.@"Type.Union",
.@"Type.UnionField",
.@"Type.Struct",
.@"Type.StructField",
.@"Type.ContainerLayout",
.@"Type.Opaque",
.@"Type.Declaration",
=> .type,
.panic => .type,
.@"panic.call",
.@"panic.sentinelMismatch",
.@"panic.unwrapError",
.@"panic.outOfBounds",
.@"panic.startGreaterThanEnd",
.@"panic.inactiveUnionField",
.@"panic.sliceCastLenRemainder",
.@"panic.reachedUnreachable",
.@"panic.unwrapNull",
.@"panic.castToNull",
.@"panic.incorrectAlignment",
.@"panic.invalidErrorCode",
.@"panic.integerOutOfBounds",
.@"panic.integerOverflow",
.@"panic.shlOverflow",
.@"panic.shrOverflow",
.@"panic.divideByZero",
.@"panic.exactDivisionRemainder",
.@"panic.integerPartOutOfBounds",
.@"panic.corruptSwitch",
.@"panic.shiftRhsTooBig",
.@"panic.invalidEnumValue",
.@"panic.forLenMismatch",
.@"panic.copyLenMismatch",
.@"panic.memcpyAlias",
.@"panic.noreturnReturned",
=> .func,
};
}
/// Resolution of these values is done in three distinct stages:
/// * Resolution of `std.builtin.Panic` and everything under it
/// * Resolution of `VaList`
/// * Resolution of `assembly`
/// * Everything else
///
/// Panics are separated because they are provided by the user, so must be able to use
/// things like reification.
///
/// `VaList` is separate because its value depends on the target, so it needs some reflection
/// machinery to work; additionally, it is `@compileError` on some targets, so must be referenced
/// by itself.
///
/// `assembly` is separate because its value depends on the target.
pub fn stage(decl: BuiltinDecl) InternPool.MemoizedStateStage {
return switch (decl) {
.VaList => .va_list,
.assembly, .@"assembly.Clobbers" => .assembly,
else => {
if (@intFromEnum(decl) <= @intFromEnum(BuiltinDecl.@"Type.Declaration")) {
return .main;
} else {
return .panic;
}
},
};
}
/// Based on the tag name, determines how to access this decl; either as a direct child of the
/// `std.builtin` namespace, or as a child of some preceding `BuiltinDecl` value.
pub fn access(decl: BuiltinDecl) union(enum) {
direct: []const u8,
nested: struct { BuiltinDecl, []const u8 },
} {
@setEvalBranchQuota(2000);
return switch (decl) {
inline else => |tag| {
const name = @tagName(tag);
const split = (comptime std.mem.lastIndexOfScalar(u8, name, '.')) orelse return .{ .direct = name };
const parent = @field(BuiltinDecl, name[0..split]);
comptime assert(@intFromEnum(parent) < @intFromEnum(tag)); // dependencies ordered correctly
return .{ .nested = .{ parent, name[split + 1 ..] } };
},
};
}
const Memoized = std.enums.EnumArray(BuiltinDecl, InternPool.Index);
};
pub const SimplePanicId = enum {
reached_unreachable,
unwrap_null,
cast_to_null,
incorrect_alignment,
invalid_error_code,
integer_out_of_bounds,
integer_overflow,
shl_overflow,
shr_overflow,
divide_by_zero,
exact_division_remainder,
integer_part_out_of_bounds,
corrupt_switch,
shift_rhs_too_big,
invalid_enum_value,
for_len_mismatch,
copy_len_mismatch,
memcpy_alias,
noreturn_returned,
pub fn toBuiltin(id: SimplePanicId) BuiltinDecl {
return switch (id) {
// zig fmt: off
.reached_unreachable => .@"panic.reachedUnreachable",
.unwrap_null => .@"panic.unwrapNull",
.cast_to_null => .@"panic.castToNull",
.incorrect_alignment => .@"panic.incorrectAlignment",
.invalid_error_code => .@"panic.invalidErrorCode",
.integer_out_of_bounds => .@"panic.integerOutOfBounds",
.integer_overflow => .@"panic.integerOverflow",
.shl_overflow => .@"panic.shlOverflow",
.shr_overflow => .@"panic.shrOverflow",
.divide_by_zero => .@"panic.divideByZero",
.exact_division_remainder => .@"panic.exactDivisionRemainder",
.integer_part_out_of_bounds => .@"panic.integerPartOutOfBounds",
.corrupt_switch => .@"panic.corruptSwitch",
.shift_rhs_too_big => .@"panic.shiftRhsTooBig",
.invalid_enum_value => .@"panic.invalidEnumValue",
.for_len_mismatch => .@"panic.forLenMismatch",
.copy_len_mismatch => .@"panic.copyLenMismatch",
.memcpy_alias => .@"panic.memcpyAlias",
.noreturn_returned => .@"panic.noreturnReturned",
// zig fmt: on
};
}
};
pub const GlobalErrorSet = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void);
pub const CImportError = struct {
offset: u32,
line: u32,
column: u32,
path: ?[*:0]u8,
source_line: ?[*:0]u8,
msg: [*:0]u8,
pub fn deinit(err: CImportError, gpa: Allocator) void {
if (err.path) |some| gpa.free(std.mem.span(some));
if (err.source_line) |some| gpa.free(std.mem.span(some));
gpa.free(std.mem.span(err.msg));
}
};
pub const ErrorInt = u32;
pub const Exported = union(enum) {
/// The Nav being exported. Note this is *not* the Nav corresponding to the AnalUnit performing the export.
nav: InternPool.Nav.Index,
/// Constant value being exported.
uav: InternPool.Index,
pub fn getValue(exported: Exported, zcu: *Zcu) Value {
return switch (exported) {
.nav => |nav| zcu.navValue(nav),
.uav => |uav| Value.fromInterned(uav),
};
}
pub fn getAlign(exported: Exported, zcu: *Zcu) Alignment {
return switch (exported) {
.nav => |nav| switch (zcu.intern_pool.getNav(nav).status) {
.unresolved => unreachable,
.type_resolved => |r| r.alignment,
.fully_resolved => |r| r.alignment,
},
.uav => .none,
};
}
};
pub const Export = struct {
opts: Options,
src: LazySrcLoc,
exported: Exported,
status: enum {
in_progress,
failed,
/// Indicates that the failure was due to a temporary issue, such as an I/O error
/// when writing to the output file. Retrying the export may succeed.
failed_retryable,
complete,
},
pub const Options = struct {
name: InternPool.NullTerminatedString,
linkage: std.builtin.GlobalLinkage = .strong,
section: InternPool.OptionalNullTerminatedString = .none,
visibility: std.builtin.SymbolVisibility = .default,
};
/// Index into `all_exports`.
pub const Index = enum(u32) {
_,
pub fn ptr(i: Index, zcu: *const Zcu) *Export {
return &zcu.all_exports.items[@intFromEnum(i)];
}
};
};
pub const CompileLogLine = struct {
next: Index.Optional,
/// Does *not* include the trailing newline.
data: InternPool.NullTerminatedString,
pub const Index = enum(u32) {
_,
pub fn get(idx: Index, zcu: *Zcu) *CompileLogLine {
return &zcu.compile_log_lines.items[@intFromEnum(idx)];
}
pub fn toOptional(idx: Index) Optional {
return @enumFromInt(@intFromEnum(idx));
}
pub const Optional = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn unwrap(opt: Optional) ?Index {
return switch (opt) {
.none => null,
_ => @enumFromInt(@intFromEnum(opt)),
};
}
};
};
};
pub const Reference = struct {
/// The `AnalUnit` whose semantic analysis was triggered by this reference.
referenced: AnalUnit,
/// Index into `all_references` of the next `Reference` triggered by the same `AnalUnit`.
/// `std.math.maxInt(u32)` is the sentinel.
next: u32,
/// The source location of the reference.
src: LazySrcLoc,
/// If not `.none`, this is the index of the `InlineReferenceFrame` which should appear
/// between the referencer and `referenced` in the reference trace. These frames represent
/// inline calls, which do not create actual references (since they happen in the caller's
/// `AnalUnit`), but do show in the reference trace.
inline_frame: InlineReferenceFrame.Index.Optional,
};
pub const InlineReferenceFrame = struct {
/// The inline *callee*; that is, the function which was called inline.
/// The *caller* is either `parent`, or else the unit causing the original `Reference`.
callee: InternPool.Index,
/// The source location of the inline call, in the *caller*.
call_src: LazySrcLoc,
/// If not `.none`, a frame which should appear directly below this one.
/// This will be the "parent" inline call; this frame's `callee` is our caller.
parent: InlineReferenceFrame.Index.Optional,
pub const Index = enum(u32) {
_,
pub fn ptr(idx: Index, zcu: *Zcu) *InlineReferenceFrame {
return &zcu.inline_reference_frames.items[@intFromEnum(idx)];
}
pub fn toOptional(idx: Index) Optional {
return @enumFromInt(@intFromEnum(idx));
}
pub const Optional = enum(u32) {
none = std.math.maxInt(u32),
_,
pub fn unwrap(opt: Optional) ?Index {
return switch (opt) {
.none => null,
_ => @enumFromInt(@intFromEnum(opt)),
};
}
};
};
};
pub const TypeReference = struct {
/// The container type which was referenced.
referenced: InternPool.Index,
/// Index into `all_type_references` of the next `TypeReference` triggered by the same `AnalUnit`.
/// `std.math.maxInt(u32)` is the sentinel.
next: u32,
/// The source location of the reference.
src: LazySrcLoc,
};
/// The container that structs, enums, unions, and opaques have.
pub const Namespace = struct {
parent: OptionalIndex,
file_scope: File.Index,
generation: u32,
/// Will be a struct, enum, union, or opaque.
owner_type: InternPool.Index,
/// Members of the namespace which are marked `pub`.
pub_decls: std.ArrayHashMapUnmanaged(InternPool.Nav.Index, void, NavNameContext, true) = .empty,
/// Members of the namespace which are *not* marked `pub`.
priv_decls: std.ArrayHashMapUnmanaged(InternPool.Nav.Index, void, NavNameContext, true) = .empty,
/// All `comptime` declarations in this namespace. We store these purely so that incremental
/// compilation can re-use the existing `ComptimeUnit`s when a namespace changes.
comptime_decls: std.ArrayListUnmanaged(InternPool.ComptimeUnit.Id) = .empty,
/// All `test` declarations in this namespace. We store these purely so that incremental
/// compilation can re-use the existing `Nav`s when a namespace changes.
test_decls: std.ArrayListUnmanaged(InternPool.Nav.Index) = .empty,
pub const Index = InternPool.NamespaceIndex;
pub const OptionalIndex = InternPool.OptionalNamespaceIndex;
const NavNameContext = struct {
zcu: *Zcu,
pub fn hash(ctx: NavNameContext, nav: InternPool.Nav.Index) u32 {
const name = ctx.zcu.intern_pool.getNav(nav).name;
return std.hash.int(@intFromEnum(name));
}
pub fn eql(ctx: NavNameContext, a_nav: InternPool.Nav.Index, b_nav: InternPool.Nav.Index, b_index: usize) bool {
_ = b_index;
const a_name = ctx.zcu.intern_pool.getNav(a_nav).name;
const b_name = ctx.zcu.intern_pool.getNav(b_nav).name;
return a_name == b_name;
}
};
pub const NameAdapter = struct {
zcu: *Zcu,
pub fn hash(ctx: NameAdapter, s: InternPool.NullTerminatedString) u32 {
_ = ctx;
return std.hash.int(@intFromEnum(s));
}
pub fn eql(ctx: NameAdapter, a: InternPool.NullTerminatedString, b_nav: InternPool.Nav.Index, b_index: usize) bool {
_ = b_index;
return a == ctx.zcu.intern_pool.getNav(b_nav).name;
}
};
pub fn fileScope(ns: Namespace, zcu: *Zcu) *File {
return zcu.fileByIndex(ns.file_scope);
}
pub fn fileScopeIp(ns: Namespace, ip: *InternPool) *File {
return ip.filePtr(ns.file_scope);
}
/// This renders e.g. "std/fs.zig:Dir.OpenOptions"
pub fn renderFullyQualifiedDebugName(
ns: Namespace,
zcu: *Zcu,
name: InternPool.NullTerminatedString,
writer: anytype,
) @TypeOf(writer).Error!void {
const sep: u8 = if (ns.parent.unwrap()) |parent| sep: {
try zcu.namespacePtr(parent).renderFullyQualifiedDebugName(
zcu,
zcu.declPtr(ns.decl_index).name,
writer,
);
break :sep '.';
} else sep: {
try ns.fileScope(zcu).renderFullyQualifiedDebugName(writer);
break :sep ':';
};
if (name != .empty) try writer.print("{c}{f}", .{ sep, name.fmt(&zcu.intern_pool) });
}
pub fn internFullyQualifiedName(
ns: Namespace,
ip: *InternPool,
gpa: Allocator,
tid: Zcu.PerThread.Id,
name: InternPool.NullTerminatedString,
) !InternPool.NullTerminatedString {
const ns_name = Type.fromInterned(ns.owner_type).containerTypeName(ip);
if (name == .empty) return ns_name;
return ip.getOrPutStringFmt(gpa, tid, "{f}.{f}", .{ ns_name.fmt(ip), name.fmt(ip) }, .no_embedded_nulls);
}
};
pub const File = struct {
status: enum {
/// We have not yet attempted to load this file.
/// `stat` is not populated and may be `undefined`.
never_loaded,
/// A filesystem access failed. It should be retried on the next update.
/// There is guaranteed to be a `failed_files` entry with at least one message.
/// ZIR/ZOIR errors should not be emitted as `zir`/`zoir` is not up-to-date.
/// `stat` is not populated and may be `undefined`.
retryable_failure,
/// This file has failed parsing, AstGen, or ZonGen.
/// There is guaranteed to be a `failed_files` entry, which may or may not have messages.
/// ZIR/ZOIR errors *should* be emitted as `zir`/`zoir` is up-to-date.
/// `stat` is populated.
astgen_failure,
/// Parsing and AstGen/ZonGen of this file has succeeded.
/// There may still be a `failed_files` entry, e.g. for non-fatal AstGen errors.
/// `stat` is populated.
success,
},
/// Whether this is populated depends on `status`.
stat: Cache.File.Stat,
/// Whether this file is the generated file of a "builtin" module. This matters because those
/// files are generated and stored in-nemory rather than being read off-disk. The rest of the
/// pipeline generally shouldn't care about this.
is_builtin: bool,
/// The path of this file. It is important that this path has a "canonical form" because files
/// are deduplicated based on path; `Compilation.Path` guarantees this. Owned by this `File`,
/// allocated into `gpa`.
path: Compilation.Path,
source: ?[:0]const u8,
tree: ?Ast,
zir: ?Zir,
zoir: ?Zoir,
/// Module that this file is a part of, managed externally.
/// This is initially `null`. After AstGen, a pass is run to determine which module each
/// file belongs to, at which point this field is set. It is never set to `null` again;
/// this is so that if the file starts belonging to a different module instead, we can
/// tell, and invalidate dependencies as needed (see `module_changed`).
/// During semantic analysis, this is always non-`null` for alive files (i.e. those which
/// have imports targeting them).
mod: ?*Package.Module,
/// Relative to the root directory of `mod`. If `mod == null`, this field is `undefined`.
/// This memory is managed externally and must not be directly freed.
/// Its lifetime is at least equal to that of this `File`.
sub_file_path: []const u8,
/// If this file's module identity changes on an incremental update, this flag is set to signal
/// to `Zcu.updateZirRefs` that all references to this file must be invalidated. This matters
/// because changing your module changes things like your optimization mode and codegen flags,
/// so everything needs to be re-done. `updateZirRefs` is responsible for resetting this flag.
module_changed: bool,
/// The ZIR for this file from the last update with no file failures. As such, this ZIR is never
/// failed (although it may have compile errors).
///
/// Because updates with file failures do not perform ZIR mapping or semantic analysis, we keep
/// this around so we have the "old" ZIR to map when an update is ready to do so. Once such an
/// update occurs, this field is unloaded, since it is no longer necessary.
///
/// In other words, if `TrackedInst`s are tied to ZIR other than what's in the `zir` field, this
/// field is populated with that old ZIR.
prev_zir: ?*Zir,
/// This field serves a similar purpose to `prev_zir`, but for ZOIR. However, since we do not
/// need to map old ZOIR to new ZOIR -- instead only invalidating dependencies if the ZOIR
/// changed -- this field is just a simple boolean.
///
/// When `zoir` is updated, this field is set to `true`. In `updateZirRefs`, if this is `true`,
/// we invalidate the corresponding `zon_file` dependency, and reset it to `false`.
zoir_invalidated: bool,
pub const Path = struct {
root: enum {
cwd,
fs_root,
local_cache,
global_cache,
lib_dir,
},
};
/// A single reference to a file.
pub const Reference = union(enum) {
analysis_root: *Package.Module,
import: struct {
importer: Zcu.File.Index,
tok: Ast.TokenIndex,
/// If the file is imported as the root of a module, this is that module.
/// `null` means the file was imported directly by path.
module: ?*Package.Module,
},
};
pub fn getMode(self: File) Ast.Mode {
// We never create a `File` whose path doesn't give a mode.
return modeFromPath(self.path.sub_path).?;
}
pub fn modeFromPath(path: []const u8) ?Ast.Mode {
if (std.mem.endsWith(u8, path, ".zon")) {
return .zon;
} else if (std.mem.endsWith(u8, path, ".zig")) {
return .zig;
} else {
return null;
}
}
pub fn unload(file: *File, gpa: Allocator) void {
if (file.zoir) |zoir| zoir.deinit(gpa);
file.unloadTree(gpa);
file.unloadSource(gpa);
file.unloadZir(gpa);
}
pub fn unloadTree(file: *File, gpa: Allocator) void {
if (file.tree) |*tree| {
tree.deinit(gpa);
file.tree = null;
}
}
pub fn unloadSource(file: *File, gpa: Allocator) void {
if (file.source) |source| {
gpa.free(source);
file.source = null;
}
}
pub fn unloadZir(file: *File, gpa: Allocator) void {
if (file.zir) |*zir| {
zir.deinit(gpa);
file.zir = null;
}
}
pub const Source = struct {
bytes: [:0]const u8,
stat: Cache.File.Stat,
};
pub const GetSourceError = error{ OutOfMemory, FileTooBig } || std.fs.File.OpenError || std.fs.File.ReadError;
pub fn getSource(file: *File, zcu: *const Zcu) GetSourceError!Source {
const gpa = zcu.gpa;
if (file.source) |source| return .{
.bytes = source,
.stat = file.stat,
};
var f = f: {
const dir, const sub_path = file.path.openInfo(zcu.comp.dirs);
break :f try dir.openFile(sub_path, .{});
};
defer f.close();
const stat = try f.stat();
if (stat.size > std.math.maxInt(u32))
return error.FileTooBig;
const source = try gpa.allocSentinel(u8, @intCast(stat.size), 0);
errdefer gpa.free(source);
var file_reader = f.reader(&.{});
file_reader.size = stat.size;
file_reader.interface.readSliceAll(source) catch return file_reader.err.?;
// Here we do not modify stat fields because this function is the one
// used for error reporting. We need to keep the stat fields stale so that
// updateFile can know to regenerate ZIR.
file.source = source;
errdefer comptime unreachable; // don't error after populating `source`
return .{
.bytes = source,
.stat = .{
.size = stat.size,
.inode = stat.inode,
.mtime = stat.mtime,
},
};
}
pub fn getTree(file: *File, zcu: *const Zcu) GetSourceError!*const Ast {
if (file.tree) |*tree| return tree;
const source = try file.getSource(zcu);
file.tree = try .parse(zcu.gpa, source.bytes, file.getMode());
return &file.tree.?;
}
pub fn fullyQualifiedNameLen(file: File) usize {
const ext = std.fs.path.extension(file.sub_file_path);
return file.sub_file_path.len - ext.len;
}
pub fn renderFullyQualifiedName(file: File, writer: anytype) !void {
// Convert all the slashes into dots and truncate the extension.
const ext = std.fs.path.extension(file.sub_file_path);
const noext = file.sub_file_path[0 .. file.sub_file_path.len - ext.len];
for (noext) |byte| switch (byte) {
'/', '\\' => try writer.writeByte('.'),
else => try writer.writeByte(byte),
};
}
pub fn renderFullyQualifiedDebugName(file: File, writer: anytype) !void {
for (file.sub_file_path) |byte| switch (byte) {
'/', '\\' => try writer.writeByte('/'),
else => try writer.writeByte(byte),
};
}
pub fn internFullyQualifiedName(file: File, pt: Zcu.PerThread) !InternPool.NullTerminatedString {
const gpa = pt.zcu.gpa;
const ip = &pt.zcu.intern_pool;
const strings = ip.getLocal(pt.tid).getMutableStrings(gpa);
var w: Writer = .fixed((try strings.addManyAsSlice(file.fullyQualifiedNameLen()))[0]);
file.renderFullyQualifiedName(&w) catch unreachable;
assert(w.end == w.buffer.len);
return ip.getOrPutTrailingString(gpa, pt.tid, @intCast(w.end), .no_embedded_nulls);
}
pub const Index = InternPool.FileIndex;
pub fn errorBundleWholeFileSrc(
file: *File,
zcu: *const Zcu,
eb: *std.zig.ErrorBundle.Wip,
) Allocator.Error!std.zig.ErrorBundle.SourceLocationIndex {
return eb.addSourceLocation(.{
.src_path = try eb.printString("{f}", .{file.path.fmt(zcu.comp)}),
.span_start = 0,
.span_main = 0,
.span_end = 0,
.line = 0,
.column = 0,
.source_line = 0,
});
}
/// Asserts that the tree has already been loaded with `getTree`.
pub fn errorBundleTokenSrc(
file: *File,
tok: Ast.TokenIndex,
zcu: *const Zcu,
eb: *std.zig.ErrorBundle.Wip,
) Allocator.Error!std.zig.ErrorBundle.SourceLocationIndex {
const tree = &file.tree.?;
const start = tree.tokenStart(tok);
const end = start + tree.tokenSlice(tok).len;
const loc = std.zig.findLineColumn(file.source.?, start);
return eb.addSourceLocation(.{
.src_path = try eb.printString("{f}", .{file.path.fmt(zcu.comp)}),
.span_start = start,
.span_main = start,
.span_end = @intCast(end),
.line = @intCast(loc.line),
.column = @intCast(loc.column),
.source_line = try eb.addString(loc.source_line),
});
}
};
/// Represents the contents of a file loaded with `@embedFile`.
pub const EmbedFile = struct {
path: Compilation.Path,
/// `.none` means the file was not loaded, so `stat` is undefined.
val: InternPool.Index,
/// If this is `null` and `val` is `.none`, the file has never been loaded.
err: ?(std.fs.File.OpenError || std.fs.File.StatError || std.fs.File.ReadError || error{UnexpectedEof}),
stat: Cache.File.Stat,
pub const Index = enum(u32) {
_,
pub fn get(idx: Index, zcu: *const Zcu) *EmbedFile {
return zcu.embed_table.keys()[@intFromEnum(idx)];
}
};
};
/// This struct holds data necessary to construct API-facing `AllErrors.Message`.
/// Its memory is managed with the general purpose allocator so that they
/// can be created and destroyed in response to incremental updates.
pub const ErrorMsg = struct {
src_loc: LazySrcLoc,
msg: []const u8,
notes: []ErrorMsg = &.{},
reference_trace_root: AnalUnit.Optional = .none,
pub fn create(
gpa: Allocator,
src_loc: LazySrcLoc,
comptime format: []const u8,
args: anytype,
) !*ErrorMsg {
assert(src_loc.offset != .unneeded);
const err_msg = try gpa.create(ErrorMsg);
errdefer gpa.destroy(err_msg);
err_msg.* = try ErrorMsg.init(gpa, src_loc, format, args);
return err_msg;
}
/// Assumes the ErrorMsg struct and msg were both allocated with `gpa`,
/// as well as all notes.
pub fn destroy(err_msg: *ErrorMsg, gpa: Allocator) void {
err_msg.deinit(gpa);
gpa.destroy(err_msg);
}
pub fn init(gpa: Allocator, src_loc: LazySrcLoc, comptime format: []const u8, args: anytype) !ErrorMsg {
return .{
.src_loc = src_loc,
.msg = try std.fmt.allocPrint(gpa, format, args),
};
}
pub fn deinit(err_msg: *ErrorMsg, gpa: Allocator) void {
for (err_msg.notes) |*note| {
note.deinit(gpa);
}
gpa.free(err_msg.notes);
gpa.free(err_msg.msg);
err_msg.* = undefined;
}
};
pub const AstGenSrc = union(enum) {
root,
import: struct {
importing_file: Zcu.File.Index,
import_tok: std.zig.Ast.TokenIndex,
},
};
/// Canonical reference to a position within a source file.
pub const SrcLoc = struct {
file_scope: *File,
base_node: Ast.Node.Index,
/// Relative to `base_node`.
lazy: LazySrcLoc.Offset,
pub fn baseSrcToken(src_loc: SrcLoc) Ast.TokenIndex {
const tree = src_loc.file_scope.tree.?;
return tree.firstToken(src_loc.base_node);
}
pub const Span = Ast.Span;
pub fn span(src_loc: SrcLoc, zcu: *const Zcu) !Span {
switch (src_loc.lazy) {
.unneeded => unreachable,
.byte_abs => |byte_index| return Span{ .start = byte_index, .end = byte_index + 1, .main = byte_index },
.token_abs => |tok_index| {
const tree = try src_loc.file_scope.getTree(zcu);
const start = tree.tokenStart(tok_index);
const end = start + @as(u32, @intCast(tree.tokenSlice(tok_index).len));
return Span{ .start = start, .end = end, .main = start };
},
.node_abs => |node| {
const tree = try src_loc.file_scope.getTree(zcu);
return tree.nodeToSpan(node);
},
.byte_offset => |byte_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const tok_index = src_loc.baseSrcToken();
const start = tree.tokenStart(tok_index) + byte_off;
const end = start + @as(u32, @intCast(tree.tokenSlice(tok_index).len));
return Span{ .start = start, .end = end, .main = start };
},
.token_offset => |tok_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const tok_index = tok_off.toAbsolute(src_loc.baseSrcToken());
const start = tree.tokenStart(tok_index);
const end = start + @as(u32, @intCast(tree.tokenSlice(tok_index).len));
return Span{ .start = start, .end = end, .main = start };
},
.node_offset => |traced_off| {
const node_off = traced_off.x;
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(node);
},
.node_offset_main_token => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const main_token = tree.nodeMainToken(node);
return tree.tokensToSpan(main_token, main_token, main_token);
},
.node_offset_bin_op => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(node);
},
.node_offset_initializer => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.tokensToSpan(
tree.firstToken(node) - 3,
tree.lastToken(node),
tree.nodeMainToken(node) - 2,
);
},
.node_offset_var_decl_ty => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const full = switch (tree.nodeTag(node)) {
.global_var_decl,
.local_var_decl,
.simple_var_decl,
.aligned_var_decl,
=> tree.fullVarDecl(node).?,
else => unreachable,
};
if (full.ast.type_node.unwrap()) |type_node| {
return tree.nodeToSpan(type_node);
}
const tok_index = full.ast.mut_token + 1; // the name token
const start = tree.tokenStart(tok_index);
const end = start + @as(u32, @intCast(tree.tokenSlice(tok_index).len));
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_var_decl_align => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const align_node = if (tree.fullVarDecl(node)) |v|
v.ast.align_node.unwrap().?
else if (tree.fullFnProto(&buf, node)) |f|
f.ast.align_expr.unwrap().?
else
unreachable;
return tree.nodeToSpan(align_node);
},
.node_offset_var_decl_section => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const section_node = if (tree.fullVarDecl(node)) |v|
v.ast.section_node.unwrap().?
else if (tree.fullFnProto(&buf, node)) |f|
f.ast.section_expr.unwrap().?
else
unreachable;
return tree.nodeToSpan(section_node);
},
.node_offset_var_decl_addrspace => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const addrspace_node = if (tree.fullVarDecl(node)) |v|
v.ast.addrspace_node.unwrap().?
else if (tree.fullFnProto(&buf, node)) |f|
f.ast.addrspace_expr.unwrap().?
else
unreachable;
return tree.nodeToSpan(addrspace_node);
},
.node_offset_var_decl_init => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const init_node = switch (tree.nodeTag(node)) {
.global_var_decl,
.local_var_decl,
.aligned_var_decl,
.simple_var_decl,
=> tree.fullVarDecl(node).?.ast.init_node.unwrap().?,
.assign_destructure => tree.assignDestructure(node).ast.value_expr,
else => unreachable,
};
return tree.nodeToSpan(init_node);
},
.node_offset_builtin_call_arg => |builtin_arg| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = builtin_arg.builtin_call_node.toAbsolute(src_loc.base_node);
var buf: [2]Ast.Node.Index = undefined;
const params = tree.builtinCallParams(&buf, node).?;
return tree.nodeToSpan(params[builtin_arg.arg_index]);
},
.node_offset_ptrcast_operand => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
var node = node_off.toAbsolute(src_loc.base_node);
while (true) {
switch (tree.nodeTag(node)) {
.builtin_call_two, .builtin_call_two_comma => {},
else => break,
}
const first_arg, const second_arg = tree.nodeData(node).opt_node_and_opt_node;
if (first_arg == .none) break; // 0 args
if (second_arg != .none) break; // 2 args
const builtin_token = tree.nodeMainToken(node);
const builtin_name = tree.tokenSlice(builtin_token);
const info = BuiltinFn.list.get(builtin_name) orelse break;
switch (info.tag) {
else => break,
.ptr_cast,
.align_cast,
.addrspace_cast,
.const_cast,
.volatile_cast,
=> {},
}
node = first_arg.unwrap().?;
}
return tree.nodeToSpan(node);
},
.node_offset_array_access_index => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(tree.nodeData(node).node_and_node[1]);
},
.node_offset_slice_ptr,
.node_offset_slice_start,
.node_offset_slice_end,
.node_offset_slice_sentinel,
=> |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullSlice(node).?;
const part_node = switch (src_loc.lazy) {
.node_offset_slice_ptr => full.ast.sliced,
.node_offset_slice_start => full.ast.start,
.node_offset_slice_end => full.ast.end.unwrap().?,
.node_offset_slice_sentinel => full.ast.sentinel.unwrap().?,
else => unreachable,
};
return tree.nodeToSpan(part_node);
},
.node_offset_call_func => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullCall(&buf, node).?;
return tree.nodeToSpan(full.ast.fn_expr);
},
.node_offset_field_name => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const tok_index = switch (tree.nodeTag(node)) {
.field_access => tree.nodeData(node).node_and_token[1],
.call_one,
.call_one_comma,
.call,
.call_comma,
=> blk: {
const full = tree.fullCall(&buf, node).?;
break :blk tree.lastToken(full.ast.fn_expr);
},
else => tree.firstToken(node) - 2,
};
const start = tree.tokenStart(tok_index);
const end = start + @as(u32, @intCast(tree.tokenSlice(tok_index).len));
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_field_name_init => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const tok_index = tree.firstToken(node) - 2;
const start = tree.tokenStart(tok_index);
const end = start + @as(u32, @intCast(tree.tokenSlice(tok_index).len));
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_deref_ptr => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(node);
},
.node_offset_asm_source => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullAsm(node).?;
return tree.nodeToSpan(full.ast.template);
},
.node_offset_asm_ret_ty => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullAsm(node).?;
const asm_output = full.outputs[0];
return tree.nodeToSpan(tree.nodeData(asm_output).opt_node_and_token[0].unwrap().?);
},
.node_offset_if_cond => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const src_node = switch (tree.nodeTag(node)) {
.if_simple,
.@"if",
=> tree.fullIf(node).?.ast.cond_expr,
.while_simple,
.while_cont,
.@"while",
=> tree.fullWhile(node).?.ast.cond_expr,
.for_simple,
.@"for",
=> {
const inputs = tree.fullFor(node).?.ast.inputs;
const start = tree.firstToken(inputs[0]);
const end = tree.lastToken(inputs[inputs.len - 1]);
return tree.tokensToSpan(start, end, start);
},
.@"orelse" => node,
.@"catch" => node,
else => unreachable,
};
return tree.nodeToSpan(src_node);
},
.for_input => |for_input| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = for_input.for_node_offset.toAbsolute(src_loc.base_node);
const for_full = tree.fullFor(node).?;
const src_node = for_full.ast.inputs[for_input.input_index];
return tree.nodeToSpan(src_node);
},
.for_capture_from_input => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const input_node = node_off.toAbsolute(src_loc.base_node);
// We have to actually linear scan the whole AST to find the for loop
// that contains this input.
const node_tags = tree.nodes.items(.tag);
for (node_tags, 0..) |node_tag, node_usize| {
const node: Ast.Node.Index = @enumFromInt(node_usize);
switch (node_tag) {
.for_simple, .@"for" => {
const for_full = tree.fullFor(node).?;
for (for_full.ast.inputs, 0..) |input, input_index| {
if (input_node == input) {
var count = input_index;
var tok = for_full.payload_token;
while (true) {
switch (tree.tokenTag(tok)) {
.comma => {
count -= 1;
tok += 1;
},
.identifier => {
if (count == 0)
return tree.tokensToSpan(tok, tok + 1, tok);
tok += 1;
},
.asterisk => {
if (count == 0)
return tree.tokensToSpan(tok, tok + 2, tok);
tok += 1;
},
else => unreachable,
}
}
}
}
},
else => continue,
}
} else unreachable;
},
.call_arg => |call_arg| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = call_arg.call_node_offset.toAbsolute(src_loc.base_node);
var buf: [2]Ast.Node.Index = undefined;
const call_full = tree.fullCall(buf[0..1], node) orelse {
assert(tree.nodeTag(node) == .builtin_call);
const call_args_node: Ast.Node.Index = @enumFromInt(tree.extra_data[@intFromEnum(tree.nodeData(node).extra_range.end) - 1]);
switch (tree.nodeTag(call_args_node)) {
.array_init_one,
.array_init_one_comma,
.array_init_dot_two,
.array_init_dot_two_comma,
.array_init_dot,
.array_init_dot_comma,
.array_init,
.array_init_comma,
=> {
const full = tree.fullArrayInit(&buf, call_args_node).?.ast.elements;
return tree.nodeToSpan(full[call_arg.arg_index]);
},
.struct_init_one,
.struct_init_one_comma,
.struct_init_dot_two,
.struct_init_dot_two_comma,
.struct_init_dot,
.struct_init_dot_comma,
.struct_init,
.struct_init_comma,
=> {
const full = tree.fullStructInit(&buf, call_args_node).?.ast.fields;
return tree.nodeToSpan(full[call_arg.arg_index]);
},
else => return tree.nodeToSpan(call_args_node),
}
};
return tree.nodeToSpan(call_full.ast.params[call_arg.arg_index]);
},
.fn_proto_param, .fn_proto_param_type => |fn_proto_param| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = fn_proto_param.fn_proto_node_offset.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
var it = full.iterate(tree);
var i: usize = 0;
while (it.next()) |param| : (i += 1) {
if (i != fn_proto_param.param_index) continue;
switch (src_loc.lazy) {
.fn_proto_param_type => if (param.anytype_ellipsis3) |tok| {
return tree.tokenToSpan(tok);
} else {
return tree.nodeToSpan(param.type_expr.?);
},
.fn_proto_param => if (param.anytype_ellipsis3) |tok| {
const first = param.comptime_noalias orelse param.name_token orelse tok;
return tree.tokensToSpan(first, tok, first);
} else {
const first = param.comptime_noalias orelse param.name_token orelse tree.firstToken(param.type_expr.?);
return tree.tokensToSpan(first, tree.lastToken(param.type_expr.?), first);
},
else => unreachable,
}
}
unreachable;
},
.node_offset_bin_lhs => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(tree.nodeData(node).node_and_node[0]);
},
.node_offset_bin_rhs => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(tree.nodeData(node).node_and_node[1]);
},
.array_cat_lhs, .array_cat_rhs => |cat| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = cat.array_cat_offset.toAbsolute(src_loc.base_node);
const arr_node = if (src_loc.lazy == .array_cat_lhs)
tree.nodeData(node).node_and_node[0]
else
tree.nodeData(node).node_and_node[1];
var buf: [2]Ast.Node.Index = undefined;
switch (tree.nodeTag(arr_node)) {
.array_init_one,
.array_init_one_comma,
.array_init_dot_two,
.array_init_dot_two_comma,
.array_init_dot,
.array_init_dot_comma,
.array_init,
.array_init_comma,
=> {
const full = tree.fullArrayInit(&buf, arr_node).?.ast.elements;
return tree.nodeToSpan(full[cat.elem_index]);
},
else => return tree.nodeToSpan(arr_node),
}
},
.node_offset_try_operand => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(tree.nodeData(node).node);
},
.node_offset_switch_operand => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
const condition, _ = tree.nodeData(node).node_and_extra;
return tree.nodeToSpan(condition);
},
.node_offset_switch_else_prong => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const switch_node = node_off.toAbsolute(src_loc.base_node);
_, const extra_index = tree.nodeData(switch_node).node_and_extra;
const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
if (case.ast.values.len == 0) {
return tree.nodeToSpan(case_node);
}
} else unreachable;
},
.node_offset_switch_under_prong => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const switch_node = node_off.toAbsolute(src_loc.base_node);
_, const extra_index = tree.nodeData(switch_node).node_and_extra;
const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
for (case.ast.values) |val| {
if (tree.nodeTag(val) == .identifier and
mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val)), "_"))
{
return tree.tokensToSpan(
tree.firstToken(case_node),
tree.lastToken(case_node),
tree.nodeMainToken(val),
);
}
}
} else unreachable;
},
.node_offset_switch_range => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const switch_node = node_off.toAbsolute(src_loc.base_node);
_, const extra_index = tree.nodeData(switch_node).node_and_extra;
const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
for (case.ast.values) |item_node| {
if (tree.nodeTag(item_node) == .switch_range) {
return tree.nodeToSpan(item_node);
}
}
} else unreachable;
},
.node_offset_fn_type_align => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return tree.nodeToSpan(full.ast.align_expr.unwrap().?);
},
.node_offset_fn_type_addrspace => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return tree.nodeToSpan(full.ast.addrspace_expr.unwrap().?);
},
.node_offset_fn_type_section => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return tree.nodeToSpan(full.ast.section_expr.unwrap().?);
},
.node_offset_fn_type_cc => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return tree.nodeToSpan(full.ast.callconv_expr.unwrap().?);
},
.node_offset_fn_type_ret_ty => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, node).?;
return tree.nodeToSpan(full.ast.return_type.unwrap().?);
},
.node_offset_param => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
var first_tok = tree.firstToken(node);
while (true) switch (tree.tokenTag(first_tok - 1)) {
.colon, .identifier, .keyword_comptime, .keyword_noalias => first_tok -= 1,
else => break,
};
return tree.tokensToSpan(
first_tok,
tree.lastToken(node),
first_tok,
);
},
.token_offset_param => |token_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const main_token = tree.nodeMainToken(src_loc.base_node);
const tok_index = token_off.toAbsolute(main_token);
var first_tok = tok_index;
while (true) switch (tree.tokenTag(first_tok - 1)) {
.colon, .identifier, .keyword_comptime, .keyword_noalias => first_tok -= 1,
else => break,
};
return tree.tokensToSpan(
first_tok,
tok_index,
first_tok,
);
},
.node_offset_anyframe_type => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
_, const child_type = tree.nodeData(parent_node).token_and_node;
return tree.nodeToSpan(child_type);
},
.node_offset_lib_name => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, parent_node).?;
const tok_index = full.lib_name.?;
const start = tree.tokenStart(tok_index);
const end = start + @as(u32, @intCast(tree.tokenSlice(tok_index).len));
return Span{ .start = start, .end = end, .main = start };
},
.node_offset_array_type_len => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullArrayType(parent_node).?;
return tree.nodeToSpan(full.ast.elem_count);
},
.node_offset_array_type_sentinel => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullArrayType(parent_node).?;
return tree.nodeToSpan(full.ast.sentinel.unwrap().?);
},
.node_offset_array_type_elem => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullArrayType(parent_node).?;
return tree.nodeToSpan(full.ast.elem_type);
},
.node_offset_un_op => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
return tree.nodeToSpan(tree.nodeData(node).node);
},
.node_offset_ptr_elem => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullPtrType(parent_node).?;
return tree.nodeToSpan(full.ast.child_type);
},
.node_offset_ptr_sentinel => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullPtrType(parent_node).?;
return tree.nodeToSpan(full.ast.sentinel.unwrap().?);
},
.node_offset_ptr_align => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullPtrType(parent_node).?;
return tree.nodeToSpan(full.ast.align_node.unwrap().?);
},
.node_offset_ptr_addrspace => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullPtrType(parent_node).?;
return tree.nodeToSpan(full.ast.addrspace_node.unwrap().?);
},
.node_offset_ptr_bitoffset => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullPtrType(parent_node).?;
return tree.nodeToSpan(full.ast.bit_range_start.unwrap().?);
},
.node_offset_ptr_hostsize => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full = tree.fullPtrType(parent_node).?;
return tree.nodeToSpan(full.ast.bit_range_end.unwrap().?);
},
.node_offset_container_tag => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
switch (tree.nodeTag(parent_node)) {
.container_decl_arg, .container_decl_arg_trailing => {
const full = tree.containerDeclArg(parent_node);
const arg_node = full.ast.arg.unwrap().?;
return tree.nodeToSpan(arg_node);
},
.tagged_union_enum_tag, .tagged_union_enum_tag_trailing => {
const full = tree.taggedUnionEnumTag(parent_node);
const arg_node = full.ast.arg.unwrap().?;
return tree.tokensToSpan(
tree.firstToken(arg_node) - 2,
tree.lastToken(arg_node) + 1,
tree.nodeMainToken(arg_node),
);
},
else => unreachable,
}
},
.node_offset_field_default => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
const full: Ast.full.ContainerField = switch (tree.nodeTag(parent_node)) {
.container_field => tree.containerField(parent_node),
.container_field_init => tree.containerFieldInit(parent_node),
else => unreachable,
};
return tree.nodeToSpan(full.ast.value_expr.unwrap().?);
},
.node_offset_init_ty => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const parent_node = node_off.toAbsolute(src_loc.base_node);
var buf: [2]Ast.Node.Index = undefined;
const type_expr = if (tree.fullArrayInit(&buf, parent_node)) |array_init|
array_init.ast.type_expr.unwrap().?
else
tree.fullStructInit(&buf, parent_node).?.ast.type_expr.unwrap().?;
return tree.nodeToSpan(type_expr);
},
.node_offset_store_ptr => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
switch (tree.nodeTag(node)) {
.assign,
.assign_mul,
.assign_div,
.assign_mod,
.assign_add,
.assign_sub,
.assign_shl,
.assign_shl_sat,
.assign_shr,
.assign_bit_and,
.assign_bit_xor,
.assign_bit_or,
.assign_mul_wrap,
.assign_add_wrap,
.assign_sub_wrap,
.assign_mul_sat,
.assign_add_sat,
.assign_sub_sat,
=> return tree.nodeToSpan(tree.nodeData(node).node_and_node[0]),
else => return tree.nodeToSpan(node),
}
},
.node_offset_store_operand => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
switch (tree.nodeTag(node)) {
.assign,
.assign_mul,
.assign_div,
.assign_mod,
.assign_add,
.assign_sub,
.assign_shl,
.assign_shl_sat,
.assign_shr,
.assign_bit_and,
.assign_bit_xor,
.assign_bit_or,
.assign_mul_wrap,
.assign_add_wrap,
.assign_sub_wrap,
.assign_mul_sat,
.assign_add_sat,
.assign_sub_sat,
=> return tree.nodeToSpan(tree.nodeData(node).node_and_node[1]),
else => return tree.nodeToSpan(node),
}
},
.node_offset_return_operand => |node_off| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = node_off.toAbsolute(src_loc.base_node);
if (tree.nodeTag(node) == .@"return") {
if (tree.nodeData(node).opt_node.unwrap()) |lhs| {
return tree.nodeToSpan(lhs);
}
}
return tree.nodeToSpan(node);
},
.container_field_name,
.container_field_value,
.container_field_type,
.container_field_align,
=> |field_idx| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = src_loc.base_node;
var buf: [2]Ast.Node.Index = undefined;
const container_decl = tree.fullContainerDecl(&buf, node) orelse
return tree.nodeToSpan(node);
var cur_field_idx: usize = 0;
for (container_decl.ast.members) |member_node| {
const field = tree.fullContainerField(member_node) orelse continue;
if (cur_field_idx < field_idx) {
cur_field_idx += 1;
continue;
}
const field_component_node = switch (src_loc.lazy) {
.container_field_name => .none,
.container_field_value => field.ast.value_expr,
.container_field_type => field.ast.type_expr,
.container_field_align => field.ast.align_expr,
else => unreachable,
};
if (field_component_node.unwrap()) |component_node| {
return tree.nodeToSpan(component_node);
} else {
return tree.tokenToSpan(field.ast.main_token);
}
} else unreachable;
},
.tuple_field_type, .tuple_field_init => |field_info| {
const tree = try src_loc.file_scope.getTree(zcu);
const node = src_loc.base_node;
var buf: [2]Ast.Node.Index = undefined;
const container_decl = tree.fullContainerDecl(&buf, node) orelse
return tree.nodeToSpan(node);
const field = tree.fullContainerField(container_decl.ast.members[field_info.elem_index]).?;
return tree.nodeToSpan(switch (src_loc.lazy) {
.tuple_field_type => field.ast.type_expr.unwrap().?,
.tuple_field_init => field.ast.value_expr.unwrap().?,
else => unreachable,
});
},
.init_elem => |init_elem| {
const tree = try src_loc.file_scope.getTree(zcu);
const init_node = init_elem.init_node_offset.toAbsolute(src_loc.base_node);
var buf: [2]Ast.Node.Index = undefined;
if (tree.fullArrayInit(&buf, init_node)) |full| {
const elem_node = full.ast.elements[init_elem.elem_index];
return tree.nodeToSpan(elem_node);
} else if (tree.fullStructInit(&buf, init_node)) |full| {
const field_node = full.ast.fields[init_elem.elem_index];
return tree.tokensToSpan(
tree.firstToken(field_node) - 3,
tree.lastToken(field_node),
tree.nodeMainToken(field_node) - 2,
);
} else unreachable;
},
.init_field_name,
.init_field_linkage,
.init_field_section,
.init_field_visibility,
.init_field_rw,
.init_field_locality,
.init_field_cache,
.init_field_library,
.init_field_thread_local,
.init_field_dll_import,
.init_field_relocation,
=> |builtin_call_node| {
const wanted = switch (src_loc.lazy) {
.init_field_name => "name",
.init_field_linkage => "linkage",
.init_field_section => "section",
.init_field_visibility => "visibility",
.init_field_rw => "rw",
.init_field_locality => "locality",
.init_field_cache => "cache",
.init_field_library => "library",
.init_field_thread_local => "thread_local",
.init_field_dll_import => "dll_import",
.init_field_relocation => "relocation",
else => unreachable,
};
const tree = try src_loc.file_scope.getTree(zcu);
const node = builtin_call_node.toAbsolute(src_loc.base_node);
var builtin_buf: [2]Ast.Node.Index = undefined;
const args = tree.builtinCallParams(&builtin_buf, node).?;
const arg_node = args[1];
var buf: [2]Ast.Node.Index = undefined;
const full = tree.fullStructInit(&buf, arg_node) orelse
return tree.nodeToSpan(arg_node);
for (full.ast.fields) |field_node| {
// . IDENTIFIER = field_node
const name_token = tree.firstToken(field_node) - 2;
const name = tree.tokenSlice(name_token);
if (std.mem.eql(u8, name, wanted)) {
return tree.tokensToSpan(
name_token - 1,
tree.lastToken(field_node),
tree.nodeMainToken(field_node) - 2,
);
}
}
return tree.nodeToSpan(arg_node);
},
.switch_case_item,
.switch_case_item_range_first,
.switch_case_item_range_last,
.switch_capture,
.switch_tag_capture,
=> {
const switch_node_offset, const want_case_idx = switch (src_loc.lazy) {
.switch_case_item,
.switch_case_item_range_first,
.switch_case_item_range_last,
=> |x| .{ x.switch_node_offset, x.case_idx },
.switch_capture,
.switch_tag_capture,
=> |x| .{ x.switch_node_offset, x.case_idx },
else => unreachable,
};
const tree = try src_loc.file_scope.getTree(zcu);
const switch_node = switch_node_offset.toAbsolute(src_loc.base_node);
_, const extra_index = tree.nodeData(switch_node).node_and_extra;
const case_nodes = tree.extraDataSlice(tree.extraData(extra_index, Ast.Node.SubRange), Ast.Node.Index);
var multi_i: u32 = 0;
var scalar_i: u32 = 0;
var underscore_node: Ast.Node.OptionalIndex = .none;
const case = case: for (case_nodes) |case_node| {
const case = tree.fullSwitchCase(case_node).?;
if (case.ast.values.len == 0) {
if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_else) {
break :case case;
}
continue :case;
}
if (underscore_node == .none) for (case.ast.values) |val_node| {
if (tree.nodeTag(val_node) == .identifier and
mem.eql(u8, tree.tokenSlice(tree.nodeMainToken(val_node)), "_"))
{
underscore_node = val_node.toOptional();
if (want_case_idx == LazySrcLoc.Offset.SwitchCaseIndex.special_under) {
break :case case;
}
continue :case;
}
};
const is_multi = case.ast.values.len != 1 or
tree.nodeTag(case.ast.values[0]) == .switch_range;
switch (want_case_idx.kind) {
.scalar => if (!is_multi and want_case_idx.index == scalar_i)
break :case case,
.multi => if (is_multi and want_case_idx.index == multi_i)
break :case case,
}
if (is_multi) {
multi_i += 1;
} else {
scalar_i += 1;
}
} else unreachable;
const want_item = switch (src_loc.lazy) {
.switch_case_item,
.switch_case_item_range_first,
.switch_case_item_range_last,
=> |x| item_idx: {
assert(want_case_idx != LazySrcLoc.Offset.SwitchCaseIndex.special_else);
break :item_idx x.item_idx;
},
.switch_capture, .switch_tag_capture => {
const start = switch (src_loc.lazy) {
.switch_capture => case.payload_token.?,
.switch_tag_capture => tok: {
var tok = case.payload_token.?;
if (tree.tokenTag(tok) == .asterisk) tok += 1;
tok = tok + 2; // skip over comma
break :tok tok;
},
else => unreachable,
};
const end = switch (tree.tokenTag(start)) {
.asterisk => start + 1,
else => start,
};
return tree.tokensToSpan(start, end, start);
},
else => unreachable,
};
switch (want_item.kind) {
.single => {
var item_i: u32 = 0;
for (case.ast.values) |item_node| {
if (item_node.toOptional() == underscore_node or
tree.nodeTag(item_node) == .switch_range)
{
continue;
}
if (item_i != want_item.index) {
item_i += 1;
continue;
}
return tree.nodeToSpan(item_node);
} else unreachable;
},
.range => {
var range_i: u32 = 0;
for (case.ast.values) |item_node| {
if (tree.nodeTag(item_node) != .switch_range) {
continue;
}
if (range_i != want_item.index) {
range_i += 1;
continue;
}
const first, const last = tree.nodeData(item_node).node_and_node;
return switch (src_loc.lazy) {
.switch_case_item => tree.nodeToSpan(item_node),
.switch_case_item_range_first => tree.nodeToSpan(first),
.switch_case_item_range_last => tree.nodeToSpan(last),
else => unreachable,
};
} else unreachable;
},
}
},
.func_decl_param_comptime => |param_idx| {
const tree = try src_loc.file_scope.getTree(zcu);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, src_loc.base_node).?;
var param_it = full.iterate(tree);
for (0..param_idx) |_| assert(param_it.next() != null);
const param = param_it.next().?;
return tree.tokenToSpan(param.comptime_noalias.?);
},
.func_decl_param_ty => |param_idx| {
const tree = try src_loc.file_scope.getTree(zcu);
var buf: [1]Ast.Node.Index = undefined;
const full = tree.fullFnProto(&buf, src_loc.base_node).?;
var param_it = full.iterate(tree);
for (0..param_idx) |_| assert(param_it.next() != null);
const param = param_it.next().?;
return tree.nodeToSpan(param.type_expr.?);
},
}
}
};
pub const LazySrcLoc = struct {
/// This instruction provides the source node locations are resolved relative to.
/// It is a `declaration`, `struct_decl`, `union_decl`, `enum_decl`, or `opaque_decl`.
/// This must be valid even if `relative` is an absolute value, since it is required to
/// determine the file which the `LazySrcLoc` refers to.
base_node_inst: InternPool.TrackedInst.Index,
/// This field determines the source location relative to `base_node_inst`.
offset: Offset,
pub const Offset = union(enum) {
/// When this tag is set, the code that constructed this `LazySrcLoc` is asserting
/// that all code paths which would need to resolve the source location are
/// unreachable. If you are debugging this tag incorrectly being this value,
/// look into using reverse-continue with a memory watchpoint to see where the
/// value is being set to this tag.
/// `base_node_inst` is unused.
unneeded,
/// The source location points to a byte offset within a source file,
/// offset from 0. The source file is determined contextually.
byte_abs: u32,
/// The source location points to a token within a source file,
/// offset from 0. The source file is determined contextually.
token_abs: Ast.TokenIndex,
/// The source location points to an AST node within a source file,
/// offset from 0. The source file is determined contextually.
node_abs: Ast.Node.Index,
/// The source location points to a byte offset within a source file,
/// offset from the byte offset of the base node within the file.
byte_offset: u32,
/// This data is the offset into the token list from the base node's first token.
token_offset: Ast.TokenOffset,
/// The source location points to an AST node, which is this value offset
/// from its containing base node AST index.
node_offset: TracedOffset,
/// The source location points to the main token of an AST node, found
/// by taking this AST node index offset from the containing base node.
node_offset_main_token: Ast.Node.Offset,
/// The source location points to the beginning of a struct initializer.
node_offset_initializer: Ast.Node.Offset,
/// The source location points to a variable declaration type expression,
/// found by taking this AST node index offset from the containing
/// base node, which points to a variable declaration AST node. Next, navigate
/// to the type expression.
node_offset_var_decl_ty: Ast.Node.Offset,
/// The source location points to the alignment expression of a var decl.
node_offset_var_decl_align: Ast.Node.Offset,
/// The source location points to the linksection expression of a var decl.
node_offset_var_decl_section: Ast.Node.Offset,
/// The source location points to the addrspace expression of a var decl.
node_offset_var_decl_addrspace: Ast.Node.Offset,
/// The source location points to the initializer of a var decl.
node_offset_var_decl_init: Ast.Node.Offset,
/// The source location points to the given argument of a builtin function call.
/// `builtin_call_node` points to the builtin call.
/// `arg_index` is the index of the argument which hte source location refers to.
node_offset_builtin_call_arg: struct {
builtin_call_node: Ast.Node.Offset,
arg_index: u32,
},
/// Like `node_offset_builtin_call_arg` but recurses through arbitrarily many calls
/// to pointer cast builtins (taking the first argument of the most nested).
node_offset_ptrcast_operand: Ast.Node.Offset,
/// The source location points to the index expression of an array access
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to an array access AST node. Next, navigate
/// to the index expression.
node_offset_array_access_index: Ast.Node.Offset,
/// The source location points to the LHS of a slice expression
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
node_offset_slice_ptr: Ast.Node.Offset,
/// The source location points to start expression of a slice expression
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
node_offset_slice_start: Ast.Node.Offset,
/// The source location points to the end expression of a slice
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
node_offset_slice_end: Ast.Node.Offset,
/// The source location points to the sentinel expression of a slice
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a slice AST node. Next, navigate
/// to the sentinel expression.
node_offset_slice_sentinel: Ast.Node.Offset,
/// The source location points to the callee expression of a function
/// call expression, found by taking this AST node index offset from the containing
/// base node, which points to a function call AST node. Next, navigate
/// to the callee expression.
node_offset_call_func: Ast.Node.Offset,
/// The payload is offset from the containing base node.
/// The source location points to the field name of:
/// * a field access expression (`a.b`), or
/// * the callee of a method call (`a.b()`)
node_offset_field_name: Ast.Node.Offset,
/// The payload is offset from the containing base node.
/// The source location points to the field name of the operand ("b" node)
/// of a field initialization expression (`.a = b`)
node_offset_field_name_init: Ast.Node.Offset,
/// The source location points to the pointer of a pointer deref expression,
/// found by taking this AST node index offset from the containing
/// base node, which points to a pointer deref AST node. Next, navigate
/// to the pointer expression.
node_offset_deref_ptr: Ast.Node.Offset,
/// The source location points to the assembly source code of an inline assembly
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to inline assembly AST node. Next, navigate
/// to the asm template source code.
node_offset_asm_source: Ast.Node.Offset,
/// The source location points to the return type of an inline assembly
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to inline assembly AST node. Next, navigate
/// to the return type expression.
node_offset_asm_ret_ty: Ast.Node.Offset,
/// The source location points to the condition expression of an if
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to an if expression AST node. Next, navigate
/// to the condition expression.
node_offset_if_cond: Ast.Node.Offset,
/// The source location points to a binary expression, such as `a + b`, found
/// by taking this AST node index offset from the containing base node.
node_offset_bin_op: Ast.Node.Offset,
/// The source location points to the LHS of a binary expression, found
/// by taking this AST node index offset from the containing base node,
/// which points to a binary expression AST node. Next, navigate to the LHS.
node_offset_bin_lhs: Ast.Node.Offset,
/// The source location points to the RHS of a binary expression, found
/// by taking this AST node index offset from the containing base node,
/// which points to a binary expression AST node. Next, navigate to the RHS.
node_offset_bin_rhs: Ast.Node.Offset,
/// The source location points to the operand of a try expression, found
/// by taking this AST node index offset from the containing base node,
/// which points to a try expression AST node. Next, navigate to the
/// operand expression.
node_offset_try_operand: Ast.Node.Offset,
/// The source location points to the operand of a switch expression, found
/// by taking this AST node index offset from the containing base node,
/// which points to a switch expression AST node. Next, navigate to the operand.
node_offset_switch_operand: Ast.Node.Offset,
/// The source location points to the else prong of a switch expression, found
/// by taking this AST node index offset from the containing base node,
/// which points to a switch expression AST node. Next, navigate to the else prong.
node_offset_switch_else_prong: Ast.Node.Offset,
/// The source location points to the `_` prong of a switch expression, found
/// by taking this AST node index offset from the containing base node,
/// which points to a switch expression AST node. Next, navigate to the `_` prong.
node_offset_switch_under_prong: Ast.Node.Offset,
/// The source location points to all the ranges of a switch expression, found
/// by taking this AST node index offset from the containing base node,
/// which points to a switch expression AST node. Next, navigate to any of the
/// range nodes. The error applies to all of them.
node_offset_switch_range: Ast.Node.Offset,
/// The source location points to the align expr of a function type
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
node_offset_fn_type_align: Ast.Node.Offset,
/// The source location points to the addrspace expr of a function type
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
node_offset_fn_type_addrspace: Ast.Node.Offset,
/// The source location points to the linksection expr of a function type
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
node_offset_fn_type_section: Ast.Node.Offset,
/// The source location points to the calling convention of a function type
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a function type AST node. Next, navigate to
/// the calling convention node.
node_offset_fn_type_cc: Ast.Node.Offset,
/// The source location points to the return type of a function type
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a function type AST node. Next, navigate to
/// the return type node.
node_offset_fn_type_ret_ty: Ast.Node.Offset,
node_offset_param: Ast.Node.Offset,
token_offset_param: Ast.TokenOffset,
/// The source location points to the type expression of an `anyframe->T`
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to a `anyframe->T` expression AST node. Next, navigate
/// to the type expression.
node_offset_anyframe_type: Ast.Node.Offset,
/// The source location points to the string literal of `extern "foo"`, found
/// by taking this AST node index offset from the containing
/// base node, which points to a function prototype or variable declaration
/// expression AST node. Next, navigate to the string literal of the `extern "foo"`.
node_offset_lib_name: Ast.Node.Offset,
/// The source location points to the len expression of an `[N:S]T`
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to an `[N:S]T` expression AST node. Next, navigate
/// to the len expression.
node_offset_array_type_len: Ast.Node.Offset,
/// The source location points to the sentinel expression of an `[N:S]T`
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to an `[N:S]T` expression AST node. Next, navigate
/// to the sentinel expression.
node_offset_array_type_sentinel: Ast.Node.Offset,
/// The source location points to the elem expression of an `[N:S]T`
/// expression, found by taking this AST node index offset from the containing
/// base node, which points to an `[N:S]T` expression AST node. Next, navigate
/// to the elem expression.
node_offset_array_type_elem: Ast.Node.Offset,
/// The source location points to the operand of an unary expression.
node_offset_un_op: Ast.Node.Offset,
/// The source location points to the elem type of a pointer.
node_offset_ptr_elem: Ast.Node.Offset,
/// The source location points to the sentinel of a pointer.
node_offset_ptr_sentinel: Ast.Node.Offset,
/// The source location points to the align expr of a pointer.
node_offset_ptr_align: Ast.Node.Offset,
/// The source location points to the addrspace expr of a pointer.
node_offset_ptr_addrspace: Ast.Node.Offset,
/// The source location points to the bit-offset of a pointer.
node_offset_ptr_bitoffset: Ast.Node.Offset,
/// The source location points to the host size of a pointer.
node_offset_ptr_hostsize: Ast.Node.Offset,
/// The source location points to the tag type of an union or an enum.
node_offset_container_tag: Ast.Node.Offset,
/// The source location points to the default value of a field.
node_offset_field_default: Ast.Node.Offset,
/// The source location points to the type of an array or struct initializer.
node_offset_init_ty: Ast.Node.Offset,
/// The source location points to the LHS of an assignment (or assign-op, e.g. `+=`).
node_offset_store_ptr: Ast.Node.Offset,
/// The source location points to the RHS of an assignment (or assign-op, e.g. `+=`).
node_offset_store_operand: Ast.Node.Offset,
/// The source location points to the operand of a `return` statement, or
/// the `return` itself if there is no explicit operand.
node_offset_return_operand: Ast.Node.Offset,
/// The source location points to a for loop input.
for_input: struct {
/// Points to the for loop AST node.
for_node_offset: Ast.Node.Offset,
/// Picks one of the inputs from the condition.
input_index: u32,
},
/// The source location points to one of the captures of a for loop, found
/// by taking this AST node index offset from the containing
/// base node, which points to one of the input nodes of a for loop.
/// Next, navigate to the corresponding capture.
for_capture_from_input: Ast.Node.Offset,
/// The source location points to the argument node of a function call.
call_arg: struct {
/// Points to the function call AST node.
call_node_offset: Ast.Node.Offset,
/// The index of the argument the source location points to.
arg_index: u32,
},
fn_proto_param: FnProtoParam,
fn_proto_param_type: FnProtoParam,
array_cat_lhs: ArrayCat,
array_cat_rhs: ArrayCat,
/// The source location points to the name of the field at the given index
/// of the container type declaration at the base node.
container_field_name: u32,
/// Like `continer_field_name`, but points at the field's default value.
container_field_value: u32,
/// Like `continer_field_name`, but points at the field's type.
container_field_type: u32,
/// Like `continer_field_name`, but points at the field's alignment.
container_field_align: u32,
/// The source location points to the type of the field at the given index
/// of the tuple type declaration at `tuple_decl_node_offset`.
tuple_field_type: TupleField,
/// The source location points to the default init of the field at the given index
/// of the tuple type declaration at `tuple_decl_node_offset`.
tuple_field_init: TupleField,
/// The source location points to the given element/field of a struct or
/// array initialization expression.
init_elem: struct {
/// Points to the AST node of the initialization expression.
init_node_offset: Ast.Node.Offset,
/// The index of the field/element the source location points to.
elem_index: u32,
},
// The following source locations are like `init_elem`, but refer to a
// field with a specific name. If such a field is not given, the entire
// initialization expression is used instead.
// The `Ast.Node.Offset` points to the AST node of a builtin call, whose *second*
// argument is the init expression.
init_field_name: Ast.Node.Offset,
init_field_linkage: Ast.Node.Offset,
init_field_section: Ast.Node.Offset,
init_field_visibility: Ast.Node.Offset,
init_field_rw: Ast.Node.Offset,
init_field_locality: Ast.Node.Offset,
init_field_cache: Ast.Node.Offset,
init_field_library: Ast.Node.Offset,
init_field_thread_local: Ast.Node.Offset,
init_field_dll_import: Ast.Node.Offset,
init_field_relocation: Ast.Node.Offset,
/// The source location points to the value of an item in a specific
/// case of a `switch`.
switch_case_item: SwitchItem,
/// The source location points to the "first" value of a range item in
/// a specific case of a `switch`.
switch_case_item_range_first: SwitchItem,
/// The source location points to the "last" value of a range item in
/// a specific case of a `switch`.
switch_case_item_range_last: SwitchItem,
/// The source location points to the main capture of a specific case of
/// a `switch`.
switch_capture: SwitchCapture,
/// The source location points to the "tag" capture (second capture) of
/// a specific case of a `switch`.
switch_tag_capture: SwitchCapture,
/// The source location points to the `comptime` token on the given comptime parameter,
/// where the base node is a function declaration. The value is the parameter index.
func_decl_param_comptime: u32,
/// The source location points to the type annotation on the given function parameter,
/// where the base node is a function declaration. The value is the parameter index.
func_decl_param_ty: u32,
pub const FnProtoParam = struct {
/// The offset of the function prototype AST node.
fn_proto_node_offset: Ast.Node.Offset,
/// The index of the parameter the source location points to.
param_index: u32,
};
pub const SwitchItem = struct {
/// The offset of the switch AST node.
switch_node_offset: Ast.Node.Offset,
/// The index of the case to point to within this switch.
case_idx: SwitchCaseIndex,
/// The index of the item to point to within this case.
item_idx: SwitchItemIndex,
};
pub const SwitchCapture = struct {
/// The offset of the switch AST node.
switch_node_offset: Ast.Node.Offset,
/// The index of the case whose capture to point to.
case_idx: SwitchCaseIndex,
};
pub const SwitchCaseIndex = packed struct(u32) {
kind: enum(u1) { scalar, multi },
index: u31,
pub const special_else: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32)));
pub const special_under: SwitchCaseIndex = @bitCast(@as(u32, std.math.maxInt(u32) - 1));
};
pub const SwitchItemIndex = packed struct(u32) {
kind: enum(u1) { single, range },
index: u31,
};
pub const ArrayCat = struct {
/// Points to the array concat AST node.
array_cat_offset: Ast.Node.Offset,
/// The index of the element the source location points to.
elem_index: u32,
};
pub const TupleField = struct {
/// Points to the AST node of the tuple type decaration.
tuple_decl_node_offset: Ast.Node.Offset,
/// The index of the tuple field the source location points to.
elem_index: u32,
};
pub const nodeOffset = if (TracedOffset.want_tracing) nodeOffsetDebug else nodeOffsetRelease;
noinline fn nodeOffsetDebug(node_offset: Ast.Node.Offset) Offset {
var result: LazySrcLoc = .{ .node_offset = .{ .x = node_offset } };
result.node_offset.trace.addAddr(@returnAddress(), "init");
return result;
}
fn nodeOffsetRelease(node_offset: Ast.Node.Offset) Offset {
return .{ .node_offset = .{ .x = node_offset } };
}
/// This wraps a simple integer in debug builds so that later on we can find out
/// where in semantic analysis the value got set.
pub const TracedOffset = struct {
x: Ast.Node.Offset,
trace: std.debug.Trace = std.debug.Trace.init,
const want_tracing = false;
};
};
pub const unneeded: LazySrcLoc = .{
.base_node_inst = undefined,
.offset = .unneeded,
};
/// Returns `null` if the ZIR instruction has been lost across incremental updates.
pub fn resolveBaseNode(base_node_inst: InternPool.TrackedInst.Index, zcu: *Zcu) ?struct { *File, Ast.Node.Index } {
comptime assert(Zir.inst_tracking_version == 0);
const ip = &zcu.intern_pool;
const file_index, const zir_inst = inst: {
const info = base_node_inst.resolveFull(ip) orelse return null;
break :inst .{ info.file, info.inst };
};
const file = zcu.fileByIndex(file_index);
// If we're relative to .main_struct_inst, we know the ast node is the root and don't need to resolve the ZIR,
// which may not exist e.g. in the case of errors in ZON files.
if (zir_inst == .main_struct_inst) return .{ file, .root };
// Otherwise, make sure ZIR is loaded.
const zir = file.zir.?;
const inst = zir.instructions.get(@intFromEnum(zir_inst));
const base_node: Ast.Node.Index = switch (inst.tag) {
.declaration => inst.data.declaration.src_node,
.struct_init, .struct_init_ref => zir.extraData(Zir.Inst.StructInit, inst.data.pl_node.payload_index).data.abs_node,
.struct_init_anon => zir.extraData(Zir.Inst.StructInitAnon, inst.data.pl_node.payload_index).data.abs_node,
.extended => switch (inst.data.extended.opcode) {
.struct_decl => zir.extraData(Zir.Inst.StructDecl, inst.data.extended.operand).data.src_node,
.union_decl => zir.extraData(Zir.Inst.UnionDecl, inst.data.extended.operand).data.src_node,
.enum_decl => zir.extraData(Zir.Inst.EnumDecl, inst.data.extended.operand).data.src_node,
.opaque_decl => zir.extraData(Zir.Inst.OpaqueDecl, inst.data.extended.operand).data.src_node,
.reify => zir.extraData(Zir.Inst.Reify, inst.data.extended.operand).data.node,
else => unreachable,
},
else => unreachable,
};
return .{ file, base_node };
}
/// Resolve the file and AST node of `base_node_inst` to get a resolved `SrcLoc`.
/// The resulting `SrcLoc` should only be used ephemerally, as it is not correct across incremental updates.
pub fn upgrade(lazy: LazySrcLoc, zcu: *Zcu) SrcLoc {
return lazy.upgradeOrLost(zcu).?;
}
/// Like `upgrade`, but returns `null` if the source location has been lost across incremental updates.
pub fn upgradeOrLost(lazy: LazySrcLoc, zcu: *Zcu) ?SrcLoc {
const file, const base_node: Ast.Node.Index = resolveBaseNode(lazy.base_node_inst, zcu) orelse return null;
return .{
.file_scope = file,
.base_node = base_node,
.lazy = lazy.offset,
};
}
/// Used to sort error messages, so that they're printed in a consistent order.
/// If an error is returned, a file could not be read in order to resolve a source location.
/// In that case, `bad_file_out` is populated, and sorting is impossible.
pub fn lessThan(lhs_lazy: LazySrcLoc, rhs_lazy: LazySrcLoc, zcu: *Zcu, bad_file_out: **Zcu.File) File.GetSourceError!bool {
const lhs_src = lhs_lazy.upgradeOrLost(zcu) orelse {
// LHS source location lost, so should never be referenced. Just sort it to the end.
return false;
};
const rhs_src = rhs_lazy.upgradeOrLost(zcu) orelse {
// RHS source location lost, so should never be referenced. Just sort it to the end.
return true;
};
if (lhs_src.file_scope != rhs_src.file_scope) {
const lhs_path = lhs_src.file_scope.path;
const rhs_path = rhs_src.file_scope.path;
if (lhs_path.root != rhs_path.root) {
return @intFromEnum(lhs_path.root) < @intFromEnum(rhs_path.root);
}
return std.mem.order(u8, lhs_path.sub_path, rhs_path.sub_path).compare(.lt);
}
const lhs_span = lhs_src.span(zcu) catch |err| {
bad_file_out.* = lhs_src.file_scope;
return err;
};
const rhs_span = rhs_src.span(zcu) catch |err| {
bad_file_out.* = rhs_src.file_scope;
return err;
};
return lhs_span.main < rhs_span.main;
}
};
pub const SemaError = error{ OutOfMemory, AnalysisFail };
pub const CompileError = error{
OutOfMemory,
/// When this is returned, the compile error for the failure has already been recorded.
AnalysisFail,
/// In a comptime scope, a return instruction was encountered. This error is only seen when
/// doing a comptime function call.
ComptimeReturn,
/// In a comptime scope, a break instruction was encountered. This error is only seen when
/// evaluating a comptime block.
ComptimeBreak,
};
pub fn init(zcu: *Zcu, thread_count: usize) !void {
const gpa = zcu.gpa;
try zcu.intern_pool.init(gpa, thread_count);
}
pub fn deinit(zcu: *Zcu) void {
const gpa = zcu.gpa;
{
const pt: Zcu.PerThread = .activate(zcu, .main);
defer pt.deactivate();
if (zcu.llvm_object) |llvm_object| llvm_object.deinit();
zcu.builtin_modules.deinit(gpa);
zcu.module_roots.deinit(gpa);
for (zcu.import_table.keys()) |file_index| {
pt.destroyFile(file_index);
}
zcu.import_table.deinit(gpa);
zcu.alive_files.deinit(gpa);
for (zcu.embed_table.keys()) |embed_file| {
embed_file.path.deinit(gpa);
gpa.destroy(embed_file);
}
zcu.embed_table.deinit(gpa);
zcu.local_zir_cache.handle.close();
zcu.global_zir_cache.handle.close();
for (zcu.failed_analysis.values()) |value| value.destroy(gpa);
for (zcu.failed_codegen.values()) |value| value.destroy(gpa);
for (zcu.failed_types.values()) |value| value.destroy(gpa);
zcu.analysis_in_progress.deinit(gpa);
zcu.failed_analysis.deinit(gpa);
zcu.transitive_failed_analysis.deinit(gpa);
zcu.failed_codegen.deinit(gpa);
zcu.failed_types.deinit(gpa);
for (zcu.failed_files.values()) |value| {
if (value) |msg| gpa.free(msg);
}
zcu.failed_files.deinit(gpa);
zcu.failed_imports.deinit(gpa);
for (zcu.failed_exports.values()) |value| {
value.destroy(gpa);
}
zcu.failed_exports.deinit(gpa);
for (zcu.cimport_errors.values()) |*errs| {
errs.deinit(gpa);
}
zcu.cimport_errors.deinit(gpa);
zcu.compile_logs.deinit(gpa);
zcu.compile_log_lines.deinit(gpa);
zcu.free_compile_log_lines.deinit(gpa);
zcu.all_exports.deinit(gpa);
zcu.free_exports.deinit(gpa);
zcu.single_exports.deinit(gpa);
zcu.multi_exports.deinit(gpa);
zcu.potentially_outdated.deinit(gpa);
zcu.outdated.deinit(gpa);
zcu.outdated_ready.deinit(gpa);
zcu.retryable_failures.deinit(gpa);
zcu.func_body_analysis_queued.deinit(gpa);
zcu.nav_val_analysis_queued.deinit(gpa);
zcu.test_functions.deinit(gpa);
for (zcu.global_assembly.values()) |s| {
gpa.free(s);
}
zcu.global_assembly.deinit(gpa);
zcu.reference_table.deinit(gpa);
zcu.all_references.deinit(gpa);
zcu.free_references.deinit(gpa);
zcu.inline_reference_frames.deinit(gpa);
zcu.free_inline_reference_frames.deinit(gpa);
zcu.type_reference_table.deinit(gpa);
zcu.all_type_references.deinit(gpa);
zcu.free_type_references.deinit(gpa);
if (zcu.resolved_references) |*r| r.deinit(gpa);
if (zcu.comp.debugIncremental()) {
zcu.incremental_debug_state.deinit(gpa);
}
}
zcu.intern_pool.deinit(gpa);
}
pub fn namespacePtr(zcu: *Zcu, index: Namespace.Index) *Namespace {
return zcu.intern_pool.namespacePtr(index);
}
pub fn namespacePtrUnwrap(zcu: *Zcu, index: Namespace.OptionalIndex) ?*Namespace {
return zcu.namespacePtr(index.unwrap() orelse return null);
}
// TODO https://github.com/ziglang/zig/issues/8643
pub const data_has_safety_tag = @sizeOf(Zir.Inst.Data) != 8;
pub const HackDataLayout = extern struct {
data: [8]u8 align(@alignOf(Zir.Inst.Data)),
safety_tag: u8,
};
comptime {
if (data_has_safety_tag) {
assert(@sizeOf(HackDataLayout) == @sizeOf(Zir.Inst.Data));
}
}
pub fn loadZirCache(gpa: Allocator, cache_file: std.fs.File) !Zir {
var buffer: [2000]u8 = undefined;
var file_reader = cache_file.reader(&buffer);
return result: {
const header = file_reader.interface.takeStructPointer(Zir.Header) catch |err| break :result err;
break :result loadZirCacheBody(gpa, header.*, &file_reader.interface);
} catch |err| switch (err) {
error.ReadFailed => return file_reader.err.?,
else => |e| return e,
};
}
pub fn loadZirCacheBody(gpa: Allocator, header: Zir.Header, cache_br: *std.Io.Reader) !Zir {
var instructions: std.MultiArrayList(Zir.Inst) = .{};
errdefer instructions.deinit(gpa);
try instructions.setCapacity(gpa, header.instructions_len);
instructions.len = header.instructions_len;
var zir: Zir = .{
.instructions = instructions.toOwnedSlice(),
.string_bytes = &.{},
.extra = &.{},
};
errdefer zir.deinit(gpa);
zir.string_bytes = try gpa.alloc(u8, header.string_bytes_len);
zir.extra = try gpa.alloc(u32, header.extra_len);
const safety_buffer = if (data_has_safety_tag)
try gpa.alloc([8]u8, header.instructions_len)
else
undefined;
defer if (data_has_safety_tag) gpa.free(safety_buffer);
var vecs = [_][]u8{
@ptrCast(zir.instructions.items(.tag)),
if (data_has_safety_tag)
@ptrCast(safety_buffer)
else
@ptrCast(zir.instructions.items(.data)),
zir.string_bytes,
@ptrCast(zir.extra),
};
try cache_br.readVecAll(&vecs);
if (data_has_safety_tag) {
const tags = zir.instructions.items(.tag);
for (zir.instructions.items(.data), 0..) |*data, i| {
const union_tag = Zir.Inst.Tag.data_tags[@intFromEnum(tags[i])];
const as_struct = @as(*HackDataLayout, @ptrCast(data));
as_struct.* = .{
.safety_tag = @intFromEnum(union_tag),
.data = safety_buffer[i],
};
}
}
return zir;
}
pub fn saveZirCache(gpa: Allocator, cache_file: std.fs.File, stat: std.fs.File.Stat, zir: Zir) (std.fs.File.WriteError || Allocator.Error)!void {
const safety_buffer = if (data_has_safety_tag)
try gpa.alloc([8]u8, zir.instructions.len)
else
undefined;
defer if (data_has_safety_tag) gpa.free(safety_buffer);
if (data_has_safety_tag) {
// The `Data` union has a safety tag but in the file format we store it without.
for (zir.instructions.items(.data), 0..) |*data, i| {
const as_struct: *const HackDataLayout = @ptrCast(data);
safety_buffer[i] = as_struct.data;
}
}
const header: Zir.Header = .{
.instructions_len = @intCast(zir.instructions.len),
.string_bytes_len = @intCast(zir.string_bytes.len),
.extra_len = @intCast(zir.extra.len),
.stat_size = stat.size,
.stat_inode = stat.inode,
.stat_mtime = stat.mtime,
};
var vecs = [_][]const u8{
@ptrCast((&header)[0..1]),
@ptrCast(zir.instructions.items(.tag)),
if (data_has_safety_tag)
@ptrCast(safety_buffer)
else
@ptrCast(zir.instructions.items(.data)),
zir.string_bytes,
@ptrCast(zir.extra),
};
var cache_fw = cache_file.writer(&.{});
cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) {
error.WriteFailed => return cache_fw.err.?,
};
}
pub fn saveZoirCache(cache_file: std.fs.File, stat: std.fs.File.Stat, zoir: Zoir) std.fs.File.WriteError!void {
const header: Zoir.Header = .{
.nodes_len = @intCast(zoir.nodes.len),
.extra_len = @intCast(zoir.extra.len),
.limbs_len = @intCast(zoir.limbs.len),
.string_bytes_len = @intCast(zoir.string_bytes.len),
.compile_errors_len = @intCast(zoir.compile_errors.len),
.error_notes_len = @intCast(zoir.error_notes.len),
.stat_size = stat.size,
.stat_inode = stat.inode,
.stat_mtime = stat.mtime,
};
var vecs = [_][]const u8{
@ptrCast((&header)[0..1]),
@ptrCast(zoir.nodes.items(.tag)),
@ptrCast(zoir.nodes.items(.data)),
@ptrCast(zoir.nodes.items(.ast_node)),
@ptrCast(zoir.extra),
@ptrCast(zoir.limbs),
zoir.string_bytes,
@ptrCast(zoir.compile_errors),
@ptrCast(zoir.error_notes),
};
var cache_fw = cache_file.writer(&.{});
cache_fw.interface.writeVecAll(&vecs) catch |err| switch (err) {
error.WriteFailed => return cache_fw.err.?,
};
}
pub fn loadZoirCacheBody(gpa: Allocator, header: Zoir.Header, cache_br: *std.Io.Reader) !Zoir {
var zoir: Zoir = .{
.nodes = .empty,
.extra = &.{},
.limbs = &.{},
.string_bytes = &.{},
.compile_errors = &.{},
.error_notes = &.{},
};
errdefer zoir.deinit(gpa);
zoir.nodes = nodes: {
var nodes: std.MultiArrayList(Zoir.Node.Repr) = .empty;
defer nodes.deinit(gpa);
try nodes.setCapacity(gpa, header.nodes_len);
nodes.len = header.nodes_len;
break :nodes nodes.toOwnedSlice();
};
zoir.extra = try gpa.alloc(u32, header.extra_len);
zoir.limbs = try gpa.alloc(std.math.big.Limb, header.limbs_len);
zoir.string_bytes = try gpa.alloc(u8, header.string_bytes_len);
zoir.compile_errors = try gpa.alloc(Zoir.CompileError, header.compile_errors_len);
zoir.error_notes = try gpa.alloc(Zoir.CompileError.Note, header.error_notes_len);
var vecs = [_][]u8{
@ptrCast(zoir.nodes.items(.tag)),
@ptrCast(zoir.nodes.items(.data)),
@ptrCast(zoir.nodes.items(.ast_node)),
@ptrCast(zoir.extra),
@ptrCast(zoir.limbs),
zoir.string_bytes,
@ptrCast(zoir.compile_errors),
@ptrCast(zoir.error_notes),
};
try cache_br.readVecAll(&vecs);
return zoir;
}
pub fn markDependeeOutdated(
zcu: *Zcu,
/// When we are diffing ZIR and marking things as outdated, we won't yet have marked the dependencies as PO.
/// However, when we discover during analysis that something was outdated, the `Dependee` was already
/// marked as PO, so we need to decrement the PO dep count for each depender.
marked_po: enum { not_marked_po, marked_po },
dependee: InternPool.Dependee,
) !void {
log.debug("outdated dependee: {f}", .{zcu.fmtDependee(dependee)});
var it = zcu.intern_pool.dependencyIterator(dependee);
while (it.next()) |depender| {
if (zcu.outdated.getPtr(depender)) |po_dep_count| {
switch (marked_po) {
.not_marked_po => {},
.marked_po => {
po_dep_count.* -= 1;
log.debug("outdated {f} => already outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });
if (po_dep_count.* == 0) {
log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});
try zcu.outdated_ready.put(zcu.gpa, depender, {});
}
},
}
continue;
}
const opt_po_entry = zcu.potentially_outdated.fetchSwapRemove(depender);
const new_po_dep_count = switch (marked_po) {
.not_marked_po => if (opt_po_entry) |e| e.value else 0,
.marked_po => if (opt_po_entry) |e| e.value - 1 else {
// This `AnalUnit` has already been re-analyzed this update, and registered a dependency
// on this thing, but already has sufficiently up-to-date information. Nothing to do.
continue;
},
};
try zcu.outdated.putNoClobber(
zcu.gpa,
depender,
new_po_dep_count,
);
log.debug("outdated {f} => new outdated {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), new_po_dep_count });
if (new_po_dep_count == 0) {
log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});
try zcu.outdated_ready.put(zcu.gpa, depender, {});
}
// If this is a Decl and was not previously PO, we must recursively
// mark dependencies on its tyval as PO.
if (opt_po_entry == null) {
assert(marked_po == .not_marked_po);
try zcu.markTransitiveDependersPotentiallyOutdated(depender);
}
}
}
pub fn markPoDependeeUpToDate(zcu: *Zcu, dependee: InternPool.Dependee) !void {
log.debug("up-to-date dependee: {f}", .{zcu.fmtDependee(dependee)});
var it = zcu.intern_pool.dependencyIterator(dependee);
while (it.next()) |depender| {
if (zcu.outdated.getPtr(depender)) |po_dep_count| {
// This depender is already outdated, but it now has one
// less PO dependency!
po_dep_count.* -= 1;
log.debug("up-to-date {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), po_dep_count.* });
if (po_dep_count.* == 0) {
log.debug("outdated ready: {f}", .{zcu.fmtAnalUnit(depender)});
try zcu.outdated_ready.put(zcu.gpa, depender, {});
}
continue;
}
// This depender is definitely at least PO, because this Decl was just analyzed
// due to being outdated.
const ptr = zcu.potentially_outdated.getPtr(depender) orelse {
// This dependency has been registered during in-progress analysis, but the unit is
// not in `potentially_outdated` because analysis is in-progress. Nothing to do.
continue;
};
if (ptr.* > 1) {
ptr.* -= 1;
log.debug("up-to-date {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender), ptr.* });
continue;
}
log.debug("up-to-date {f} => {f} po_deps=0 (up-to-date)", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(depender) });
// This dependency is no longer PO, i.e. is known to be up-to-date.
assert(zcu.potentially_outdated.swapRemove(depender));
// If this is a Decl, we must recursively mark dependencies on its tyval
// as no longer PO.
switch (depender.unwrap()) {
.@"comptime" => {},
.nav_val => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_val = nav }),
.nav_ty => |nav| try zcu.markPoDependeeUpToDate(.{ .nav_ty = nav }),
.type => |ty| try zcu.markPoDependeeUpToDate(.{ .interned = ty }),
.func => |func| try zcu.markPoDependeeUpToDate(.{ .interned = func }),
.memoized_state => |stage| try zcu.markPoDependeeUpToDate(.{ .memoized_state = stage }),
}
}
}
/// Given a AnalUnit which is newly outdated or PO, mark all AnalUnits which may
/// in turn be PO, due to a dependency on the original AnalUnit's tyval or IES.
fn markTransitiveDependersPotentiallyOutdated(zcu: *Zcu, maybe_outdated: AnalUnit) !void {
const ip = &zcu.intern_pool;
const dependee: InternPool.Dependee = switch (maybe_outdated.unwrap()) {
.@"comptime" => return, // analysis of a comptime decl can't outdate any dependencies
.nav_val => |nav| .{ .nav_val = nav },
.nav_ty => |nav| .{ .nav_ty = nav },
.type => |ty| .{ .interned = ty },
.func => |func_index| .{ .interned = func_index }, // IES
.memoized_state => |stage| .{ .memoized_state = stage },
};
log.debug("potentially outdated dependee: {f}", .{zcu.fmtDependee(dependee)});
var it = ip.dependencyIterator(dependee);
while (it.next()) |po| {
if (zcu.outdated.getPtr(po)) |po_dep_count| {
// This dependency is already outdated, but it now has one more PO
// dependency.
if (po_dep_count.* == 0) {
_ = zcu.outdated_ready.swapRemove(po);
}
po_dep_count.* += 1;
log.debug("po {f} => {f} [outdated] po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), po_dep_count.* });
continue;
}
if (zcu.potentially_outdated.getPtr(po)) |n| {
// There is now one more PO dependency.
n.* += 1;
log.debug("po {f} => {f} po_deps={}", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po), n.* });
continue;
}
try zcu.potentially_outdated.putNoClobber(zcu.gpa, po, 1);
log.debug("po {f} => {f} po_deps=1", .{ zcu.fmtDependee(dependee), zcu.fmtAnalUnit(po) });
// This AnalUnit was not already PO, so we must recursively mark its dependers as also PO.
try zcu.markTransitiveDependersPotentiallyOutdated(po);
}
}
pub fn findOutdatedToAnalyze(zcu: *Zcu) Allocator.Error!?AnalUnit {
if (!zcu.comp.incremental) return null;
if (zcu.outdated.count() == 0) {
// Any units in `potentially_outdated` must just be stuck in loops with one another: none of those
// units have had any outdated dependencies so far, and all of their remaining PO deps are triggered
// by other units in `potentially_outdated`. So, we can safety assume those units up-to-date.
zcu.potentially_outdated.clearRetainingCapacity();
log.debug("findOutdatedToAnalyze: no outdated depender", .{});
return null;
}
// Our goal is to find an outdated AnalUnit which itself has no outdated or
// PO dependencies. Most of the time, such an AnalUnit will exist - we track
// them in the `outdated_ready` set for efficiency. However, this is not
// necessarily the case, since the Decl dependency graph may contain loops
// via mutually recursive definitions:
// pub const A = struct { b: *B };
// pub const B = struct { b: *A };
// In this case, we must defer to more complex logic below.
if (zcu.outdated_ready.count() > 0) {
const unit = zcu.outdated_ready.keys()[0];
log.debug("findOutdatedToAnalyze: trivial {f}", .{zcu.fmtAnalUnit(unit)});
return unit;
}
// There is no single AnalUnit which is ready for re-analysis. Instead, we must assume that some
// AnalUnit with PO dependencies is outdated -- e.g. in the above example we arbitrarily pick one of
// A or B. We should definitely not select a function, since a function can't be responsible for the
// loop (IES dependencies can't have loops). We should also, of course, not select a `comptime`
// declaration, since you can't depend on those!
// The choice of this unit could have a big impact on how much total analysis we perform, since
// if analysis concludes any dependencies on its result are up-to-date, then other PO AnalUnit
// may be resolved as up-to-date. To hopefully avoid doing too much work, let's find a unit
// which the most things depend on - the idea is that this will resolve a lot of loops (but this
// is only a heuristic).
log.debug("findOutdatedToAnalyze: no trivial ready, using heuristic; {d} outdated, {d} PO", .{
zcu.outdated.count(),
zcu.potentially_outdated.count(),
});
const ip = &zcu.intern_pool;
var chosen_unit: ?AnalUnit = null;
var chosen_unit_dependers: u32 = undefined;
inline for (.{ zcu.outdated.keys(), zcu.potentially_outdated.keys() }) |outdated_units| {
for (outdated_units) |unit| {
var n: u32 = 0;
var it = ip.dependencyIterator(switch (unit.unwrap()) {
.func => continue, // a `func` definitely can't be causing the loop so it is a bad choice
.@"comptime" => continue, // a `comptime` block can't even be depended on so it is a terrible choice
.type => |ty| .{ .interned = ty },
.nav_val => |nav| .{ .nav_val = nav },
.nav_ty => |nav| .{ .nav_ty = nav },
.memoized_state => {
// If we've hit a loop and some `.memoized_state` is outdated, we should make that choice eagerly.
// In general, it's good to resolve this early on, since -- for instance -- almost every function
// references the panic handler.
return unit;
},
});
while (it.next()) |_| n += 1;
if (chosen_unit == null or n > chosen_unit_dependers) {
chosen_unit = unit;
chosen_unit_dependers = n;
}
}
}
log.debug("findOutdatedToAnalyze: heuristic returned '{f}' ({d} dependers)", .{
zcu.fmtAnalUnit(chosen_unit.?),
chosen_unit_dependers,
});
return chosen_unit.?;
}
/// During an incremental update, before semantic analysis, call this to flush all values from
/// `retryable_failures` and mark them as outdated so they get re-analyzed.
pub fn flushRetryableFailures(zcu: *Zcu) !void {
const gpa = zcu.gpa;
for (zcu.retryable_failures.items) |depender| {
if (zcu.outdated.contains(depender)) continue;
if (zcu.potentially_outdated.fetchSwapRemove(depender)) |kv| {
// This AnalUnit was already PO, but we now consider it outdated.
// Any transitive dependencies are already marked PO.
try zcu.outdated.put(gpa, depender, kv.value);
continue;
}
// This AnalUnit was not marked PO, but is now outdated. Mark it as
// such, then recursively mark transitive dependencies as PO.
try zcu.outdated.put(gpa, depender, 0);
try zcu.markTransitiveDependersPotentiallyOutdated(depender);
}
zcu.retryable_failures.clearRetainingCapacity();
}
pub fn mapOldZirToNew(
gpa: Allocator,
old_zir: Zir,
new_zir: Zir,
inst_map: *std.AutoHashMapUnmanaged(Zir.Inst.Index, Zir.Inst.Index),
) Allocator.Error!void {
// Contain ZIR indexes of namespace declaration instructions, e.g. struct_decl, union_decl, etc.
// Not `declaration`, as this does not create a namespace.
const MatchedZirDecl = struct {
old_inst: Zir.Inst.Index,
new_inst: Zir.Inst.Index,
};
var match_stack: std.ArrayListUnmanaged(MatchedZirDecl) = .empty;
defer match_stack.deinit(gpa);
// Used as temporary buffers for namespace declaration instructions
var old_contents: Zir.DeclContents = .init;
defer old_contents.deinit(gpa);
var new_contents: Zir.DeclContents = .init;
defer new_contents.deinit(gpa);
// Map the main struct inst (and anything in its fields)
{
try old_zir.findTrackableRoot(gpa, &old_contents);
try new_zir.findTrackableRoot(gpa, &new_contents);
assert(old_contents.explicit_types.items[0] == .main_struct_inst);
assert(new_contents.explicit_types.items[0] == .main_struct_inst);
assert(old_contents.func_decl == null);
assert(new_contents.func_decl == null);
// We don't have any smart way of matching up these instructions, so we correlate them based on source order
// in their respective arrays.
const num_explicit_types = @min(old_contents.explicit_types.items.len, new_contents.explicit_types.items.len);
try match_stack.ensureUnusedCapacity(gpa, @intCast(num_explicit_types));
for (
old_contents.explicit_types.items[0..num_explicit_types],
new_contents.explicit_types.items[0..num_explicit_types],
) |old_inst, new_inst| {
// Here we use `match_stack`, so that we will recursively consider declarations on these types.
match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
}
const num_other = @min(old_contents.other.items.len, new_contents.other.items.len);
try inst_map.ensureUnusedCapacity(gpa, @intCast(num_other));
for (
old_contents.other.items[0..num_other],
new_contents.other.items[0..num_other],
) |old_inst, new_inst| {
// These instructions don't have declarations, so we just modify `inst_map` directly.
inst_map.putAssumeCapacity(old_inst, new_inst);
}
}
while (match_stack.pop()) |match_item| {
// First, a check: if the number of captures of this type has changed, we can't map it, because
// we wouldn't know how to correlate type information with the last update.
// Synchronizes with logic in `Zcu.PerThread.recreateStructType` etc.
if (old_zir.typeCapturesLen(match_item.old_inst) != new_zir.typeCapturesLen(match_item.new_inst)) {
// Don't map this type or anything within it.
continue;
}
// Match the namespace declaration itself
try inst_map.put(gpa, match_item.old_inst, match_item.new_inst);
// Maps decl name to `declaration` instruction.
var named_decls: std.StringHashMapUnmanaged(Zir.Inst.Index) = .empty;
defer named_decls.deinit(gpa);
// Maps test name to `declaration` instruction.
var named_tests: std.StringHashMapUnmanaged(Zir.Inst.Index) = .empty;
defer named_tests.deinit(gpa);
// Maps test name to `declaration` instruction.
var named_decltests: std.StringHashMapUnmanaged(Zir.Inst.Index) = .empty;
defer named_decltests.deinit(gpa);
// All unnamed tests, in order, for a best-effort match.
var unnamed_tests: std.ArrayListUnmanaged(Zir.Inst.Index) = .empty;
defer unnamed_tests.deinit(gpa);
// All comptime declarations, in order, for a best-effort match.
var comptime_decls: std.ArrayListUnmanaged(Zir.Inst.Index) = .empty;
defer comptime_decls.deinit(gpa);
{
var old_decl_it = old_zir.declIterator(match_item.old_inst);
while (old_decl_it.next()) |old_decl_inst| {
const old_decl = old_zir.getDeclaration(old_decl_inst);
switch (old_decl.kind) {
.@"comptime" => try comptime_decls.append(gpa, old_decl_inst),
.unnamed_test => try unnamed_tests.append(gpa, old_decl_inst),
.@"test" => try named_tests.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst),
.decltest => try named_decltests.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst),
.@"const", .@"var" => try named_decls.put(gpa, old_zir.nullTerminatedString(old_decl.name), old_decl_inst),
}
}
}
var unnamed_test_idx: u32 = 0;
var comptime_decl_idx: u32 = 0;
var new_decl_it = new_zir.declIterator(match_item.new_inst);
while (new_decl_it.next()) |new_decl_inst| {
const new_decl = new_zir.getDeclaration(new_decl_inst);
// Attempt to match this to a declaration in the old ZIR:
// * For named declarations (`const`/`var`/`fn`), we match based on name.
// * For named tests (`test "foo"`) and decltests (`test foo`), we also match based on name.
// * For unnamed tests, we match based on order.
// * For comptime blocks, we match based on order.
// If we cannot match this declaration, we can't match anything nested inside of it either, so we just `continue`.
const old_decl_inst = switch (new_decl.kind) {
.@"comptime" => inst: {
if (comptime_decl_idx == comptime_decls.items.len) continue;
defer comptime_decl_idx += 1;
break :inst comptime_decls.items[comptime_decl_idx];
},
.unnamed_test => inst: {
if (unnamed_test_idx == unnamed_tests.items.len) continue;
defer unnamed_test_idx += 1;
break :inst unnamed_tests.items[unnamed_test_idx];
},
.@"test" => inst: {
const name = new_zir.nullTerminatedString(new_decl.name);
break :inst named_tests.get(name) orelse continue;
},
.decltest => inst: {
const name = new_zir.nullTerminatedString(new_decl.name);
break :inst named_decltests.get(name) orelse continue;
},
.@"const", .@"var" => inst: {
const name = new_zir.nullTerminatedString(new_decl.name);
break :inst named_decls.get(name) orelse continue;
},
};
// Match the `declaration` instruction
try inst_map.put(gpa, old_decl_inst, new_decl_inst);
// Find trackable instructions within this declaration
try old_zir.findTrackable(gpa, &old_contents, old_decl_inst);
try new_zir.findTrackable(gpa, &new_contents, new_decl_inst);
// We don't have any smart way of matching up these instructions, so we correlate them based on source order
// in their respective arrays.
const num_explicit_types = @min(old_contents.explicit_types.items.len, new_contents.explicit_types.items.len);
try match_stack.ensureUnusedCapacity(gpa, @intCast(num_explicit_types));
for (
old_contents.explicit_types.items[0..num_explicit_types],
new_contents.explicit_types.items[0..num_explicit_types],
) |old_inst, new_inst| {
// Here we use `match_stack`, so that we will recursively consider declarations on these types.
match_stack.appendAssumeCapacity(.{ .old_inst = old_inst, .new_inst = new_inst });
}
const num_other = @min(old_contents.other.items.len, new_contents.other.items.len);
try inst_map.ensureUnusedCapacity(gpa, @intCast(num_other));
for (
old_contents.other.items[0..num_other],
new_contents.other.items[0..num_other],
) |old_inst, new_inst| {
// These instructions don't have declarations, so we just modify `inst_map` directly.
inst_map.putAssumeCapacity(old_inst, new_inst);
}
if (old_contents.func_decl) |old_func_inst| {
if (new_contents.func_decl) |new_func_inst| {
// There are no declarations on a function either, so again, we just directly add it to `inst_map`.
try inst_map.put(gpa, old_func_inst, new_func_inst);
}
}
}
}
}
/// 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.
///
/// The caller is responsible for ensuring the function decl itself is already
/// analyzed, and for ensuring it can exist at runtime (see
/// `Type.fnHasRuntimeBitsSema`). This function does *not* guarantee that the body
/// will be analyzed when it returns: for that, see `ensureFuncBodyAnalyzed`.
pub fn ensureFuncBodyAnalysisQueued(zcu: *Zcu, func_index: InternPool.Index) !void {
const ip = &zcu.intern_pool;
const func = zcu.funcInfo(func_index);
assert(func.ty == func.uncoerced_ty); // analyze the body of the original function, not a coerced one
if (zcu.func_body_analysis_queued.contains(func_index)) return;
if (func.analysisUnordered(ip).is_analyzed) {
if (!zcu.outdated.contains(.wrap(.{ .func = func_index })) and
!zcu.potentially_outdated.contains(.wrap(.{ .func = func_index })))
{
// This function has been analyzed before and is definitely up-to-date.
return;
}
}
try zcu.func_body_analysis_queued.ensureUnusedCapacity(zcu.gpa, 1);
try zcu.comp.queueJob(.{ .analyze_func = func_index });
zcu.func_body_analysis_queued.putAssumeCapacityNoClobber(func_index, {});
}
pub fn ensureNavValAnalysisQueued(zcu: *Zcu, nav_id: InternPool.Nav.Index) !void {
const ip = &zcu.intern_pool;
if (zcu.nav_val_analysis_queued.contains(nav_id)) return;
if (ip.getNav(nav_id).status == .fully_resolved) {
if (!zcu.outdated.contains(.wrap(.{ .nav_val = nav_id })) and
!zcu.potentially_outdated.contains(.wrap(.{ .nav_val = nav_id })))
{
// This `Nav` has been analyzed before and is definitely up-to-date.
return;
}
}
try zcu.nav_val_analysis_queued.ensureUnusedCapacity(zcu.gpa, 1);
try zcu.comp.queueJob(.{ .analyze_comptime_unit = .wrap(.{ .nav_val = nav_id }) });
zcu.nav_val_analysis_queued.putAssumeCapacityNoClobber(nav_id, {});
}
pub const ImportResult = struct {
/// Whether `file` has been newly created; in other words, whether this is the first import of
/// this file. This should only be `true` when importing files during AstGen. After that, all
/// files should have already been discovered.
is_new: bool,
/// `file.mod` is not populated by this function, so if `is_new`, then it is `undefined`.
file: *Zcu.File,
file_index: File.Index,
/// If this import was a simple file path, this is `null`; the imported file should exist within
/// the importer's module. Otherwise, it's the module which the import resolved to. This module
/// could match the module of `cur_file`, since a module can depend on itself.
module: ?*Package.Module,
};
/// 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 {
const gpa = zcu.gpa;
const exports_base, const exports_len = if (zcu.single_exports.fetchSwapRemove(anal_unit)) |kv|
.{ @intFromEnum(kv.value), 1 }
else if (zcu.multi_exports.fetchSwapRemove(anal_unit)) |info|
.{ info.value.index, info.value.len }
else
return;
const exports = zcu.all_exports.items[exports_base..][0..exports_len];
// In an only-c build, we're guaranteed to never use incremental compilation, so there are
// guaranteed not to be any exports in the output file that need deleting (since we only call
// `updateExports` on flush).
// This case is needed because in some rare edge cases, `Sema` wants to add and delete exports
// within a single update.
if (dev.env.supports(.incremental)) {
for (exports, exports_base..) |exp, export_index_usize| {
const export_idx: Export.Index = @enumFromInt(export_index_usize);
if (zcu.comp.bin_file) |lf| {
lf.deleteExport(exp.exported, exp.opts.name);
}
if (zcu.failed_exports.fetchSwapRemove(export_idx)) |failed_kv| {
failed_kv.value.destroy(gpa);
}
}
}
zcu.free_exports.ensureUnusedCapacity(gpa, exports_len) catch {
// This space will be reused eventually, so we need not propagate this error.
// Just leak it for now, and let GC reclaim it later on.
return;
};
for (exports_base..exports_base + exports_len) |export_idx| {
zcu.free_exports.appendAssumeCapacity(@enumFromInt(export_idx));
}
}
/// Delete all references in `reference_table` which are caused by this `AnalUnit`.
/// Re-analysis of the `AnalUnit` will cause appropriate references to be recreated.
pub fn deleteUnitReferences(zcu: *Zcu, anal_unit: AnalUnit) void {
const gpa = zcu.gpa;
zcu.clearCachedResolvedReferences();
unit_refs: {
const kv = zcu.reference_table.fetchSwapRemove(anal_unit) orelse break :unit_refs;
var idx = kv.value;
while (idx != std.math.maxInt(u32)) {
const ref = zcu.all_references.items[idx];
zcu.free_references.append(gpa, idx) catch {
// This space will be reused eventually, so we need not propagate this error.
// Just leak it for now, and let GC reclaim it later on.
break :unit_refs;
};
idx = ref.next;
var opt_inline_frame = ref.inline_frame;
while (opt_inline_frame.unwrap()) |inline_frame| {
// The same inline frame could be used multiple times by one unit. We need to
// detect this case to avoid adding it to `free_inline_reference_frames` more
// than once. We do that by setting `parent` to itself as a marker.
if (inline_frame.ptr(zcu).parent == inline_frame.toOptional()) break;
zcu.free_inline_reference_frames.append(gpa, inline_frame) catch {
// This space will be reused eventually, so we need not propagate this error.
// Just leak it for now, and let GC reclaim it later on.
break :unit_refs;
};
opt_inline_frame = inline_frame.ptr(zcu).parent;
inline_frame.ptr(zcu).parent = inline_frame.toOptional(); // signal to code above
}
}
}
type_refs: {
const kv = zcu.type_reference_table.fetchSwapRemove(anal_unit) orelse break :type_refs;
var idx = kv.value;
while (idx != std.math.maxInt(u32)) {
zcu.free_type_references.append(gpa, idx) catch {
// This space will be reused eventually, so we need not propagate this error.
// Just leak it for now, and let GC reclaim it later on.
break :type_refs;
};
idx = zcu.all_type_references.items[idx].next;
}
}
}
/// Delete all compile logs performed by this `AnalUnit`.
/// Re-analysis of the `AnalUnit` will cause logs to be rediscovered.
pub fn deleteUnitCompileLogs(zcu: *Zcu, anal_unit: AnalUnit) void {
const kv = zcu.compile_logs.fetchSwapRemove(anal_unit) orelse return;
const gpa = zcu.gpa;
var opt_line_idx = kv.value.first_line.toOptional();
while (opt_line_idx.unwrap()) |line_idx| {
zcu.free_compile_log_lines.append(gpa, line_idx) catch {
// This space will be reused eventually, so we need not propagate this error.
// Just leak it for now, and let GC reclaim it later on.
return;
};
opt_line_idx = line_idx.get(zcu).next;
}
}
pub fn addInlineReferenceFrame(zcu: *Zcu, frame: InlineReferenceFrame) Allocator.Error!Zcu.InlineReferenceFrame.Index {
const frame_idx: InlineReferenceFrame.Index = zcu.free_inline_reference_frames.pop() orelse idx: {
_ = try zcu.inline_reference_frames.addOne(zcu.gpa);
break :idx @enumFromInt(zcu.inline_reference_frames.items.len - 1);
};
frame_idx.ptr(zcu).* = frame;
return frame_idx;
}
pub fn addUnitReference(
zcu: *Zcu,
src_unit: AnalUnit,
referenced_unit: AnalUnit,
ref_src: LazySrcLoc,
inline_frame: InlineReferenceFrame.Index.Optional,
) Allocator.Error!void {
const gpa = zcu.gpa;
zcu.clearCachedResolvedReferences();
try zcu.reference_table.ensureUnusedCapacity(gpa, 1);
const ref_idx = zcu.free_references.pop() orelse idx: {
_ = try zcu.all_references.addOne(gpa);
break :idx zcu.all_references.items.len - 1;
};
errdefer comptime unreachable;
const gop = zcu.reference_table.getOrPutAssumeCapacity(src_unit);
zcu.all_references.items[ref_idx] = .{
.referenced = referenced_unit,
.next = if (gop.found_existing) gop.value_ptr.* else std.math.maxInt(u32),
.src = ref_src,
.inline_frame = inline_frame,
};
gop.value_ptr.* = @intCast(ref_idx);
}
pub fn addTypeReference(zcu: *Zcu, src_unit: AnalUnit, referenced_type: InternPool.Index, ref_src: LazySrcLoc) Allocator.Error!void {
const gpa = zcu.gpa;
zcu.clearCachedResolvedReferences();
try zcu.type_reference_table.ensureUnusedCapacity(gpa, 1);
const ref_idx = zcu.free_type_references.pop() orelse idx: {
_ = try zcu.all_type_references.addOne(gpa);
break :idx zcu.all_type_references.items.len - 1;
};
errdefer comptime unreachable;
const gop = zcu.type_reference_table.getOrPutAssumeCapacity(src_unit);
zcu.all_type_references.items[ref_idx] = .{
.referenced = referenced_type,
.next = if (gop.found_existing) gop.value_ptr.* else std.math.maxInt(u32),
.src = ref_src,
};
gop.value_ptr.* = @intCast(ref_idx);
}
fn clearCachedResolvedReferences(zcu: *Zcu) void {
if (zcu.resolved_references) |*r| r.deinit(zcu.gpa);
zcu.resolved_references = null;
}
pub fn errorSetBits(zcu: *const Zcu) u16 {
const target = zcu.getTarget();
if (zcu.error_limit == 0) return 0;
if (target.cpu.arch.isSpirV()) {
// As expected by https://github.com/Snektron/zig-spirv-test-executor
if (zcu.comp.config.is_test) return 32;
}
return @as(u16, std.math.log2_int(ErrorInt, zcu.error_limit)) + 1;
}
pub fn errNote(
zcu: *Zcu,
src_loc: LazySrcLoc,
parent: *ErrorMsg,
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!void {
const msg = try std.fmt.allocPrint(zcu.gpa, format, args);
errdefer zcu.gpa.free(msg);
parent.notes = try zcu.gpa.realloc(parent.notes, parent.notes.len + 1);
parent.notes[parent.notes.len - 1] = .{
.src_loc = src_loc,
.msg = msg,
};
}
/// Deprecated. There is no global target for a Zig Compilation Unit. Instead,
/// look up the target based on the Module that contains the source code being
/// analyzed.
pub fn getTarget(zcu: *const Zcu) *const Target {
return &zcu.root_mod.resolved_target.result;
}
/// Deprecated. There is no global optimization mode for a Zig Compilation
/// Unit. Instead, look up the optimization mode based on the Module that
/// contains the source code being analyzed.
pub fn optimizeMode(zcu: *const Zcu) std.builtin.OptimizeMode {
return zcu.root_mod.optimize_mode;
}
pub fn handleUpdateExports(
zcu: *Zcu,
export_indices: []const Export.Index,
result: link.File.UpdateExportsError!void,
) Allocator.Error!void {
const gpa = zcu.gpa;
result catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {
const export_idx = export_indices[0];
const new_export = export_idx.ptr(zcu);
new_export.status = .failed_retryable;
try zcu.failed_exports.ensureUnusedCapacity(gpa, 1);
const msg = try ErrorMsg.create(gpa, new_export.src, "unable to export: {s}", .{@errorName(err)});
zcu.failed_exports.putAssumeCapacityNoClobber(export_idx, msg);
},
};
}
pub fn addGlobalAssembly(zcu: *Zcu, unit: AnalUnit, source: []const u8) !void {
const gpa = zcu.gpa;
const gop = try zcu.global_assembly.getOrPut(gpa, unit);
if (gop.found_existing) {
const new_value = try std.fmt.allocPrint(gpa, "{s}\n{s}", .{ gop.value_ptr.*, source });
gpa.free(gop.value_ptr.*);
gop.value_ptr.* = new_value;
} else {
gop.value_ptr.* = try gpa.dupe(u8, source);
}
}
pub const Feature = enum {
/// When this feature is enabled, Sema will emit calls to
/// `std.builtin.panic` functions for things like safety checks and
/// unreachables. Otherwise traps will be emitted.
panic_fn,
/// When this feature is enabled, Sema will insert tracer functions for gathering a stack
/// trace for error returns.
error_return_trace,
/// When this feature is enabled, Sema will emit the `is_named_enum_value` AIR instructions
/// and use it to check for corrupt switches. Backends currently need to implement their own
/// logic to determine whether an enum value is in the set of named values.
is_named_enum_value,
error_set_has_value,
field_reordering,
/// In theory, backends are supposed to work like this:
///
/// * The AIR emitted by `Sema` is converted into MIR by `codegen.generateFunction`. This pass
/// is "pure", in that it does not depend on or modify any external mutable state.
///
/// * That MIR is sent to the linker, which calls `codegen.emitFunction` to convert the MIR to
/// finalized machine code. This process is permitted to query and modify linker state.
///
/// * The linker stores the resulting machine code in the binary as needed.
///
/// The first stage described above can run in parallel to the rest of the compiler, and even to
/// other code generation work; we can run as many codegen threads as we want in parallel because
/// of the fact that this pass is pure. Emit and link must be single-threaded, but are generally
/// very fast, so that isn't a problem.
///
/// Unfortunately, some code generation implementations currently query and/or mutate linker state
/// or even (in the case of the LLVM backend) semantic analysis state. Such backends cannot be run
/// in parallel with each other, with linking, or (potentially) with semantic analysis.
///
/// Additionally, some backends continue to need the AIR in the "emit" stage, despite this pass
/// operating on MIR. This complicates memory management under the threading model above.
///
/// These are both **bugs** in backend implementations, left over from legacy code. However, they
/// are difficult to fix. So, this `Feature` currently guards correct threading of code generation:
///
/// * With this feature enabled, the backend is threaded as described above. The "emit" stage does
/// not have access to AIR (it will be `undefined`; see `codegen.emitFunction`).
///
/// * With this feature disabled, semantic analysis, code generation, and linking all occur on the
/// same thread, and the "emit" stage has access to AIR.
separate_thread,
};
pub fn backendSupportsFeature(zcu: *const Zcu, comptime feature: Feature) bool {
const backend = target_util.zigBackend(&zcu.root_mod.resolved_target.result, zcu.comp.config.use_llvm);
return target_util.backendSupportsFeature(backend, feature);
}
pub const AtomicPtrAlignmentError = error{
FloatTooBig,
IntTooBig,
BadType,
OutOfMemory,
};
pub const AtomicPtrAlignmentDiagnostics = struct {
bits: u16 = undefined,
max_bits: u16 = undefined,
};
/// If ABI alignment of `ty` is OK for atomic operations, returns 0.
/// Otherwise returns the alignment required on a pointer for the target
/// to perform atomic operations.
// TODO this function does not take into account CPU features, which can affect
// this value. Audit this!
pub fn atomicPtrAlignment(
zcu: *Zcu,
ty: Type,
diags: *AtomicPtrAlignmentDiagnostics,
) AtomicPtrAlignmentError!Alignment {
const target = zcu.getTarget();
const max_atomic_bits: u16 = switch (target.cpu.arch) {
.avr,
.msp430,
=> 16,
.arc,
.arm,
.armeb,
.hexagon,
.m68k,
.mips,
.mipsel,
.nvptx,
.or1k,
.powerpc,
.powerpcle,
.riscv32,
.riscv32be,
.sparc,
.thumb,
.thumbeb,
.x86,
.xcore,
.kalimba,
.lanai,
.wasm32,
.csky,
.spirv32,
.loongarch32,
.xtensa,
.propeller,
=> 32,
.amdgcn,
.bpfel,
.bpfeb,
.mips64,
.mips64el,
.nvptx64,
.powerpc64,
.powerpc64le,
.riscv64,
.riscv64be,
.sparc64,
.s390x,
.wasm64,
.ve,
.spirv64,
.loongarch64,
=> 64,
.aarch64,
.aarch64_be,
=> 128,
.x86_64 => if (target.cpu.has(.x86, .cx16)) 128 else 64,
};
if (ty.toIntern() == .bool_type) return .none;
if (ty.isRuntimeFloat()) {
const bit_count = ty.floatBits(target);
if (bit_count > max_atomic_bits) {
diags.* = .{
.bits = bit_count,
.max_bits = max_atomic_bits,
};
return error.FloatTooBig;
}
return .none;
}
if (switch (ty.zigTypeTag(zcu)) {
.int, .@"enum" => true,
.@"struct" => ty.containerLayout(zcu) == .@"packed",
else => false,
}) {
assert(ty.isAbiInt(zcu));
const bit_count = ty.intInfo(zcu).bits;
if (bit_count > max_atomic_bits) {
diags.* = .{
.bits = bit_count,
.max_bits = max_atomic_bits,
};
return error.IntTooBig;
}
return .none;
}
if (ty.isPtrAtRuntime(zcu)) return .none;
return error.BadType;
}
/// Returns null in the following cases:
/// * Not a struct.
pub fn typeToStruct(zcu: *const Zcu, ty: Type) ?InternPool.LoadedStructType {
if (ty.ip_index == .none) return null;
const ip = &zcu.intern_pool;
return switch (ip.indexToKey(ty.ip_index)) {
.struct_type => ip.loadStructType(ty.ip_index),
else => null,
};
}
pub fn typeToPackedStruct(zcu: *const Zcu, ty: Type) ?InternPool.LoadedStructType {
const s = zcu.typeToStruct(ty) orelse return null;
if (s.layout != .@"packed") return null;
return s;
}
/// 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(
zcu: *Zcu,
struct_type: InternPool.LoadedStructType,
field_index: u32,
) u16 {
const ip = &zcu.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(zcu);
}
unreachable; // index out of bounds
}
pub fn typeToUnion(zcu: *const Zcu, ty: Type) ?InternPool.LoadedUnionType {
if (ty.ip_index == .none) return null;
const ip = &zcu.intern_pool;
return switch (ip.indexToKey(ty.ip_index)) {
.union_type => ip.loadUnionType(ty.ip_index),
else => null,
};
}
pub fn typeToFunc(zcu: *const Zcu, ty: Type) ?InternPool.Key.FuncType {
if (ty.ip_index == .none) return null;
return zcu.intern_pool.indexToFuncType(ty.toIntern());
}
pub fn iesFuncIndex(zcu: *const Zcu, ies_index: InternPool.Index) InternPool.Index {
return zcu.intern_pool.iesFuncIndex(ies_index);
}
pub fn funcInfo(zcu: *const Zcu, func_index: InternPool.Index) InternPool.Key.Func {
return zcu.intern_pool.toFunc(func_index);
}
pub const UnionLayout = struct {
abi_size: u64,
abi_align: Alignment,
most_aligned_field: u32,
most_aligned_field_size: u64,
biggest_field: u32,
payload_size: u64,
payload_align: Alignment,
tag_align: Alignment,
tag_size: u64,
padding: u32,
pub fn tagOffset(layout: UnionLayout) u64 {
return if (layout.tag_align.compare(.lt, layout.payload_align)) layout.payload_size else 0;
}
pub fn payloadOffset(layout: UnionLayout) u64 {
return if (layout.tag_align.compare(.lt, layout.payload_align)) 0 else layout.tag_size;
}
};
/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(zcu: *const Zcu, loaded_union: InternPool.LoadedUnionType, enum_tag: Value) ?u32 {
const ip = &zcu.intern_pool;
if (enum_tag.toIntern() == .none) return null;
assert(ip.typeOf(enum_tag.toIntern()) == loaded_union.enum_tag_ty);
return loaded_union.loadTagType(ip).tagValueIndex(ip, enum_tag.toIntern());
}
pub const ResolvedReference = struct {
referencer: AnalUnit,
/// If `inline_frame` is not `.none`, this is the *deepest* source location in the chain of
/// inline calls. For source locations further up the inline call stack, consult `inline_frame`.
src: LazySrcLoc,
inline_frame: InlineReferenceFrame.Index.Optional,
};
/// Returns a mapping from an `AnalUnit` to where it is referenced.
/// If the value is `null`, the `AnalUnit` is a root of analysis.
/// If an `AnalUnit` is not in the returned map, it is unreferenced.
/// The returned hashmap is owned by the `Zcu`, so should not be freed by the caller.
/// This hashmap is cached, so repeated calls to this function are cheap.
pub fn resolveReferences(zcu: *Zcu) !*const std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) {
if (zcu.resolved_references == null) {
zcu.resolved_references = try zcu.resolveReferencesInner();
}
return &zcu.resolved_references.?;
}
fn resolveReferencesInner(zcu: *Zcu) !std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) {
const gpa = zcu.gpa;
const comp = zcu.comp;
const ip = &zcu.intern_pool;
var result: std.AutoHashMapUnmanaged(AnalUnit, ?ResolvedReference) = .empty;
errdefer result.deinit(gpa);
var checked_types: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .empty;
var type_queue: std.AutoArrayHashMapUnmanaged(InternPool.Index, ?ResolvedReference) = .empty;
var unit_queue: std.AutoArrayHashMapUnmanaged(AnalUnit, ?ResolvedReference) = .empty;
defer {
checked_types.deinit(gpa);
type_queue.deinit(gpa);
unit_queue.deinit(gpa);
}
// This is not a sufficient size, but a lower bound.
try result.ensureTotalCapacity(gpa, @intCast(zcu.reference_table.count()));
try type_queue.ensureTotalCapacity(gpa, zcu.analysis_roots_len);
for (zcu.analysisRoots()) |mod| {
const file = zcu.module_roots.get(mod).?.unwrap() orelse continue;
const root_ty = zcu.fileRootType(file);
if (root_ty == .none) continue;
type_queue.putAssumeCapacityNoClobber(root_ty, null);
}
while (true) {
if (type_queue.pop()) |kv| {
const ty = kv.key;
const referencer = kv.value;
try checked_types.putNoClobber(gpa, ty, {});
log.debug("handle type '{f}'", .{Type.fromInterned(ty).containerTypeName(ip).fmt(ip)});
// If this type undergoes type resolution, the corresponding `AnalUnit` is automatically referenced.
const has_resolution: bool = switch (ip.indexToKey(ty)) {
.struct_type, .union_type => true,
.enum_type => |k| k != .generated_tag,
.opaque_type => false,
else => unreachable,
};
if (has_resolution) {
// this should only be referenced by the type
const unit: AnalUnit = .wrap(.{ .type = ty });
assert(!result.contains(unit));
try unit_queue.putNoClobber(gpa, unit, referencer);
}
// If this is a union with a generated tag, its tag type is automatically referenced.
// We don't add this reference for non-generated tags, as those will already be referenced via the union's type resolution, with a better source location.
if (zcu.typeToUnion(Type.fromInterned(ty))) |union_obj| {
const tag_ty = union_obj.enum_tag_ty;
if (tag_ty != .none) {
if (ip.indexToKey(tag_ty).enum_type == .generated_tag) {
if (!checked_types.contains(tag_ty)) {
try type_queue.put(gpa, tag_ty, referencer);
}
}
}
}
// Queue any decls within this type which would be automatically analyzed.
// Keep in sync with analysis queueing logic in `Zcu.PerThread.ScanDeclIter.scanDecl`.
const ns = Type.fromInterned(ty).getNamespace(zcu).unwrap().?;
for (zcu.namespacePtr(ns).comptime_decls.items) |cu| {
// `comptime` decls are always analyzed.
const unit: AnalUnit = .wrap(.{ .@"comptime" = cu });
if (!result.contains(unit)) {
log.debug("type '{f}': ref comptime %{}", .{
Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
@intFromEnum(ip.getComptimeUnit(cu).zir_index.resolve(ip) orelse continue),
});
try unit_queue.put(gpa, unit, referencer);
}
}
for (zcu.namespacePtr(ns).test_decls.items) |nav_id| {
const nav = ip.getNav(nav_id);
// `test` declarations are analyzed depending on the test filter.
const inst_info = nav.analysis.?.zir_index.resolveFull(ip) orelse continue;
const file = zcu.fileByIndex(inst_info.file);
const decl = file.zir.?.getDeclaration(inst_info.inst);
if (!comp.config.is_test or file.mod != zcu.main_mod) continue;
const want_analysis = switch (decl.kind) {
.@"const", .@"var" => unreachable,
.@"comptime" => unreachable,
.unnamed_test => true,
.@"test", .decltest => a: {
const fqn_slice = nav.fqn.toSlice(ip);
if (comp.test_filters.len > 0) {
for (comp.test_filters) |test_filter| {
if (std.mem.indexOf(u8, fqn_slice, test_filter) != null) break;
} else break :a false;
}
break :a true;
},
};
if (want_analysis) {
log.debug("type '{f}': ref test %{}", .{
Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
@intFromEnum(inst_info.inst),
});
try unit_queue.put(gpa, .wrap(.{ .nav_val = nav_id }), referencer);
// Non-fatal AstGen errors could mean this test decl failed
if (nav.status == .fully_resolved) {
try unit_queue.put(gpa, .wrap(.{ .func = nav.status.fully_resolved.val }), referencer);
}
}
}
for (zcu.namespacePtr(ns).pub_decls.keys()) |nav| {
// These are named declarations. They are analyzed only if marked `export`.
const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue;
const file = zcu.fileByIndex(inst_info.file);
const decl = file.zir.?.getDeclaration(inst_info.inst);
if (decl.linkage == .@"export") {
const unit: AnalUnit = .wrap(.{ .nav_val = nav });
if (!result.contains(unit)) {
log.debug("type '{f}': ref named %{}", .{
Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
@intFromEnum(inst_info.inst),
});
try unit_queue.put(gpa, unit, referencer);
}
}
}
for (zcu.namespacePtr(ns).priv_decls.keys()) |nav| {
// These are named declarations. They are analyzed only if marked `export`.
const inst_info = ip.getNav(nav).analysis.?.zir_index.resolveFull(ip) orelse continue;
const file = zcu.fileByIndex(inst_info.file);
const decl = file.zir.?.getDeclaration(inst_info.inst);
if (decl.linkage == .@"export") {
const unit: AnalUnit = .wrap(.{ .nav_val = nav });
if (!result.contains(unit)) {
log.debug("type '{f}': ref named %{}", .{
Type.fromInterned(ty).containerTypeName(ip).fmt(ip),
@intFromEnum(inst_info.inst),
});
try unit_queue.put(gpa, unit, referencer);
}
}
}
continue;
}
if (unit_queue.pop()) |kv| {
const unit = kv.key;
try result.putNoClobber(gpa, unit, kv.value);
// `nav_val` and `nav_ty` reference each other *implicitly* to save memory.
queue_paired: {
const other: AnalUnit = .wrap(switch (unit.unwrap()) {
.nav_val => |n| .{ .nav_ty = n },
.nav_ty => |n| .{ .nav_val = n },
.@"comptime", .type, .func, .memoized_state => break :queue_paired,
});
if (result.contains(other)) break :queue_paired;
try unit_queue.put(gpa, other, kv.value); // same reference location
}
log.debug("handle unit '{f}'", .{zcu.fmtAnalUnit(unit)});
if (zcu.reference_table.get(unit)) |first_ref_idx| {
assert(first_ref_idx != std.math.maxInt(u32));
var ref_idx = first_ref_idx;
while (ref_idx != std.math.maxInt(u32)) {
const ref = zcu.all_references.items[ref_idx];
if (!result.contains(ref.referenced)) {
log.debug("unit '{f}': ref unit '{f}'", .{
zcu.fmtAnalUnit(unit),
zcu.fmtAnalUnit(ref.referenced),
});
try unit_queue.put(gpa, ref.referenced, .{
.referencer = unit,
.src = ref.src,
.inline_frame = ref.inline_frame,
});
}
ref_idx = ref.next;
}
}
if (zcu.type_reference_table.get(unit)) |first_ref_idx| {
assert(first_ref_idx != std.math.maxInt(u32));
var ref_idx = first_ref_idx;
while (ref_idx != std.math.maxInt(u32)) {
const ref = zcu.all_type_references.items[ref_idx];
if (!checked_types.contains(ref.referenced)) {
log.debug("unit '{f}': ref type '{f}'", .{
zcu.fmtAnalUnit(unit),
Type.fromInterned(ref.referenced).containerTypeName(ip).fmt(ip),
});
try type_queue.put(gpa, ref.referenced, .{
.referencer = unit,
.src = ref.src,
.inline_frame = .none,
});
}
ref_idx = ref.next;
}
}
continue;
}
break;
}
return result;
}
pub fn analysisRoots(zcu: *Zcu) []*Package.Module {
return zcu.analysis_roots_buffer[0..zcu.analysis_roots_len];
}
pub fn fileByIndex(zcu: *const Zcu, file_index: File.Index) *File {
return zcu.intern_pool.filePtr(file_index);
}
/// Returns the struct that represents this `File`.
/// If the struct has not been created, returns `.none`.
pub fn fileRootType(zcu: *const Zcu, file_index: File.Index) InternPool.Index {
const ip = &zcu.intern_pool;
const file_index_unwrapped = file_index.unwrap(ip);
const files = ip.getLocalShared(file_index_unwrapped.tid).files.acquire();
return files.view().items(.root_type)[file_index_unwrapped.index];
}
pub fn setFileRootType(zcu: *Zcu, file_index: File.Index, root_type: InternPool.Index) void {
const ip = &zcu.intern_pool;
const file_index_unwrapped = file_index.unwrap(ip);
const files = ip.getLocalShared(file_index_unwrapped.tid).files.acquire();
files.view().items(.root_type)[file_index_unwrapped.index] = root_type;
}
pub fn navSrcLoc(zcu: *const Zcu, nav_index: InternPool.Nav.Index) LazySrcLoc {
const ip = &zcu.intern_pool;
return .{
.base_node_inst = ip.getNav(nav_index).srcInst(ip),
.offset = LazySrcLoc.Offset.nodeOffset(.zero),
};
}
pub fn typeSrcLoc(zcu: *const Zcu, ty_index: InternPool.Index) LazySrcLoc {
_ = zcu;
_ = ty_index;
@panic("TODO");
}
pub fn typeFileScope(zcu: *Zcu, ty_index: InternPool.Index) *File {
_ = zcu;
_ = ty_index;
@panic("TODO");
}
pub fn navSrcLine(zcu: *Zcu, nav_index: InternPool.Nav.Index) u32 {
const ip = &zcu.intern_pool;
const inst_info = ip.getNav(nav_index).srcInst(ip).resolveFull(ip).?;
const zir = zcu.fileByIndex(inst_info.file).zir;
return zir.?.getDeclaration(inst_info.inst).src_line;
}
pub fn navValue(zcu: *const Zcu, nav_index: InternPool.Nav.Index) Value {
return Value.fromInterned(zcu.intern_pool.getNav(nav_index).status.fully_resolved.val);
}
pub fn navFileScopeIndex(zcu: *Zcu, nav: InternPool.Nav.Index) File.Index {
const ip = &zcu.intern_pool;
return ip.getNav(nav).srcInst(ip).resolveFile(ip);
}
pub fn navFileScope(zcu: *Zcu, nav: InternPool.Nav.Index) *File {
return zcu.fileByIndex(zcu.navFileScopeIndex(nav));
}
pub fn fmtAnalUnit(zcu: *Zcu, unit: AnalUnit) std.fmt.Alt(FormatAnalUnit, formatAnalUnit) {
return .{ .data = .{ .unit = unit, .zcu = zcu } };
}
pub fn fmtDependee(zcu: *Zcu, d: InternPool.Dependee) std.fmt.Alt(FormatDependee, formatDependee) {
return .{ .data = .{ .dependee = d, .zcu = zcu } };
}
const FormatAnalUnit = struct {
unit: AnalUnit,
zcu: *Zcu,
};
fn formatAnalUnit(data: FormatAnalUnit, writer: *std.Io.Writer) std.Io.Writer.Error!void {
const zcu = data.zcu;
const ip = &zcu.intern_pool;
switch (data.unit.unwrap()) {
.@"comptime" => |cu_id| {
const cu = ip.getComptimeUnit(cu_id);
if (cu.zir_index.resolveFull(ip)) |resolved| {
const file_path = zcu.fileByIndex(resolved.file).path;
return writer.print("comptime(inst=('{f}', %{}) [{}])", .{ file_path.fmt(zcu.comp), @intFromEnum(resolved.inst), @intFromEnum(cu_id) });
} else {
return writer.print("comptime(inst=<lost> [{}])", .{@intFromEnum(cu_id)});
}
},
.nav_val => |nav| return writer.print("nav_val('{f}' [{}])", .{ ip.getNav(nav).fqn.fmt(ip), @intFromEnum(nav) }),
.nav_ty => |nav| return writer.print("nav_ty('{f}' [{}])", .{ ip.getNav(nav).fqn.fmt(ip), @intFromEnum(nav) }),
.type => |ty| return writer.print("ty('{f}' [{}])", .{ Type.fromInterned(ty).containerTypeName(ip).fmt(ip), @intFromEnum(ty) }),
.func => |func| {
const nav = zcu.funcInfo(func).owner_nav;
return writer.print("func('{f}' [{}])", .{ ip.getNav(nav).fqn.fmt(ip), @intFromEnum(func) });
},
.memoized_state => return writer.writeAll("memoized_state"),
}
}
const FormatDependee = struct { dependee: InternPool.Dependee, zcu: *Zcu };
fn formatDependee(data: FormatDependee, writer: *std.Io.Writer) std.Io.Writer.Error!void {
const zcu = data.zcu;
const ip = &zcu.intern_pool;
switch (data.dependee) {
.src_hash => |ti| {
const info = ti.resolveFull(ip) orelse {
return writer.writeAll("inst(<lost>)");
};
const file_path = zcu.fileByIndex(info.file).path;
return writer.print("inst('{f}', %{d})", .{ file_path.fmt(zcu.comp), @intFromEnum(info.inst) });
},
.nav_val => |nav| {
const fqn = ip.getNav(nav).fqn;
return writer.print("nav_val('{f}')", .{fqn.fmt(ip)});
},
.nav_ty => |nav| {
const fqn = ip.getNav(nav).fqn;
return writer.print("nav_ty('{f}')", .{fqn.fmt(ip)});
},
.interned => |ip_index| switch (ip.indexToKey(ip_index)) {
.struct_type, .union_type, .enum_type => return writer.print("type('{f}')", .{Type.fromInterned(ip_index).containerTypeName(ip).fmt(ip)}),
.func => |f| return writer.print("ies('{f}')", .{ip.getNav(f.owner_nav).fqn.fmt(ip)}),
else => unreachable,
},
.zon_file => |file| {
const file_path = zcu.fileByIndex(file).path;
return writer.print("zon_file('{f}')", .{file_path.fmt(zcu.comp)});
},
.embed_file => |ef_idx| {
const ef = ef_idx.get(zcu);
return writer.print("embed_file('{f}')", .{ef.path.fmt(zcu.comp)});
},
.namespace => |ti| {
const info = ti.resolveFull(ip) orelse {
return writer.writeAll("namespace(<lost>)");
};
const file_path = zcu.fileByIndex(info.file).path;
return writer.print("namespace('{f}', %{d})", .{ file_path.fmt(zcu.comp), @intFromEnum(info.inst) });
},
.namespace_name => |k| {
const info = k.namespace.resolveFull(ip) orelse {
return writer.print("namespace(<lost>, '{f}')", .{k.name.fmt(ip)});
};
const file_path = zcu.fileByIndex(info.file).path;
return writer.print("namespace('{f}', %{d}, '{f}')", .{ file_path.fmt(zcu.comp), @intFromEnum(info.inst), k.name.fmt(ip) });
},
.memoized_state => return writer.writeAll("memoized_state"),
}
}
/// Given the `InternPool.Index` of a function, set its resolved IES to `.none` if it
/// may be outdated. `Sema` should do this before ever loading a resolved IES.
pub fn maybeUnresolveIes(zcu: *Zcu, func_index: InternPool.Index) !void {
const unit = AnalUnit.wrap(.{ .func = func_index });
if (zcu.outdated.contains(unit) or zcu.potentially_outdated.contains(unit)) {
// We're consulting the resolved IES now, but the function is outdated, so its
// IES may have changed. We have to assume the IES is outdated and set the resolved
// set back to `.none`.
//
// This will cause `PerThread.analyzeFnBody` to mark the IES as outdated when it's
// eventually hit.
//
// Since the IES needs to be resolved, the function body will now definitely need
// re-analysis (even if the IES turns out to be the same!), so mark it as
// definitely-outdated if it's only PO.
if (zcu.potentially_outdated.fetchSwapRemove(unit)) |kv| {
const gpa = zcu.gpa;
try zcu.outdated.putNoClobber(gpa, unit, kv.value);
if (kv.value == 0) {
try zcu.outdated_ready.put(gpa, unit, {});
}
}
zcu.intern_pool.funcSetIesResolved(func_index, .none);
}
}
pub fn callconvSupported(zcu: *Zcu, cc: std.builtin.CallingConvention) union(enum) {
ok,
bad_arch: []const std.Target.Cpu.Arch, // value is allowed archs for cc
bad_backend: std.builtin.CompilerBackend, // value is current backend
} {
const target = zcu.getTarget();
const backend = target_util.zigBackend(target, zcu.comp.config.use_llvm);
switch (cc) {
.auto, .@"inline" => return .ok,
.async => return .{ .bad_backend = backend }, // nothing supports async currently
.naked => {}, // depends only on backend
else => for (cc.archs()) |allowed_arch| {
if (allowed_arch == target.cpu.arch) break;
} else return .{ .bad_arch = cc.archs() },
}
const backend_ok = switch (backend) {
.stage1 => unreachable,
.other => unreachable,
_ => unreachable,
.stage2_llvm => @import("codegen/llvm.zig").toLlvmCallConv(cc, target) != null,
.stage2_c => ok: {
if (target.cCallingConvention()) |default_c| {
if (cc.eql(default_c)) {
break :ok true;
}
}
break :ok switch (cc) {
.x86_64_sysv,
.x86_64_win,
.x86_64_vectorcall,
.x86_64_regcall_v3_sysv,
.x86_64_regcall_v4_win,
.x86_64_interrupt,
.x86_fastcall,
.x86_thiscall,
.x86_vectorcall,
.x86_regcall_v3,
.x86_regcall_v4_win,
.x86_interrupt,
.aarch64_vfabi,
.aarch64_vfabi_sve,
.arm_aapcs,
.csky_interrupt,
.riscv64_lp64_v,
.riscv32_ilp32_v,
.m68k_rtd,
.m68k_interrupt,
=> |opts| opts.incoming_stack_alignment == null,
.arm_aapcs_vfp,
=> |opts| opts.incoming_stack_alignment == null,
.arm_interrupt,
=> |opts| opts.incoming_stack_alignment == null,
.mips_interrupt,
.mips64_interrupt,
=> |opts| opts.incoming_stack_alignment == null,
.riscv32_interrupt,
.riscv64_interrupt,
=> |opts| opts.incoming_stack_alignment == null,
.x86_sysv,
.x86_win,
.x86_stdcall,
=> |opts| opts.incoming_stack_alignment == null and opts.register_params == 0,
.avr_interrupt,
.avr_signal,
=> true,
.naked => true,
else => false,
};
},
.stage2_wasm => switch (cc) {
.wasm_mvp => |opts| opts.incoming_stack_alignment == null,
else => false,
},
.stage2_arm => switch (cc) {
.arm_aapcs => |opts| opts.incoming_stack_alignment == null,
.naked => true,
else => false,
},
.stage2_x86_64 => switch (cc) {
.x86_64_sysv, .x86_64_win, .naked => true, // incoming stack alignment supported
else => false,
},
.stage2_aarch64 => switch (cc) {
.aarch64_aapcs, .aarch64_aapcs_darwin, .naked => true,
else => false,
},
.stage2_x86 => switch (cc) {
.x86_sysv,
.x86_win,
=> |opts| opts.incoming_stack_alignment == null and opts.register_params == 0,
.naked => true,
else => false,
},
.stage2_powerpc => switch (target.cpu.arch) {
.powerpc, .powerpcle => switch (cc) {
.powerpc_sysv,
.powerpc_sysv_altivec,
.powerpc_aix,
.powerpc_aix_altivec,
.naked,
=> true,
else => false,
},
.powerpc64, .powerpc64le => switch (cc) {
.powerpc64_elf,
.powerpc64_elf_altivec,
.powerpc64_elf_v2,
.naked,
=> true,
else => false,
},
else => unreachable,
},
.stage2_riscv64 => switch (cc) {
.riscv64_lp64 => |opts| opts.incoming_stack_alignment == null,
.naked => true,
else => false,
},
.stage2_sparc64 => switch (cc) {
.sparc64_sysv => |opts| opts.incoming_stack_alignment == null,
.naked => true,
else => false,
},
.stage2_spirv => switch (cc) {
.spirv_device, .spirv_kernel => true,
.spirv_fragment, .spirv_vertex => target.os.tag == .vulkan or target.os.tag == .opengl,
else => false,
},
};
if (!backend_ok) return .{ .bad_backend = backend };
return .ok;
}
pub const CodegenFailError = error{
/// Indicates the error message has been already stored at `Zcu.failed_codegen`.
CodegenFail,
OutOfMemory,
};
pub fn codegenFail(
zcu: *Zcu,
nav_index: InternPool.Nav.Index,
comptime format: []const u8,
args: anytype,
) CodegenFailError {
const msg = try Zcu.ErrorMsg.create(zcu.gpa, zcu.navSrcLoc(nav_index), format, args);
return zcu.codegenFailMsg(nav_index, msg);
}
/// Takes ownership of `msg`, even on OOM.
pub fn codegenFailMsg(zcu: *Zcu, nav_index: InternPool.Nav.Index, msg: *ErrorMsg) CodegenFailError {
const gpa = zcu.gpa;
{
zcu.comp.mutex.lock();
defer zcu.comp.mutex.unlock();
errdefer msg.deinit(gpa);
try zcu.failed_codegen.putNoClobber(gpa, nav_index, msg);
}
return error.CodegenFail;
}
/// Asserts that `zcu.failed_codegen` contains the key `nav`, with the necessary lock held.
pub fn assertCodegenFailed(zcu: *Zcu, nav: InternPool.Nav.Index) void {
zcu.comp.mutex.lock();
defer zcu.comp.mutex.unlock();
assert(zcu.failed_codegen.contains(nav));
}
pub fn codegenFailType(
zcu: *Zcu,
ty_index: InternPool.Index,
comptime format: []const u8,
args: anytype,
) CodegenFailError {
const gpa = zcu.gpa;
try zcu.failed_types.ensureUnusedCapacity(gpa, 1);
const msg = try Zcu.ErrorMsg.create(gpa, zcu.typeSrcLoc(ty_index), format, args);
zcu.failed_types.putAssumeCapacityNoClobber(ty_index, msg);
return error.CodegenFail;
}
pub fn codegenFailTypeMsg(zcu: *Zcu, ty_index: InternPool.Index, msg: *ErrorMsg) CodegenFailError {
const gpa = zcu.gpa;
{
errdefer msg.deinit(gpa);
try zcu.failed_types.ensureUnusedCapacity(gpa, 1);
}
zcu.failed_types.putAssumeCapacityNoClobber(ty_index, msg);
return error.CodegenFail;
}
/// Asserts that `zcu.multi_module_err != null`.
pub fn addFileInMultipleModulesError(
zcu: *Zcu,
eb: *std.zig.ErrorBundle.Wip,
) Allocator.Error!void {
const gpa = zcu.gpa;
const info = zcu.multi_module_err.?;
const file = info.file;
// error: file exists in modules 'root.foo' and 'root.bar'
// note: files must belong to only one module
// note: file is imported here
// note: which is imported here
// note: which is the root of module 'root.foo' imported here
// note: file is the root of module 'root.bar' imported here
const file_src = try zcu.fileByIndex(file).errorBundleWholeFileSrc(zcu, eb);
const root_msg = try eb.printString("file exists in modules '{s}' and '{s}'", .{
info.modules[0].fully_qualified_name,
info.modules[1].fully_qualified_name,
});
var notes: std.ArrayListUnmanaged(std.zig.ErrorBundle.MessageIndex) = .empty;
defer notes.deinit(gpa);
try notes.append(gpa, try eb.addErrorMessage(.{
.msg = try eb.addString("files must belong to only one module"),
.src_loc = file_src,
}));
try zcu.explainWhyFileIsInModule(eb, &notes, file, info.modules[0], info.refs[0]);
try zcu.explainWhyFileIsInModule(eb, &notes, file, info.modules[1], info.refs[1]);
try eb.addRootErrorMessage(.{
.msg = root_msg,
.src_loc = file_src,
.notes_len = @intCast(notes.items.len),
});
const notes_start = try eb.reserveNotes(@intCast(notes.items.len));
const notes_slice: []std.zig.ErrorBundle.MessageIndex = @ptrCast(eb.extra.items[notes_start..]);
@memcpy(notes_slice, notes.items);
}
fn explainWhyFileIsInModule(
zcu: *Zcu,
eb: *std.zig.ErrorBundle.Wip,
notes_out: *std.ArrayListUnmanaged(std.zig.ErrorBundle.MessageIndex),
file: File.Index,
in_module: *Package.Module,
ref: File.Reference,
) Allocator.Error!void {
const gpa = zcu.gpa;
// error: file is the root of module 'foo'
//
// error: file is imported here by the root of module 'foo'
//
// error: file is imported here
// note: which is imported here
// note: which is imported here by the root of module 'foo'
var import = switch (ref) {
.analysis_root => |mod| {
assert(mod == in_module);
try notes_out.append(gpa, try eb.addErrorMessage(.{
.msg = try eb.printString("file is the root of module '{s}'", .{mod.fully_qualified_name}),
.src_loc = try zcu.fileByIndex(file).errorBundleWholeFileSrc(zcu, eb),
}));
return;
},
.import => |import| if (import.module) |mod| {
assert(mod == in_module);
try notes_out.append(gpa, try eb.addErrorMessage(.{
.msg = try eb.printString("file is the root of module '{s}'", .{mod.fully_qualified_name}),
.src_loc = try zcu.fileByIndex(file).errorBundleWholeFileSrc(zcu, eb),
}));
return;
} else import,
};
var is_first = true;
while (true) {
const thing: []const u8 = if (is_first) "file" else "which";
is_first = false;
const importer_file = zcu.fileByIndex(import.importer);
// `errorBundleTokenSrc` expects the tree to be loaded
_ = importer_file.getTree(zcu) catch |err| {
try Compilation.unableToLoadZcuFile(zcu, eb, importer_file, err);
return; // stop the explanation early
};
const import_src = try importer_file.errorBundleTokenSrc(import.tok, zcu, eb);
const importer_ref = zcu.alive_files.get(import.importer).?;
const importer_root: ?*Package.Module = switch (importer_ref) {
.analysis_root => |mod| mod,
.import => |i| i.module,
};
if (importer_root) |m| {
try notes_out.append(gpa, try eb.addErrorMessage(.{
.msg = try eb.printString("{s} is imported here by the root of module '{s}'", .{ thing, m.fully_qualified_name }),
.src_loc = import_src,
}));
return;
}
try notes_out.append(gpa, try eb.addErrorMessage(.{
.msg = try eb.printString("{s} is imported here", .{thing}),
.src_loc = import_src,
}));
import = importer_ref.import;
}
}
const TrackedUnitSema = struct {
/// `null` means we created the node, so should end it.
old_name: ?[std.Progress.Node.max_name_len]u8,
old_analysis_timer: ?Compilation.Timer,
analysis_timer_decl: ?InternPool.TrackedInst.Index,
pub fn end(tus: TrackedUnitSema, zcu: *Zcu) void {
const comp = zcu.comp;
if (tus.old_name) |old_name| {
zcu.sema_prog_node.completeOne(); // we're just renaming, but it's effectively completion
zcu.cur_sema_prog_node.setName(&old_name);
} else {
zcu.cur_sema_prog_node.end();
zcu.cur_sema_prog_node = .none;
}
report_time: {
const sema_ns = zcu.cur_analysis_timer.?.finish() orelse break :report_time;
const zir_decl = tus.analysis_timer_decl orelse break :report_time;
comp.mutex.lock();
defer comp.mutex.unlock();
comp.time_report.?.stats.cpu_ns_sema += sema_ns;
const gop = comp.time_report.?.decl_sema_info.getOrPut(comp.gpa, zir_decl) catch |err| switch (err) {
error.OutOfMemory => {
comp.setAllocFailure();
break :report_time;
},
};
if (!gop.found_existing) gop.value_ptr.* = .{ .ns = 0, .count = 0 };
gop.value_ptr.ns += sema_ns;
gop.value_ptr.count += 1;
}
zcu.cur_analysis_timer = tus.old_analysis_timer;
if (zcu.cur_analysis_timer) |*t| t.@"resume"();
}
};
pub fn trackUnitSema(zcu: *Zcu, name: []const u8, zir_inst: ?InternPool.TrackedInst.Index) TrackedUnitSema {
if (zcu.cur_analysis_timer) |*t| t.pause();
const old_analysis_timer = zcu.cur_analysis_timer;
zcu.cur_analysis_timer = zcu.comp.startTimer();
const old_name: ?[std.Progress.Node.max_name_len]u8 = old_name: {
if (zcu.cur_sema_prog_node.index == .none) {
zcu.cur_sema_prog_node = zcu.sema_prog_node.start(name, 0);
break :old_name null;
}
const old_name = zcu.cur_sema_prog_node.getName();
zcu.cur_sema_prog_node.setName(name);
break :old_name old_name;
};
return .{
.old_name = old_name,
.old_analysis_timer = old_analysis_timer,
.analysis_timer_decl = zir_inst,
};
}