zig/src/Sema.zig
Jay Petacat 47bb210317 Sema: Allow small integer types to coerce to floats
If the float can store all possible values of the integer without
rounding, coercion is allowed. The integer's precision must be less than
or equal to the float's significand precision.

Closes #18614
2025-12-04 20:40:13 +01:00

37761 lines
1.5 MiB

//! Semantic analysis of ZIR instructions.
//! Shared to every Block. Stored on the stack.
//! State used for compiling a ZIR into AIR.
//! Transforms untyped ZIR instructions into semantically-analyzed AIR instructions.
//! Does type checking, comptime control flow, and safety-check generation.
//! This is the the heart of the Zig compiler.
const std = @import("std");
const math = std.math;
const mem = std.mem;
const Allocator = mem.Allocator;
const assert = std.debug.assert;
const log = std.log.scoped(.sema);
const Sema = @This();
const Value = @import("Value.zig");
const MutableValue = @import("mutable_value.zig").MutableValue;
const Type = @import("Type.zig");
const Air = @import("Air.zig");
const Zir = std.zig.Zir;
const Zcu = @import("Zcu.zig");
const trace = @import("tracy.zig").trace;
const Namespace = Zcu.Namespace;
const CompileError = Zcu.CompileError;
const SemaError = Zcu.SemaError;
const LazySrcLoc = Zcu.LazySrcLoc;
const RangeSet = @import("RangeSet.zig");
const target_util = @import("target.zig");
const Package = @import("Package.zig");
const crash_report = @import("crash_report.zig");
const build_options = @import("build_options");
const Compilation = @import("Compilation.zig");
const InternPool = @import("InternPool.zig");
const Alignment = InternPool.Alignment;
const AnalUnit = InternPool.AnalUnit;
const ComptimeAllocIndex = InternPool.ComptimeAllocIndex;
const Cache = std.Build.Cache;
const LowerZon = @import("Sema/LowerZon.zig");
const arith = @import("Sema/arith.zig");
pt: Zcu.PerThread,
/// Alias to `zcu.gpa`.
gpa: Allocator,
/// Points to the temporary arena allocator of the Sema.
/// This arena will be cleared when the sema is destroyed.
arena: Allocator,
code: Zir,
air_instructions: std.MultiArrayList(Air.Inst) = .{},
air_extra: std.ArrayList(u32) = .empty,
/// Maps ZIR to AIR.
inst_map: InstMap = .{},
/// The "owner" of a `Sema` represents the root "thing" that is being analyzed.
/// This does not change throughout the entire lifetime of a `Sema`. For instance,
/// when analyzing a runtime function body, this is always `func` of that function,
/// even if an inline/comptime function call is being analyzed.
owner: AnalUnit,
/// The function this ZIR code is the body of, according to the source code.
/// This starts out the same as `sema.owner.func` if applicable, and then diverges
/// in the case of an inline or comptime function call.
/// This could be `none`, a `func_decl`, or a `func_instance`.
func_index: InternPool.Index,
/// Whether the type of func_index has a calling convention of `.naked`.
func_is_naked: bool,
/// Used to restore the error return trace when returning a non-error from a function.
error_return_trace_index_on_fn_entry: Air.Inst.Ref = .none,
comptime_err_ret_trace: *std.array_list.Managed(LazySrcLoc),
/// When semantic analysis needs to know the return type of the function whose body
/// is being analyzed, this `Type` should be used instead of going through `func`.
/// This will correctly handle the case of a comptime/inline function call of a
/// generic function which uses a type expression for the return type.
/// The type will be `void` in the case that `func` is `null`.
fn_ret_ty: Type,
/// In case of the return type being an error union with an inferred error
/// set, this is the inferred error set. `null` otherwise. Allocated with
/// `Sema.arena`.
fn_ret_ty_ies: ?*InferredErrorSet,
branch_quota: u32 = default_branch_quota,
branch_count: u32 = 0,
/// Populated when returning `error.ComptimeBreak`. Used to communicate the
/// break instruction up the stack to find the corresponding Block.
comptime_break_inst: Zir.Inst.Index = undefined,
/// These are lazily created runtime blocks from block_inline instructions.
/// They are created when an break_inline passes through a runtime condition, because
/// Sema must convert comptime control flow to runtime control flow, which means
/// breaking from a block.
post_hoc_blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, *LabeledBlock) = .empty,
/// Populated with the last compile error created.
err: ?*Zcu.ErrorMsg = null,
/// The temporary arena is used for the memory of the `InferredAlloc` values
/// here so the values can be dropped without any cleanup.
unresolved_inferred_allocs: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, InferredAlloc) = .empty,
/// Links every pointer derived from a base `alloc` back to that `alloc`. Used
/// to detect comptime-known `const`s.
/// TODO: ZIR liveness analysis would allow us to remove elements from this map.
base_allocs: std.AutoHashMapUnmanaged(Air.Inst.Index, Air.Inst.Index) = .empty,
/// Runtime `alloc`s are placed in this map to track all comptime-known writes
/// before the corresponding `make_ptr_const` instruction.
/// If any store to the alloc depends on a runtime condition or stores a runtime
/// value, the corresponding element in this map is erased, to indicate that the
/// alloc is not comptime-known.
/// If the alloc remains in this map when `make_ptr_const` is reached, its value
/// is comptime-known, and all stores to the pointer must be applied at comptime
/// to determine the comptime value.
/// Backed by gpa.
maybe_comptime_allocs: std.AutoHashMapUnmanaged(Air.Inst.Index, MaybeComptimeAlloc) = .empty,
/// Comptime-mutable allocs, and any comptime allocs which reference it, are
/// stored as elements of this array.
/// Pointers to such memory are represented via an index into this array.
/// Backed by gpa.
comptime_allocs: std.ArrayList(ComptimeAlloc) = .empty,
/// A list of exports performed by this analysis. After this `Sema` terminates,
/// these are flushed to `Zcu.single_exports` or `Zcu.multi_exports`.
exports: std.ArrayList(Zcu.Export) = .empty,
/// All references registered so far by this `Sema`. This is a temporary duplicate
/// of data stored in `Zcu.all_references`. It exists to avoid adding references to
/// a given `AnalUnit` multiple times.
references: std.AutoArrayHashMapUnmanaged(AnalUnit, void) = .empty,
type_references: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .empty,
/// All dependencies registered so far by this `Sema`. This is a temporary duplicate
/// of the main dependency data. It exists to avoid adding dependencies to a given
/// `AnalUnit` multiple times.
dependencies: std.AutoArrayHashMapUnmanaged(InternPool.Dependee, void) = .empty,
/// Whether memoization of this call is permitted. Operations with side effects global
/// to the `Sema`, such as `@setEvalBranchQuota`, set this to `false`. It is observed
/// by `analyzeCall`.
allow_memoize: bool = true,
/// The `BranchHint` for the current branch of runtime control flow.
/// This state is on `Sema` so that `cold` hints can be propagated up through blocks with less special handling.
branch_hint: ?std.builtin.BranchHint = null,
const RuntimeIndex = enum(u32) {
zero = 0,
comptime_field_ptr = std.math.maxInt(u32),
_,
pub fn increment(ri: *RuntimeIndex) void {
ri.* = @enumFromInt(@intFromEnum(ri.*) + 1);
}
};
const MaybeComptimeAlloc = struct {
/// The runtime index of the `alloc` instruction.
runtime_index: RuntimeIndex,
/// Backed by sema.arena. Tracks all comptime-known stores to this `alloc`. Due to
/// RLS, a single comptime-known allocation may have arbitrarily many stores.
/// This list also contains `set_union_tag`, `optional_payload_ptr_set`, and
/// `errunion_payload_ptr_set` instructions.
/// If the instruction is one of these three tags, `src` may be `.unneeded`.
stores: std.MultiArrayList(struct {
inst: Air.Inst.Index,
src: LazySrcLoc,
}) = .{},
};
const ComptimeAlloc = struct {
val: MutableValue,
is_const: bool,
src: LazySrcLoc,
/// `.none` indicates that the alignment is the natural alignment of `val`.
alignment: Alignment,
/// This is the `runtime_index` at the point of this allocation. If an store
/// to this alloc ever occurs with a runtime index greater than this one, it
/// is behind a runtime condition, so a compile error will be emitted.
runtime_index: RuntimeIndex,
};
/// `src` may be `null` if `is_const` will be set.
fn newComptimeAlloc(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, alignment: Alignment) !ComptimeAllocIndex {
const pt = sema.pt;
const init_val = try sema.typeHasOnePossibleValue(ty) orelse try pt.undefValue(ty);
const idx = sema.comptime_allocs.items.len;
try sema.comptime_allocs.append(sema.gpa, .{
.val = .{ .interned = init_val.toIntern() },
.is_const = false,
.src = src,
.alignment = alignment,
.runtime_index = block.runtime_index,
});
return @enumFromInt(idx);
}
pub fn getComptimeAlloc(sema: *Sema, idx: ComptimeAllocIndex) *ComptimeAlloc {
return &sema.comptime_allocs.items[@intFromEnum(idx)];
}
pub const default_branch_quota = 1000;
pub const InferredErrorSet = struct {
/// The function body from which this error set originates.
/// This is `none` in the case of a comptime/inline function call, corresponding to
/// `InternPool.Index.adhoc_inferred_error_set_type`.
/// The function's resolved error set is not set until analysis of the
/// function body completes.
func: InternPool.Index,
/// All currently known errors that this error set contains. This includes
/// direct additions via `return error.Foo;`, and possibly also errors that
/// are returned from any dependent functions.
errors: NameMap = .{},
/// Other inferred error sets which this inferred error set should include.
inferred_error_sets: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .empty,
/// The regular error set created by resolving this inferred error set.
resolved: InternPool.Index = .none,
pub const NameMap = std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void);
pub fn addErrorSet(
self: *InferredErrorSet,
err_set_ty: Type,
ip: *InternPool,
arena: Allocator,
) !void {
switch (err_set_ty.toIntern()) {
.anyerror_type => self.resolved = .anyerror_type,
.adhoc_inferred_error_set_type => {}, // Adding an inferred error set to itself.
else => switch (ip.indexToKey(err_set_ty.toIntern())) {
.error_set_type => |error_set_type| {
for (error_set_type.names.get(ip)) |name| {
try self.errors.put(arena, name, {});
}
},
.inferred_error_set_type => {
try self.inferred_error_sets.put(arena, err_set_ty.toIntern(), {});
},
else => unreachable,
},
}
}
};
/// Stores the mapping from `Zir.Inst.Index -> Air.Inst.Ref`, which is used by sema to resolve
/// instructions during analysis.
/// Instead of a hash table approach, InstMap is simply a slice that is indexed into using the
/// zir instruction index and a start offset. An index is not present in the map if the value
/// at the index is `Air.Inst.Ref.none`.
/// `ensureSpaceForInstructions` can be called to force InstMap to have a mapped range that
/// includes all instructions in a slice. After calling this function, `putAssumeCapacity*` can
/// be called safely for any of the instructions passed in.
pub const InstMap = struct {
items: []Air.Inst.Ref = &[_]Air.Inst.Ref{},
start: Zir.Inst.Index = @enumFromInt(0),
pub fn deinit(map: InstMap, allocator: mem.Allocator) void {
allocator.free(map.items);
}
pub fn get(map: InstMap, key: Zir.Inst.Index) ?Air.Inst.Ref {
if (!map.contains(key)) return null;
return map.items[@intFromEnum(key) - @intFromEnum(map.start)];
}
pub fn putAssumeCapacity(
map: *InstMap,
key: Zir.Inst.Index,
ref: Air.Inst.Ref,
) void {
map.items[@intFromEnum(key) - @intFromEnum(map.start)] = ref;
}
pub fn putAssumeCapacityNoClobber(
map: *InstMap,
key: Zir.Inst.Index,
ref: Air.Inst.Ref,
) void {
assert(!map.contains(key));
map.putAssumeCapacity(key, ref);
}
pub const GetOrPutResult = struct {
value_ptr: *Air.Inst.Ref,
found_existing: bool,
};
pub fn getOrPutAssumeCapacity(
map: *InstMap,
key: Zir.Inst.Index,
) GetOrPutResult {
const index = @intFromEnum(key) - @intFromEnum(map.start);
return GetOrPutResult{
.value_ptr = &map.items[index],
.found_existing = map.items[index] != .none,
};
}
pub fn remove(map: InstMap, key: Zir.Inst.Index) bool {
if (!map.contains(key)) return false;
map.items[@intFromEnum(key) - @intFromEnum(map.start)] = .none;
return true;
}
pub fn contains(map: InstMap, key: Zir.Inst.Index) bool {
return map.items[@intFromEnum(key) - @intFromEnum(map.start)] != .none;
}
pub fn ensureSpaceForInstructions(
map: *InstMap,
allocator: mem.Allocator,
insts: []const Zir.Inst.Index,
) !void {
const start, const end = mem.minMax(u32, @ptrCast(insts));
const map_start = @intFromEnum(map.start);
if (map_start <= start and end < map.items.len + map_start)
return;
const old_start = if (map.items.len == 0) start else map_start;
var better_capacity = map.items.len;
var better_start = old_start;
while (true) {
const extra_capacity = better_capacity / 2 + 16;
better_capacity += extra_capacity;
better_start -|= @intCast(extra_capacity / 2);
if (better_start <= start and end < better_capacity + better_start)
break;
}
const start_diff = old_start - better_start;
const new_items = try allocator.alloc(Air.Inst.Ref, better_capacity);
@memset(new_items[0..start_diff], .none);
@memcpy(new_items[start_diff..][0..map.items.len], map.items);
@memset(new_items[start_diff + map.items.len ..], .none);
allocator.free(map.items);
map.items = new_items;
map.start = @enumFromInt(better_start);
}
};
/// This is the context needed to semantically analyze ZIR instructions and
/// produce AIR instructions.
/// This is a temporary structure stored on the stack; references to it are valid only
/// during semantic analysis of the block.
pub const Block = struct {
parent: ?*Block,
/// Shared among all child blocks.
sema: *Sema,
/// The namespace to use for lookups from this source block
namespace: InternPool.NamespaceIndex,
/// The AIR instructions generated for this block.
instructions: std.ArrayList(Air.Inst.Index),
// `param` instructions are collected here to be used by the `func` instruction.
/// When doing a generic function instantiation, this array collects a type
/// for each *runtime-known* parameter. This array corresponds to the instance
/// function type, while `Sema.comptime_args` corresponds to the generic owner
/// function type.
/// This memory is allocated by a parent `Sema` in the temporary arena, and is
/// used to add a `func_instance` into the `InternPool`.
params: std.MultiArrayList(Param) = .{},
label: ?*Label = null,
inlining: ?*Inlining,
/// If runtime_index is not 0 then one of these is guaranteed to be non null.
runtime_cond: ?LazySrcLoc = null,
runtime_loop: ?LazySrcLoc = null,
/// Non zero if a non-inline loop or a runtime conditional have been encountered.
/// Stores to comptime variables are only allowed when var.runtime_index <= runtime_index.
runtime_index: RuntimeIndex = .zero,
inline_block: Zir.Inst.OptionalIndex = .none,
comptime_reason: ?BlockComptimeReason = null,
is_typeof: bool = false,
/// Keep track of the active error return trace index around blocks so that we can correctly
/// pop the error trace upon block exit.
error_return_trace_index: Air.Inst.Ref = .none,
/// when null, it is determined by build mode, changed by @setRuntimeSafety
want_safety: ?bool = null,
/// What mode to generate float operations in, set by @setFloatMode
float_mode: std.builtin.FloatMode = .strict,
c_import_buf: ?*std.array_list.Managed(u8) = null,
/// If not `null`, this boolean is set when a `dbg_var_ptr`, `dbg_var_val`, or `dbg_arg_inline`.
/// instruction is emitted. It signals that the innermost lexically
/// enclosing `block`/`block_inline` should be translated into a real AIR
/// `block` in order for codegen to match lexical scoping for debug vars.
need_debug_scope: ?*bool = null,
/// Relative source locations encountered while traversing this block should be
/// treated as relative to the AST node of this ZIR instruction.
src_base_inst: InternPool.TrackedInst.Index,
/// The name of the current "context" for naming namespace types.
/// The interpretation of this depends on the name strategy in ZIR, but the name
/// is always incorporated into the type name somehow.
/// See `Sema.createTypeName`.
type_name_ctx: InternPool.NullTerminatedString,
/// Create a `LazySrcLoc` based on an `Offset` from the code being analyzed in this block.
/// Specifically, the given `Offset` is treated as relative to `block.src_base_inst`.
pub fn src(block: Block, offset: LazySrcLoc.Offset) LazySrcLoc {
return .{
.base_node_inst = block.src_base_inst,
.offset = offset,
};
}
fn isComptime(block: Block) bool {
return block.comptime_reason != null;
}
fn builtinCallArgSrc(block: *Block, builtin_call_node: std.zig.Ast.Node.Offset, arg_index: u32) LazySrcLoc {
return block.src(.{ .node_offset_builtin_call_arg = .{
.builtin_call_node = builtin_call_node,
.arg_index = arg_index,
} });
}
pub fn nodeOffset(block: Block, node_offset: std.zig.Ast.Node.Offset) LazySrcLoc {
return block.src(LazySrcLoc.Offset.nodeOffset(node_offset));
}
fn tokenOffset(block: Block, tok_offset: std.zig.Ast.TokenOffset) LazySrcLoc {
return block.src(.{ .token_offset = tok_offset });
}
const Param = struct {
/// `none` means `anytype`.
ty: InternPool.Index,
is_comptime: bool,
name: Zir.NullTerminatedString,
};
/// This `Block` maps a block ZIR instruction to the corresponding
/// AIR instruction for break instruction analysis.
pub const Label = struct {
zir_block: Zir.Inst.Index,
merges: Merges,
};
/// This `Block` indicates that an inline function call is happening
/// and return instructions should be analyzed as a break instruction
/// to this AIR block instruction.
/// It is shared among all the blocks in an inline or comptime called
/// function.
pub const Inlining = struct {
call_block: *Block,
call_src: LazySrcLoc,
func: InternPool.Index,
/// Populated lazily by `refFrame`.
ref_frame: Zcu.InlineReferenceFrame.Index.Optional = .none,
/// If `true`, the following fields are `undefined`. This doesn't represent a true inline
/// call, but rather a generic call analyzing the instantiation's generic type bodies.
is_generic_instantiation: bool,
has_comptime_args: bool,
comptime_result: Air.Inst.Ref,
merges: Merges,
fn refFrame(inlining: *Inlining, zcu: *Zcu) Allocator.Error!Zcu.InlineReferenceFrame.Index {
if (inlining.ref_frame == .none) {
inlining.ref_frame = (try zcu.addInlineReferenceFrame(.{
.callee = inlining.func,
.call_src = inlining.call_src,
.parent = if (inlining.call_block.inlining) |parent_inlining| p: {
break :p (try parent_inlining.refFrame(zcu)).toOptional();
} else .none,
})).toOptional();
}
return inlining.ref_frame.unwrap().?;
}
};
pub const Merges = struct {
block_inst: Air.Inst.Index,
/// Separate array list from break_inst_list so that it can be passed directly
/// to resolvePeerTypes.
results: std.ArrayList(Air.Inst.Ref),
/// Keeps track of the break instructions so that the operand can be replaced
/// if we need to add type coercion at the end of block analysis.
/// Same indexes, capacity, length as `results`.
br_list: std.ArrayList(Air.Inst.Index),
/// Keeps the source location of the rhs operand of the break instruction,
/// to enable more precise compile errors.
/// Same indexes, capacity, length as `results`.
src_locs: std.ArrayList(?LazySrcLoc),
/// Most blocks do not utilize this field. When it is used, its use is
/// contextual. The possible uses are as follows:
/// * for a `switch_block[_ref]`, this refers to dummy `br` instructions
/// which correspond to `switch_continue` ZIR. The switch logic will
/// rewrite these to appropriate AIR switch dispatches.
extra_insts: std.ArrayList(Air.Inst.Index) = .empty,
/// Same indexes, capacity, length as `extra_insts`.
extra_src_locs: std.ArrayList(LazySrcLoc) = .empty,
pub fn deinit(merges: *@This(), allocator: Allocator) void {
merges.results.deinit(allocator);
merges.br_list.deinit(allocator);
merges.src_locs.deinit(allocator);
merges.extra_insts.deinit(allocator);
merges.extra_src_locs.deinit(allocator);
}
};
pub fn makeSubBlock(parent: *Block) Block {
return .{
.parent = parent,
.sema = parent.sema,
.namespace = parent.namespace,
.instructions = .{},
.label = null,
.inlining = parent.inlining,
.comptime_reason = parent.comptime_reason,
.is_typeof = parent.is_typeof,
.runtime_cond = parent.runtime_cond,
.runtime_loop = parent.runtime_loop,
.runtime_index = parent.runtime_index,
.want_safety = parent.want_safety,
.float_mode = parent.float_mode,
.c_import_buf = parent.c_import_buf,
.error_return_trace_index = parent.error_return_trace_index,
.need_debug_scope = parent.need_debug_scope,
.src_base_inst = parent.src_base_inst,
.type_name_ctx = parent.type_name_ctx,
};
}
fn wantSafeTypes(block: *const Block) bool {
return block.want_safety orelse switch (block.sema.pt.zcu.optimizeMode()) {
.Debug => true,
.ReleaseSafe => true,
.ReleaseFast => false,
.ReleaseSmall => false,
};
}
fn wantSafety(block: *const Block) bool {
if (block.isComptime()) return false; // runtime safety checks are pointless in comptime blocks
return block.want_safety orelse switch (block.sema.pt.zcu.optimizeMode()) {
.Debug => true,
.ReleaseSafe => true,
.ReleaseFast => false,
.ReleaseSmall => false,
};
}
pub fn getFileScope(block: *Block, zcu: *Zcu) *Zcu.File {
return zcu.fileByIndex(getFileScopeIndex(block, zcu));
}
pub fn getFileScopeIndex(block: *Block, zcu: *Zcu) Zcu.File.Index {
return zcu.namespacePtr(block.namespace).file_scope;
}
fn addTy(
block: *Block,
tag: Air.Inst.Tag,
ty: Type,
) error{OutOfMemory}!Air.Inst.Ref {
return block.addInst(.{
.tag = tag,
.data = .{ .ty = ty },
});
}
fn addTyOp(
block: *Block,
tag: Air.Inst.Tag,
ty: Type,
operand: Air.Inst.Ref,
) error{OutOfMemory}!Air.Inst.Ref {
return block.addInst(.{
.tag = tag,
.data = .{ .ty_op = .{
.ty = Air.internedToRef(ty.toIntern()),
.operand = operand,
} },
});
}
fn addBitCast(block: *Block, ty: Type, operand: Air.Inst.Ref) Allocator.Error!Air.Inst.Ref {
return block.addInst(.{
.tag = .bitcast,
.data = .{ .ty_op = .{
.ty = Air.internedToRef(ty.toIntern()),
.operand = operand,
} },
});
}
fn addNoOp(block: *Block, tag: Air.Inst.Tag) error{OutOfMemory}!Air.Inst.Ref {
return block.addInst(.{
.tag = tag,
.data = .{ .no_op = {} },
});
}
fn addUnOp(
block: *Block,
tag: Air.Inst.Tag,
operand: Air.Inst.Ref,
) error{OutOfMemory}!Air.Inst.Ref {
return block.addInst(.{
.tag = tag,
.data = .{ .un_op = operand },
});
}
fn addBr(
block: *Block,
target_block: Air.Inst.Index,
operand: Air.Inst.Ref,
) error{OutOfMemory}!Air.Inst.Ref {
return block.addInst(.{
.tag = .br,
.data = .{ .br = .{
.block_inst = target_block,
.operand = operand,
} },
});
}
fn addBinOp(
block: *Block,
tag: Air.Inst.Tag,
lhs: Air.Inst.Ref,
rhs: Air.Inst.Ref,
) error{OutOfMemory}!Air.Inst.Ref {
return block.addInst(.{
.tag = tag,
.data = .{ .bin_op = .{
.lhs = lhs,
.rhs = rhs,
} },
});
}
fn addStructFieldPtr(
block: *Block,
struct_ptr: Air.Inst.Ref,
field_index: u32,
ptr_field_ty: Type,
) !Air.Inst.Ref {
const ty = Air.internedToRef(ptr_field_ty.toIntern());
const tag: Air.Inst.Tag = switch (field_index) {
0 => .struct_field_ptr_index_0,
1 => .struct_field_ptr_index_1,
2 => .struct_field_ptr_index_2,
3 => .struct_field_ptr_index_3,
else => {
return block.addInst(.{
.tag = .struct_field_ptr,
.data = .{ .ty_pl = .{
.ty = ty,
.payload = try block.sema.addExtra(Air.StructField{
.struct_operand = struct_ptr,
.field_index = field_index,
}),
} },
});
},
};
return block.addInst(.{
.tag = tag,
.data = .{ .ty_op = .{
.ty = ty,
.operand = struct_ptr,
} },
});
}
fn addStructFieldVal(
block: *Block,
struct_val: Air.Inst.Ref,
field_index: u32,
field_ty: Type,
) !Air.Inst.Ref {
return block.addInst(.{
.tag = .struct_field_val,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(field_ty.toIntern()),
.payload = try block.sema.addExtra(Air.StructField{
.struct_operand = struct_val,
.field_index = field_index,
}),
} },
});
}
fn addSliceElemPtr(
block: *Block,
slice: Air.Inst.Ref,
elem_index: Air.Inst.Ref,
elem_ptr_ty: Type,
) !Air.Inst.Ref {
return block.addInst(.{
.tag = .slice_elem_ptr,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(elem_ptr_ty.toIntern()),
.payload = try block.sema.addExtra(Air.Bin{
.lhs = slice,
.rhs = elem_index,
}),
} },
});
}
fn addPtrElemPtr(
block: *Block,
array_ptr: Air.Inst.Ref,
elem_index: Air.Inst.Ref,
elem_ptr_ty: Type,
) !Air.Inst.Ref {
const ty_ref = Air.internedToRef(elem_ptr_ty.toIntern());
return block.addPtrElemPtrTypeRef(array_ptr, elem_index, ty_ref);
}
fn addPtrElemPtrTypeRef(
block: *Block,
array_ptr: Air.Inst.Ref,
elem_index: Air.Inst.Ref,
elem_ptr_ty: Air.Inst.Ref,
) !Air.Inst.Ref {
return block.addInst(.{
.tag = .ptr_elem_ptr,
.data = .{ .ty_pl = .{
.ty = elem_ptr_ty,
.payload = try block.sema.addExtra(Air.Bin{
.lhs = array_ptr,
.rhs = elem_index,
}),
} },
});
}
fn addCmpVector(block: *Block, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, cmp_op: std.math.CompareOperator) !Air.Inst.Ref {
const sema = block.sema;
const pt = sema.pt;
const zcu = pt.zcu;
return block.addInst(.{
.tag = if (block.float_mode == .optimized) .cmp_vector_optimized else .cmp_vector,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef((try pt.vectorType(.{
.len = sema.typeOf(lhs).vectorLen(zcu),
.child = .bool_type,
})).toIntern()),
.payload = try sema.addExtra(Air.VectorCmp{
.lhs = lhs,
.rhs = rhs,
.op = Air.VectorCmp.encodeOp(cmp_op),
}),
} },
});
}
fn addReduce(block: *Block, operand: Air.Inst.Ref, operation: std.builtin.ReduceOp) !Air.Inst.Ref {
const sema = block.sema;
const zcu = sema.pt.zcu;
const allow_optimized = switch (sema.typeOf(operand).childType(zcu).zigTypeTag(zcu)) {
.float => true,
.bool, .int => false,
else => unreachable,
};
return block.addInst(.{
.tag = if (allow_optimized and block.float_mode == .optimized) .reduce_optimized else .reduce,
.data = .{ .reduce = .{
.operand = operand,
.operation = operation,
} },
});
}
fn addAggregateInit(
block: *Block,
aggregate_ty: Type,
elements: []const Air.Inst.Ref,
) !Air.Inst.Ref {
const sema = block.sema;
const ty_ref = Air.internedToRef(aggregate_ty.toIntern());
try sema.air_extra.ensureUnusedCapacity(sema.gpa, elements.len);
const extra_index: u32 = @intCast(sema.air_extra.items.len);
sema.appendRefsAssumeCapacity(elements);
return block.addInst(.{
.tag = .aggregate_init,
.data = .{ .ty_pl = .{
.ty = ty_ref,
.payload = extra_index,
} },
});
}
fn addUnionInit(
block: *Block,
union_ty: Type,
field_index: u32,
init: Air.Inst.Ref,
) !Air.Inst.Ref {
return block.addInst(.{
.tag = .union_init,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(union_ty.toIntern()),
.payload = try block.sema.addExtra(Air.UnionInit{
.field_index = field_index,
.init = init,
}),
} },
});
}
pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
return (try block.addInstAsIndex(inst)).toRef();
}
pub fn addInstAsIndex(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
const sema = block.sema;
const gpa = sema.gpa;
try sema.air_instructions.ensureUnusedCapacity(gpa, 1);
try block.instructions.ensureUnusedCapacity(gpa, 1);
const result_index: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
sema.air_instructions.appendAssumeCapacity(inst);
block.instructions.appendAssumeCapacity(result_index);
return result_index;
}
/// Insert an instruction into the block at `index`. Moves all following
/// instructions forward in the block to make room. Operation is O(N).
pub fn insertInst(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
return (try block.insertInstAsIndex(index, inst)).toRef();
}
pub fn insertInstAsIndex(block: *Block, index: Air.Inst.Index, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
const sema = block.sema;
const gpa = sema.gpa;
try sema.air_instructions.ensureUnusedCapacity(gpa, 1);
const result_index: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
sema.air_instructions.appendAssumeCapacity(inst);
try block.instructions.insert(gpa, @intFromEnum(index), result_index);
return result_index;
}
pub fn ownerModule(block: Block) *Package.Module {
const zcu = block.sema.pt.zcu;
return zcu.namespacePtr(block.namespace).fileScope(zcu).mod.?;
}
fn trackZir(block: *Block, inst: Zir.Inst.Index) Allocator.Error!InternPool.TrackedInst.Index {
const pt = block.sema.pt;
block.sema.code.assertTrackable(inst);
return pt.zcu.intern_pool.trackZir(pt.zcu.gpa, pt.tid, .{
.file = block.getFileScopeIndex(pt.zcu),
.inst = inst,
});
}
/// Returns the `*Block` that should be passed to `Sema.failWithOwnedErrorMsg`, because all inline
/// calls below it have already been reported with "called at comptime from here" notes.
fn explainWhyBlockIsComptime(start_block: *Block, err_msg: *Zcu.ErrorMsg) !*Block {
const sema = start_block.sema;
var block = start_block;
while (true) {
switch (block.comptime_reason.?) {
.inlining_parent => {
const inlining = block.inlining.?;
try sema.errNote(inlining.call_src, err_msg, "called at comptime from here", .{});
block = inlining.call_block;
},
.reason => |r| {
try r.r.explain(sema, r.src, err_msg);
return block;
},
}
}
}
};
/// Represents the reason we are resolving a value or evaluating code at comptime.
/// Most reasons are represented by a `std.zig.SimpleComptimeReason`, which provides a plain message.
const ComptimeReason = union(enum) {
/// Evaluating at comptime for a reason in the `std.zig.SimpleComptimeReason` enum.
simple: std.zig.SimpleComptimeReason,
/// Evaluating at comptime because of a comptime-only type. This field is separate so that
/// the type in question can be included in the error message. AstGen could never emit this
/// reason, because it knows nothing of types.
/// The format string looks like "foo '{f}' bar", where "{f}" is the comptime-only type.
/// We will then explain why this type is comptime-only.
comptime_only: struct {
ty: Type,
msg: enum {
union_init,
struct_init,
tuple_init,
},
},
/// Like `comptime_only`, but for a parameter type.
/// Includes a "parameter type declared here" note.
comptime_only_param_ty: struct {
ty: Type,
param_ty_src: LazySrcLoc,
},
/// Like `comptime_only`, but for a return type.
/// Includes a "return type declared here" note.
comptime_only_ret_ty: struct {
ty: Type,
is_generic_inst: bool,
ret_ty_src: LazySrcLoc,
},
/// Evaluating at comptime because we're evaluating an argument to a parameter marked `comptime`.
comptime_param: struct {
comptime_src: LazySrcLoc,
},
fn explain(reason: ComptimeReason, sema: *Sema, src: LazySrcLoc, err_msg: *Zcu.ErrorMsg) !void {
switch (reason) {
.simple => |simple| {
try sema.errNote(src, err_msg, "{s}", .{simple.message()});
},
.comptime_only => |co| {
const pre, const post = switch (co.msg) {
.union_init => .{ "initializer of comptime-only union", "must be comptime-known" },
.struct_init => .{ "initializer of comptime-only struct", "must be comptime-known" },
.tuple_init => .{ "initializer of comptime-only tuple", "must be comptime-known" },
};
try sema.errNote(src, err_msg, "{s} '{f}' {s}", .{ pre, co.ty.fmt(sema.pt), post });
try sema.explainWhyTypeIsComptime(err_msg, src, co.ty);
},
.comptime_only_param_ty => |co| {
try sema.errNote(src, err_msg, "argument to parameter with comptime-only type '{f}' must be comptime-known", .{co.ty.fmt(sema.pt)});
try sema.errNote(co.param_ty_src, err_msg, "parameter type declared here", .{});
try sema.explainWhyTypeIsComptime(err_msg, src, co.ty);
},
.comptime_only_ret_ty => |co| {
const function_with: []const u8 = if (co.is_generic_inst) "generic function instantiated with" else "function with";
try sema.errNote(src, err_msg, "call to {s} comptime-only return type '{f}' is evaluated at comptime", .{ function_with, co.ty.fmt(sema.pt) });
try sema.errNote(co.ret_ty_src, err_msg, "return type declared here", .{});
try sema.explainWhyTypeIsComptime(err_msg, src, co.ty);
},
.comptime_param => |cp| {
try sema.errNote(src, err_msg, "argument to comptime parameter must be comptime-known", .{});
try sema.errNote(cp.comptime_src, err_msg, "parameter declared comptime here", .{});
},
}
}
};
/// Represents the reason a `Block` is being evaluated at comptime.
const BlockComptimeReason = union(enum) {
/// This block inherits being comptime-only from the `inlining` call site.
inlining_parent,
/// Comptime evaluation began somewhere in the current function for a given `ComptimeReason`.
reason: struct {
/// The source location which this reason originates from. `r` is reported here.
src: LazySrcLoc,
r: ComptimeReason,
},
};
const LabeledBlock = struct {
block: Block,
label: Block.Label,
fn destroy(lb: *LabeledBlock, gpa: Allocator) void {
lb.block.instructions.deinit(gpa);
lb.label.merges.deinit(gpa);
gpa.destroy(lb);
}
};
/// The value stored in the inferred allocation. This will go into
/// peer type resolution. This is stored in a separate list so that
/// the items are contiguous in memory and thus can be passed to
/// `Zcu.resolvePeerTypes`.
const InferredAlloc = struct {
/// The placeholder `store` instructions used before the result pointer type
/// is known. These should be rewritten to perform any required coercions
/// when the type is resolved.
/// Allocated from `sema.arena`.
prongs: std.ArrayList(Air.Inst.Index) = .empty,
};
pub fn deinit(sema: *Sema) void {
const gpa = sema.gpa;
sema.air_instructions.deinit(gpa);
sema.air_extra.deinit(gpa);
sema.inst_map.deinit(gpa);
{
var it = sema.post_hoc_blocks.iterator();
while (it.next()) |entry| {
const labeled_block = entry.value_ptr.*;
labeled_block.destroy(gpa);
}
sema.post_hoc_blocks.deinit(gpa);
}
sema.unresolved_inferred_allocs.deinit(gpa);
sema.base_allocs.deinit(gpa);
sema.maybe_comptime_allocs.deinit(gpa);
sema.comptime_allocs.deinit(gpa);
sema.exports.deinit(gpa);
sema.references.deinit(gpa);
sema.type_references.deinit(gpa);
sema.dependencies.deinit(gpa);
sema.* = undefined;
}
/// Performs semantic analysis of a ZIR body which is behind a runtime condition. If comptime
/// control flow happens here, Sema will convert it to runtime control flow by introducing post-hoc
/// blocks where necessary.
/// Returns the branch hint for this branch.
fn analyzeBodyRuntimeBreak(sema: *Sema, block: *Block, body: []const Zir.Inst.Index) !std.builtin.BranchHint {
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint;
sema.branch_hint = null;
sema.analyzeBodyInner(block, body) catch |err| switch (err) {
error.ComptimeBreak => {
const zir_datas = sema.code.instructions.items(.data);
const break_data = zir_datas[@intFromEnum(sema.comptime_break_inst)].@"break";
const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;
try sema.addRuntimeBreak(block, extra.block_inst, break_data.operand);
},
else => |e| return e,
};
return sema.branch_hint orelse .none;
}
/// Semantically analyze a ZIR function body. It is guaranteed by AstGen that such a body cannot
/// trigger comptime control flow to move above the function body.
pub fn analyzeFnBody(
sema: *Sema,
block: *Block,
body: []const Zir.Inst.Index,
) !void {
sema.analyzeBodyInner(block, body) catch |err| switch (err) {
error.ComptimeBreak => unreachable, // unexpected comptime control flow
else => |e| return e,
};
}
/// Given a ZIR body which can be exited via a `break_inline` instruction, or a non-inline body which
/// we are evaluating at comptime, semantically analyze the body and return the result from it.
/// Returns `null` if control flow did not break from this block, but instead terminated with some
/// other runtime noreturn instruction. Compile-time breaks to blocks further up the stack still
/// return `error.ComptimeBreak`. If `block.isComptime()`, this function will never return `null`.
fn analyzeInlineBody(
sema: *Sema,
block: *Block,
body: []const Zir.Inst.Index,
/// The index which a break instruction can target to break from this body.
break_target: Zir.Inst.Index,
) CompileError!?Air.Inst.Ref {
if (sema.analyzeBodyInner(block, body)) |_| {
return null;
} else |err| switch (err) {
error.ComptimeBreak => {},
else => |e| return e,
}
const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
switch (break_inst.tag) {
.switch_continue => {
// This is handled by separate logic.
return error.ComptimeBreak;
},
.break_inline, .@"break" => {},
else => unreachable,
}
const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
if (extra.block_inst != break_target) {
// This control flow goes further up the stack.
return error.ComptimeBreak;
}
return try sema.resolveInst(break_inst.data.@"break".operand);
}
/// Like `analyzeInlineBody`, but if the body does not break with a value, returns
/// `.unreachable_value` instead of `null`. Notably, use this to evaluate an arbitrary
/// body at comptime to a single result value.
pub fn resolveInlineBody(
sema: *Sema,
block: *Block,
body: []const Zir.Inst.Index,
/// The index which a break instruction can target to break from this body.
break_target: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
return (try sema.analyzeInlineBody(block, body, break_target)) orelse .unreachable_value;
}
/// This function is the main loop of `Sema`. It analyzes a single body of ZIR instructions.
///
/// If this function returns normally, the merges of `block` were populated with all possible
/// (runtime) results of this block. Peer type resolution should be performed on the result,
/// and relevant runtime instructions written to perform necessary coercions and breaks. See
/// `resolveAnalyzedBlock`. This form of return is impossible if `block.isComptime()`.
///
/// Alternatively, this function may return `error.ComptimeBreak`. This indicates that comptime
/// control flow is happening, and we are breaking at comptime from a block indicated by the
/// break instruction in `sema.comptime_break_inst`. This occurs for any `break_inline`, or for a
/// standard `break` at comptime. This error is pushed up the stack until the target block is
/// reached, at which point the break operand will be fetched.
///
/// It is rare to call this function directly. Usually, you want one of the following wrappers:
/// * If the body is exited via a `break_inline`, or is being evaluated at comptime,
/// use `Sema.analyzeInlineBody` or `Sema.resolveInlineBody`.
/// * If the body is behind a fresh runtime condition, use `Sema.analyzeBodyRuntimeBreak`.
/// * If the body is an entire function body, use `Sema.analyzeFnBody`.
/// * If the body is to be generated into an AIR `block`, use `Sema.resolveBlockBody`.
/// * Otherwise, direct usage of `Sema.analyzeBodyInner` may be necessary.
fn analyzeBodyInner(
sema: *Sema,
block: *Block,
body: []const Zir.Inst.Index,
) CompileError!void {
// No tracy calls here, to avoid interfering with the tail call mechanism.
try sema.inst_map.ensureSpaceForInstructions(sema.gpa, body);
const pt = sema.pt;
const zcu = pt.zcu;
const map = &sema.inst_map;
const tags = sema.code.instructions.items(.tag);
const datas = sema.code.instructions.items(.data);
var crash_info: crash_report.AnalyzeBody = undefined;
crash_info.push(sema, block, body);
defer crash_info.pop();
// We use a while (true) loop here to avoid a redundant way of breaking out of
// the loop. The only way to break out of the loop is with a `noreturn`
// instruction.
var i: u32 = 0;
while (true) {
crash_info.setBodyIndex(i);
const inst = body[i];
// The hashmap lookup in here is a little expensive, and LLVM fails to optimize it away.
if (build_options.enable_logging) {
std.log.scoped(.sema_zir).debug("sema ZIR {f} %{d}", .{ path: {
const file_index = block.src_base_inst.resolveFile(&zcu.intern_pool);
const file = zcu.fileByIndex(file_index);
break :path file.path.fmt(zcu.comp);
}, inst });
}
const air_inst: Air.Inst.Ref = inst: switch (tags[@intFromEnum(inst)]) {
// zig fmt: off
.alloc => try sema.zirAlloc(block, inst),
.alloc_inferred => try sema.zirAllocInferred(block, true),
.alloc_inferred_mut => try sema.zirAllocInferred(block, false),
.alloc_inferred_comptime => try sema.zirAllocInferredComptime(true),
.alloc_inferred_comptime_mut => try sema.zirAllocInferredComptime(false),
.resolve_inferred_alloc => try sema.zirResolveInferredAlloc(block, inst),
.alloc_mut => try sema.zirAllocMut(block, inst),
.alloc_comptime_mut => try sema.zirAllocComptime(block, inst),
.make_ptr_const => try sema.zirMakePtrConst(block, inst),
.anyframe_type => try sema.zirAnyframeType(block, inst),
.array_cat => try sema.zirArrayCat(block, inst),
.array_mul => try sema.zirArrayMul(block, inst),
.array_type => try sema.zirArrayType(block, inst),
.array_type_sentinel => try sema.zirArrayTypeSentinel(block, inst),
.reify_int => try sema.zirReifyInt(block, inst),
.vector_type => try sema.zirVectorType(block, inst),
.as_node => try sema.zirAsNode(block, inst),
.as_shift_operand => try sema.zirAsShiftOperand(block, inst),
.bit_and => try sema.zirBitwise(block, inst, .bit_and),
.bit_not => try sema.zirBitNot(block, inst),
.bit_or => try sema.zirBitwise(block, inst, .bit_or),
.bitcast => try sema.zirBitcast(block, inst),
.suspend_block => try sema.zirSuspendBlock(block, inst),
.bool_not => try sema.zirBoolNot(block, inst),
.bool_br_and => try sema.zirBoolBr(block, inst, false),
.bool_br_or => try sema.zirBoolBr(block, inst, true),
.c_import => try sema.zirCImport(block, inst),
.call => try sema.zirCall(block, inst, .direct),
.field_call => try sema.zirCall(block, inst, .field),
.cmp_lt => try sema.zirCmp(block, inst, .lt),
.cmp_lte => try sema.zirCmp(block, inst, .lte),
.cmp_eq => try sema.zirCmpEq(block, inst, .eq, Air.Inst.Tag.fromCmpOp(.eq, block.float_mode == .optimized)),
.cmp_gte => try sema.zirCmp(block, inst, .gte),
.cmp_gt => try sema.zirCmp(block, inst, .gt),
.cmp_neq => try sema.zirCmpEq(block, inst, .neq, Air.Inst.Tag.fromCmpOp(.neq, block.float_mode == .optimized)),
.decl_ref => try sema.zirDeclRef(block, inst),
.decl_val => try sema.zirDeclVal(block, inst),
.load => try sema.zirLoad(block, inst),
.elem_ptr => try sema.zirElemPtr(block, inst),
.elem_ptr_node => try sema.zirElemPtrNode(block, inst),
.elem_val => try sema.zirElemVal(block, inst),
.elem_ptr_load => try sema.zirElemPtrLoad(block, inst),
.elem_val_imm => try sema.zirElemValImm(block, inst),
.elem_type => try sema.zirElemType(block, inst),
.indexable_ptr_elem_type => try sema.zirIndexablePtrElemType(block, inst),
.splat_op_result_ty => try sema.zirSplatOpResultType(block, inst),
.enum_literal => try sema.zirEnumLiteral(block, inst),
.decl_literal => try sema.zirDeclLiteral(block, inst, true),
.decl_literal_no_coerce => try sema.zirDeclLiteral(block, inst, false),
.int_from_enum => try sema.zirIntFromEnum(block, inst),
.enum_from_int => try sema.zirEnumFromInt(block, inst),
.err_union_code => try sema.zirErrUnionCode(block, inst),
.err_union_code_ptr => try sema.zirErrUnionCodePtr(block, inst),
.err_union_payload_unsafe => try sema.zirErrUnionPayload(block, inst),
.err_union_payload_unsafe_ptr => try sema.zirErrUnionPayloadPtr(block, inst),
.error_union_type => try sema.zirErrorUnionType(block, inst),
.error_value => try sema.zirErrorValue(block, inst),
.field_ptr => try sema.zirFieldPtr(block, inst),
.field_ptr_named => try sema.zirFieldPtrNamed(block, inst),
.field_ptr_load => try sema.zirFieldPtrLoad(block, inst),
.field_ptr_named_load => try sema.zirFieldPtrNamedLoad(block, inst),
.func => try sema.zirFunc(block, inst, false),
.func_inferred => try sema.zirFunc(block, inst, true),
.func_fancy => try sema.zirFuncFancy(block, inst),
.import => try sema.zirImport(block, inst),
.indexable_ptr_len => try sema.zirIndexablePtrLen(block, inst),
.int => try sema.zirInt(block, inst),
.int_big => try sema.zirIntBig(block, inst),
.float => try sema.zirFloat(block, inst),
.float128 => try sema.zirFloat128(block, inst),
.int_type => try sema.zirIntType(inst),
.is_non_err => try sema.zirIsNonErr(block, inst),
.is_non_err_ptr => try sema.zirIsNonErrPtr(block, inst),
.ret_is_non_err => try sema.zirRetIsNonErr(block, inst),
.is_non_null => try sema.zirIsNonNull(block, inst),
.is_non_null_ptr => try sema.zirIsNonNullPtr(block, inst),
.merge_error_sets => try sema.zirMergeErrorSets(block, inst),
.negate => try sema.zirNegate(block, inst),
.negate_wrap => try sema.zirNegateWrap(block, inst),
.optional_payload_safe => try sema.zirOptionalPayload(block, inst, true),
.optional_payload_safe_ptr => try sema.zirOptionalPayloadPtr(block, inst, true),
.optional_payload_unsafe => try sema.zirOptionalPayload(block, inst, false),
.optional_payload_unsafe_ptr => try sema.zirOptionalPayloadPtr(block, inst, false),
.optional_type => try sema.zirOptionalType(block, inst),
.ptr_type => try sema.zirPtrType(block, inst),
.ref => try sema.zirRef(block, inst),
.ret_err_value_code => try sema.zirRetErrValueCode(inst),
.shr => try sema.zirShr(block, inst, .shr),
.shr_exact => try sema.zirShr(block, inst, .shr_exact),
.slice_end => try sema.zirSliceEnd(block, inst),
.slice_sentinel => try sema.zirSliceSentinel(block, inst),
.slice_start => try sema.zirSliceStart(block, inst),
.slice_length => try sema.zirSliceLength(block, inst),
.slice_sentinel_ty => try sema.zirSliceSentinelTy(block, inst),
.str => try sema.zirStr(inst),
.switch_block => try sema.zirSwitchBlock(block, inst, false),
.switch_block_ref => try sema.zirSwitchBlock(block, inst, true),
.switch_block_err_union => try sema.zirSwitchBlockErrUnion(block, inst),
.type_info => try sema.zirTypeInfo(block, inst),
.size_of => try sema.zirSizeOf(block, inst),
.bit_size_of => try sema.zirBitSizeOf(block, inst),
.typeof => try sema.zirTypeof(block, inst),
.typeof_builtin => try sema.zirTypeofBuiltin(block, inst),
.typeof_log2_int_type => try sema.zirTypeofLog2IntType(block, inst),
.xor => try sema.zirBitwise(block, inst, .xor),
.struct_init_empty => try sema.zirStructInitEmpty(block, inst),
.struct_init_empty_result => try sema.zirStructInitEmptyResult(block, inst, false),
.struct_init_empty_ref_result => try sema.zirStructInitEmptyResult(block, inst, true),
.struct_init_anon => try sema.zirStructInitAnon(block, inst),
.struct_init => try sema.zirStructInit(block, inst, false),
.struct_init_ref => try sema.zirStructInit(block, inst, true),
.struct_init_field_type => try sema.zirStructInitFieldType(block, inst),
.struct_init_field_ptr => try sema.zirStructInitFieldPtr(block, inst),
.array_init_anon => try sema.zirArrayInitAnon(block, inst),
.array_init => try sema.zirArrayInit(block, inst, false),
.array_init_ref => try sema.zirArrayInit(block, inst, true),
.array_init_elem_type => try sema.zirArrayInitElemType(block, inst),
.array_init_elem_ptr => try sema.zirArrayInitElemPtr(block, inst),
.union_init => try sema.zirUnionInit(block, inst),
.field_type_ref => try sema.zirFieldTypeRef(block, inst),
.int_from_ptr => try sema.zirIntFromPtr(block, inst),
.align_of => try sema.zirAlignOf(block, inst),
.int_from_bool => try sema.zirIntFromBool(block, inst),
.embed_file => try sema.zirEmbedFile(block, inst),
.error_name => try sema.zirErrorName(block, inst),
.tag_name => try sema.zirTagName(block, inst),
.type_name => try sema.zirTypeName(block, inst),
.frame_type => try sema.zirFrameType(block, inst),
.int_from_float => try sema.zirIntFromFloat(block, inst),
.float_from_int => try sema.zirFloatFromInt(block, inst),
.ptr_from_int => try sema.zirPtrFromInt(block, inst),
.float_cast => try sema.zirFloatCast(block, inst),
.int_cast => try sema.zirIntCast(block, inst),
.ptr_cast => try sema.zirPtrCast(block, inst),
.truncate => try sema.zirTruncate(block, inst),
.has_decl => try sema.zirHasDecl(block, inst),
.has_field => try sema.zirHasField(block, inst),
.byte_swap => try sema.zirByteSwap(block, inst),
.bit_reverse => try sema.zirBitReverse(block, inst),
.bit_offset_of => try sema.zirBitOffsetOf(block, inst),
.offset_of => try sema.zirOffsetOf(block, inst),
.splat => try sema.zirSplat(block, inst),
.reduce => try sema.zirReduce(block, inst),
.shuffle => try sema.zirShuffle(block, inst),
.atomic_load => try sema.zirAtomicLoad(block, inst),
.atomic_rmw => try sema.zirAtomicRmw(block, inst),
.mul_add => try sema.zirMulAdd(block, inst),
.builtin_call => try sema.zirBuiltinCall(block, inst),
.@"resume" => try sema.zirResume(block, inst),
.for_len => try sema.zirForLen(block, inst),
.validate_array_init_ref_ty => try sema.zirValidateArrayInitRefTy(block, inst),
.opt_eu_base_ptr_init => try sema.zirOptEuBasePtrInit(block, inst),
.coerce_ptr_elem_ty => try sema.zirCoercePtrElemTy(block, inst),
.clz => try sema.zirBitCount(block, inst, .clz, Value.clz),
.ctz => try sema.zirBitCount(block, inst, .ctz, Value.ctz),
.pop_count => try sema.zirBitCount(block, inst, .popcount, Value.popCount),
.abs => try sema.zirAbs(block, inst),
.sqrt => try sema.zirUnaryMath(block, inst, .sqrt, Value.sqrt),
.sin => try sema.zirUnaryMath(block, inst, .sin, Value.sin),
.cos => try sema.zirUnaryMath(block, inst, .cos, Value.cos),
.tan => try sema.zirUnaryMath(block, inst, .tan, Value.tan),
.exp => try sema.zirUnaryMath(block, inst, .exp, Value.exp),
.exp2 => try sema.zirUnaryMath(block, inst, .exp2, Value.exp2),
.log => try sema.zirUnaryMath(block, inst, .log, Value.log),
.log2 => try sema.zirUnaryMath(block, inst, .log2, Value.log2),
.log10 => try sema.zirUnaryMath(block, inst, .log10, Value.log10),
.floor => try sema.zirUnaryMath(block, inst, .floor, Value.floor),
.ceil => try sema.zirUnaryMath(block, inst, .ceil, Value.ceil),
.round => try sema.zirUnaryMath(block, inst, .round, Value.round),
.trunc => try sema.zirUnaryMath(block, inst, .trunc_float, Value.trunc),
.error_set_decl => try sema.zirErrorSetDecl(inst),
.add => try sema.zirArithmetic(block, inst, .add, true),
.addwrap => try sema.zirArithmetic(block, inst, .addwrap, true),
.add_sat => try sema.zirArithmetic(block, inst, .add_sat, true),
.add_unsafe => try sema.zirArithmetic(block, inst, .add_unsafe, false),
.mul => try sema.zirArithmetic(block, inst, .mul, true),
.mulwrap => try sema.zirArithmetic(block, inst, .mulwrap, true),
.mul_sat => try sema.zirArithmetic(block, inst, .mul_sat, true),
.sub => try sema.zirArithmetic(block, inst, .sub, true),
.subwrap => try sema.zirArithmetic(block, inst, .subwrap, true),
.sub_sat => try sema.zirArithmetic(block, inst, .sub_sat, true),
.div => try sema.zirDiv(block, inst),
.div_exact => try sema.zirDivExact(block, inst),
.div_floor => try sema.zirDivFloor(block, inst),
.div_trunc => try sema.zirDivTrunc(block, inst),
.mod_rem => try sema.zirModRem(block, inst),
.mod => try sema.zirMod(block, inst),
.rem => try sema.zirRem(block, inst),
.max => try sema.zirMinMax(block, inst, .max),
.min => try sema.zirMinMax(block, inst, .min),
.shl => try sema.zirShl(block, inst, .shl),
.shl_exact => try sema.zirShl(block, inst, .shl_exact),
.shl_sat => try sema.zirShl(block, inst, .shl_sat),
.ret_ptr => try sema.zirRetPtr(block, inst),
.ret_type => Air.internedToRef(sema.fn_ret_ty.toIntern()),
// Instructions that we know to *always* be noreturn based solely on their tag.
// These functions match the return type of analyzeBody so that we can
// tail call them here.
.compile_error => break try sema.zirCompileError(block, inst),
.ret_implicit => break try sema.zirRetImplicit(block, inst),
.ret_node => break try sema.zirRetNode(block, inst),
.ret_load => break try sema.zirRetLoad(block, inst),
.ret_err_value => break try sema.zirRetErrValue(block, inst),
.@"unreachable" => break try sema.zirUnreachable(block, inst),
.panic => break try sema.zirPanic(block, inst),
.trap => break try sema.zirTrap(block, inst),
// zig fmt: on
// This instruction never exists in an analyzed body. It exists only in the declaration
// list for a container type.
.declaration => unreachable,
.extended => ext: {
const extended = datas[@intFromEnum(inst)].extended;
break :ext switch (extended.opcode) {
// zig fmt: off
.struct_decl => try sema.zirStructDecl( block, extended, inst),
.enum_decl => try sema.zirEnumDecl( block, extended, inst),
.union_decl => try sema.zirUnionDecl( block, extended, inst),
.opaque_decl => try sema.zirOpaqueDecl( block, extended, inst),
.tuple_decl => try sema.zirTupleDecl( block, extended),
.this => try sema.zirThis( block, extended),
.ret_addr => try sema.zirRetAddr( block, extended),
.builtin_src => try sema.zirBuiltinSrc( block, extended),
.error_return_trace => try sema.zirErrorReturnTrace( block),
.frame => try sema.zirFrame( block, extended),
.frame_address => try sema.zirFrameAddress( block, extended),
.alloc => try sema.zirAllocExtended( block, extended),
.builtin_extern => try sema.zirBuiltinExtern( block, extended),
.@"asm" => try sema.zirAsm( block, extended, false),
.asm_expr => try sema.zirAsm( block, extended, true),
.typeof_peer => try sema.zirTypeofPeer( block, extended, inst),
.compile_log => try sema.zirCompileLog( block, extended),
.min_multi => try sema.zirMinMaxMulti( block, extended, .min),
.max_multi => try sema.zirMinMaxMulti( block, extended, .max),
.add_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),
.sub_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),
.mul_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),
.shl_with_overflow => try sema.zirOverflowArithmetic(block, extended, extended.opcode),
.c_undef => try sema.zirCUndef( block, extended),
.c_include => try sema.zirCInclude( block, extended),
.c_define => try sema.zirCDefine( block, extended),
.wasm_memory_size => try sema.zirWasmMemorySize( block, extended),
.wasm_memory_grow => try sema.zirWasmMemoryGrow( block, extended),
.prefetch => try sema.zirPrefetch( block, extended),
.error_cast => try sema.zirErrorCast( block, extended),
.select => try sema.zirSelect( block, extended),
.int_from_error => try sema.zirIntFromError( block, extended),
.error_from_int => try sema.zirErrorFromInt( block, extended),
.cmpxchg => try sema.zirCmpxchg( block, extended),
.c_va_arg => try sema.zirCVaArg( block, extended),
.c_va_copy => try sema.zirCVaCopy( block, extended),
.c_va_end => try sema.zirCVaEnd( block, extended),
.c_va_start => try sema.zirCVaStart( block, extended),
.ptr_cast_full => try sema.zirPtrCastFull( block, extended),
.ptr_cast_no_dest => try sema.zirPtrCastNoDest( block, extended),
.work_item_id => try sema.zirWorkItem( block, extended, extended.opcode),
.work_group_size => try sema.zirWorkItem( block, extended, extended.opcode),
.work_group_id => try sema.zirWorkItem( block, extended, extended.opcode),
.in_comptime => try sema.zirInComptime( block),
.closure_get => try sema.zirClosureGet( block, extended),
.reify_slice_arg_ty => try sema.zirReifySliceArgTy( block, extended),
.reify_enum_value_slice_ty => try sema.zirReifyEnumValueSliceTy(block, extended),
.reify_pointer_sentinel_ty => try sema.zirReifyPointerSentinelTy(block, extended),
.reify_tuple => try sema.zirReifyTuple( block, extended),
.reify_pointer => try sema.zirReifyPointer( block, extended),
.reify_fn => try sema.zirReifyFn( block, extended),
.reify_struct => try sema.zirReifyStruct( block, extended, inst),
.reify_union => try sema.zirReifyUnion( block, extended, inst),
.reify_enum => try sema.zirReifyEnum( block, extended, inst),
// zig fmt: on
.set_float_mode => {
try sema.zirSetFloatMode(block, extended);
i += 1;
continue;
},
.breakpoint => {
if (!block.isComptime()) {
_ = try block.addNoOp(.breakpoint);
}
i += 1;
continue;
},
.disable_instrumentation => {
try sema.zirDisableInstrumentation();
i += 1;
continue;
},
.disable_intrinsics => {
try sema.zirDisableIntrinsics();
i += 1;
continue;
},
.restore_err_ret_index => {
try sema.zirRestoreErrRetIndex(block, extended);
i += 1;
continue;
},
.branch_hint => {
try sema.zirBranchHint(block, extended);
i += 1;
continue;
},
.value_placeholder => unreachable, // never appears in a body
.field_parent_ptr => try sema.zirFieldParentPtr(block, extended),
.builtin_value => try sema.zirBuiltinValue(block, extended),
.inplace_arith_result_ty => try sema.zirInplaceArithResultTy(extended),
.dbg_empty_stmt => {
try sema.zirDbgEmptyStmt(block, inst);
i += 1;
continue;
},
.astgen_error => return error.AnalysisFail,
.float_op_result_ty => try sema.zirFloatOpResultType(block, extended),
};
},
// Instructions that we know can *never* be noreturn based solely on
// their tag. We avoid needlessly checking if they are noreturn and
// continue the loop.
// We also know that they cannot be referenced later, so we avoid
// putting them into the map.
.dbg_stmt => {
try sema.zirDbgStmt(block, inst);
i += 1;
continue;
},
.dbg_var_ptr => {
try sema.zirDbgVar(block, inst, .dbg_var_ptr);
i += 1;
continue;
},
.dbg_var_val => {
try sema.zirDbgVar(block, inst, .dbg_var_val);
i += 1;
continue;
},
.ensure_err_union_payload_void => {
try sema.zirEnsureErrUnionPayloadVoid(block, inst);
i += 1;
continue;
},
.ensure_result_non_error => {
try sema.zirEnsureResultNonError(block, inst);
i += 1;
continue;
},
.ensure_result_used => {
try sema.zirEnsureResultUsed(block, inst);
i += 1;
continue;
},
.set_eval_branch_quota => {
try sema.zirSetEvalBranchQuota(block, inst);
i += 1;
continue;
},
.atomic_store => {
try sema.zirAtomicStore(block, inst);
i += 1;
continue;
},
.store_node => {
try sema.zirStoreNode(block, inst);
i += 1;
continue;
},
.store_to_inferred_ptr => {
try sema.zirStoreToInferredPtr(block, inst);
i += 1;
continue;
},
.validate_struct_init_ty => {
try sema.zirValidateStructInitTy(block, inst, false);
i += 1;
continue;
},
.validate_struct_init_result_ty => {
try sema.zirValidateStructInitTy(block, inst, true);
i += 1;
continue;
},
.validate_array_init_ty => {
try sema.zirValidateArrayInitTy(block, inst, false);
i += 1;
continue;
},
.validate_array_init_result_ty => {
try sema.zirValidateArrayInitTy(block, inst, true);
i += 1;
continue;
},
.validate_ptr_struct_init => {
try sema.zirValidatePtrStructInit(block, inst);
i += 1;
continue;
},
.validate_ptr_array_init => {
try sema.zirValidatePtrArrayInit(block, inst);
i += 1;
continue;
},
.validate_deref => {
try sema.zirValidateDeref(block, inst);
i += 1;
continue;
},
.validate_destructure => {
try sema.zirValidateDestructure(block, inst);
i += 1;
continue;
},
.validate_ref_ty => {
try sema.zirValidateRefTy(block, inst);
i += 1;
continue;
},
.validate_const => {
try sema.zirValidateConst(block, inst);
i += 1;
continue;
},
.@"export" => {
try sema.zirExport(block, inst);
i += 1;
continue;
},
.set_runtime_safety => {
try sema.zirSetRuntimeSafety(block, inst);
i += 1;
continue;
},
.param => {
try sema.zirParam(block, inst, false);
i += 1;
continue;
},
.param_comptime => {
try sema.zirParam(block, inst, true);
i += 1;
continue;
},
.param_anytype => {
try sema.zirParamAnytype(block, inst, false);
i += 1;
continue;
},
.param_anytype_comptime => {
try sema.zirParamAnytype(block, inst, true);
i += 1;
continue;
},
.memcpy => {
try sema.zirMemcpy(block, inst, .memcpy, true);
i += 1;
continue;
},
.memmove => {
try sema.zirMemcpy(block, inst, .memmove, false);
i += 1;
continue;
},
.memset => {
try sema.zirMemset(block, inst);
i += 1;
continue;
},
.check_comptime_control_flow => {
if (!block.isComptime()) {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const inline_block = inst_data.operand.toIndex().?;
var check_block = block;
const target_runtime_index = while (true) {
if (check_block.inline_block == inline_block.toOptional()) {
break check_block.runtime_index;
}
check_block = check_block.parent.?;
};
if (@intFromEnum(target_runtime_index) < @intFromEnum(block.runtime_index)) {
const runtime_src = block.runtime_cond orelse block.runtime_loop.?;
const msg = msg: {
const msg = try sema.errMsg(src, "comptime control flow inside runtime block", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(runtime_src, msg, "runtime control flow here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
i += 1;
continue;
},
.save_err_ret_index => {
try sema.zirSaveErrRetIndex(block, inst);
i += 1;
continue;
},
.restore_err_ret_index_unconditional => {
const un_node = datas[@intFromEnum(inst)].un_node;
try sema.restoreErrRetIndex(block, block.nodeOffset(un_node.src_node), un_node.operand, .none);
i += 1;
continue;
},
.restore_err_ret_index_fn_entry => {
const un_node = datas[@intFromEnum(inst)].un_node;
try sema.restoreErrRetIndex(block, block.nodeOffset(un_node.src_node), .none, un_node.operand);
i += 1;
continue;
},
// Special case instructions to handle comptime control flow.
.@"break" => {
if (block.isComptime()) {
sema.comptime_break_inst = inst;
return error.ComptimeBreak;
} else {
try sema.zirBreak(block, inst);
break;
}
},
.break_inline => {
sema.comptime_break_inst = inst;
return error.ComptimeBreak;
},
.repeat => {
if (block.isComptime()) {
// Send comptime control flow back to the beginning of this block.
const src = block.nodeOffset(datas[@intFromEnum(inst)].node);
try sema.emitBackwardBranch(block, src);
i = 0;
continue;
} else {
// We are definitely called by `zirLoop`, which will treat the
// fact that this body does not terminate `noreturn` as an
// implicit repeat.
// TODO: since AIR has `repeat` now, we could change ZIR to generate
// more optimal code utilizing `repeat` instructions across blocks!
break;
}
},
.repeat_inline => {
// Send comptime control flow back to the beginning of this block.
const src = block.nodeOffset(datas[@intFromEnum(inst)].node);
try sema.emitBackwardBranch(block, src);
i = 0;
continue;
},
.switch_continue => if (block.isComptime()) {
sema.comptime_break_inst = inst;
return error.ComptimeBreak;
} else {
try sema.zirSwitchContinue(block, inst);
break;
},
.loop => if (block.isComptime()) {
continue :inst .block_inline;
} else try sema.zirLoop(block, inst),
.block => if (block.isComptime()) {
continue :inst .block_inline;
} else try sema.zirBlock(block, inst),
.block_comptime => {
const pl_node = datas[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(pl_node.src_node);
const extra = sema.code.extraData(Zir.Inst.BlockComptime, pl_node.payload_index);
const block_body = sema.code.bodySlice(extra.end, extra.data.body_len);
var child_block = block.makeSubBlock();
defer child_block.instructions.deinit(sema.gpa);
// We won't have any merges, but we must ensure this block is properly labeled for
// any `.restore_err_ret_index_*` instructions.
var label: Block.Label = .{
.zir_block = inst,
.merges = undefined,
};
child_block.label = &label;
child_block.comptime_reason = .{ .reason = .{
.src = src,
.r = .{ .simple = extra.data.reason },
} };
const result = try sema.resolveInlineBody(&child_block, block_body, inst);
// Only check for the result being comptime-known in the outermost `block_comptime`.
// That way, AstGen can safely elide redundant `block_comptime` without affecting semantics.
if (!block.isComptime() and !try sema.isComptimeKnown(result)) {
return sema.failWithNeededComptime(&child_block, src, null);
}
break :inst result;
},
.block_inline => blk: {
// Directly analyze the block body without introducing a new block.
// However, in the case of a corresponding break_inline which reaches
// through a runtime conditional branch, we must retroactively emit
// a block, so we remember the block index here just in case.
const block_index = block.instructions.items.len;
const inst_data = datas[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
const inline_body = sema.code.bodySlice(extra.end, extra.data.body_len);
const gpa = sema.gpa;
const BreakResult = struct {
block_inst: Zir.Inst.Index,
operand: Zir.Inst.Ref,
};
const opt_break_data: ?BreakResult, const need_debug_scope = b: {
// Create a temporary child block so that this inline block is properly
// labeled for any .restore_err_ret_index instructions
var child_block = block.makeSubBlock();
var need_debug_scope = false;
child_block.need_debug_scope = &need_debug_scope;
// If this block contains a function prototype, we need to reset the
// current list of parameters and restore it later.
// Note: this probably needs to be resolved in a more general manner.
const tag_index = @intFromEnum(inline_body[inline_body.len - 1]);
child_block.inline_block = (if (tags[tag_index] == .repeat_inline)
inline_body[0]
else
inst).toOptional();
var label: Block.Label = .{
.zir_block = inst,
.merges = undefined,
};
child_block.label = &label;
// Write these instructions directly into the parent block
child_block.instructions = block.instructions;
defer block.instructions = child_block.instructions;
const break_result: ?BreakResult = if (sema.analyzeBodyInner(&child_block, inline_body)) |_| r: {
break :r null;
} else |err| switch (err) {
error.ComptimeBreak => brk_res: {
const break_inst = sema.comptime_break_inst;
const break_data = sema.code.instructions.items(.data)[@intFromEnum(break_inst)].@"break";
const break_extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;
break :brk_res .{
.block_inst = break_extra.block_inst,
.operand = break_data.operand,
};
},
else => |e| return e,
};
if (need_debug_scope) {
_ = try sema.ensurePostHoc(block, inst);
}
break :b .{ break_result, need_debug_scope };
};
// A runtime conditional branch that needs a post-hoc block to be
// emitted communicates this by mapping the block index into the inst map.
if (map.get(inst)) |new_block_ref| ph: {
// Comptime control flow populates the map, so we don't actually know
// if this is a post-hoc runtime block until we check the
// post_hoc_block map.
const new_block_inst = new_block_ref.toIndex() orelse break :ph;
const labeled_block = sema.post_hoc_blocks.get(new_block_inst) orelse
break :ph;
// In this case we need to move all the instructions starting at
// block_index from the current block into this new one.
if (opt_break_data) |break_data| {
// This is a comptime break which we now change to a runtime break
// since it crosses a runtime branch.
// It may pass through our currently being analyzed block_inline or it
// may point directly to it. In the latter case, this modifies the
// block that we looked up in the post_hoc_blocks map above.
try sema.addRuntimeBreak(block, break_data.block_inst, break_data.operand);
}
try labeled_block.block.instructions.appendSlice(gpa, block.instructions.items[block_index..]);
block.instructions.items.len = block_index;
const block_result = try sema.resolveAnalyzedBlock(block, block.nodeOffset(inst_data.src_node), &labeled_block.block, &labeled_block.label.merges, need_debug_scope);
{
// Destroy the ad-hoc block entry so that it does not interfere with
// the next iteration of comptime control flow, if any.
labeled_block.destroy(gpa);
assert(sema.post_hoc_blocks.remove(new_block_inst));
}
break :blk block_result;
}
const break_data = opt_break_data orelse break;
if (inst == break_data.block_inst) {
break :blk try sema.resolveInst(break_data.operand);
} else {
// `comptime_break_inst` preserved from `analyzeBodyInner` above.
return error.ComptimeBreak;
}
},
.condbr => if (block.isComptime()) {
continue :inst .condbr_inline;
} else {
try sema.zirCondbr(block, inst);
break;
},
.condbr_inline => {
const inst_data = datas[@intFromEnum(inst)].pl_node;
const cond_src = block.src(.{ .node_offset_if_cond = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.CondBr, inst_data.payload_index);
const then_body = sema.code.bodySlice(extra.end, extra.data.then_body_len);
const else_body = sema.code.bodySlice(
extra.end + then_body.len,
extra.data.else_body_len,
);
const uncasted_cond = try sema.resolveInst(extra.data.condition);
const cond = try sema.coerce(block, .bool, uncasted_cond, cond_src);
const cond_val = try sema.resolveConstDefinedValue(
block,
cond_src,
cond,
// If this block is comptime, it's more helpful to just give the outer message.
// This is particularly true if this came from a comptime `condbr` above.
if (block.isComptime()) null else .{ .simple = .inline_loop_operand },
);
const inline_body = if (cond_val.toBool()) then_body else else_body;
try sema.maybeErrorUnwrapCondbr(block, inline_body, extra.data.condition, cond_src);
const old_runtime_index = block.runtime_index;
defer block.runtime_index = old_runtime_index;
const result = try sema.analyzeInlineBody(block, inline_body, inst) orelse break;
break :inst result;
},
.@"try" => blk: {
if (!block.isComptime()) break :blk try sema.zirTry(block, inst);
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.src(.{ .node_offset_try_operand = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index);
const inline_body = sema.code.bodySlice(extra.end, extra.data.body_len);
const err_union = try sema.resolveInst(extra.data.operand);
const err_union_ty = sema.typeOf(err_union);
if (err_union_ty.zigTypeTag(zcu) != .error_union) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(operand_src, "expected error union type, found '{f}'", .{err_union_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, err_union_ty);
try sema.errNote(operand_src, msg, "consider omitting 'try'", .{});
break :msg msg;
});
}
const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union);
assert(is_non_err != .none);
const is_non_err_val = try sema.resolveConstDefinedValue(block, operand_src, is_non_err, null);
if (is_non_err_val.toBool()) {
break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, err_union, operand_src, false);
}
const result = try sema.analyzeInlineBody(block, inline_body, inst) orelse break;
break :blk result;
},
.try_ptr => blk: {
if (!block.isComptime()) break :blk try sema.zirTryPtr(block, inst);
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.src(.{ .node_offset_try_operand = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index);
const inline_body = sema.code.bodySlice(extra.end, extra.data.body_len);
const operand = try sema.resolveInst(extra.data.operand);
const err_union = try sema.analyzeLoad(block, src, operand, operand_src);
const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union);
assert(is_non_err != .none);
const is_non_err_val = try sema.resolveConstDefinedValue(block, operand_src, is_non_err, null);
if (is_non_err_val.toBool()) {
break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false);
}
const result = try sema.analyzeInlineBody(block, inline_body, inst) orelse break;
break :blk result;
},
.@"defer" => blk: {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"defer";
const defer_body = sema.code.bodySlice(inst_data.index, inst_data.len);
if (sema.analyzeBodyInner(block, defer_body)) |_| {
// The defer terminated noreturn - no more analysis needed.
break;
} else |err| switch (err) {
error.ComptimeBreak => {},
else => |e| return e,
}
if (sema.comptime_break_inst != defer_body[defer_body.len - 1]) {
return error.ComptimeBreak;
}
break :blk .void_value;
},
.defer_err_code => blk: {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].defer_err_code;
const extra = sema.code.extraData(Zir.Inst.DeferErrCode, inst_data.payload_index).data;
const defer_body = sema.code.bodySlice(extra.index, extra.len);
const err_code = try sema.resolveInst(inst_data.err_code);
try map.ensureSpaceForInstructions(sema.gpa, defer_body);
map.putAssumeCapacity(extra.remapped_err_code, err_code);
if (sema.analyzeBodyInner(block, defer_body)) |_| {
// The defer terminated noreturn - no more analysis needed.
break;
} else |err| switch (err) {
error.ComptimeBreak => {},
else => |e| return e,
}
if (sema.comptime_break_inst != defer_body[defer_body.len - 1]) {
return error.ComptimeBreak;
}
break :blk .void_value;
},
};
if (sema.isNoReturn(air_inst)) {
// We're going to assume that the body itself is noreturn, so let's ensure that now
assert(block.instructions.items.len > 0);
assert(sema.isNoReturn(block.instructions.items[block.instructions.items.len - 1].toRef()));
break;
}
map.putAssumeCapacity(inst, air_inst);
i += 1;
}
}
pub fn resolveInstAllowNone(sema: *Sema, zir_ref: Zir.Inst.Ref) !Air.Inst.Ref {
if (zir_ref == .none) {
return .none;
} else {
return resolveInst(sema, zir_ref);
}
}
pub fn resolveInst(sema: *Sema, zir_ref: Zir.Inst.Ref) !Air.Inst.Ref {
assert(zir_ref != .none);
if (zir_ref.toIndex()) |i| {
return sema.inst_map.get(i).?;
}
// First section of indexes correspond to a set number of constant values.
// We intentionally map the same indexes to the same values between ZIR and AIR.
return @enumFromInt(@intFromEnum(zir_ref));
}
fn resolveConstBool(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
reason: ComptimeReason,
) !bool {
const air_inst = try sema.resolveInst(zir_ref);
const wanted_type: Type = .bool;
const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src);
const val = try sema.resolveConstDefinedValue(block, src, coerced_inst, reason);
return val.toBool();
}
fn resolveConstString(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
/// `null` may be passed only if `block.isComptime()`. It indicates that the reason for the value
/// being comptime-resolved is that the block is being comptime-evaluated.
reason: ?ComptimeReason,
) ![]u8 {
const air_inst = try sema.resolveInst(zir_ref);
return sema.toConstString(block, src, air_inst, reason);
}
pub fn toConstString(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_inst: Air.Inst.Ref,
/// `null` may be passed only if `block.isComptime()`. It indicates that the reason for the value
/// being comptime-resolved is that the block is being comptime-evaluated.
reason: ?ComptimeReason,
) ![]u8 {
const pt = sema.pt;
const coerced_inst = try sema.coerce(block, .slice_const_u8, air_inst, src);
const slice_val = try sema.resolveConstDefinedValue(block, src, coerced_inst, reason);
const arr_val = try sema.derefSliceAsArray(block, src, slice_val, reason);
return arr_val.toAllocatedBytes(arr_val.typeOf(pt.zcu), sema.arena, pt);
}
pub fn resolveConstStringIntern(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
reason: ComptimeReason,
) !InternPool.NullTerminatedString {
const air_inst = try sema.resolveInst(zir_ref);
const wanted_type: Type = .slice_const_u8;
const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src);
const val = try sema.resolveConstDefinedValue(block, src, coerced_inst, reason);
return sema.sliceToIpString(block, src, val, reason);
}
fn resolveTypeOrPoison(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) !?Type {
const air_inst = try sema.resolveInst(zir_ref);
const ty = try sema.analyzeAsType(block, src, air_inst);
if (ty.isGenericPoison()) return null;
return ty;
}
pub fn resolveType(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) !Type {
return (try sema.resolveTypeOrPoison(block, src, zir_ref)).?;
}
fn resolveDestType(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
strat: enum { remove_eu_opt, remove_eu, remove_opt },
builtin_name: []const u8,
) !Type {
const pt = sema.pt;
const zcu = pt.zcu;
const remove_eu = switch (strat) {
.remove_eu_opt, .remove_eu => true,
.remove_opt => false,
};
const remove_opt = switch (strat) {
.remove_eu_opt, .remove_opt => true,
.remove_eu => false,
};
const raw_ty = try sema.resolveTypeOrPoison(block, src, zir_ref) orelse {
// Cast builtins use their result type as the destination type, but
// it could be an anytype argument, which we can't catch in AstGen.
const msg = msg: {
const msg = try sema.errMsg(src, "{s} must have a known result type", .{builtin_name});
errdefer msg.destroy(sema.gpa);
switch (sema.genericPoisonReason(block, zir_ref)) {
.anytype_param => |call_src| try sema.errNote(call_src, msg, "result type is unknown due to anytype parameter", .{}),
.anyopaque_ptr => |ptr_src| try sema.errNote(ptr_src, msg, "result type is unknown due to opaque pointer type", .{}),
.unknown => {},
}
try sema.errNote(src, msg, "use @as to provide explicit result type", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
};
if (remove_eu and raw_ty.zigTypeTag(zcu) == .error_union) {
const eu_child = raw_ty.errorUnionPayload(zcu);
if (remove_opt and eu_child.zigTypeTag(zcu) == .optional) {
return eu_child.childType(zcu);
}
return eu_child;
}
if (remove_opt and raw_ty.zigTypeTag(zcu) == .optional) {
return raw_ty.childType(zcu);
}
return raw_ty;
}
const GenericPoisonReason = union(enum) {
anytype_param: LazySrcLoc,
anyopaque_ptr: LazySrcLoc,
unknown,
};
/// Backtracks through ZIR instructions to determine the reason a generic poison
/// type was created. Used for error reporting.
fn genericPoisonReason(sema: *Sema, block: *Block, ref: Zir.Inst.Ref) GenericPoisonReason {
var cur = ref;
while (true) {
const inst = cur.toIndex() orelse return .unknown;
switch (sema.code.instructions.items(.tag)[@intFromEnum(inst)]) {
.validate_array_init_ref_ty => {
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.ArrayInitRefTy, pl_node.payload_index).data;
cur = extra.ptr_ty;
},
.array_init_elem_type => {
const bin = sema.code.instructions.items(.data)[@intFromEnum(inst)].bin;
cur = bin.lhs;
},
.indexable_ptr_elem_type, .splat_op_result_ty => {
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
cur = un_node.operand;
},
.struct_init_field_type => {
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.FieldType, pl_node.payload_index).data;
cur = extra.container_type;
},
.elem_type => {
// There are two cases here: the pointer type may already have been
// generic poison, or it may have been an anyopaque pointer.
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_ref = try sema.resolveInst(un_node.operand);
const operand_val = operand_ref.toInterned() orelse return .unknown;
if (operand_val == .generic_poison_type) {
// The pointer was generic poison - keep looking.
cur = un_node.operand;
} else {
// This must be an anyopaque pointer!
return .{ .anyopaque_ptr = block.nodeOffset(un_node.src_node) };
}
},
.call, .field_call => {
// A function call can never return generic poison, so we must be
// evaluating an `anytype` function parameter.
// TODO: better source location - function decl rather than call
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
return .{ .anytype_param = block.nodeOffset(pl_node.src_node) };
},
else => return .unknown,
}
}
}
fn analyzeAsType(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_inst: Air.Inst.Ref,
) !Type {
const wanted_type: Type = .type;
const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src);
const val = try sema.resolveConstDefinedValue(block, src, coerced_inst, .{ .simple = .type });
return val.toType();
}
pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const comp = zcu.comp;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
if (!comp.config.any_error_tracing) return;
assert(!block.isComptime());
var err_trace_block = block.makeSubBlock();
defer err_trace_block.instructions.deinit(gpa);
const src: LazySrcLoc = LazySrcLoc.unneeded;
// var addrs: [err_return_trace_addr_count]usize = undefined;
const err_return_trace_addr_count = 32;
const addr_arr_ty = try pt.arrayType(.{
.len = err_return_trace_addr_count,
.child = .usize_type,
});
const addrs_ptr = try err_trace_block.addTy(.alloc, try pt.singleMutPtrType(addr_arr_ty));
// var st: StackTrace = undefined;
const stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(.zero), .StackTrace);
try stack_trace_ty.resolveFields(pt);
const st_ptr = try err_trace_block.addTy(.alloc, try pt.singleMutPtrType(stack_trace_ty));
// st.instruction_addresses = &addrs;
const instruction_addresses_field_name = try ip.getOrPutString(gpa, pt.tid, "instruction_addresses", .no_embedded_nulls);
const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, instruction_addresses_field_name, src, true);
try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store);
// st.index = 0;
const index_field_name = try ip.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, index_field_name, src, true);
try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, .zero_usize, src, .store);
// @errorReturnTrace() = &st;
_ = try err_trace_block.addUnOp(.set_err_return_trace, st_ptr);
try block.instructions.insertSlice(gpa, last_arg_index, err_trace_block.instructions.items);
}
/// Return the Value corresponding to a given AIR ref, or `null` if it refers to a runtime value.
fn resolveValue(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value {
const zcu = sema.pt.zcu;
assert(inst != .none);
if (try sema.typeHasOnePossibleValue(sema.typeOf(inst))) |opv| {
return opv;
}
if (inst.toInterned()) |ip_index| {
const val: Value = .fromInterned(ip_index);
assert(val.getVariable(zcu) == null);
return val;
} else {
// Runtime-known value.
const air_tags = sema.air_instructions.items(.tag);
switch (air_tags[@intFromEnum(inst.toIndex().?)]) {
.inferred_alloc => unreachable, // assertion failure
.inferred_alloc_comptime => unreachable, // assertion failure
else => {},
}
return null;
}
}
/// Like `resolveValue`, but emits an error if the value is not comptime-known.
fn resolveConstValue(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
inst: Air.Inst.Ref,
/// `null` may be passed only if `block.isComptime()`. It indicates that the reason for the value
/// being comptime-resolved is that the block is being comptime-evaluated.
reason: ?ComptimeReason,
) CompileError!Value {
return try sema.resolveValue(inst) orelse {
return sema.failWithNeededComptime(block, src, reason);
};
}
/// Like `resolveValue`, but emits an error if the value is comptime-known to be undefined.
fn resolveDefinedValue(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_ref: Air.Inst.Ref,
) CompileError!?Value {
const pt = sema.pt;
const zcu = pt.zcu;
const val = try sema.resolveValue(air_ref) orelse return null;
if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src, null);
return val;
}
/// Like `resolveValue`, but emits an error if the value is not comptime-known or is undefined.
fn resolveConstDefinedValue(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_ref: Air.Inst.Ref,
/// `null` may be passed only if `block.isComptime()`. It indicates that the reason for the value
/// being comptime-resolved is that the block is being comptime-evaluated.
reason: ?ComptimeReason,
) CompileError!Value {
const val = try sema.resolveConstValue(block, src, air_ref, reason);
if (val.isUndef(sema.pt.zcu)) return sema.failWithUseOfUndef(block, src, null);
return val;
}
/// Like `resolveValue`, but recursively resolves lazy values before returning.
fn resolveValueResolveLazy(sema: *Sema, inst: Air.Inst.Ref) CompileError!?Value {
return try sema.resolveLazyValue((try sema.resolveValue(inst)) orelse return null);
}
/// Value Tag may be `undef` or `variable`.
pub fn resolveFinalDeclValue(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_ref: Air.Inst.Ref,
) CompileError!Value {
const zcu = sema.pt.zcu;
const val = try sema.resolveConstValue(block, src, air_ref, .{ .simple = .container_var_init });
if (val.canMutateComptimeVarState(zcu)) {
const ip = &zcu.intern_pool;
const nav = ip.getNav(sema.owner.unwrap().nav_val);
return sema.failWithContainsReferenceToComptimeVar(block, src, nav.name, "global variable", val);
}
return val;
}
fn failWithNeededComptime(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
/// `null` may be passed only if `block.isComptime()`. It indicates that the reason for the value
/// being comptime-resolved is that the block is being comptime-evaluated.
reason: ?ComptimeReason,
) CompileError {
const msg, const fail_block = msg: {
const msg = try sema.errMsg(src, "unable to resolve comptime value", .{});
errdefer msg.destroy(sema.gpa);
const fail_block = if (reason) |r| b: {
try r.explain(sema, src, msg);
break :b block;
} else b: {
break :b try block.explainWhyBlockIsComptime(msg);
};
break :msg .{ msg, fail_block };
};
return sema.failWithOwnedErrorMsg(fail_block, msg);
}
pub fn failWithUseOfUndef(sema: *Sema, block: *Block, src: LazySrcLoc, vector_index: ?usize) CompileError {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "use of undefined value here causes illegal behavior", .{});
errdefer msg.destroy(sema.gpa);
if (vector_index) |i| try sema.errNote(src, msg, "when computing vector element at index '{d}'", .{i});
break :msg msg;
});
}
pub fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
return sema.fail(block, src, "division by zero here causes illegal behavior", .{});
}
pub fn failWithTooLargeShiftAmount(
sema: *Sema,
block: *Block,
operand_ty: Type,
shift_amt: Value,
shift_src: LazySrcLoc,
vector_index: ?usize,
) CompileError {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(
shift_src,
"shift amount '{f}' is too large for operand type '{f}'",
.{ shift_amt.fmtValueSema(sema.pt, sema), operand_ty.fmt(sema.pt) },
);
errdefer msg.destroy(sema.gpa);
if (vector_index) |i| try sema.errNote(shift_src, msg, "when computing vector element at index '{d}'", .{i});
break :msg msg;
});
}
pub fn failWithNegativeShiftAmount(sema: *Sema, block: *Block, src: LazySrcLoc, shift_amt: Value, vector_index: ?usize) CompileError {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "shift by negative amount '{f}'", .{shift_amt.fmtValueSema(sema.pt, sema)});
errdefer msg.destroy(sema.gpa);
if (vector_index) |i| try sema.errNote(src, msg, "when computing vector element at index '{d}'", .{i});
break :msg msg;
});
}
pub fn failWithUnsupportedComptimeShiftAmount(sema: *Sema, block: *Block, src: LazySrcLoc, vector_index: ?usize) CompileError {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(
src,
"this implementation only supports comptime shift amounts of up to 2^{d} - 1 bits",
.{@min(@bitSizeOf(usize), 64)},
);
errdefer msg.destroy(sema.gpa);
if (vector_index) |i| try sema.errNote(src, msg, "when computing vector element at index '{d}'", .{i});
break :msg msg;
});
}
fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: Type, rhs_ty: Type) CompileError {
const pt = sema.pt;
return sema.fail(block, src, "remainder division with '{f}' and '{f}': signed integers and floats must use @rem or @mod", .{
lhs_ty.fmt(pt), rhs_ty.fmt(pt),
});
}
fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, non_optional_ty: Type) CompileError {
const pt = sema.pt;
const msg = msg: {
const msg = try sema.errMsg(src, "expected optional type, found '{f}'", .{
non_optional_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
if (non_optional_ty.zigTypeTag(pt.zcu) == .error_union) {
try sema.errNote(src, msg, "consider using 'try', 'catch', or 'if'", .{});
}
try addDeclaredHereNote(sema, msg, non_optional_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn failWithArrayInitNotSupported(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError {
const pt = sema.pt;
const msg = msg: {
const msg = try sema.errMsg(src, "type '{f}' does not support array initialization syntax", .{
ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
if (ty.isSlice(pt.zcu)) {
try sema.errNote(src, msg, "inferred array length is specified with an underscore: '[_]{f}'", .{ty.elemType2(pt.zcu).fmt(pt)});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn failWithStructInitNotSupported(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError {
const pt = sema.pt;
return sema.fail(block, src, "type '{f}' does not support struct initialization syntax", .{
ty.fmt(pt),
});
}
pub fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: Type, val: Value, vector_index: ?usize) CompileError {
const pt = sema.pt;
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "overflow of integer type '{f}' with value '{f}'", .{
int_ty.fmt(pt), val.fmtValueSema(pt, sema),
});
errdefer msg.destroy(sema.gpa);
if (vector_index) |i| try sema.errNote(src, msg, "when computing vector element at index '{d}'", .{i});
break :msg msg;
});
}
fn failWithInvalidComptimeFieldStore(sema: *Sema, block: *Block, init_src: LazySrcLoc, container_ty: Type, field_index: usize) CompileError {
const pt = sema.pt;
const zcu = pt.zcu;
const msg = msg: {
const msg = try sema.errMsg(init_src, "value stored in comptime field does not match the default value of the field", .{});
errdefer msg.destroy(sema.gpa);
const struct_type = zcu.typeToStruct(container_ty) orelse break :msg msg;
try sema.errNote(.{
.base_node_inst = struct_type.zir_index,
.offset = .{ .container_field_value = @intCast(field_index) },
}, msg, "default value set here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn failWithUseOfAsync(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError {
const msg = msg: {
const msg = try sema.errMsg(src, "async has not been implemented in the self-hosted compiler yet", .{});
errdefer msg.destroy(sema.gpa);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn failWithInvalidFieldAccess(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
object_ty: Type,
field_name: InternPool.NullTerminatedString,
) CompileError {
const pt = sema.pt;
const zcu = pt.zcu;
const inner_ty = if (object_ty.isSinglePointer(zcu)) object_ty.childType(zcu) else object_ty;
if (inner_ty.zigTypeTag(zcu) == .optional) opt: {
const child_ty = inner_ty.optionalChild(zcu);
if (!typeSupportsFieldAccess(zcu, child_ty, field_name)) break :opt;
const msg = msg: {
const msg = try sema.errMsg(src, "optional type '{f}' does not support field access", .{object_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "consider using '.?', 'orelse', or 'if'", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
} else if (inner_ty.zigTypeTag(zcu) == .error_union) err: {
const child_ty = inner_ty.errorUnionPayload(zcu);
if (!typeSupportsFieldAccess(zcu, child_ty, field_name)) break :err;
const msg = msg: {
const msg = try sema.errMsg(src, "error union type '{f}' does not support field access", .{object_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "consider using 'try', 'catch', or 'if'", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
return sema.fail(block, src, "type '{f}' does not support field access", .{object_ty.fmt(pt)});
}
fn typeSupportsFieldAccess(zcu: *const Zcu, ty: Type, field_name: InternPool.NullTerminatedString) bool {
const ip = &zcu.intern_pool;
switch (ty.zigTypeTag(zcu)) {
.array => return field_name.eqlSlice("len", ip),
.pointer => {
const ptr_info = ty.ptrInfo(zcu);
if (ptr_info.flags.size == .slice) {
return field_name.eqlSlice("ptr", ip) or field_name.eqlSlice("len", ip);
} else if (Type.fromInterned(ptr_info.child).zigTypeTag(zcu) == .array) {
return field_name.eqlSlice("len", ip);
} else return false;
},
.type, .@"struct", .@"union" => return true,
else => return false,
}
}
fn failWithComptimeErrorRetTrace(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
name: InternPool.NullTerminatedString,
) CompileError {
const pt = sema.pt;
const zcu = pt.zcu;
const msg = msg: {
const msg = try sema.errMsg(src, "caught unexpected error '{f}'", .{name.fmt(&zcu.intern_pool)});
errdefer msg.destroy(sema.gpa);
for (sema.comptime_err_ret_trace.items) |src_loc| {
try sema.errNote(src_loc, msg, "error returned here", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn failWithInvalidPtrArithmetic(sema: *Sema, block: *Block, src: LazySrcLoc, arithmetic: []const u8, supports: []const u8) CompileError {
const msg = msg: {
const msg = try sema.errMsg(src, "invalid {s} arithmetic operator", .{arithmetic});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "{s} arithmetic only supports {s}", .{ arithmetic, supports });
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
/// We don't return a pointer to the new error note because the pointer
/// becomes invalid when you add another one.
pub fn errNote(
sema: *Sema,
src: LazySrcLoc,
parent: *Zcu.ErrorMsg,
comptime format: []const u8,
args: anytype,
) error{OutOfMemory}!void {
return sema.pt.zcu.errNote(src, parent, format, args);
}
fn addFieldErrNote(
sema: *Sema,
container_ty: Type,
field_index: usize,
parent: *Zcu.ErrorMsg,
comptime format: []const u8,
args: anytype,
) !void {
@branchHint(.cold);
const type_src = container_ty.srcLocOrNull(sema.pt.zcu) orelse return;
const field_src: LazySrcLoc = .{
.base_node_inst = type_src.base_node_inst,
.offset = .{ .container_field_name = @intCast(field_index) },
};
try sema.errNote(field_src, parent, format, args);
}
pub fn errMsg(
sema: *Sema,
src: LazySrcLoc,
comptime format: []const u8,
args: anytype,
) Allocator.Error!*Zcu.ErrorMsg {
assert(src.offset != .unneeded);
return Zcu.ErrorMsg.create(sema.gpa, src, format, args);
}
fn typeMismatchErrMsg(sema: *Sema, src: LazySrcLoc, expected: Type, found: Type) Allocator.Error!*Zcu.ErrorMsg {
const pt = sema.pt;
var cmp: Type.Comparison = try .init(&.{ expected, found }, pt);
defer cmp.deinit(pt);
const msg = try sema.errMsg(src, "expected type '{f}', found '{f}'", .{
cmp.fmtType(expected, pt),
cmp.fmtType(found, pt),
});
errdefer msg.destroy(sema.gpa);
for (cmp.type_dedupe_cache.keys(), cmp.type_dedupe_cache.values()) |ty, value| {
if (value == .dont_dedupe) continue;
const placeholder = value.dedupe;
try sema.errNote(src, msg, "{f} = {f}", .{ placeholder, ty.fmt(pt) });
}
return msg;
}
pub fn fail(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
comptime format: []const u8,
args: anytype,
) CompileError {
const err_msg = try sema.errMsg(src, format, args);
inline for (args) |arg| {
if (@TypeOf(arg) == Type.Formatter) {
try addDeclaredHereNote(sema, err_msg, arg.data.ty);
}
}
return sema.failWithOwnedErrorMsg(block, err_msg);
}
fn failWithTypeMismatch(sema: *Sema, block: *Block, src: LazySrcLoc, expected: Type, found: Type) CompileError {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.typeMismatchErrMsg(src, expected, found);
errdefer msg.destroy(sema.gpa);
try addDeclaredHereNote(sema, msg, expected);
try addDeclaredHereNote(sema, msg, found);
break :msg msg;
});
}
pub fn failWithOwnedErrorMsg(sema: *Sema, block: ?*Block, err_msg: *Zcu.ErrorMsg) error{ AnalysisFail, OutOfMemory } {
@branchHint(.cold);
const gpa = sema.gpa;
const zcu = sema.pt.zcu;
if (build_options.enable_debug_extensions and zcu.comp.debug_compile_errors) {
var wip_errors: std.zig.ErrorBundle.Wip = undefined;
wip_errors.init(gpa) catch @panic("out of memory");
Compilation.addModuleErrorMsg(zcu, &wip_errors, err_msg.*, false) catch @panic("out of memory");
std.debug.print("compile error during Sema:\n", .{});
var error_bundle = wip_errors.toOwnedBundle("") catch @panic("out of memory");
error_bundle.renderToStdErr(.{}, .auto);
std.debug.panicExtra(@returnAddress(), "unexpected compile error occurred", .{});
}
if (block) |start_block| {
var block_it = start_block;
while (block_it.inlining) |inlining| {
const note_str = note: {
if (inlining.is_generic_instantiation) break :note "generic function instantiated here";
if (inlining.call_block.isComptime()) break :note "called at comptime here";
break :note "called inline here";
};
try sema.errNote(inlining.call_src, err_msg, "{s}", .{note_str});
block_it = inlining.call_block;
}
}
err_msg.reference_trace_root = sema.owner.toOptional();
const gop = try zcu.failed_analysis.getOrPut(gpa, sema.owner);
if (gop.found_existing) {
// If there are multiple errors for the same Decl, prefer the first one added.
sema.err = null;
err_msg.destroy(gpa);
} else {
sema.err = err_msg;
gop.value_ptr.* = err_msg;
}
return error.AnalysisFail;
}
/// Given an ErrorMsg, modify its message and source location to the given values, turning the
/// original message into a note. Notes on the original message are preserved as further notes.
/// Reference trace is preserved.
fn reparentOwnedErrorMsg(
sema: *Sema,
src: LazySrcLoc,
msg: *Zcu.ErrorMsg,
comptime format: []const u8,
args: anytype,
) !void {
const msg_str = try std.fmt.allocPrint(sema.gpa, format, args);
const orig_notes = msg.notes.len;
msg.notes = try sema.gpa.realloc(msg.notes, orig_notes + 1);
@memmove(msg.notes[1..][0..orig_notes], msg.notes[0..orig_notes]);
msg.notes[0] = .{
.src_loc = msg.src_loc,
.msg = msg.msg,
};
msg.src_loc = src;
msg.msg = msg_str;
}
const align_ty: Type = .u29;
pub fn analyzeAsAlign(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_ref: Air.Inst.Ref,
) !Alignment {
const alignment_big = try sema.analyzeAsInt(
block,
src,
air_ref,
align_ty,
.{ .simple = .@"align" },
);
return sema.validateAlign(block, src, alignment_big);
}
fn validateAlign(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
alignment: u64,
) !Alignment {
if (alignment == 0) return sema.fail(block, src, "alignment must be >= 1", .{});
if (!std.math.isPowerOfTwo(alignment)) {
return sema.fail(block, src, "alignment value '{d}' is not a power of two", .{
alignment,
});
}
return Alignment.fromNonzeroByteUnits(alignment);
}
fn resolveAlign(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
) !Alignment {
const air_ref = try sema.resolveInst(zir_ref);
return sema.analyzeAsAlign(block, src, air_ref);
}
fn resolveInt(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
dest_ty: Type,
reason: ComptimeReason,
) !u64 {
const air_ref = try sema.resolveInst(zir_ref);
return sema.analyzeAsInt(block, src, air_ref, dest_ty, reason);
}
fn analyzeAsInt(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_ref: Air.Inst.Ref,
dest_ty: Type,
reason: ComptimeReason,
) !u64 {
const coerced = try sema.coerce(block, dest_ty, air_ref, src);
const val = try sema.resolveConstDefinedValue(block, src, coerced, reason);
return try val.toUnsignedIntSema(sema.pt);
}
fn analyzeValueAsCallconv(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
unresolved_val: Value,
) !std.builtin.CallingConvention {
return interpretBuiltinType(sema, block, src, unresolved_val, std.builtin.CallingConvention);
}
fn interpretBuiltinType(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
unresolved_val: Value,
comptime T: type,
) !T {
const resolved_val = try sema.resolveLazyValue(unresolved_val);
return resolved_val.interpret(T, sema.pt) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.UndefinedValue => return sema.failWithUseOfUndef(block, src, null),
error.TypeMismatch => @panic("std.builtin is corrupt"),
};
}
fn zirTupleDecl(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const gpa = sema.gpa;
const pt = sema.pt;
const zcu = pt.zcu;
const fields_len = extended.small;
const extra = sema.code.extraData(Zir.Inst.TupleDecl, extended.operand);
var extra_index = extra.end;
const types = try sema.arena.alloc(InternPool.Index, fields_len);
const inits = try sema.arena.alloc(InternPool.Index, fields_len);
const extra_as_refs: []const Zir.Inst.Ref = @ptrCast(sema.code.extra);
for (types, inits, 0..) |*field_ty, *field_init, field_index| {
const zir_field_ty, const zir_field_init = extra_as_refs[extra_index..][0..2].*;
extra_index += 2;
const type_src = block.src(.{ .tuple_field_type = .{
.tuple_decl_node_offset = extra.data.src_node,
.elem_index = @intCast(field_index),
} });
const init_src = block.src(.{ .tuple_field_init = .{
.tuple_decl_node_offset = extra.data.src_node,
.elem_index = @intCast(field_index),
} });
const field_type = try sema.resolveType(block, type_src, zir_field_ty);
try sema.validateTupleFieldType(block, field_type, type_src);
field_ty.* = field_type.toIntern();
field_init.* = init: {
if (zir_field_init != .none) {
const uncoerced_field_init = try sema.resolveInst(zir_field_init);
const coerced_field_init = try sema.coerce(block, field_type, uncoerced_field_init, init_src);
const field_init_val = try sema.resolveConstDefinedValue(block, init_src, coerced_field_init, .{ .simple = .tuple_field_default_value });
if (field_init_val.canMutateComptimeVarState(zcu)) {
const field_name = try zcu.intern_pool.getOrPutStringFmt(gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
return sema.failWithContainsReferenceToComptimeVar(block, init_src, field_name, "field default value", field_init_val);
}
break :init field_init_val.toIntern();
}
break :init .none;
};
}
return Air.internedToRef(try zcu.intern_pool.getTupleType(gpa, pt.tid, .{
.types = types,
.values = inits,
}));
}
fn validateTupleFieldType(
sema: *Sema,
block: *Block,
field_ty: Type,
field_ty_src: LazySrcLoc,
) CompileError!void {
const gpa = sema.gpa;
const zcu = sema.pt.zcu;
if (field_ty.zigTypeTag(zcu) == .@"opaque") {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_ty_src, "opaque types have unknown size and therefore cannot be directly embedded in tuples", .{});
errdefer msg.destroy(gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
});
}
if (field_ty.zigTypeTag(zcu) == .noreturn) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_ty_src, "tuple fields cannot be 'noreturn'", .{});
errdefer msg.destroy(gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
});
}
}
/// Given a ZIR extra index which points to a list of `Zir.Inst.Capture`,
/// resolves this into a list of `InternPool.CaptureValue` allocated by `arena`.
fn getCaptures(sema: *Sema, block: *Block, type_src: LazySrcLoc, extra_index: usize, captures_len: u32) ![]InternPool.CaptureValue {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const parent_ty: Type = .fromInterned(zcu.namespacePtr(block.namespace).owner_type);
const parent_captures: InternPool.CaptureValue.Slice = parent_ty.getCaptures(zcu);
const captures = try sema.arena.alloc(InternPool.CaptureValue, captures_len);
for (sema.code.extra[extra_index..][0..captures_len], sema.code.extra[extra_index + captures_len ..][0..captures_len], captures) |raw, raw_name, *capture| {
const zir_capture: Zir.Inst.Capture = @bitCast(raw);
const zir_name: Zir.NullTerminatedString = @enumFromInt(raw_name);
const zir_name_slice = sema.code.nullTerminatedString(zir_name);
capture.* = switch (zir_capture.unwrap()) {
.nested => |parent_idx| parent_captures.get(ip)[parent_idx],
.instruction_load => |ptr_inst| InternPool.CaptureValue.wrap(capture: {
const ptr_ref = try sema.resolveInst(ptr_inst.toRef());
const ptr_val = try sema.resolveValue(ptr_ref) orelse {
break :capture .{ .runtime = sema.typeOf(ptr_ref).childType(zcu).toIntern() };
};
// TODO: better source location
const unresolved_loaded_val = try sema.pointerDeref(block, type_src, ptr_val, sema.typeOf(ptr_ref)) orelse {
break :capture .{ .runtime = sema.typeOf(ptr_ref).childType(zcu).toIntern() };
};
const loaded_val = try sema.resolveLazyValue(unresolved_loaded_val);
if (loaded_val.canMutateComptimeVarState(zcu)) {
const field_name = try ip.getOrPutString(zcu.gpa, pt.tid, zir_name_slice, .no_embedded_nulls);
return sema.failWithContainsReferenceToComptimeVar(block, type_src, field_name, "captured value", loaded_val);
}
break :capture .{ .@"comptime" = loaded_val.toIntern() };
}),
.instruction => |inst| InternPool.CaptureValue.wrap(capture: {
const air_ref = try sema.resolveInst(inst.toRef());
if (try sema.resolveValueResolveLazy(air_ref)) |val| {
if (val.canMutateComptimeVarState(zcu)) {
const field_name = try ip.getOrPutString(zcu.gpa, pt.tid, zir_name_slice, .no_embedded_nulls);
return sema.failWithContainsReferenceToComptimeVar(block, type_src, field_name, "captured value", val);
}
break :capture .{ .@"comptime" = val.toIntern() };
}
break :capture .{ .runtime = sema.typeOf(air_ref).toIntern() };
}),
.decl_val => |str| capture: {
const decl_name = try ip.getOrPutString(
sema.gpa,
pt.tid,
sema.code.nullTerminatedString(str),
.no_embedded_nulls,
);
const nav = try sema.lookupIdentifier(block, decl_name);
break :capture InternPool.CaptureValue.wrap(.{ .nav_val = nav });
},
.decl_ref => |str| capture: {
const decl_name = try ip.getOrPutString(
sema.gpa,
pt.tid,
sema.code.nullTerminatedString(str),
.no_embedded_nulls,
);
const nav = try sema.lookupIdentifier(block, decl_name);
break :capture InternPool.CaptureValue.wrap(.{ .nav_ref = nav });
},
};
}
return captures;
}
fn zirStructDecl(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
const extra = sema.code.extraData(Zir.Inst.StructDecl, extended.operand);
const tracked_inst = try block.trackZir(inst);
const src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = LazySrcLoc.Offset.nodeOffset(.zero),
};
var extra_index = extra.end;
const captures_len = if (small.has_captures_len) blk: {
const captures_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk captures_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) blk: {
const decls_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk decls_len;
} else 0;
const captures = try sema.getCaptures(block, src, extra_index, captures_len);
extra_index += captures_len * 2;
if (small.has_backing_int) {
const backing_int_body_len = sema.code.extra[extra_index];
extra_index += 1; // backing_int_body_len
if (backing_int_body_len == 0) {
extra_index += 1; // backing_int_ref
} else {
extra_index += backing_int_body_len; // backing_int_body_inst
}
}
const struct_init: InternPool.StructTypeInit = .{
.layout = small.layout,
.fields_len = fields_len,
.known_non_opv = small.known_non_opv,
.requires_comptime = if (small.known_comptime_only) .yes else .unknown,
.any_comptime_fields = small.any_comptime_fields,
.any_default_inits = small.any_default_inits,
.inits_resolved = false,
.any_aligned_fields = small.any_aligned_fields,
.key = .{ .declared = .{
.zir_index = tracked_inst,
.captures = captures,
} },
};
const wip_ty = switch (try ip.getStructType(gpa, pt.tid, struct_init, false)) {
.existing => |ty| {
const new_ty = try pt.ensureTypeUpToDate(ty);
// Make sure we update the namespace if the declaration is re-analyzed, to pick
// up on e.g. changed comptime decls.
try pt.ensureNamespaceUpToDate(Type.fromInterned(new_ty).getNamespaceIndex(zcu));
try sema.declareDependency(.{ .interned = new_ty });
try sema.addTypeReferenceEntry(src, new_ty);
return Air.internedToRef(new_ty);
},
.wip => |wip| wip,
};
errdefer wip_ty.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(
block,
small.name_strategy,
"struct",
inst,
wip_ty.index,
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const new_namespace_index: InternPool.NamespaceIndex = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip_ty.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
errdefer pt.destroyNamespace(new_namespace_index);
if (pt.zcu.comp.config.incremental) {
try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
}
const decls = sema.code.bodySlice(extra_index, decls_len);
try pt.scanNamespace(new_namespace_index, decls);
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
// This job depends on any resolve_type_fully jobs queued up before it.
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
}
try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
}
pub fn createTypeName(
sema: *Sema,
block: *Block,
name_strategy: Zir.Inst.NameStrategy,
anon_prefix: []const u8,
inst: ?Zir.Inst.Index,
/// This is used purely to give the type a unique name in the `anon` case.
type_index: InternPool.Index,
) CompileError!struct {
name: InternPool.NullTerminatedString,
nav: InternPool.Nav.Index.Optional,
} {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
switch (name_strategy) {
.anon => {}, // handled after switch
.parent => return .{
.name = block.type_name_ctx,
.nav = sema.owner.unwrap().nav_val.toOptional(),
},
.func => func_strat: {
const fn_info = sema.code.getFnInfo(ip.funcZirBodyInst(sema.func_index).resolve(ip) orelse return error.AnalysisFail);
const zir_tags = sema.code.instructions.items(.tag);
var aw: std.Io.Writer.Allocating = .init(gpa);
defer aw.deinit();
const w = &aw.writer;
w.print("{f}(", .{block.type_name_ctx.fmt(ip)}) catch return error.OutOfMemory;
var arg_i: usize = 0;
for (fn_info.param_body) |zir_inst| switch (zir_tags[@intFromEnum(zir_inst)]) {
.param, .param_comptime, .param_anytype, .param_anytype_comptime => {
const arg = sema.inst_map.get(zir_inst).?;
// If this is being called in a generic function then analyzeCall will
// have already resolved the args and this will work.
// If not then this is a struct type being returned from a non-generic
// function and the name doesn't matter since it will later
// result in a compile error.
const arg_val = try sema.resolveValue(arg) orelse break :func_strat; // fall through to anon strat
if (arg_i != 0) w.writeByte(',') catch return error.OutOfMemory;
// Limiting the depth here helps avoid type names getting too long, which
// in turn helps to avoid unreasonably long symbol names for namespaced
// symbols. Such names should ideally be human-readable, and additionally,
// some tooling may not support very long symbol names.
w.print("{f}", .{Value.fmtValueSemaFull(.{
.val = arg_val,
.pt = pt,
.opt_sema = sema,
.depth = 1,
})}) catch return error.OutOfMemory;
arg_i += 1;
continue;
},
else => continue,
};
w.writeByte(')') catch return error.OutOfMemory;
return .{
.name = try ip.getOrPutString(gpa, pt.tid, aw.written(), .no_embedded_nulls),
.nav = .none,
};
},
.dbg_var => {
// TODO: this logic is questionable. We ideally should be traversing the `Block` rather than relying on the order of AstGen instructions.
const ref = inst.?.toRef();
const zir_tags = sema.code.instructions.items(.tag);
const zir_data = sema.code.instructions.items(.data);
for (@intFromEnum(inst.?)..zir_tags.len) |i| switch (zir_tags[i]) {
.dbg_var_ptr, .dbg_var_val => if (zir_data[i].str_op.operand == ref) {
return .{
.name = try ip.getOrPutStringFmt(gpa, pt.tid, "{f}.{s}", .{
block.type_name_ctx.fmt(ip), zir_data[i].str_op.getStr(sema.code),
}, .no_embedded_nulls),
.nav = .none,
};
},
else => {},
};
// fall through to anon strat
},
}
// anon strat handling
// It would be neat to have "struct:line:column" but this name has
// to survive incremental updates, where it may have been shifted down
// or up to a different line, but unchanged, and thus not unnecessarily
// semantically analyzed.
// TODO: that would be possible, by detecting line number changes and renaming
// types appropriately. However, `@typeName` becomes a problem then. If we remove
// that builtin from the language, we can consider this.
return .{
.name = try ip.getOrPutStringFmt(gpa, pt.tid, "{f}__{s}_{d}", .{
block.type_name_ctx.fmt(ip), anon_prefix, @intFromEnum(type_index),
}, .no_embedded_nulls),
.nav = .none,
};
}
fn zirEnumDecl(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const small: Zir.Inst.EnumDecl.Small = @bitCast(extended.small);
const extra = sema.code.extraData(Zir.Inst.EnumDecl, extended.operand);
var extra_index: usize = extra.end;
const tracked_inst = try block.trackZir(inst);
const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(.zero) };
const tag_type_ref = if (small.has_tag_type) blk: {
const tag_type_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
break :blk tag_type_ref;
} else .none;
const captures_len = if (small.has_captures_len) blk: {
const captures_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk captures_len;
} else 0;
const body_len = if (small.has_body_len) blk: {
const body_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk body_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) blk: {
const decls_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk decls_len;
} else 0;
const captures = try sema.getCaptures(block, src, extra_index, captures_len);
extra_index += captures_len * 2;
const decls = sema.code.bodySlice(extra_index, decls_len);
extra_index += decls_len;
const body = sema.code.bodySlice(extra_index, body_len);
extra_index += body.len;
const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;
const body_end = extra_index;
extra_index += bit_bags_count;
const any_values = for (sema.code.extra[body_end..][0..bit_bags_count]) |bag| {
if (bag != 0) break true;
} else false;
const enum_init: InternPool.EnumTypeInit = .{
.has_values = any_values,
.tag_mode = if (small.nonexhaustive)
.nonexhaustive
else if (tag_type_ref == .none)
.auto
else
.explicit,
.fields_len = fields_len,
.key = .{ .declared = .{
.zir_index = tracked_inst,
.captures = captures,
} },
};
const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, enum_init, false)) {
.existing => |ty| {
const new_ty = try pt.ensureTypeUpToDate(ty);
// Make sure we update the namespace if the declaration is re-analyzed, to pick
// up on e.g. changed comptime decls.
try pt.ensureNamespaceUpToDate(Type.fromInterned(new_ty).getNamespaceIndex(zcu));
try sema.declareDependency(.{ .interned = new_ty });
try sema.addTypeReferenceEntry(src, new_ty);
// Since this is an enum, it has to be resolved immediately.
// `ensureTypeUpToDate` has resolved the new type if necessary.
// We just need to check for resolution failures.
const ty_unit: AnalUnit = .wrap(.{ .type = new_ty });
if (zcu.failed_analysis.contains(ty_unit) or zcu.transitive_failed_analysis.contains(ty_unit)) {
return error.AnalysisFail;
}
return Air.internedToRef(new_ty);
},
.wip => |wip| wip,
};
// Once this is `true`, we will not delete the decl or type even upon failure, since we
// have finished constructing the type and are in the process of analyzing it.
var done = false;
errdefer if (!done) wip_ty.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(
block,
small.name_strategy,
"enum",
inst,
wip_ty.index,
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const new_namespace_index: InternPool.NamespaceIndex = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip_ty.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
errdefer if (!done) pt.destroyNamespace(new_namespace_index);
try pt.scanNamespace(new_namespace_index, decls);
try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index);
// We've finished the initial construction of this type, and are about to perform analysis.
// Set the namespace appropriately, and don't destroy anything on failure.
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
wip_ty.prepare(ip, new_namespace_index);
done = true;
{
const tracked_unit = zcu.trackUnitSema(type_name.name.toSlice(ip), null);
defer tracked_unit.end(zcu);
try Sema.resolveDeclaredEnum(
pt,
wip_ty,
inst,
tracked_inst,
new_namespace_index,
type_name.name,
small,
body,
tag_type_ref,
any_values,
fields_len,
sema.code,
body_end,
);
}
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
// This job depends on any resolve_type_fully jobs queued up before it.
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
}
return Air.internedToRef(wip_ty.index);
}
fn zirUnionDecl(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small);
const extra = sema.code.extraData(Zir.Inst.UnionDecl, extended.operand);
var extra_index: usize = extra.end;
const tracked_inst = try block.trackZir(inst);
const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(.zero) };
extra_index += @intFromBool(small.has_tag_type);
const captures_len = if (small.has_captures_len) blk: {
const captures_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk captures_len;
} else 0;
extra_index += @intFromBool(small.has_body_len);
const fields_len = if (small.has_fields_len) blk: {
const fields_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) blk: {
const decls_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk decls_len;
} else 0;
const captures = try sema.getCaptures(block, src, extra_index, captures_len);
extra_index += captures_len * 2;
const union_init: InternPool.UnionTypeInit = .{
.flags = .{
.layout = small.layout,
.status = .none,
.runtime_tag = if (small.has_tag_type or small.auto_enum_tag)
.tagged
else if (small.layout != .auto)
.none
else switch (block.wantSafeTypes()) {
true => .safety,
false => .none,
},
.any_aligned_fields = small.any_aligned_fields,
.requires_comptime = .unknown,
.assumed_runtime_bits = false,
.assumed_pointer_aligned = false,
.alignment = .none,
},
.fields_len = fields_len,
.enum_tag_ty = .none, // set later
.field_types = &.{}, // set later
.field_aligns = &.{}, // set later
.key = .{ .declared = .{
.zir_index = tracked_inst,
.captures = captures,
} },
};
const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, union_init, false)) {
.existing => |ty| {
const new_ty = try pt.ensureTypeUpToDate(ty);
// Make sure we update the namespace if the declaration is re-analyzed, to pick
// up on e.g. changed comptime decls.
try pt.ensureNamespaceUpToDate(Type.fromInterned(new_ty).getNamespaceIndex(zcu));
try sema.declareDependency(.{ .interned = new_ty });
try sema.addTypeReferenceEntry(src, new_ty);
return Air.internedToRef(new_ty);
},
.wip => |wip| wip,
};
errdefer wip_ty.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(
block,
small.name_strategy,
"union",
inst,
wip_ty.index,
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const new_namespace_index: InternPool.NamespaceIndex = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip_ty.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
errdefer pt.destroyNamespace(new_namespace_index);
if (pt.zcu.comp.config.incremental) {
try pt.addDependency(.wrap(.{ .type = wip_ty.index }), .{ .src_hash = tracked_inst });
}
const decls = sema.code.bodySlice(extra_index, decls_len);
try pt.scanNamespace(new_namespace_index, decls);
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
// This job depends on any resolve_type_fully jobs queued up before it.
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
}
try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
}
fn zirOpaqueDecl(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const small: Zir.Inst.OpaqueDecl.Small = @bitCast(extended.small);
const extra = sema.code.extraData(Zir.Inst.OpaqueDecl, extended.operand);
var extra_index: usize = extra.end;
const tracked_inst = try block.trackZir(inst);
const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(.zero) };
const captures_len = if (small.has_captures_len) blk: {
const captures_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk captures_len;
} else 0;
const decls_len = if (small.has_decls_len) blk: {
const decls_len = sema.code.extra[extra_index];
extra_index += 1;
break :blk decls_len;
} else 0;
const captures = try sema.getCaptures(block, src, extra_index, captures_len);
extra_index += captures_len * 2;
const opaque_init: InternPool.OpaqueTypeInit = .{
.zir_index = tracked_inst,
.captures = captures,
};
const wip_ty = switch (try ip.getOpaqueType(gpa, pt.tid, opaque_init)) {
.existing => |ty| {
// Make sure we update the namespace if the declaration is re-analyzed, to pick
// up on e.g. changed comptime decls.
try pt.ensureNamespaceUpToDate(Type.fromInterned(ty).getNamespaceIndex(zcu));
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(src, ty);
return Air.internedToRef(ty);
},
.wip => |wip| wip,
};
errdefer wip_ty.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(
block,
small.name_strategy,
"opaque",
inst,
wip_ty.index,
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const new_namespace_index: InternPool.NamespaceIndex = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip_ty.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
errdefer pt.destroyNamespace(new_namespace_index);
const decls = sema.code.bodySlice(extra_index, decls_len);
try pt.scanNamespace(new_namespace_index, decls);
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
// This job depends on any resolve_type_fully jobs queued up before it.
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
}
try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return Air.internedToRef(wip_ty.finish(ip, new_namespace_index));
}
fn zirErrorSetDecl(
sema: *Sema,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.ErrorSetDecl, inst_data.payload_index);
var names: InferredErrorSet.NameMap = .{};
try names.ensureUnusedCapacity(sema.arena, extra.data.fields_len);
var extra_index: u32 = @intCast(extra.end);
const extra_index_end = extra_index + extra.data.fields_len;
while (extra_index < extra_index_end) : (extra_index += 1) {
const name_index: Zir.NullTerminatedString = @enumFromInt(sema.code.extra[extra_index]);
const name = sema.code.nullTerminatedString(name_index);
const name_ip = try zcu.intern_pool.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
_ = try pt.getErrorValue(name_ip);
const result = names.getOrPutAssumeCapacity(name_ip);
assert(!result.found_existing); // verified in AstGen
}
return Air.internedToRef((try pt.errorSetFromUnsortedNames(names.keys())).toIntern());
}
fn zirRetPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const src = block.nodeOffset(sema.code.instructions.items(.data)[@intFromEnum(inst)].node);
if (block.isComptime() or try sema.fn_ret_ty.comptimeOnlySema(pt)) {
try sema.fn_ret_ty.resolveFields(pt);
return sema.analyzeComptimeAlloc(block, src, sema.fn_ret_ty, .none);
}
const target = pt.zcu.getTarget();
const ptr_type = try pt.ptrTypeSema(.{
.child = sema.fn_ret_ty.toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
if (block.inlining != null) {
// We are inlining a function call; this should be emitted as an alloc, not a ret_ptr.
// TODO when functions gain result location support, the inlining struct in
// Block should contain the return pointer, and we would pass that through here.
return block.addTy(.alloc, ptr_type);
}
return block.addTy(.ret_ptr, ptr_type);
}
fn zirRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_tok;
const operand = try sema.resolveInst(inst_data.operand);
return sema.analyzeRef(block, block.tokenOffset(inst_data.src_tok), operand);
}
fn zirEnsureResultUsed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand = try sema.resolveInst(inst_data.operand);
const src = block.nodeOffset(inst_data.src_node);
return sema.ensureResultUsed(block, sema.typeOf(operand), src);
}
fn ensureResultUsed(
sema: *Sema,
block: *Block,
ty: Type,
src: LazySrcLoc,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.void, .noreturn => return,
.error_set => return sema.fail(block, src, "error set is ignored", .{}),
.error_union => {
const msg = msg: {
const msg = try sema.errMsg(src, "error union is ignored", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "consider using 'try', 'catch', or 'if'", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
else => {
const msg = msg: {
const msg = try sema.errMsg(src, "value of type '{f}' ignored", .{ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "all non-void values must be used", .{});
try sema.errNote(src, msg, "to discard the value, assign it to '_'", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
}
}
fn zirEnsureResultNonError(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand = try sema.resolveInst(inst_data.operand);
const src = block.nodeOffset(inst_data.src_node);
const operand_ty = sema.typeOf(operand);
switch (operand_ty.zigTypeTag(zcu)) {
.error_set => return sema.fail(block, src, "error set is discarded", .{}),
.error_union => {
const msg = msg: {
const msg = try sema.errMsg(src, "error union is discarded", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "consider using 'try', 'catch', or 'if'", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
else => return,
}
}
fn zirEnsureErrUnionPayloadVoid(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const err_union_ty = if (operand_ty.zigTypeTag(zcu) == .pointer)
operand_ty.childType(zcu)
else
operand_ty;
if (err_union_ty.zigTypeTag(zcu) != .error_union) return;
const payload_ty = err_union_ty.errorUnionPayload(zcu).zigTypeTag(zcu);
if (payload_ty != .void and payload_ty != .noreturn) {
const msg = msg: {
const msg = try sema.errMsg(src, "error union payload is ignored", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "payload value can be explicitly ignored with '|_|'", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const object = try sema.resolveInst(inst_data.operand);
return indexablePtrLen(sema, block, src, object);
}
fn indexablePtrLen(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
object: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const object_ty = sema.typeOf(object);
const is_pointer_to = object_ty.isSinglePointer(zcu);
const indexable_ty = if (is_pointer_to) object_ty.childType(zcu) else object_ty;
try sema.checkIndexable(block, src, indexable_ty);
const field_name = try zcu.intern_pool.getOrPutString(sema.gpa, pt.tid, "len", .no_embedded_nulls);
return sema.fieldVal(block, src, object, field_name, src);
}
fn indexablePtrLenOrNone(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
try checkMemOperand(sema, block, src, operand_ty);
switch (operand_ty.ptrSize(zcu)) {
.many, .c => return .none,
.one, .slice => {},
}
const field_name = try zcu.intern_pool.getOrPutString(sema.gpa, pt.tid, "len", .no_embedded_nulls);
return sema.fieldVal(block, src, operand, field_name, src);
}
fn zirAllocExtended(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const gpa = sema.gpa;
const extra = sema.code.extraData(Zir.Inst.AllocExtended, extended.operand);
const var_src = block.nodeOffset(extra.data.src_node);
const ty_src = block.src(.{ .node_offset_var_decl_ty = extra.data.src_node });
const align_src = block.src(.{ .node_offset_var_decl_align = extra.data.src_node });
const small: Zir.Inst.AllocExtended.Small = @bitCast(extended.small);
var extra_index: usize = extra.end;
const var_ty: Type = if (small.has_type) blk: {
const type_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
break :blk try sema.resolveType(block, ty_src, type_ref);
} else undefined;
const alignment = if (small.has_align) blk: {
const align_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
break :blk try sema.resolveAlign(block, align_src, align_ref);
} else .none;
if (block.isComptime() or small.is_comptime) {
if (small.has_type) {
return sema.analyzeComptimeAlloc(block, var_src, var_ty, alignment);
} else {
try sema.air_instructions.append(gpa, .{
.tag = .inferred_alloc_comptime,
.data = .{ .inferred_alloc_comptime = .{
.alignment = alignment,
.is_const = small.is_const,
.ptr = undefined,
} },
});
return @as(Air.Inst.Index, @enumFromInt(sema.air_instructions.len - 1)).toRef();
}
}
if (small.has_type and try var_ty.comptimeOnlySema(pt)) {
return sema.analyzeComptimeAlloc(block, var_src, var_ty, alignment);
}
if (small.has_type) {
if (!small.is_const) {
try sema.validateVarType(block, ty_src, var_ty, false);
}
const target = pt.zcu.getTarget();
try var_ty.resolveLayout(pt);
if (sema.func_is_naked and try var_ty.hasRuntimeBitsSema(pt)) {
const store_src = block.src(.{ .node_offset_store_ptr = extra.data.src_node });
return sema.fail(block, store_src, "local variable in naked function", .{});
}
const ptr_type = try sema.pt.ptrTypeSema(.{
.child = var_ty.toIntern(),
.flags = .{
.alignment = alignment,
.address_space = target_util.defaultAddressSpace(target, .local),
},
});
const ptr = try block.addTy(.alloc, ptr_type);
if (small.is_const) {
const ptr_inst = ptr.toIndex().?;
try sema.maybe_comptime_allocs.put(gpa, ptr_inst, .{ .runtime_index = block.runtime_index });
try sema.base_allocs.put(gpa, ptr_inst, ptr_inst);
}
return ptr;
}
const result_index = try block.addInstAsIndex(.{
.tag = .inferred_alloc,
.data = .{ .inferred_alloc = .{
.alignment = alignment,
.is_const = small.is_const,
} },
});
try sema.unresolved_inferred_allocs.putNoClobber(gpa, result_index, .{});
if (small.is_const) {
try sema.maybe_comptime_allocs.put(gpa, result_index, .{ .runtime_index = block.runtime_index });
try sema.base_allocs.put(gpa, result_index, result_index);
}
return result_index.toRef();
}
fn zirAllocComptime(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const ty_src = block.src(.{ .node_offset_var_decl_ty = inst_data.src_node });
const var_src = block.nodeOffset(inst_data.src_node);
const var_ty = try sema.resolveType(block, ty_src, inst_data.operand);
return sema.analyzeComptimeAlloc(block, var_src, var_ty, .none);
}
fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const alloc = try sema.resolveInst(inst_data.operand);
const alloc_ty = sema.typeOf(alloc);
const ptr_info = alloc_ty.ptrInfo(zcu);
const elem_ty: Type = .fromInterned(ptr_info.child);
// If the alloc was created in a comptime scope, we already created a comptime alloc for it.
// However, if the final constructed value does not reference comptime-mutable memory, we wish
// to promote it to an anon decl.
already_ct: {
const ptr_val = try sema.resolveValue(alloc) orelse break :already_ct;
// If this was a comptime inferred alloc, then `storeToInferredAllocComptime`
// might have already done our job and created an anon decl ref.
switch (zcu.intern_pool.indexToKey(ptr_val.toIntern())) {
.ptr => |ptr| switch (ptr.base_addr) {
.uav => {
// The comptime-ification was already done for us.
// Just make sure the pointer is const.
return sema.makePtrConst(block, alloc);
},
else => {},
},
else => {},
}
if (!sema.isComptimeMutablePtr(ptr_val)) break :already_ct;
const ptr = zcu.intern_pool.indexToKey(ptr_val.toIntern()).ptr;
assert(ptr.byte_offset == 0);
const alloc_index = ptr.base_addr.comptime_alloc;
const ct_alloc = sema.getComptimeAlloc(alloc_index);
const interned = try ct_alloc.val.intern(pt, sema.arena);
if (interned.canMutateComptimeVarState(zcu)) {
// Preserve the comptime alloc, just make the pointer const.
ct_alloc.val = .{ .interned = interned.toIntern() };
ct_alloc.is_const = true;
return sema.makePtrConst(block, alloc);
} else {
// Promote the constant to an anon decl.
const new_mut_ptr = Air.internedToRef(try pt.intern(.{ .ptr = .{
.ty = alloc_ty.toIntern(),
.base_addr = .{ .uav = .{
.val = interned.toIntern(),
.orig_ty = alloc_ty.toIntern(),
} },
.byte_offset = 0,
} }));
return sema.makePtrConst(block, new_mut_ptr);
}
}
// Otherwise, check if the alloc is comptime-known despite being in a runtime scope.
if (try sema.resolveComptimeKnownAllocPtr(block, alloc, null)) |ptr_val| {
return sema.makePtrConst(block, Air.internedToRef(ptr_val));
}
if (try elem_ty.comptimeOnlySema(pt)) {
// The value was initialized through RLS, so we didn't detect the runtime condition earlier.
// TODO: source location of runtime control flow
const init_src = block.src(.{ .node_offset_var_decl_init = inst_data.src_node });
return sema.fail(block, init_src, "value with comptime-only type '{f}' depends on runtime control flow", .{elem_ty.fmt(pt)});
}
// This is a runtime value.
return sema.makePtrConst(block, alloc);
}
/// If `alloc` is an inferred allocation, `resolved_inferred_ty` is taken to be its resolved
/// type. Otherwise, it may be `null`, and the type will be inferred from `alloc`.
fn resolveComptimeKnownAllocPtr(sema: *Sema, block: *Block, alloc: Air.Inst.Ref, resolved_alloc_ty: ?Type) CompileError!?InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const alloc_ty = resolved_alloc_ty orelse sema.typeOf(alloc);
const ptr_info = alloc_ty.ptrInfo(zcu);
const elem_ty: Type = .fromInterned(ptr_info.child);
const alloc_inst = alloc.toIndex() orelse return null;
const comptime_info = sema.maybe_comptime_allocs.fetchRemove(alloc_inst) orelse return null;
const stores = comptime_info.value.stores.items(.inst);
// Since the entry existed in `maybe_comptime_allocs`, the allocation is comptime-known.
// We will resolve and return its value.
// We expect to have emitted at least one store, unless the elem type is OPV.
if (stores.len == 0) {
const val = (try sema.typeHasOnePossibleValue(elem_ty)).?.toIntern();
return sema.finishResolveComptimeKnownAllocPtr(block, alloc_ty, val, null, alloc_inst, comptime_info.value);
}
// In general, we want to create a comptime alloc of the correct type and
// apply the stores to that alloc in order. However, before going to all
// that effort, let's optimize for the common case of a single store.
simple: {
if (stores.len != 1) break :simple;
const store_inst = sema.air_instructions.get(@intFromEnum(stores[0]));
switch (store_inst.tag) {
.store, .store_safe => {},
.set_union_tag, .optional_payload_ptr_set, .errunion_payload_ptr_set => break :simple, // there's OPV stuff going on!
else => unreachable,
}
if (store_inst.data.bin_op.lhs != alloc) break :simple;
const val = store_inst.data.bin_op.rhs.toInterned().?;
assert(zcu.intern_pool.typeOf(val) == elem_ty.toIntern());
return sema.finishResolveComptimeKnownAllocPtr(block, alloc_ty, val, null, alloc_inst, comptime_info.value);
}
// The simple strategy failed: we must create a mutable comptime alloc and
// perform all of the runtime store operations at comptime.
const ct_alloc = try sema.newComptimeAlloc(block, .unneeded, elem_ty, ptr_info.flags.alignment);
const alloc_ptr = try pt.intern(.{ .ptr = .{
.ty = alloc_ty.toIntern(),
.base_addr = .{ .comptime_alloc = ct_alloc },
.byte_offset = 0,
} });
// Maps from pointers into the runtime allocs, to comptime-mutable pointers into the comptime alloc
var ptr_mapping = std.AutoHashMap(Air.Inst.Index, InternPool.Index).init(sema.arena);
try ptr_mapping.ensureTotalCapacity(@intCast(stores.len));
ptr_mapping.putAssumeCapacity(alloc_inst, alloc_ptr);
// Whilst constructing our mapping, we will also initialize optional and error union payloads when
// we encounter the corresponding pointers. For this reason, the ordering of `to_map` matters.
var to_map = try std.array_list.Managed(Air.Inst.Index).initCapacity(sema.arena, stores.len);
for (stores) |store_inst_idx| {
const store_inst = sema.air_instructions.get(@intFromEnum(store_inst_idx));
const ptr_to_map = switch (store_inst.tag) {
.store, .store_safe => store_inst.data.bin_op.lhs.toIndex().?, // Map the pointer being stored to.
.set_union_tag => store_inst.data.bin_op.lhs.toIndex().?, // Map the union pointer.
.optional_payload_ptr_set, .errunion_payload_ptr_set => store_inst_idx, // Map the generated pointer itself.
else => unreachable,
};
to_map.appendAssumeCapacity(ptr_to_map);
}
const tmp_air = sema.getTmpAir();
while (to_map.pop()) |air_ptr| {
if (ptr_mapping.contains(air_ptr)) continue;
const PointerMethod = union(enum) {
same_addr,
opt_payload,
eu_payload,
field: u32,
elem: u64,
};
const inst_tag = tmp_air.instructions.items(.tag)[@intFromEnum(air_ptr)];
const air_parent_ptr: Air.Inst.Ref, const method: PointerMethod = switch (inst_tag) {
.struct_field_ptr => blk: {
const data = tmp_air.extraData(
Air.StructField,
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_pl.payload,
).data;
break :blk .{
data.struct_operand,
.{ .field = data.field_index },
};
},
.struct_field_ptr_index_0,
.struct_field_ptr_index_1,
.struct_field_ptr_index_2,
.struct_field_ptr_index_3,
=> .{
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_op.operand,
.{ .field = switch (inst_tag) {
.struct_field_ptr_index_0 => 0,
.struct_field_ptr_index_1 => 1,
.struct_field_ptr_index_2 => 2,
.struct_field_ptr_index_3 => 3,
else => unreachable,
} },
},
.ptr_slice_ptr_ptr => .{
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_op.operand,
.{ .field = Value.slice_ptr_index },
},
.ptr_slice_len_ptr => .{
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_op.operand,
.{ .field = Value.slice_len_index },
},
.ptr_elem_ptr => blk: {
const data = tmp_air.extraData(
Air.Bin,
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_pl.payload,
).data;
const idx_val = (try sema.resolveValue(data.rhs)).?;
break :blk .{
data.lhs,
.{ .elem = try idx_val.toUnsignedIntSema(pt) },
};
},
.bitcast => .{
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_op.operand,
.same_addr,
},
.optional_payload_ptr_set => .{
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_op.operand,
.opt_payload,
},
.errunion_payload_ptr_set => .{
tmp_air.instructions.items(.data)[@intFromEnum(air_ptr)].ty_op.operand,
.eu_payload,
},
else => unreachable,
};
const decl_parent_ptr = ptr_mapping.get(air_parent_ptr.toIndex().?) orelse {
// Resolve the parent pointer first.
// Note that we add in what seems like the wrong order, because we're popping from the end of this array.
try to_map.appendSlice(&.{ air_ptr, air_parent_ptr.toIndex().? });
continue;
};
const new_ptr_ty = tmp_air.typeOfIndex(air_ptr, &zcu.intern_pool).toIntern();
const new_ptr = switch (method) {
.same_addr => try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, decl_parent_ptr, new_ptr_ty),
.opt_payload => ptr: {
// Set the optional to non-null at comptime.
// If the payload is OPV, we must use that value instead of undef.
const opt_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu);
const payload_ty = opt_ty.optionalChild(zcu);
const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try pt.undefValue(payload_ty);
const opt_val = try pt.intern(.{ .opt = .{
.ty = opt_ty.toIntern(),
.val = payload_val.toIntern(),
} });
try sema.storePtrVal(block, LazySrcLoc.unneeded, Value.fromInterned(decl_parent_ptr), Value.fromInterned(opt_val), opt_ty);
break :ptr (try Value.fromInterned(decl_parent_ptr).ptrOptPayload(pt)).toIntern();
},
.eu_payload => ptr: {
// Set the error union to non-error at comptime.
// If the payload is OPV, we must use that value instead of undef.
const eu_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu);
const payload_ty = eu_ty.errorUnionPayload(zcu);
const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try pt.undefValue(payload_ty);
const eu_val = try pt.intern(.{ .error_union = .{
.ty = eu_ty.toIntern(),
.val = .{ .payload = payload_val.toIntern() },
} });
try sema.storePtrVal(block, LazySrcLoc.unneeded, Value.fromInterned(decl_parent_ptr), Value.fromInterned(eu_val), eu_ty);
break :ptr (try Value.fromInterned(decl_parent_ptr).ptrEuPayload(pt)).toIntern();
},
.field => |idx| ptr: {
const maybe_union_ty = Value.fromInterned(decl_parent_ptr).typeOf(zcu).childType(zcu);
if (zcu.typeToUnion(maybe_union_ty)) |union_obj| {
// As this is a union field, we must store to the pointer now to set the tag.
// The payload value will be stored later, so undef is a sufficent payload for now.
const payload_ty: Type = .fromInterned(union_obj.field_types.get(&zcu.intern_pool)[idx]);
const payload_val = try pt.undefValue(payload_ty);
const tag_val = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), idx);
const store_val = try pt.unionValue(maybe_union_ty, tag_val, payload_val);
try sema.storePtrVal(block, .unneeded, .fromInterned(decl_parent_ptr), store_val, maybe_union_ty);
}
break :ptr (try Value.fromInterned(decl_parent_ptr).ptrField(idx, pt)).toIntern();
},
.elem => |idx| (try Value.fromInterned(decl_parent_ptr).ptrElem(idx, pt)).toIntern(),
};
try ptr_mapping.put(air_ptr, new_ptr);
}
// We have a correlation between AIR pointers and decl pointers. Perform all stores at comptime.
// Any implicit stores performed by `optional_payload_ptr_set` or `errunion_payload_ptr_set`
// instructions were already done above.
for (stores) |store_inst_idx| {
const store_inst = sema.air_instructions.get(@intFromEnum(store_inst_idx));
switch (store_inst.tag) {
.optional_payload_ptr_set, .errunion_payload_ptr_set => {}, // Handled explicitly above
.set_union_tag => {
// Usually, we can ignore these, because the creation of the field pointer above
// already did it for us. However, if the field is OPV, this is relevant, because
// there is not going to be a store to the field. So we must initialize the union
// tag if the field is OPV.
const union_ptr_inst = store_inst.data.bin_op.lhs.toIndex().?;
const union_ptr_val: Value = .fromInterned(ptr_mapping.get(union_ptr_inst).?);
const tag_val: Value = .fromInterned(store_inst.data.bin_op.rhs.toInterned().?);
const union_ty = union_ptr_val.typeOf(zcu).childType(zcu);
const field_ty = union_ty.unionFieldType(tag_val, zcu).?;
if (try sema.typeHasOnePossibleValue(field_ty)) |payload_val| {
const new_union_val = try pt.unionValue(union_ty, tag_val, payload_val);
try sema.storePtrVal(block, .unneeded, union_ptr_val, new_union_val, union_ty);
}
},
.store, .store_safe => {
const air_ptr_inst = store_inst.data.bin_op.lhs.toIndex().?;
const store_val = (try sema.resolveValue(store_inst.data.bin_op.rhs)).?;
const new_ptr = ptr_mapping.get(air_ptr_inst).?;
try sema.storePtrVal(block, .unneeded, .fromInterned(new_ptr), store_val, store_val.typeOf(zcu));
},
else => unreachable,
}
}
// The value is finalized - load it!
const val = (try sema.pointerDeref(block, LazySrcLoc.unneeded, Value.fromInterned(alloc_ptr), alloc_ty)).?.toIntern();
return sema.finishResolveComptimeKnownAllocPtr(block, alloc_ty, val, ct_alloc, alloc_inst, comptime_info.value);
}
/// Given the resolved comptime-known value, rewrites the dead AIR to not
/// create a runtime stack allocation. Also places the resulting value into
/// either an anon decl ref or a comptime alloc depending on whether it
/// references comptime-mutable memory. If `existing_comptime_alloc` is
/// passed, it is a scratch allocation which already contains `result_val`.
/// Same return type as `resolveComptimeKnownAllocPtr` so we can tail call.
fn finishResolveComptimeKnownAllocPtr(
sema: *Sema,
block: *Block,
alloc_ty: Type,
result_val: InternPool.Index,
existing_comptime_alloc: ?ComptimeAllocIndex,
alloc_inst: Air.Inst.Index,
comptime_info: MaybeComptimeAlloc,
) CompileError!?InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
// We're almost done - we have the resolved comptime value. We just need to
// eliminate the now-dead runtime instructions.
// This instruction has type `alloc_ty`, meaning we can rewrite the `alloc` AIR instruction to
// this one to drop the side effect. We also need to rewrite the stores; we'll turn them to this
// too because it doesn't really matter what they become.
const nop_inst: Air.Inst = .{ .tag = .bitcast, .data = .{ .ty_op = .{
.ty = .fromIntern(alloc_ty.toIntern()),
.operand = .zero_usize,
} } };
sema.air_instructions.set(@intFromEnum(alloc_inst), nop_inst);
for (comptime_info.stores.items(.inst)) |store_inst| {
sema.air_instructions.set(@intFromEnum(store_inst), nop_inst);
}
if (Value.fromInterned(result_val).canMutateComptimeVarState(zcu)) {
const alloc_index = existing_comptime_alloc orelse a: {
const idx = try sema.newComptimeAlloc(block, .unneeded, alloc_ty.childType(zcu), alloc_ty.ptrAlignment(zcu));
const alloc = sema.getComptimeAlloc(idx);
alloc.val = .{ .interned = result_val };
break :a idx;
};
sema.getComptimeAlloc(alloc_index).is_const = true;
return try pt.intern(.{ .ptr = .{
.ty = alloc_ty.toIntern(),
.base_addr = .{ .comptime_alloc = alloc_index },
.byte_offset = 0,
} });
} else {
return try pt.intern(.{ .ptr = .{
.ty = alloc_ty.toIntern(),
.base_addr = .{ .uav = .{
.orig_ty = alloc_ty.toIntern(),
.val = result_val,
} },
.byte_offset = 0,
} });
}
}
fn makePtrTyConst(sema: *Sema, ptr_ty: Type) CompileError!Type {
var ptr_info = ptr_ty.ptrInfo(sema.pt.zcu);
ptr_info.flags.is_const = true;
return sema.pt.ptrTypeSema(ptr_info);
}
fn makePtrConst(sema: *Sema, block: *Block, alloc: Air.Inst.Ref) CompileError!Air.Inst.Ref {
const alloc_ty = sema.typeOf(alloc);
const const_ptr_ty = try sema.makePtrTyConst(alloc_ty);
// Detect if a comptime value simply needs to have its type changed.
if (try sema.resolveValue(alloc)) |val| {
return Air.internedToRef((try sema.pt.getCoerced(val, const_ptr_ty)).toIntern());
}
return block.addBitCast(const_ptr_ty, alloc);
}
fn zirAllocInferredComptime(
sema: *Sema,
is_const: bool,
) CompileError!Air.Inst.Ref {
const gpa = sema.gpa;
try sema.air_instructions.append(gpa, .{
.tag = .inferred_alloc_comptime,
.data = .{ .inferred_alloc_comptime = .{
.alignment = .none,
.is_const = is_const,
.ptr = undefined,
} },
});
return @as(Air.Inst.Index, @enumFromInt(sema.air_instructions.len - 1)).toRef();
}
fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const ty_src = block.src(.{ .node_offset_var_decl_ty = inst_data.src_node });
const var_src = block.nodeOffset(inst_data.src_node);
const var_ty = try sema.resolveType(block, ty_src, inst_data.operand);
if (block.isComptime() or try var_ty.comptimeOnlySema(pt)) {
return sema.analyzeComptimeAlloc(block, var_src, var_ty, .none);
}
if (sema.func_is_naked and try var_ty.hasRuntimeBitsSema(pt)) {
const mut_src = block.src(.{ .node_offset_store_ptr = inst_data.src_node });
return sema.fail(block, mut_src, "local variable in naked function", .{});
}
const target = pt.zcu.getTarget();
const ptr_type = try pt.ptrTypeSema(.{
.child = var_ty.toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const ptr = try block.addTy(.alloc, ptr_type);
const ptr_inst = ptr.toIndex().?;
try sema.maybe_comptime_allocs.put(sema.gpa, ptr_inst, .{ .runtime_index = block.runtime_index });
try sema.base_allocs.put(sema.gpa, ptr_inst, ptr_inst);
return ptr;
}
fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const ty_src = block.src(.{ .node_offset_var_decl_ty = inst_data.src_node });
const var_src = block.nodeOffset(inst_data.src_node);
const var_ty = try sema.resolveType(block, ty_src, inst_data.operand);
if (block.isComptime()) {
return sema.analyzeComptimeAlloc(block, var_src, var_ty, .none);
}
if (sema.func_is_naked and try var_ty.hasRuntimeBitsSema(pt)) {
const store_src = block.src(.{ .node_offset_store_ptr = inst_data.src_node });
return sema.fail(block, store_src, "local variable in naked function", .{});
}
try sema.validateVarType(block, ty_src, var_ty, false);
const target = pt.zcu.getTarget();
const ptr_type = try pt.ptrTypeSema(.{
.child = var_ty.toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
return block.addTy(.alloc, ptr_type);
}
fn zirAllocInferred(
sema: *Sema,
block: *Block,
is_const: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const gpa = sema.gpa;
if (block.isComptime()) {
try sema.air_instructions.append(gpa, .{
.tag = .inferred_alloc_comptime,
.data = .{ .inferred_alloc_comptime = .{
.alignment = .none,
.is_const = is_const,
.ptr = undefined,
} },
});
return @as(Air.Inst.Index, @enumFromInt(sema.air_instructions.len - 1)).toRef();
}
const result_index = try block.addInstAsIndex(.{
.tag = .inferred_alloc,
.data = .{ .inferred_alloc = .{
.alignment = .none,
.is_const = is_const,
} },
});
try sema.unresolved_inferred_allocs.putNoClobber(gpa, result_index, .{});
if (is_const) {
try sema.maybe_comptime_allocs.put(gpa, result_index, .{ .runtime_index = block.runtime_index });
try sema.base_allocs.put(sema.gpa, result_index, result_index);
}
return result_index.toRef();
}
fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ty_src = block.src(.{ .node_offset_var_decl_ty = inst_data.src_node });
const ptr = try sema.resolveInst(inst_data.operand);
const ptr_inst = ptr.toIndex().?;
const target = zcu.getTarget();
switch (sema.air_instructions.items(.tag)[@intFromEnum(ptr_inst)]) {
.inferred_alloc_comptime => {
// The work was already done for us by `Sema.storeToInferredAllocComptime`.
// All we need to do is return the pointer.
const iac = sema.air_instructions.items(.data)[@intFromEnum(ptr_inst)].inferred_alloc_comptime;
const resolved_ptr = iac.ptr;
if (std.debug.runtime_safety) {
// The inferred_alloc_comptime should never be referenced again
sema.air_instructions.set(@intFromEnum(ptr_inst), .{ .tag = undefined, .data = undefined });
}
const val = switch (zcu.intern_pool.indexToKey(resolved_ptr).ptr.base_addr) {
.uav => |a| a.val,
.comptime_alloc => |i| val: {
const alloc = sema.getComptimeAlloc(i);
break :val (try alloc.val.intern(pt, sema.arena)).toIntern();
},
else => unreachable,
};
if (zcu.intern_pool.isFuncBody(val)) {
const ty: Type = .fromInterned(zcu.intern_pool.typeOf(val));
if (try ty.fnHasRuntimeBitsSema(pt)) {
const orig_fn_index = zcu.intern_pool.unwrapCoercedFunc(val);
try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_fn_index }));
try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
}
}
return Air.internedToRef(resolved_ptr);
},
.inferred_alloc => {
const ia1 = sema.air_instructions.items(.data)[@intFromEnum(ptr_inst)].inferred_alloc;
const ia2 = sema.unresolved_inferred_allocs.fetchSwapRemove(ptr_inst).?.value;
const peer_vals = try sema.arena.alloc(Air.Inst.Ref, ia2.prongs.items.len);
for (peer_vals, ia2.prongs.items) |*peer_val, store_inst| {
assert(sema.air_instructions.items(.tag)[@intFromEnum(store_inst)] == .store);
const bin_op = sema.air_instructions.items(.data)[@intFromEnum(store_inst)].bin_op;
peer_val.* = bin_op.rhs;
}
const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_vals, .none);
const final_ptr_ty = try pt.ptrTypeSema(.{
.child = final_elem_ty.toIntern(),
.flags = .{
.alignment = ia1.alignment,
.address_space = target_util.defaultAddressSpace(target, .local),
},
});
if (!ia1.is_const) {
try sema.validateVarType(block, ty_src, final_elem_ty, false);
} else if (try sema.resolveComptimeKnownAllocPtr(block, ptr, final_ptr_ty)) |ptr_val| {
const const_ptr_ty = try sema.makePtrTyConst(final_ptr_ty);
const new_const_ptr = try pt.getCoerced(Value.fromInterned(ptr_val), const_ptr_ty);
// Unless the block is comptime, `alloc_inferred` always produces
// a runtime constant. The final inferred type needs to be
// fully resolved so it can be lowered in codegen.
try final_elem_ty.resolveFully(pt);
return Air.internedToRef(new_const_ptr.toIntern());
}
if (try final_elem_ty.comptimeOnlySema(pt)) {
// The alloc wasn't comptime-known per the above logic, so the
// type cannot be comptime-only.
// TODO: source location of runtime control flow
return sema.fail(block, src, "value with comptime-only type '{f}' depends on runtime control flow", .{final_elem_ty.fmt(pt)});
}
if (sema.func_is_naked and try final_elem_ty.hasRuntimeBitsSema(pt)) {
const mut_src = block.src(.{ .node_offset_store_ptr = inst_data.src_node });
return sema.fail(block, mut_src, "local variable in naked function", .{});
}
// Change it to a normal alloc.
sema.air_instructions.set(@intFromEnum(ptr_inst), .{
.tag = .alloc,
.data = .{ .ty = final_ptr_ty },
});
// Now we need to go back over all the store instructions, and do the logic as if
// the new result ptr type was available.
for (ia2.prongs.items) |placeholder_inst| {
var replacement_block = block.makeSubBlock();
defer replacement_block.instructions.deinit(gpa);
assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .store);
const bin_op = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].bin_op;
try sema.storePtr2(&replacement_block, src, bin_op.lhs, src, bin_op.rhs, src, .store);
// If only one instruction is produced then we can replace the store
// placeholder instruction with this instruction; no need for an entire block.
if (replacement_block.instructions.items.len == 1) {
const only_inst = replacement_block.instructions.items[0];
sema.air_instructions.set(@intFromEnum(placeholder_inst), sema.air_instructions.get(@intFromEnum(only_inst)));
continue;
}
// Here we replace the placeholder store instruction with a block
// that does the actual store logic.
_ = try replacement_block.addBr(placeholder_inst, .void_value);
try sema.air_extra.ensureUnusedCapacity(
gpa,
@typeInfo(Air.Block).@"struct".fields.len + replacement_block.instructions.items.len,
);
sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
.tag = .block,
.data = .{ .ty_pl = .{
.ty = .void_type,
.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = @intCast(replacement_block.instructions.items.len),
}),
} },
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
}
if (ia1.is_const) {
return sema.makePtrConst(block, ptr);
} else {
return ptr;
}
},
else => unreachable,
}
}
fn zirForLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
const all_args = sema.code.refSlice(extra.end, extra.data.operands_len);
const arg_pairs: []const [2]Zir.Inst.Ref = @as([*]const [2]Zir.Inst.Ref, @ptrCast(all_args))[0..@divExact(all_args.len, 2)];
const src = block.nodeOffset(inst_data.src_node);
var len: Air.Inst.Ref = .none;
var len_val: ?Value = null;
var len_idx: u32 = undefined;
var any_runtime = false;
const runtime_arg_lens = try gpa.alloc(Air.Inst.Ref, arg_pairs.len);
defer gpa.free(runtime_arg_lens);
// First pass to look for comptime values.
for (arg_pairs, 0..) |zir_arg_pair, i_usize| {
const i: u32 = @intCast(i_usize);
runtime_arg_lens[i] = .none;
if (zir_arg_pair[0] == .none) continue;
const arg_src = block.src(.{ .for_input = .{
.for_node_offset = inst_data.src_node,
.input_index = i,
} });
const arg_len_uncoerced = if (zir_arg_pair[1] == .none) l: {
// This argument is an indexable.
const object = try sema.resolveInst(zir_arg_pair[0]);
const object_ty = sema.typeOf(object);
if (!object_ty.isIndexable(zcu)) {
// Instead of using checkIndexable we customize this error.
const msg = msg: {
const msg = try sema.errMsg(arg_src, "type '{f}' is not indexable and not a range", .{object_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(arg_src, msg, "for loop operand must be a range, array, slice, tuple, or vector", .{});
if (object_ty.zigTypeTag(zcu) == .error_union) {
try sema.errNote(arg_src, msg, "consider using 'try', 'catch', or 'if'", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
if (!object_ty.indexableHasLen(zcu)) continue;
break :l try sema.fieldVal(block, arg_src, object, try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls), arg_src);
} else l: {
// This argument is a range.
const range_start = try sema.resolveInst(zir_arg_pair[0]);
const range_end = try sema.resolveInst(zir_arg_pair[1]);
if (try sema.resolveDefinedValue(block, arg_src, range_start)) |start| {
if (try sema.valuesEqual(start, .zero_usize, .usize)) break :l range_end;
}
break :l try sema.analyzeArithmetic(block, .sub, range_end, range_start, arg_src, arg_src, arg_src, true);
};
const arg_len = try sema.coerce(block, .usize, arg_len_uncoerced, arg_src);
if (len == .none) {
len = arg_len;
len_idx = i;
}
if (try sema.resolveDefinedValue(block, src, arg_len)) |arg_val| {
if (len_val) |v| {
if (!(try sema.valuesEqual(arg_val, v, .usize))) {
const msg = msg: {
const msg = try sema.errMsg(src, "non-matching for loop lengths", .{});
errdefer msg.destroy(gpa);
const a_src = block.src(.{ .for_input = .{
.for_node_offset = inst_data.src_node,
.input_index = len_idx,
} });
try sema.errNote(a_src, msg, "length {f} here", .{
v.fmtValueSema(pt, sema),
});
try sema.errNote(arg_src, msg, "length {f} here", .{
arg_val.fmtValueSema(pt, sema),
});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
} else {
len = arg_len;
len_val = arg_val;
len_idx = i;
}
continue;
}
runtime_arg_lens[i] = arg_len;
any_runtime = true;
}
if (len == .none) {
const msg = msg: {
const msg = try sema.errMsg(src, "unbounded for loop", .{});
errdefer msg.destroy(gpa);
for (arg_pairs, 0..) |zir_arg_pair, i_usize| {
const i: u32 = @intCast(i_usize);
if (zir_arg_pair[0] == .none) continue;
if (zir_arg_pair[1] != .none) continue;
const object = try sema.resolveInst(zir_arg_pair[0]);
const object_ty = sema.typeOf(object);
const arg_src = block.src(.{ .for_input = .{
.for_node_offset = inst_data.src_node,
.input_index = i,
} });
try sema.errNote(arg_src, msg, "type '{f}' has no upper bound", .{
object_ty.fmt(pt),
});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
// Now for the runtime checks.
if (any_runtime and block.wantSafety()) {
var ok: Air.Inst.Ref = .none;
for (runtime_arg_lens, 0..) |arg_len, i| {
if (arg_len == .none) continue;
if (i == len_idx) continue;
const eq = try block.addBinOp(.cmp_eq, len, arg_len);
ok = if (ok != .none)
try block.addBinOp(.bool_and, ok, eq)
else
eq;
}
if (ok != .none)
try sema.addSafetyCheck(block, src, ok, .for_len_mismatch);
}
return len;
}
/// Given any single pointer, retrieve a pointer to the payload of any optional
/// or error union pointed to, initializing these pointers along the way.
/// Given a `*E!?T`, returns a (valid) `*T`.
/// May invalidate already-stored payload data.
fn optEuBasePtrInit(sema: *Sema, block: *Block, ptr: Air.Inst.Ref, src: LazySrcLoc) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
var base_ptr = ptr;
while (true) switch (sema.typeOf(base_ptr).childType(zcu).zigTypeTag(zcu)) {
.error_union => base_ptr = try sema.analyzeErrUnionPayloadPtr(block, src, base_ptr, false, true),
.optional => base_ptr = try sema.analyzeOptionalPayloadPtr(block, src, base_ptr, false, true),
else => break,
};
try sema.checkKnownAllocPtr(block, ptr, base_ptr);
return base_ptr;
}
fn zirOptEuBasePtrInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const ptr = try sema.resolveInst(un_node.operand);
return sema.optEuBasePtrInit(block, ptr, block.nodeOffset(un_node.src_node));
}
fn zirCoercePtrElemTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(pl_node.src_node);
const extra = sema.code.extraData(Zir.Inst.Bin, pl_node.payload_index).data;
const uncoerced_val = try sema.resolveInst(extra.rhs);
const maybe_wrapped_ptr_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, extra.lhs) orelse return uncoerced_val;
const ptr_ty = maybe_wrapped_ptr_ty.optEuBaseType(zcu);
assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
const elem_ty = ptr_ty.childType(zcu);
switch (ptr_ty.ptrSize(zcu)) {
.one => {
const uncoerced_ty = sema.typeOf(uncoerced_val);
if (elem_ty.zigTypeTag(zcu) == .array and elem_ty.childType(zcu).toIntern() == uncoerced_ty.toIntern()) {
// We're trying to initialize a *[1]T with a reference to a T - don't perform any coercion.
return uncoerced_val;
}
// If the destination type is anyopaque, don't coerce - the pointer will coerce instead.
if (elem_ty.toIntern() == .anyopaque_type) {
return uncoerced_val;
} else {
return sema.coerce(block, elem_ty, uncoerced_val, src);
}
},
.slice, .many => {
// Our goal is to coerce `uncoerced_val` to an array of `elem_ty`.
const val_ty = sema.typeOf(uncoerced_val);
switch (val_ty.zigTypeTag(zcu)) {
.array, .vector => {},
else => if (!val_ty.isTuple(zcu)) {
return sema.fail(block, src, "expected array of '{f}', found '{f}'", .{ elem_ty.fmt(pt), val_ty.fmt(pt) });
},
}
const want_ty = try pt.arrayType(.{
.len = val_ty.arrayLen(zcu),
.child = elem_ty.toIntern(),
.sentinel = if (ptr_ty.sentinel(zcu)) |s| s.toIntern() else .none,
});
return sema.coerce(block, want_ty, uncoerced_val, src);
},
.c => {
// There's nothing meaningful to do here, because we don't know if this is meant to be a
// single-pointer or a many-pointer.
return uncoerced_val;
},
}
}
fn zirTryOperandTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(un_node.src_node);
const operand_ty = try sema.resolveTypeOrPoison(block, src, un_node.operand) orelse return .generic_poison_type;
const payload_ty = if (is_ref) ty: {
if (!operand_ty.isSinglePointer(zcu)) {
return .generic_poison_type; // we can't get a meaningful result type here, since it will be `*E![n]T`, and we don't know `n`.
}
break :ty operand_ty.childType(zcu);
} else operand_ty;
const err_set_ty: Type = err_set: {
// There are awkward cases, like `?E`. Our strategy is to repeatedly unwrap optionals
// until we hit an error union or set.
var cur_ty = sema.fn_ret_ty;
while (true) {
switch (cur_ty.zigTypeTag(zcu)) {
.error_set => break :err_set cur_ty,
.error_union => break :err_set cur_ty.errorUnionSet(zcu),
.optional => cur_ty = cur_ty.optionalChild(zcu),
else => {
// This function cannot return an error.
// `try` is still valid if the error case is impossible, i.e. no error is returned.
// So, the result type has an error set of `error{}`.
break :err_set .fromInterned(try zcu.intern_pool.getErrorSetType(zcu.gpa, pt.tid, &.{}));
},
}
}
};
const eu_ty = try pt.errorUnionType(err_set_ty, payload_ty);
if (is_ref) {
var ptr_info = operand_ty.ptrInfo(zcu);
ptr_info.child = eu_ty.toIntern();
const eu_ptr_ty = try pt.ptrTypeSema(ptr_info);
return Air.internedToRef(eu_ptr_ty.toIntern());
} else {
return Air.internedToRef(eu_ty.toIntern());
}
}
fn zirValidateRefTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const un_tok = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_tok;
const src = block.tokenOffset(un_tok.src_tok);
// In case of GenericPoison, we don't actually have a type, so this will be
// treated as an untyped address-of operator.
const ty_operand = try sema.resolveTypeOrPoison(block, src, un_tok.operand) orelse return;
if (ty_operand.optEuBaseType(zcu).zigTypeTag(zcu) != .pointer) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "expected type '{f}', found pointer", .{ty_operand.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "address-of operator always returns a pointer", .{});
break :msg msg;
});
}
}
fn zirValidateConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
if (!block.isComptime()) return;
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(un_node.src_node);
const init_ref = try sema.resolveInst(un_node.operand);
if (!try sema.isComptimeKnown(init_ref)) {
return sema.failWithNeededComptime(block, src, null);
}
}
fn zirValidateArrayInitRefTy(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(pl_node.src_node);
const extra = sema.code.extraData(Zir.Inst.ArrayInitRefTy, pl_node.payload_index).data;
const maybe_wrapped_ptr_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, extra.ptr_ty) orelse return .generic_poison_type;
const ptr_ty = maybe_wrapped_ptr_ty.optEuBaseType(zcu);
assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
switch (zcu.intern_pool.indexToKey(ptr_ty.toIntern())) {
.ptr_type => |ptr_type| switch (ptr_type.flags.size) {
.slice, .many => {
// Use array of correct length
const arr_ty = try pt.arrayType(.{
.len = extra.elem_count,
.child = ptr_ty.childType(zcu).toIntern(),
.sentinel = if (ptr_ty.sentinel(zcu)) |s| s.toIntern() else .none,
});
return Air.internedToRef(arr_ty.toIntern());
},
else => {},
},
else => {},
}
// Otherwise, we just want the pointer child type
const ret_ty = ptr_ty.childType(zcu);
if (ret_ty.toIntern() == .anyopaque_type) {
// The actual array type is unknown, which we represent with a generic poison.
return .generic_poison_type;
}
const arr_ty = ret_ty.optEuBaseType(zcu);
try sema.validateArrayInitTy(block, src, src, extra.elem_count, arr_ty);
return Air.internedToRef(ret_ty.toIntern());
}
fn zirValidateArrayInitTy(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_result_ty: bool,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const ty_src: LazySrcLoc = if (is_result_ty) src else block.src(.{ .node_offset_init_ty = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.ArrayInit, inst_data.payload_index).data;
// It's okay for the type to be poison: this will result in an anonymous array init.
const ty = try sema.resolveTypeOrPoison(block, ty_src, extra.ty) orelse return;
const arr_ty = if (is_result_ty) ty.optEuBaseType(zcu) else ty;
return sema.validateArrayInitTy(block, src, ty_src, extra.init_count, arr_ty);
}
fn validateArrayInitTy(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ty_src: LazySrcLoc,
init_count: u32,
ty: Type,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.array => {
const array_len = ty.arrayLen(zcu);
if (init_count != array_len) {
return sema.fail(block, src, "expected {d} array elements; found {d}", .{
array_len, init_count,
});
}
return;
},
.vector => {
const array_len = ty.arrayLen(zcu);
if (init_count != array_len) {
return sema.fail(block, src, "expected {d} vector elements; found {d}", .{
array_len, init_count,
});
}
return;
},
.@"struct" => if (ty.isTuple(zcu)) {
try ty.resolveFields(pt);
const array_len = ty.arrayLen(zcu);
if (init_count > array_len) {
return sema.fail(block, src, "expected at most {d} tuple fields; found {d}", .{
array_len, init_count,
});
}
return;
},
else => {},
}
return sema.failWithArrayInitNotSupported(block, ty_src, ty);
}
fn zirValidateStructInitTy(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_result_ty: bool,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
// It's okay for the type to be poison: this will result in an anonymous struct init.
const ty = try sema.resolveTypeOrPoison(block, src, inst_data.operand) orelse return;
const struct_ty = if (is_result_ty) ty.optEuBaseType(zcu) else ty;
switch (struct_ty.zigTypeTag(zcu)) {
.@"struct", .@"union" => return,
else => {},
}
return sema.failWithStructInitNotSupported(block, src, struct_ty);
}
fn zirValidatePtrStructInit(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const validate_inst = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const init_src = block.nodeOffset(validate_inst.src_node);
const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);
const instrs = sema.code.bodySlice(validate_extra.end, validate_extra.data.body_len);
const field_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(instrs[0])].pl_node;
const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
const object_ptr = try sema.resolveInst(field_ptr_extra.lhs);
const agg_ty = sema.typeOf(object_ptr).childType(zcu).optEuBaseType(zcu);
switch (agg_ty.zigTypeTag(zcu)) {
.@"struct" => return sema.validateStructInit(
block,
agg_ty,
init_src,
instrs,
object_ptr,
),
.@"union" => return sema.validateUnionInit(
block,
agg_ty,
init_src,
instrs,
),
else => unreachable,
}
}
fn validateUnionInit(
sema: *Sema,
block: *Block,
union_ty: Type,
init_src: LazySrcLoc,
instrs: []const Zir.Inst.Index,
) CompileError!void {
if (instrs.len == 1) {
// Trvial validation done, and the union tag was already set by machinery in `unionFieldPtr`.
return;
}
const msg = msg: {
const msg = try sema.errMsg(
init_src,
"cannot initialize multiple union fields at once; unions can only have one active field",
.{},
);
errdefer msg.destroy(sema.gpa);
for (instrs[1..]) |inst| {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const inst_src = block.src(.{ .node_offset_initializer = inst_data.src_node });
try sema.errNote(inst_src, msg, "additional initializer here", .{});
}
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn validateStructInit(
sema: *Sema,
block: *Block,
struct_ty: Type,
init_src: LazySrcLoc,
instrs: []const Zir.Inst.Index,
struct_ptr: Air.Inst.Ref,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
// Tracks whether each field was explicitly initialized.
const found_fields = try gpa.alloc(bool, struct_ty.structFieldCount(zcu));
defer gpa.free(found_fields);
@memset(found_fields, false);
for (instrs) |field_ptr| {
const field_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(field_ptr)].pl_node;
const field_src = block.src(.{ .node_offset_initializer = field_ptr_data.src_node });
const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data;
const field_name = try ip.getOrPutString(
gpa,
pt.tid,
sema.code.nullTerminatedString(field_ptr_extra.field_name_start),
.no_embedded_nulls,
);
const field_index = if (struct_ty.isTuple(zcu))
try sema.tupleFieldIndex(block, struct_ty, field_name, field_src)
else
try sema.structFieldIndex(block, struct_ty, field_name, field_src);
assert(found_fields[field_index] == false);
found_fields[field_index] = true;
}
// Our job is simply to deal with default field values. Specifically, any field which was not
// explicitly initialized must have its default value stored to the field pointer, or, if the
// field has no default value, a compile error must be emitted instead.
// In the past, this code had other responsibilities, which involved some nasty AIR rewrites. However,
// that work was actually all redundant:
//
// * If the struct value is comptime-known, field stores remain a perfectly valid way of initializing
// the struct through RLS; there is no need to turn the field stores into one store. Comptime-known
// consts are handled correctly either way thanks to `maybe_comptime_allocs` and friends.
//
// * If the struct type is comptime-only, we need to make sure all of the fields were comptime-known.
// But the comptime-only type means that `struct_ptr` must be a comptime-mutable pointer, so the
// field stores were to comptime-mutable pointers, so have already errored if not comptime-known.
//
// * If the value is runtime-known, then comptime-known fields must be validated as runtime values.
// But this was already handled for every field store by the machinery in `checkComptimeKnownStore`.
var root_msg: ?*Zcu.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
for (found_fields, 0..) |explicit, i_usize| {
if (explicit) continue;
const i: u32 = @intCast(i_usize);
try struct_ty.resolveStructFieldInits(pt);
const default_val = struct_ty.structFieldDefaultValue(i, zcu);
if (default_val.toIntern() == .unreachable_value) {
const field_name = struct_ty.structFieldName(i, zcu).unwrap() orelse {
const template = "missing tuple field with index {d}";
if (root_msg) |msg| {
try sema.errNote(init_src, msg, template, .{i});
} else {
root_msg = try sema.errMsg(init_src, template, .{i});
}
continue;
};
const template = "missing struct field: {f}";
const args = .{field_name.fmt(ip)};
if (root_msg) |msg| {
try sema.errNote(init_src, msg, template, args);
} else {
root_msg = try sema.errMsg(init_src, template, args);
}
continue;
}
const field_src = init_src; // TODO better source location
const default_field_ptr = if (struct_ty.isTuple(zcu))
try sema.tupleFieldPtr(block, init_src, struct_ptr, field_src, @intCast(i), true)
else
try sema.structFieldPtrByIndex(block, init_src, struct_ptr, @intCast(i), struct_ty);
try sema.checkKnownAllocPtr(block, struct_ptr, default_field_ptr);
try sema.storePtr2(block, init_src, default_field_ptr, init_src, .fromValue(default_val), field_src, .store);
}
if (root_msg) |msg| {
try sema.addDeclaredHereNote(msg, struct_ty);
root_msg = null;
return sema.failWithOwnedErrorMsg(block, msg);
}
}
fn zirValidatePtrArrayInit(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const validate_inst = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const init_src = block.nodeOffset(validate_inst.src_node);
const validate_extra = sema.code.extraData(Zir.Inst.Block, validate_inst.payload_index);
const instrs = sema.code.bodySlice(validate_extra.end, validate_extra.data.body_len);
const first_elem_ptr_data = sema.code.instructions.items(.data)[@intFromEnum(instrs[0])].pl_node;
const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, first_elem_ptr_data.payload_index).data;
const array_ptr = try sema.resolveInst(elem_ptr_extra.ptr);
const array_ty = sema.typeOf(array_ptr).childType(zcu).optEuBaseType(zcu);
const array_len = array_ty.arrayLen(zcu);
// Analagously to `validateStructInit`, our job is to handle default fields; either emitting AIR
// to initialize them, or emitting a compile error if an unspecified field has no default. For
// tuples, there are literally default field values, although they're guaranteed to be comptime
// fields so we don't need to initialize them. For arrays, we may have a sentinel, which is never
// specified so we always need to initialize here. For vectors, there's no such thing.
switch (array_ty.zigTypeTag(zcu)) {
.@"struct" => if (instrs.len != array_len) {
var root_msg: ?*Zcu.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
try array_ty.resolveStructFieldInits(pt);
var i = instrs.len;
while (i < array_len) : (i += 1) {
const default_val = array_ty.structFieldDefaultValue(i, zcu).toIntern();
if (default_val == .unreachable_value) {
const template = "missing tuple field with index {d}";
if (root_msg) |msg| {
try sema.errNote(init_src, msg, template, .{i});
} else {
root_msg = try sema.errMsg(init_src, template, .{i});
}
continue;
}
}
if (root_msg) |msg| {
root_msg = null;
return sema.failWithOwnedErrorMsg(block, msg);
}
},
.array => if (instrs.len != array_len) {
return sema.fail(block, init_src, "expected {d} array elements; found {d}", .{
array_len, instrs.len,
});
} else if (array_ty.sentinel(zcu)) |sentinel| {
const array_len_ref = try pt.intRef(.usize, array_len);
const sentinel_ptr = try sema.elemPtrArray(block, init_src, init_src, array_ptr, init_src, array_len_ref, true, true);
try sema.checkKnownAllocPtr(block, array_ptr, sentinel_ptr);
try sema.storePtr2(block, init_src, sentinel_ptr, init_src, .fromValue(sentinel), init_src, .store);
},
.vector => if (instrs.len != array_len) {
return sema.fail(block, init_src, "expected {d} vector elements; found {d}", .{
array_len, instrs.len,
});
},
else => unreachable,
}
}
fn zirValidateDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
if (operand_ty.zigTypeTag(zcu) != .pointer) {
return sema.fail(block, src, "cannot dereference non-pointer type '{f}'", .{operand_ty.fmt(pt)});
} else switch (operand_ty.ptrSize(zcu)) {
.one, .c => {},
.many => return sema.fail(block, src, "index syntax required for unknown-length pointer type '{f}'", .{operand_ty.fmt(pt)}),
.slice => return sema.fail(block, src, "index syntax required for slice type '{f}'", .{operand_ty.fmt(pt)}),
}
if ((try sema.typeHasOnePossibleValue(operand_ty.childType(zcu))) != null) {
// No need to validate the actual pointer value, we don't need it!
return;
}
const elem_ty = operand_ty.elemType2(zcu);
if (try sema.resolveValue(operand)) |val| {
if (val.isUndef(zcu)) {
return sema.fail(block, src, "cannot dereference undefined value", .{});
}
} else if (try elem_ty.comptimeOnlySema(pt)) {
const msg = msg: {
const msg = try sema.errMsg(
src,
"values of type '{f}' must be comptime-known, but operand value is runtime-known",
.{elem_ty.fmt(pt)},
);
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(msg, src, elem_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
fn typeIsDestructurable(ty: Type, zcu: *const Zcu) bool {
return switch (ty.zigTypeTag(zcu)) {
.array, .vector => true,
.@"struct" => ty.isTuple(zcu),
else => false,
};
}
fn zirValidateDestructure(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.ValidateDestructure, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const destructure_src = block.nodeOffset(extra.destructure_node);
const operand = try sema.resolveInst(extra.operand);
const operand_ty = sema.typeOf(operand);
if (!typeIsDestructurable(operand_ty, zcu)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "type '{f}' cannot be destructured", .{operand_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(destructure_src, msg, "result destructured here", .{});
if (operand_ty.zigTypeTag(pt.zcu) == .error_union) {
const base_op_ty = operand_ty.errorUnionPayload(zcu);
if (typeIsDestructurable(base_op_ty, zcu))
try sema.errNote(src, msg, "consider using 'try', 'catch', or 'if'", .{});
}
break :msg msg;
});
}
if (operand_ty.arrayLen(zcu) != extra.expect_len) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "expected {d} elements for destructure, found {d}", .{
extra.expect_len, operand_ty.arrayLen(zcu),
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(destructure_src, msg, "result destructured here", .{});
break :msg msg;
});
}
}
fn failWithBadMemberAccess(
sema: *Sema,
block: *Block,
agg_ty: Type,
field_src: LazySrcLoc,
field_name: InternPool.NullTerminatedString,
) CompileError {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const kw_name = switch (agg_ty.zigTypeTag(zcu)) {
.@"union" => "union",
.@"struct" => "struct",
.@"opaque" => "opaque",
.@"enum" => "enum",
else => unreachable,
};
if (agg_ty.typeDeclInst(zcu)) |inst| if ((inst.resolve(ip) orelse return error.AnalysisFail) == .main_struct_inst) {
return sema.fail(block, field_src, "root source file struct '{f}' has no member named '{f}'", .{
agg_ty.fmt(pt), field_name.fmt(ip),
});
};
return sema.fail(block, field_src, "{s} '{f}' has no member named '{f}'", .{
kw_name, agg_ty.fmt(pt), field_name.fmt(ip),
});
}
fn failWithBadStructFieldAccess(
sema: *Sema,
block: *Block,
struct_ty: Type,
struct_type: InternPool.LoadedStructType,
field_src: LazySrcLoc,
field_name: InternPool.NullTerminatedString,
) CompileError {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const msg = msg: {
const msg = try sema.errMsg(
field_src,
"no field named '{f}' in struct '{f}'",
.{ field_name.fmt(ip), struct_type.name.fmt(ip) },
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(struct_ty.srcLoc(zcu), msg, "struct declared here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn failWithBadUnionFieldAccess(
sema: *Sema,
block: *Block,
union_ty: Type,
union_obj: InternPool.LoadedUnionType,
field_src: LazySrcLoc,
field_name: InternPool.NullTerminatedString,
) CompileError {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const gpa = sema.gpa;
const msg = msg: {
const msg = try sema.errMsg(
field_src,
"no field named '{f}' in union '{f}'",
.{ field_name.fmt(ip), union_obj.name.fmt(ip) },
);
errdefer msg.destroy(gpa);
try sema.errNote(union_ty.srcLoc(zcu), msg, "union declared here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn addDeclaredHereNote(sema: *Sema, parent: *Zcu.ErrorMsg, decl_ty: Type) !void {
const zcu = sema.pt.zcu;
const src_loc = decl_ty.srcLocOrNull(zcu) orelse return;
const category = switch (decl_ty.zigTypeTag(zcu)) {
.@"union" => "union",
.@"struct" => "struct",
.@"enum" => "enum",
.@"opaque" => "opaque",
else => unreachable,
};
try sema.errNote(src_loc, parent, "{s} declared here", .{category});
}
fn zirStoreToInferredPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(pl_node.src_node);
const bin = sema.code.extraData(Zir.Inst.Bin, pl_node.payload_index).data;
const ptr = try sema.resolveInst(bin.lhs);
const operand = try sema.resolveInst(bin.rhs);
const ptr_inst = ptr.toIndex().?;
const air_datas = sema.air_instructions.items(.data);
switch (sema.air_instructions.items(.tag)[@intFromEnum(ptr_inst)]) {
.inferred_alloc_comptime => {
const iac = &air_datas[@intFromEnum(ptr_inst)].inferred_alloc_comptime;
return sema.storeToInferredAllocComptime(block, src, operand, iac);
},
.inferred_alloc => {
const ia = sema.unresolved_inferred_allocs.getPtr(ptr_inst).?;
return sema.storeToInferredAlloc(block, src, ptr, operand, ia);
},
else => unreachable,
}
}
fn storeToInferredAlloc(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ptr: Air.Inst.Ref,
operand: Air.Inst.Ref,
inferred_alloc: *InferredAlloc,
) CompileError!void {
// Create a store instruction as a placeholder. This will be replaced by a
// proper store sequence once we know the stored type.
const dummy_store = try block.addBinOp(.store, ptr, operand);
try sema.checkComptimeKnownStore(block, dummy_store, src);
// Add the stored instruction to the set we will use to resolve peer types
// for the inferred allocation.
try inferred_alloc.prongs.append(sema.arena, dummy_store.toIndex().?);
}
fn storeToInferredAllocComptime(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
iac: *Air.Inst.Data.InferredAllocComptime,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
// There will be only one store_to_inferred_ptr because we are running at comptime.
// The alloc will turn into a Decl or a ComptimeAlloc.
const operand_val = try sema.resolveValue(operand) orelse {
return sema.failWithNeededComptime(block, src, .{ .simple = .stored_to_comptime_var });
};
const alloc_ty = try pt.ptrTypeSema(.{
.child = operand_ty.toIntern(),
.flags = .{
.alignment = iac.alignment,
.is_const = iac.is_const,
},
});
if (iac.is_const and !operand_val.canMutateComptimeVarState(zcu)) {
iac.ptr = try pt.intern(.{ .ptr = .{
.ty = alloc_ty.toIntern(),
.base_addr = .{ .uav = .{
.val = operand_val.toIntern(),
.orig_ty = alloc_ty.toIntern(),
} },
.byte_offset = 0,
} });
} else {
const alloc_index = try sema.newComptimeAlloc(block, src, operand_ty, iac.alignment);
sema.getComptimeAlloc(alloc_index).val = .{ .interned = operand_val.toIntern() };
iac.ptr = try pt.intern(.{ .ptr = .{
.ty = alloc_ty.toIntern(),
.base_addr = .{ .comptime_alloc = alloc_index },
.byte_offset = 0,
} });
}
}
fn zirSetEvalBranchQuota(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const quota: u32 = @intCast(try sema.resolveInt(block, src, inst_data.operand, .u32, .{ .simple = .operand_setEvalBranchQuota }));
sema.branch_quota = @max(sema.branch_quota, quota);
sema.allow_memoize = false;
}
fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const zir_tags = sema.code.instructions.items(.tag);
const zir_datas = sema.code.instructions.items(.data);
const inst_data = zir_datas[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const ptr = try sema.resolveInst(extra.lhs);
const operand = try sema.resolveInst(extra.rhs);
const is_ret = if (extra.lhs.toIndex()) |ptr_index|
zir_tags[@intFromEnum(ptr_index)] == .ret_ptr
else
false;
const ptr_src = block.src(.{ .node_offset_store_ptr = inst_data.src_node });
const operand_src = block.src(.{ .node_offset_store_operand = inst_data.src_node });
const air_tag: Air.Inst.Tag = if (is_ret)
.ret_ptr
else if (block.wantSafety())
.store_safe
else
.store;
return sema.storePtr2(block, src, ptr, ptr_src, operand, operand_src, air_tag);
}
fn zirStr(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const bytes = sema.code.instructions.items(.data)[@intFromEnum(inst)].str.get(sema.code);
return sema.addStrLit(
try sema.pt.zcu.intern_pool.getOrPutString(sema.gpa, sema.pt.tid, bytes, .maybe_embedded_nulls),
bytes.len,
);
}
fn addNullTerminatedStrLit(sema: *Sema, string: InternPool.NullTerminatedString) CompileError!Air.Inst.Ref {
return sema.addStrLit(string.toString(), string.length(&sema.pt.zcu.intern_pool));
}
pub fn addStrLit(sema: *Sema, string: InternPool.String, len: u64) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const array_ty = try pt.arrayType(.{
.len = len,
.sentinel = .zero_u8,
.child = .u8_type,
});
const val = try pt.intern(.{ .aggregate = .{
.ty = array_ty.toIntern(),
.storage = .{ .bytes = string },
} });
return sema.uavRef(val);
}
fn uavRef(sema: *Sema, val: InternPool.Index) CompileError!Air.Inst.Ref {
return Air.internedToRef(try sema.pt.refValue(val));
}
fn zirInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const tracy = trace(@src());
defer tracy.end();
const int = sema.code.instructions.items(.data)[@intFromEnum(inst)].int;
return sema.pt.intRef(.comptime_int, int);
}
fn zirIntBig(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const tracy = trace(@src());
defer tracy.end();
const int = sema.code.instructions.items(.data)[@intFromEnum(inst)].str;
const byte_count = int.len * @sizeOf(std.math.big.Limb);
const limb_bytes = sema.code.string_bytes[@intFromEnum(int.start)..][0..byte_count];
// TODO: this allocation and copy is only needed because the limbs may be unaligned.
// If ZIR is adjusted so that big int limbs are guaranteed to be aligned, these
// two lines can be removed.
const limbs = try sema.arena.alloc(std.math.big.Limb, int.len);
@memcpy(mem.sliceAsBytes(limbs), limb_bytes);
return Air.internedToRef((try sema.pt.intValue_big(.comptime_int, .{
.limbs = limbs,
.positive = true,
})).toIntern());
}
fn zirFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const number = sema.code.instructions.items(.data)[@intFromEnum(inst)].float;
return Air.internedToRef((try sema.pt.floatValue(
.comptime_float,
number,
)).toIntern());
}
fn zirFloat128(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Float128, inst_data.payload_index).data;
const number = extra.get();
return Air.internedToRef((try sema.pt.floatValue(.comptime_float, number)).toIntern());
}
fn zirCompileError(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const msg = try sema.resolveConstString(block, operand_src, inst_data.operand, .{ .simple = .compile_error_string });
return sema.fail(block, src, "{s}", .{msg});
}
fn zirCompileLog(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
var aw: std.Io.Writer.Allocating = .init(gpa);
defer aw.deinit();
const writer = &aw.writer;
const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand);
const src_node = extra.data.src_node;
const args = sema.code.refSlice(extra.end, extended.small);
for (args, 0..) |arg_ref, i| {
if (i != 0) writer.writeAll(", ") catch return error.OutOfMemory;
const arg = try sema.resolveInst(arg_ref);
const arg_ty = sema.typeOf(arg);
if (try sema.resolveValueResolveLazy(arg)) |val| {
writer.print("@as({f}, {f})", .{
arg_ty.fmt(pt), val.fmtValueSema(pt, sema),
}) catch return error.OutOfMemory;
} else {
writer.print("@as({f}, [runtime value])", .{arg_ty.fmt(pt)}) catch return error.OutOfMemory;
}
}
const line_data = try zcu.intern_pool.getOrPutString(gpa, pt.tid, aw.written(), .no_embedded_nulls);
const line_idx: Zcu.CompileLogLine.Index = if (zcu.free_compile_log_lines.pop()) |idx| idx: {
zcu.compile_log_lines.items[@intFromEnum(idx)] = .{
.next = .none,
.data = line_data,
};
break :idx idx;
} else idx: {
try zcu.compile_log_lines.append(gpa, .{
.next = .none,
.data = line_data,
});
break :idx @enumFromInt(zcu.compile_log_lines.items.len - 1);
};
const gop = try zcu.compile_logs.getOrPut(gpa, sema.owner);
if (gop.found_existing) {
const prev_line = gop.value_ptr.last_line.get(zcu);
assert(prev_line.next == .none);
prev_line.next = line_idx.toOptional();
gop.value_ptr.last_line = line_idx;
} else {
gop.value_ptr.* = .{
.base_node_inst = block.src_base_inst,
.node_offset = src_node,
.first_line = line_idx,
.last_line = line_idx,
};
}
return .void_value;
}
fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const msg_inst = try sema.resolveInst(inst_data.operand);
const arg_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const coerced_msg = try sema.coerce(block, .slice_const_u8, msg_inst, arg_src);
if (block.isComptime()) {
const string = try sema.resolveConstString(block, arg_src, inst_data.operand, null);
return sema.fail(block, src, "encountered @panic at comptime: {s}", .{string});
}
// We only apply the first hint in a branch.
// This allows user-provided hints to override implicit cold hints.
if (sema.branch_hint == null) {
sema.branch_hint = .cold;
}
if (!zcu.backendSupportsFeature(.panic_fn)) {
_ = try block.addNoOp(.trap);
return;
}
try sema.ensureMemoizedStateResolved(src, .panic);
const panic_fn_index = zcu.builtin_decl_values.get(.@"panic.call");
const opt_usize_ty = try pt.optionalType(.usize_type);
const null_ret_addr = Air.internedToRef((try pt.intern(.{ .opt = .{
.ty = opt_usize_ty.toIntern(),
.val = .none,
} })));
// `callBuiltin` also calls `addReferenceEntry` to the function body for us.
try sema.callBuiltin(
block,
src,
.fromIntern(panic_fn_index),
.auto,
&.{ coerced_msg, null_ret_addr },
.@"@panic",
);
}
fn zirTrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const src_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].node;
const src = block.nodeOffset(src_node);
if (block.isComptime())
return sema.fail(block, src, "encountered @trap at comptime", .{});
_ = try block.addNoOp(.trap);
}
fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = parent_block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
const gpa = sema.gpa;
// AIR expects a block outside the loop block too.
// Reserve space for a Loop instruction so that generated Break instructions can
// point to it, even if it doesn't end up getting used because the code ends up being
// comptime evaluated.
const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
const loop_inst: Air.Inst.Index = @enumFromInt(@intFromEnum(block_inst) + 1);
try sema.air_instructions.ensureUnusedCapacity(gpa, 2);
sema.air_instructions.appendAssumeCapacity(.{
.tag = .block,
.data = undefined,
});
sema.air_instructions.appendAssumeCapacity(.{
.tag = .loop,
.data = .{ .ty_pl = .{
.ty = .noreturn_type,
.payload = undefined,
} },
});
var label: Block.Label = .{
.zir_block = inst,
.merges = .{
.src_locs = .{},
.results = .{},
.br_list = .{},
.block_inst = block_inst,
},
};
var child_block = parent_block.makeSubBlock();
child_block.label = &label;
child_block.runtime_cond = null;
child_block.runtime_loop = src;
child_block.runtime_index.increment();
const merges = &child_block.label.?.merges;
defer child_block.instructions.deinit(gpa);
defer merges.deinit(gpa);
var loop_block = child_block.makeSubBlock();
defer loop_block.instructions.deinit(gpa);
// Use `analyzeBodyInner` directly to push any comptime control flow up the stack.
try sema.analyzeBodyInner(&loop_block, body);
// TODO: since AIR has `repeat` now, we could change ZIR to generate
// more optimal code utilizing `repeat` instructions across blocks!
// For now, if the generated loop body does not terminate `noreturn`,
// then `analyzeBodyInner` is signalling that it ended with `repeat`.
const loop_block_len = loop_block.instructions.items.len;
if (loop_block_len > 0 and sema.typeOf(loop_block.instructions.items[loop_block_len - 1].toRef()).isNoReturn(zcu)) {
// If the loop ended with a noreturn terminator, then there is no way for it to loop,
// so we can just use the block instead.
try child_block.instructions.appendSlice(gpa, loop_block.instructions.items);
} else {
_ = try loop_block.addInst(.{
.tag = .repeat,
.data = .{ .repeat = .{
.loop_inst = loop_inst,
} },
});
// Note that `loop_block_len` is now off by one.
try child_block.instructions.append(gpa, loop_inst);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len + loop_block_len + 1);
sema.air_instructions.items(.data)[@intFromEnum(loop_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(
Air.Block{ .body_len = @intCast(loop_block_len + 1) },
);
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(loop_block.instructions.items));
}
return sema.resolveAnalyzedBlock(parent_block, src, &child_block, merges, false);
}
fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const comp = zcu.comp;
const gpa = sema.gpa;
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = parent_block.nodeOffset(pl_node.src_node);
const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
var c_import_buf = std.array_list.Managed(u8).init(gpa);
defer c_import_buf.deinit();
var child_block: Block = .{
.parent = parent_block,
.sema = sema,
.namespace = parent_block.namespace,
.instructions = .{},
.inlining = parent_block.inlining,
.comptime_reason = .{ .reason = .{
.src = src,
.r = .{ .simple = .operand_cImport },
} },
.c_import_buf = &c_import_buf,
.runtime_cond = parent_block.runtime_cond,
.runtime_loop = parent_block.runtime_loop,
.runtime_index = parent_block.runtime_index,
.src_base_inst = parent_block.src_base_inst,
.type_name_ctx = parent_block.type_name_ctx,
};
defer child_block.instructions.deinit(gpa);
_ = try sema.analyzeInlineBody(&child_block, body, inst);
const prog_node = zcu.cur_sema_prog_node.start("@cImport", 0);
defer prog_node.end();
var c_import_res = comp.cImport(c_import_buf.items, parent_block.ownerModule(), prog_node) catch |err|
return sema.fail(&child_block, src, "C import failed: {t}", .{err});
defer c_import_res.deinit(gpa);
if (c_import_res.errors.errorMessageCount() != 0) {
const msg = msg: {
const msg = try sema.errMsg(src, "C import failed", .{});
errdefer msg.destroy(gpa);
if (!comp.config.link_libc)
try sema.errNote(src, msg, "libc headers not available; compilation does not link against libc", .{});
const gop = try zcu.cimport_errors.getOrPut(gpa, sema.owner);
if (!gop.found_existing) {
gop.value_ptr.* = c_import_res.errors;
c_import_res.errors = std.zig.ErrorBundle.empty;
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&child_block, msg);
}
const parent_mod = parent_block.ownerModule();
const digest = Cache.binToHex(c_import_res.digest);
const new_file_index = file: {
const c_import_zig_path = try comp.arena.dupe(u8, "o" ++ std.fs.path.sep_str ++ digest);
const c_import_mod = Package.Module.create(comp.arena, .{
.paths = .{
.root = try .fromRoot(comp.arena, comp.dirs, .local_cache, c_import_zig_path),
.root_src_path = "cimport.zig",
},
.fully_qualified_name = c_import_zig_path,
.cc_argv = parent_mod.cc_argv,
.inherited = .{},
.global = comp.config,
.parent = parent_mod,
}) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
// None of these are possible because we are creating a package with
// the exact same configuration as the parent package, which already
// passed these checks.
error.ValgrindUnsupportedOnTarget => unreachable,
error.TargetRequiresSingleThreaded => unreachable,
error.BackendRequiresSingleThreaded => unreachable,
error.TargetRequiresPic => unreachable,
error.PieRequiresPic => unreachable,
error.DynamicLinkingRequiresPic => unreachable,
error.TargetHasNoRedZone => unreachable,
error.StackCheckUnsupportedByTarget => unreachable,
error.StackProtectorUnsupportedByTarget => unreachable,
error.StackProtectorUnavailableWithoutLibC => unreachable,
};
const c_import_file_path: Compilation.Path = try c_import_mod.root.join(gpa, comp.dirs, "cimport.zig");
errdefer c_import_file_path.deinit(gpa);
const c_import_file = try gpa.create(Zcu.File);
errdefer gpa.destroy(c_import_file);
const c_import_file_index = try zcu.intern_pool.createFile(gpa, pt.tid, .{
.bin_digest = c_import_file_path.digest(),
.file = c_import_file,
.root_type = .none,
});
c_import_file.* = .{
.status = .never_loaded,
.stat = undefined,
.is_builtin = false,
.path = c_import_file_path,
.source = null,
.tree = null,
.zir = null,
.zoir = null,
.mod = c_import_mod,
.sub_file_path = "cimport.zig",
.module_changed = false,
.prev_zir = null,
.zoir_invalidated = false,
};
break :file c_import_file_index;
};
pt.updateFile(new_file_index, zcu.fileByIndex(new_file_index)) catch |err|
return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
try pt.ensureFileAnalyzed(new_file_index);
const ty = zcu.fileRootType(new_file_index);
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(src, ty);
return Air.internedToRef(ty);
}
fn zirSuspendBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = parent_block.nodeOffset(inst_data.src_node);
return sema.failWithUseOfAsync(parent_block, src);
}
fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = parent_block.nodeOffset(pl_node.src_node);
const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
const gpa = sema.gpa;
// Reserve space for a Block instruction so that generated Break instructions can
// point to it, even if it doesn't end up getting used because the code ends up being
// comptime evaluated or is an unlabeled block.
const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{
.tag = .block,
.data = undefined,
});
var label: Block.Label = .{
.zir_block = inst,
.merges = .{
.src_locs = .{},
.results = .{},
.br_list = .{},
.block_inst = block_inst,
},
};
var child_block: Block = .{
.parent = parent_block,
.sema = sema,
.namespace = parent_block.namespace,
.instructions = .{},
.label = &label,
.inlining = parent_block.inlining,
.comptime_reason = parent_block.comptime_reason,
.is_typeof = parent_block.is_typeof,
.want_safety = parent_block.want_safety,
.float_mode = parent_block.float_mode,
.c_import_buf = parent_block.c_import_buf,
.runtime_cond = parent_block.runtime_cond,
.runtime_loop = parent_block.runtime_loop,
.runtime_index = parent_block.runtime_index,
.error_return_trace_index = parent_block.error_return_trace_index,
.src_base_inst = parent_block.src_base_inst,
.type_name_ctx = parent_block.type_name_ctx,
};
defer child_block.instructions.deinit(gpa);
defer label.merges.deinit(gpa);
return sema.resolveBlockBody(parent_block, src, &child_block, body, inst, &label.merges);
}
/// Semantically analyze the given ZIR body, emitting any resulting runtime code into the AIR block
/// specified by `child_block` if necessary (and emitting this block into `parent_block`).
/// TODO: `merges` is known from `child_block`, remove this parameter.
fn resolveBlockBody(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
child_block: *Block,
body: []const Zir.Inst.Index,
/// This is the instruction that a break instruction within `body` can
/// use to return from the body.
body_inst: Zir.Inst.Index,
merges: *Block.Merges,
) CompileError!Air.Inst.Ref {
if (child_block.isComptime()) {
return sema.resolveInlineBody(child_block, body, body_inst);
} else {
assert(sema.air_instructions.items(.tag)[@intFromEnum(merges.block_inst)] == .block);
var need_debug_scope = false;
child_block.need_debug_scope = &need_debug_scope;
if (sema.analyzeBodyInner(child_block, body)) |_| {
return sema.resolveAnalyzedBlock(parent_block, src, child_block, merges, need_debug_scope);
} else |err| switch (err) {
error.ComptimeBreak => {
// Comptime control flow is happening, however child_block may still contain
// runtime instructions which need to be copied to the parent block.
if (need_debug_scope and child_block.instructions.items.len > 0) {
// We need a runtime block for scoping reasons.
_ = try child_block.addBr(merges.block_inst, .void_value);
try parent_block.instructions.append(sema.gpa, merges.block_inst);
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Block).@"struct".fields.len +
child_block.instructions.items.len);
sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{
.ty = .void_type,
.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = @intCast(child_block.instructions.items.len),
}),
} };
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(child_block.instructions.items));
} else {
// We can copy instructions directly to the parent block.
try parent_block.instructions.appendSlice(sema.gpa, child_block.instructions.items);
}
const break_inst = sema.comptime_break_inst;
const break_data = sema.code.instructions.items(.data)[@intFromEnum(break_inst)].@"break";
const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;
if (extra.block_inst == body_inst) {
return try sema.resolveInst(break_data.operand);
} else {
return error.ComptimeBreak;
}
},
else => |e| return e,
}
}
}
/// After a body corresponding to an AIR `block` has been analyzed, this function places them into
/// the block pointed at by `merges.block_inst` if necessary, or the block may be elided in favor of
/// inlining the instructions directly into the parent block. Either way, it considers all merges of
/// this block, and combines them appropriately using peer type resolution, returning the final
/// value of the block.
fn resolveAnalyzedBlock(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
child_block: *Block,
merges: *Block.Merges,
need_debug_scope: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const gpa = sema.gpa;
const pt = sema.pt;
const zcu = pt.zcu;
// Blocks must terminate with noreturn instruction.
assert(child_block.instructions.items.len != 0);
assert(sema.typeOf(child_block.instructions.items[child_block.instructions.items.len - 1].toRef()).isNoReturn(zcu));
const block_tag = sema.air_instructions.items(.tag)[@intFromEnum(merges.block_inst)];
switch (block_tag) {
.block => {},
.dbg_inline_block => assert(need_debug_scope),
else => unreachable,
}
if (merges.results.items.len == 0) {
switch (block_tag) {
.block => {
// No need for a block instruction. We can put the new instructions
// directly into the parent block.
if (need_debug_scope) {
// The code following this block is unreachable, as the block has no
// merges, so we don't necessarily need to emit this as an AIR block.
// However, we need a block *somewhere* to make the scoping correct,
// so forward this request to the parent block.
if (parent_block.need_debug_scope) |ptr| ptr.* = true;
}
try parent_block.instructions.appendSlice(gpa, child_block.instructions.items);
return child_block.instructions.items[child_block.instructions.items.len - 1].toRef();
},
.dbg_inline_block => {
// Create a block containing all instruction from the body.
try parent_block.instructions.append(gpa, merges.block_inst);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.DbgInlineBlock).@"struct".fields.len +
child_block.instructions.items.len);
sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{
.ty = .noreturn_type,
.payload = sema.addExtraAssumeCapacity(Air.DbgInlineBlock{
.func = child_block.inlining.?.func,
.body_len = @intCast(child_block.instructions.items.len),
}),
} };
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(child_block.instructions.items));
return merges.block_inst.toRef();
},
else => unreachable,
}
}
if (merges.results.items.len == 1) {
// If the `break` is trailing, we may be able to elide the AIR block here
// by appending the new instructions directly to the parent block.
if (!need_debug_scope) {
const last_inst_index = child_block.instructions.items.len - 1;
const last_inst = child_block.instructions.items[last_inst_index];
if (sema.getBreakBlock(last_inst)) |br_block| {
if (br_block == merges.block_inst) {
// Great, the last instruction is the break! Put the instructions
// directly into the parent block.
try parent_block.instructions.appendSlice(gpa, child_block.instructions.items[0..last_inst_index]);
return merges.results.items[0];
}
}
}
// Okay, we need a runtime block. If the value is comptime-known, the
// block should just return void, and we return the merge result
// directly. Otherwise, we can defer to the logic below.
if (try sema.resolveValue(merges.results.items[0])) |result_val| {
// Create a block containing all instruction from the body.
try parent_block.instructions.append(gpa, merges.block_inst);
switch (block_tag) {
.block => {
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
child_block.instructions.items.len);
sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{
.ty = .void_type,
.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = @intCast(child_block.instructions.items.len),
}),
} };
},
.dbg_inline_block => {
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.DbgInlineBlock).@"struct".fields.len +
child_block.instructions.items.len);
sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{
.ty = .void_type,
.payload = sema.addExtraAssumeCapacity(Air.DbgInlineBlock{
.func = child_block.inlining.?.func,
.body_len = @intCast(child_block.instructions.items.len),
}),
} };
},
else => unreachable,
}
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(child_block.instructions.items));
// Rewrite the break to just give value {}; the value is
// comptime-known and will be returned directly.
sema.air_instructions.items(.data)[@intFromEnum(merges.br_list.items[0])].br.operand = .void_value;
return Air.internedToRef(result_val.toIntern());
}
}
// It is impossible to have the number of results be > 1 in a comptime scope.
assert(!child_block.isComptime()); // Should already got a compile error in the condbr condition.
// Note that we'll always create an AIR block here, so `need_debug_scope` is irrelevant.
// Need to set the type and emit the Block instruction. This allows machine code generation
// to emit a jump instruction to after the block when it encounters the break.
try parent_block.instructions.append(gpa, merges.block_inst);
const resolved_ty = try sema.resolvePeerTypes(parent_block, src, merges.results.items, .{ .override = merges.src_locs.items });
// TODO add note "missing else causes void value"
const type_src = src; // TODO: better source location
if (try resolved_ty.comptimeOnlySema(pt)) {
const msg = msg: {
const msg = try sema.errMsg(type_src, "value with comptime-only type '{f}' depends on runtime control flow", .{resolved_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
const runtime_src = child_block.runtime_cond orelse child_block.runtime_loop.?;
try sema.errNote(runtime_src, msg, "runtime control flow here", .{});
try sema.explainWhyTypeIsComptime(msg, type_src, resolved_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(child_block, msg);
}
for (merges.results.items, merges.src_locs.items) |merge_inst, merge_src| {
try sema.validateRuntimeValue(child_block, merge_src orelse src, merge_inst);
}
try sema.checkMergeAllowed(child_block, type_src, resolved_ty);
const ty_inst = Air.internedToRef(resolved_ty.toIntern());
switch (block_tag) {
.block => {
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
child_block.instructions.items.len);
sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{
.ty = ty_inst,
.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = @intCast(child_block.instructions.items.len),
}),
} };
},
.dbg_inline_block => {
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.DbgInlineBlock).@"struct".fields.len +
child_block.instructions.items.len);
sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{
.ty = ty_inst,
.payload = sema.addExtraAssumeCapacity(Air.DbgInlineBlock{
.func = child_block.inlining.?.func,
.body_len = @intCast(child_block.instructions.items.len),
}),
} };
},
else => unreachable,
}
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(child_block.instructions.items));
// Now that the block has its type resolved, we need to go back into all the break
// instructions, and insert type coercion on the operands.
for (merges.br_list.items) |br| {
const br_operand = sema.air_instructions.items(.data)[@intFromEnum(br)].br.operand;
const br_operand_src = src;
const br_operand_ty = sema.typeOf(br_operand);
if (br_operand_ty.eql(resolved_ty, zcu)) {
// No type coercion needed.
continue;
}
var coerce_block = parent_block.makeSubBlock();
defer coerce_block.instructions.deinit(gpa);
const coerced_operand = try sema.coerce(&coerce_block, resolved_ty, br_operand, br_operand_src);
// If no instructions were produced, such as in the case of a coercion of a
// constant value to a new type, we can simply point the br operand to it.
if (coerce_block.instructions.items.len == 0) {
sema.air_instructions.items(.data)[@intFromEnum(br)].br.operand = coerced_operand;
continue;
}
assert(coerce_block.instructions.items[coerce_block.instructions.items.len - 1].toRef() == coerced_operand);
// Convert the br instruction to a block instruction that has the coercion
// and then a new br inside that returns the coerced instruction.
const sub_block_len: u32 = @intCast(coerce_block.instructions.items.len + 1);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
sub_block_len);
try sema.air_instructions.ensureUnusedCapacity(gpa, 1);
const sub_br_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
sema.air_instructions.items(.tag)[@intFromEnum(br)] = .block;
sema.air_instructions.items(.data)[@intFromEnum(br)] = .{ .ty_pl = .{
.ty = .noreturn_type,
.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = sub_block_len,
}),
} };
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items));
sema.air_extra.appendAssumeCapacity(@intFromEnum(sub_br_inst));
sema.air_instructions.appendAssumeCapacity(.{
.tag = .br,
.data = .{ .br = .{
.block_inst = merges.block_inst,
.operand = coerced_operand,
} },
});
}
if (try sema.typeHasOnePossibleValue(resolved_ty)) |block_only_value| {
return Air.internedToRef(block_only_value.toIntern());
}
return merges.block_inst.toRef();
}
fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Export, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const ptr_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const options_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const ptr = try sema.resolveInst(extra.exported);
const ptr_val = try sema.resolveConstDefinedValue(block, ptr_src, ptr, .{ .simple = .export_target });
const ptr_ty = ptr_val.typeOf(zcu);
const options = try sema.resolveExportOptions(block, options_src, extra.options);
{
if (ptr_ty.zigTypeTag(zcu) != .pointer) {
return sema.fail(block, ptr_src, "expected pointer type, found '{f}'", .{ptr_ty.fmt(pt)});
}
const ptr_ty_info = ptr_ty.ptrInfo(zcu);
if (ptr_ty_info.flags.size == .slice) {
return sema.fail(block, ptr_src, "export target cannot be slice", .{});
}
if (ptr_ty_info.packed_offset.host_size != 0) {
return sema.fail(block, ptr_src, "export target cannot be bit-pointer", .{});
}
}
const ptr_info = ip.indexToKey(ptr_val.toIntern()).ptr;
switch (ptr_info.base_addr) {
.comptime_alloc, .int, .comptime_field => return sema.fail(block, ptr_src, "export target must be a global variable or a comptime-known constant", .{}),
.eu_payload, .opt_payload, .field, .arr_elem => return sema.fail(block, ptr_src, "TODO: export pointer in middle of value", .{}),
.uav => |uav| {
if (ptr_info.byte_offset != 0) {
return sema.fail(block, ptr_src, "TODO: export pointer in middle of value", .{});
}
if (zcu.llvm_object != null and options.linkage == .internal) return;
const export_ty = Value.fromInterned(uav.val).typeOf(zcu);
if (!try sema.validateExternType(export_ty, .other)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "unable to export type '{f}'", .{export_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, src, export_ty, .other);
try sema.addDeclaredHereNote(msg, export_ty);
break :msg msg;
});
}
try sema.exports.append(zcu.gpa, .{
.opts = options,
.src = src,
.exported = .{ .uav = uav.val },
.status = .in_progress,
});
},
.nav => |nav| {
if (ptr_info.byte_offset != 0) {
return sema.fail(block, ptr_src, "TODO: export pointer in middle of value", .{});
}
try sema.analyzeExport(block, src, options, nav);
},
}
}
pub fn analyzeExport(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
options: Zcu.Export.Options,
orig_nav_index: InternPool.Nav.Index,
) !void {
const gpa = sema.gpa;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
if (zcu.llvm_object != null and options.linkage == .internal)
return;
try sema.ensureNavResolved(block, src, orig_nav_index, .fully);
const exported_nav_index = switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) {
.variable => |v| v.owner_nav,
.@"extern" => |e| e.owner_nav,
.func => |f| f.owner_nav,
else => orig_nav_index,
};
const exported_nav = ip.getNav(exported_nav_index);
const export_ty: Type = .fromInterned(exported_nav.typeOf(ip));
if (!try sema.validateExternType(export_ty, .other)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "unable to export type '{f}'", .{export_ty.fmt(pt)});
errdefer msg.destroy(gpa);
try sema.explainWhyTypeIsNotExtern(msg, src, export_ty, .other);
try sema.addDeclaredHereNote(msg, export_ty);
break :msg msg;
});
}
// TODO: some backends might support re-exporting extern decls
if (exported_nav.getExtern(ip) != null) {
return sema.fail(block, src, "export target cannot be extern", .{});
}
try sema.maybeQueueFuncBodyAnalysis(block, src, exported_nav_index);
try sema.exports.append(gpa, .{
.opts = options,
.src = src,
.exported = .{ .nav = exported_nav_index },
.status = .in_progress,
});
}
fn zirDisableInstrumentation(sema: *Sema) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const func = switch (sema.owner.unwrap()) {
.func => |func| func,
.@"comptime",
.nav_val,
.nav_ty,
.type,
.memoized_state,
=> return, // does nothing outside a function
};
ip.funcSetDisableInstrumentation(func);
sema.allow_memoize = false;
}
fn zirDisableIntrinsics(sema: *Sema) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const func = switch (sema.owner.unwrap()) {
.func => |func| func,
.@"comptime",
.nav_val,
.nav_ty,
.type,
.memoized_state,
=> return, // does nothing outside a function
};
ip.funcSetDisableIntrinsics(func);
sema.allow_memoize = false;
}
fn zirSetFloatMode(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.builtinCallArgSrc(extra.node, 0);
block.float_mode = try sema.resolveBuiltinEnum(block, src, extra.operand, .FloatMode, .{ .simple = .operand_setFloatMode });
}
fn zirSetRuntimeSafety(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
block.want_safety = try sema.resolveConstBool(block, operand_src, inst_data.operand, .{ .simple = .operand_setRuntimeSafety });
}
fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"break";
const extra = sema.code.extraData(Zir.Inst.Break, inst_data.payload_index).data;
const operand = try sema.resolveInst(inst_data.operand);
const zir_block = extra.block_inst;
var block = start_block;
while (true) {
if (block.label) |label| {
if (label.zir_block == zir_block) {
const br_ref = try start_block.addBr(label.merges.block_inst, operand);
const src_loc = if (extra.operand_src_node.unwrap()) |operand_src_node|
start_block.nodeOffset(operand_src_node)
else
null;
try label.merges.src_locs.append(sema.gpa, src_loc);
try label.merges.results.append(sema.gpa, operand);
try label.merges.br_list.append(sema.gpa, br_ref.toIndex().?);
block.runtime_index.increment();
if (block.runtime_cond == null and block.runtime_loop == null) {
block.runtime_cond = start_block.runtime_cond orelse start_block.runtime_loop;
block.runtime_loop = start_block.runtime_loop;
}
return;
}
}
block = block.parent.?;
}
}
fn zirSwitchContinue(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"break";
const extra = sema.code.extraData(Zir.Inst.Break, inst_data.payload_index).data;
const operand_src = start_block.nodeOffset(extra.operand_src_node.unwrap().?);
const uncoerced_operand = try sema.resolveInst(inst_data.operand);
const switch_inst = extra.block_inst;
switch (sema.code.instructions.items(.tag)[@intFromEnum(switch_inst)]) {
.switch_block, .switch_block_ref => {},
else => unreachable, // assertion failure
}
const switch_payload_index = sema.code.instructions.items(.data)[@intFromEnum(switch_inst)].pl_node.payload_index;
const switch_operand_ref = sema.code.extraData(Zir.Inst.SwitchBlock, switch_payload_index).data.operand;
const switch_operand_ty = sema.typeOf(try sema.resolveInst(switch_operand_ref));
const operand = try sema.coerce(start_block, switch_operand_ty, uncoerced_operand, operand_src);
try sema.validateRuntimeValue(start_block, operand_src, operand);
// We want to generate a `switch_dispatch` instruction with the switch condition,
// possibly preceded by a store to the stack alloc containing the raw operand.
// However, to avoid too much special-case state in Sema, this is handled by the
// `switch` lowering logic. As such, we will find the `Block` corresponding to the
// parent `switch_block[_ref]` instruction, create a dummy `br`, and add a merge
// to signal to the switch logic to rewrite this into an appropriate dispatch.
var block = start_block;
while (true) {
if (block.label) |label| {
if (label.zir_block == switch_inst) {
const br_ref = try start_block.addBr(label.merges.block_inst, operand);
try label.merges.extra_insts.append(sema.gpa, br_ref.toIndex().?);
try label.merges.extra_src_locs.append(sema.gpa, operand_src);
block.runtime_index.increment();
if (block.runtime_cond == null and block.runtime_loop == null) {
block.runtime_cond = start_block.runtime_cond orelse start_block.runtime_loop;
block.runtime_loop = start_block.runtime_loop;
}
return;
}
}
block = block.parent.?;
}
}
fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
if (block.isComptime() or block.ownerModule().strip) return;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].dbg_stmt;
if (block.instructions.items.len != 0) {
const idx = block.instructions.items[block.instructions.items.len - 1];
if (sema.air_instructions.items(.tag)[@intFromEnum(idx)] == .dbg_stmt) {
// The previous dbg_stmt didn't correspond to any actual code, so replace it.
sema.air_instructions.items(.data)[@intFromEnum(idx)].dbg_stmt = .{
.line = inst_data.line,
.column = inst_data.column,
};
return;
}
}
_ = try block.addInst(.{
.tag = .dbg_stmt,
.data = .{ .dbg_stmt = .{
.line = inst_data.line,
.column = inst_data.column,
} },
});
}
fn zirDbgEmptyStmt(_: *Sema, block: *Block, _: Zir.Inst.Index) CompileError!void {
if (block.isComptime() or block.ownerModule().strip) return;
_ = try block.addNoOp(.dbg_empty_stmt);
}
fn zirDbgVar(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
air_tag: Air.Inst.Tag,
) CompileError!void {
const str_op = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_op;
const operand = try sema.resolveInst(str_op.operand);
const name = str_op.getStr(sema.code);
try sema.addDbgVar(block, operand, air_tag, name);
}
fn addDbgVar(
sema: *Sema,
block: *Block,
operand: Air.Inst.Ref,
air_tag: Air.Inst.Tag,
name: []const u8,
) CompileError!void {
if (block.isComptime() or block.ownerModule().strip) return;
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
const val_ty = switch (air_tag) {
.dbg_var_ptr => operand_ty.childType(zcu),
.dbg_var_val, .dbg_arg_inline => operand_ty,
else => unreachable,
};
if (try val_ty.comptimeOnlySema(pt)) return;
if (!(try val_ty.hasRuntimeBitsSema(pt))) return;
if (try sema.resolveValue(operand)) |operand_val| {
if (operand_val.canMutateComptimeVarState(zcu)) return;
}
// To ensure the lexical scoping is known to backends, this alloc must be
// within a real runtime block. We set a flag which communicates information
// to the closest lexically enclosing block:
// * If it is a `block_inline`, communicates to logic in `analyzeBodyInner`
// to create a post-hoc block.
// * Otherwise, communicates to logic in `resolveBlockBody` to create a
// real `block` instruction.
if (block.need_debug_scope) |ptr| ptr.* = true;
// Add the name to the AIR.
const name_nts = try sema.appendAirString(name);
_ = try block.addInst(.{
.tag = air_tag,
.data = .{ .pl_op = .{
.payload = @intFromEnum(name_nts),
.operand = operand,
} },
});
}
pub fn appendAirString(sema: *Sema, str: []const u8) Allocator.Error!Air.NullTerminatedString {
if (str.len == 0) return .none;
const nts: Air.NullTerminatedString = @enumFromInt(sema.air_extra.items.len);
const elements_used = str.len / 4 + 1;
const elements = try sema.air_extra.addManyAsSlice(sema.gpa, elements_used);
const buffer = mem.sliceAsBytes(elements);
@memcpy(buffer[0..str.len], str);
buffer[str.len] = 0;
return nts;
}
fn zirDeclRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const src = block.tokenOffset(inst_data.src_tok);
const decl_name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
inst_data.get(sema.code),
.no_embedded_nulls,
);
const nav_index = try sema.lookupIdentifier(block, decl_name);
return sema.analyzeNavRef(block, src, nav_index);
}
fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const src = block.tokenOffset(inst_data.src_tok);
const decl_name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
inst_data.get(sema.code),
.no_embedded_nulls,
);
const nav = try sema.lookupIdentifier(block, decl_name);
return sema.analyzeNavVal(block, src, nav);
}
fn lookupIdentifier(sema: *Sema, block: *Block, name: InternPool.NullTerminatedString) !InternPool.Nav.Index {
const pt = sema.pt;
const zcu = pt.zcu;
var namespace = block.namespace;
while (true) {
if (try sema.lookupInNamespace(block, namespace, name)) |lookup| {
assert(lookup.accessible);
return lookup.nav;
}
namespace = zcu.namespacePtr(namespace).parent.unwrap() orelse break;
}
unreachable; // AstGen detects use of undeclared identifiers.
}
/// This looks up a member of a specific namespace.
fn lookupInNamespace(
sema: *Sema,
block: *Block,
namespace_index: InternPool.NamespaceIndex,
ident_name: InternPool.NullTerminatedString,
) CompileError!?struct {
nav: InternPool.Nav.Index,
/// If `false`, the declaration is in a different file and is not `pub`.
/// We still return the declaration for better error reporting.
accessible: bool,
} {
const pt = sema.pt;
const zcu = pt.zcu;
try pt.ensureNamespaceUpToDate(namespace_index);
const namespace = zcu.namespacePtr(namespace_index);
const adapter: Zcu.Namespace.NameAdapter = .{ .zcu = zcu };
const src_file = zcu.namespacePtr(block.namespace).file_scope;
if (Type.fromInterned(namespace.owner_type).typeDeclInst(zcu)) |type_decl_inst| {
try sema.declareDependency(.{ .namespace_name = .{
.namespace = type_decl_inst,
.name = ident_name,
} });
}
if (namespace.pub_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {
return .{
.nav = nav_index,
.accessible = true,
};
} else if (namespace.priv_decls.getKeyAdapted(ident_name, adapter)) |nav_index| {
return .{
.nav = nav_index,
.accessible = src_file == namespace.file_scope,
};
}
return null;
}
fn funcDeclSrcInst(sema: *Sema, func_inst: Air.Inst.Ref) !?InternPool.TrackedInst.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const func_val = try sema.resolveValue(func_inst) orelse return null;
if (func_val.isUndef(zcu)) return null;
const nav = switch (ip.indexToKey(func_val.toIntern())) {
.@"extern" => |e| e.owner_nav,
.func => |f| f.owner_nav,
.ptr => |ptr| switch (ptr.base_addr) {
.nav => |nav| if (ptr.byte_offset == 0) nav else return null,
else => return null,
},
else => return null,
};
return ip.getNav(nav).srcInst(ip);
}
pub fn analyzeSaveErrRetIndex(sema: *Sema, block: *Block) SemaError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
if (block.isComptime() or block.is_typeof) {
const index_val = try pt.intValue_u64(.usize, sema.comptime_err_ret_trace.items.len);
return Air.internedToRef(index_val.toIntern());
}
if (!block.ownerModule().error_tracing) return .none;
const stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(.zero), .StackTrace);
try stack_trace_ty.resolveFields(pt);
const field_name = try zcu.intern_pool.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
const field_index = sema.structFieldIndex(block, stack_trace_ty, field_name, LazySrcLoc.unneeded) catch |err| switch (err) {
error.AnalysisFail => @panic("std.builtin.StackTrace is corrupt"),
error.ComptimeReturn, error.ComptimeBreak => unreachable,
error.OutOfMemory, error.Canceled => |e| return e,
};
return try block.addInst(.{
.tag = .save_err_return_trace_index,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(stack_trace_ty.toIntern()),
.payload = @intCast(field_index),
} },
});
}
/// Add instructions to block to "pop" the error return trace.
/// If `operand` is provided, only pops if operand is non-error.
fn popErrorReturnTrace(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
saved_error_trace_index: Air.Inst.Ref,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
var is_non_error: ?bool = null;
var is_non_error_inst: Air.Inst.Ref = undefined;
if (operand != .none) {
is_non_error_inst = try sema.analyzeIsNonErr(block, src, operand);
if (try sema.resolveDefinedValue(block, src, is_non_error_inst)) |cond_val|
is_non_error = cond_val.toBool();
} else is_non_error = true; // no operand means pop unconditionally
if (is_non_error == true) {
// AstGen determined this result does not go to an error-handling expr (try/catch/return etc.), or
// the result is comptime-known to be a non-error. Either way, pop unconditionally.
const stack_trace_ty = try sema.getBuiltinType(src, .StackTrace);
try stack_trace_ty.resolveFields(pt);
const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty);
const field_name = try zcu.intern_pool.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
const field_ptr = try sema.structFieldPtr(block, src, err_return_trace, field_name, src, stack_trace_ty, true);
try sema.storePtr2(block, src, field_ptr, src, saved_error_trace_index, src, .store);
} else if (is_non_error == null) {
// The result might be an error. If it is, we leave the error trace alone. If it isn't, we need
// to pop any error trace that may have been propagated from our arguments.
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len);
const cond_block_inst = try block.addInstAsIndex(.{
.tag = .block,
.data = .{
.ty_pl = .{
.ty = .void_type,
.payload = undefined, // updated below
},
},
});
var then_block = block.makeSubBlock();
defer then_block.instructions.deinit(gpa);
// If non-error, then pop the error return trace by restoring the index.
const stack_trace_ty = try sema.getBuiltinType(src, .StackTrace);
try stack_trace_ty.resolveFields(pt);
const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
const err_return_trace = try then_block.addTy(.err_return_trace, ptr_stack_trace_ty);
const field_name = try zcu.intern_pool.getOrPutString(gpa, pt.tid, "index", .no_embedded_nulls);
const field_ptr = try sema.structFieldPtr(&then_block, src, err_return_trace, field_name, src, stack_trace_ty, true);
try sema.storePtr2(&then_block, src, field_ptr, src, saved_error_trace_index, src, .store);
_ = try then_block.addBr(cond_block_inst, .void_value);
// Otherwise, do nothing
var else_block = block.makeSubBlock();
defer else_block.instructions.deinit(gpa);
_ = try else_block.addBr(cond_block_inst, .void_value);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +
then_block.instructions.items.len + else_block.instructions.items.len +
@typeInfo(Air.Block).@"struct".fields.len + 1); // +1 for the sole .cond_br instruction in the .block
const cond_br_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{
.tag = .cond_br,
.data = .{
.pl_op = .{
.operand = is_non_error_inst,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(then_block.instructions.items.len),
.else_body_len = @intCast(else_block.instructions.items.len),
.branch_hints = .{
// Weight against error branch.
.true = .likely,
.false = .unlikely,
// Code coverage is not valuable on either branch.
.then_cov = .none,
.else_cov = .none,
},
}),
},
},
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items));
sema.air_instructions.items(.data)[@intFromEnum(cond_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{ .body_len = 1 });
sema.air_extra.appendAssumeCapacity(@intFromEnum(cond_br_inst));
}
}
fn zirCall(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
comptime kind: enum { direct, field },
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const callee_src = block.src(.{ .node_offset_call_func = inst_data.src_node });
const call_src = block.nodeOffset(inst_data.src_node);
const ExtraType = switch (kind) {
.direct => Zir.Inst.Call,
.field => Zir.Inst.FieldCall,
};
const extra = sema.code.extraData(ExtraType, inst_data.payload_index);
const args_len = extra.data.flags.args_len;
const modifier: std.builtin.CallModifier = @enumFromInt(extra.data.flags.packed_modifier);
const ensure_result_used = extra.data.flags.ensure_result_used;
const pop_error_return_trace = extra.data.flags.pop_error_return_trace;
const callee: ResolvedFieldCallee = switch (kind) {
.direct => .{ .direct = try sema.resolveInst(extra.data.callee) },
.field => blk: {
const object_ptr = try sema.resolveInst(extra.data.obj_ptr);
const field_name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
sema.code.nullTerminatedString(extra.data.field_name_start),
.no_embedded_nulls,
);
const field_name_src = block.src(.{ .node_offset_field_name = inst_data.src_node });
break :blk try sema.fieldCallBind(block, callee_src, object_ptr, field_name, field_name_src);
},
};
const func: Air.Inst.Ref = switch (callee) {
.direct => |func_inst| func_inst,
.method => |method| method.func_inst,
};
const callee_ty = sema.typeOf(func);
const total_args = args_len + @intFromBool(callee == .method);
const func_ty = try sema.checkCallArgumentCount(block, func, callee_src, callee_ty, total_args, callee == .method);
// The block index before the call, so we can potentially insert an error trace save here later.
const block_index: Air.Inst.Index = @enumFromInt(block.instructions.items.len);
// This will be set by `analyzeCall` to indicate whether any parameter was an error (making the
// error trace potentially dirty).
var input_is_error = false;
const args_info: CallArgsInfo = .{ .zir_call = .{
.bound_arg = switch (callee) {
.direct => .none,
.method => |method| method.arg0_inst,
},
.bound_arg_src = callee_src,
.call_inst = inst,
.call_node_offset = inst_data.src_node,
.num_args = args_len,
.args_body = @ptrCast(sema.code.extra[extra.end..]),
.any_arg_is_error = &input_is_error,
} };
// AstGen ensures that a call instruction is always preceded by a dbg_stmt instruction.
const call_dbg_node: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1);
const call_inst = try sema.analyzeCall(block, func, func_ty, callee_src, call_src, modifier, ensure_result_used, args_info, call_dbg_node, .call);
if (block.ownerModule().error_tracing and
!block.isComptime() and !block.is_typeof and (input_is_error or pop_error_return_trace))
{
const return_ty = sema.typeOf(call_inst);
if (modifier != .always_tail and return_ty.isNoReturn(zcu))
return call_inst; // call to "fn (...) noreturn", don't pop
// TODO: we don't fix up the error trace for always_tail correctly, we should be doing it
// *before* the recursive call. This will be a bit tricky to do and probably requires
// moving this logic into analyzeCall. But that's probably a good idea anyway.
if (modifier == .always_tail)
return call_inst;
// If any input is an error-type, we might need to pop any trace it generated. Otherwise, we only
// need to clean-up our own trace if we were passed to a non-error-handling expression.
if (input_is_error or (pop_error_return_trace and return_ty.isError(zcu))) {
const stack_trace_ty = try sema.getBuiltinType(call_src, .StackTrace);
try stack_trace_ty.resolveFields(pt);
const field_name = try zcu.intern_pool.getOrPutString(sema.gpa, pt.tid, "index", .no_embedded_nulls);
const field_index = try sema.structFieldIndex(block, stack_trace_ty, field_name, call_src);
// Insert a save instruction before the arg resolution + call instructions we just generated
const save_inst = try block.insertInst(block_index, .{
.tag = .save_err_return_trace_index,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(stack_trace_ty.toIntern()),
.payload = @intCast(field_index),
} },
});
// Pop the error return trace, testing the result for non-error if necessary
const operand = if (pop_error_return_trace or modifier == .always_tail) .none else call_inst;
try sema.popErrorReturnTrace(block, call_src, operand, save_inst);
}
return call_inst;
} else {
return call_inst;
}
}
fn checkCallArgumentCount(
sema: *Sema,
block: *Block,
func: Air.Inst.Ref,
func_src: LazySrcLoc,
callee_ty: Type,
total_args: usize,
member_fn: bool,
) !Type {
const pt = sema.pt;
const zcu = pt.zcu;
const func_ty: Type = func_ty: {
switch (callee_ty.zigTypeTag(zcu)) {
.@"fn" => break :func_ty callee_ty,
.pointer => {
const ptr_info = callee_ty.ptrInfo(zcu);
if (ptr_info.flags.size == .one and Type.fromInterned(ptr_info.child).zigTypeTag(zcu) == .@"fn") {
break :func_ty .fromInterned(ptr_info.child);
}
},
.optional => {
const opt_child = callee_ty.optionalChild(zcu);
if (opt_child.zigTypeTag(zcu) == .@"fn" or (opt_child.isSinglePointer(zcu) and
opt_child.childType(zcu).zigTypeTag(zcu) == .@"fn"))
{
const msg = msg: {
const msg = try sema.errMsg(func_src, "cannot call optional type '{f}'", .{
callee_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(func_src, msg, "consider using '.?', 'orelse' or 'if'", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
},
else => {},
}
return sema.fail(block, func_src, "type '{f}' not a function", .{callee_ty.fmt(pt)});
};
const func_ty_info = zcu.typeToFunc(func_ty).?;
const fn_params_len = func_ty_info.param_types.len;
const args_len = total_args - @intFromBool(member_fn);
if (func_ty_info.is_var_args) {
assert(callConvSupportsVarArgs(func_ty_info.cc));
if (total_args >= fn_params_len) return func_ty;
} else if (fn_params_len == total_args) {
return func_ty;
}
const maybe_func_inst = try sema.funcDeclSrcInst(func);
const member_str = if (member_fn) "member function " else "";
const variadic_str = if (func_ty_info.is_var_args) "at least " else "";
const msg = msg: {
const msg = try sema.errMsg(
func_src,
"{s}expected {s}{d} argument(s), found {d}",
.{
member_str,
variadic_str,
fn_params_len - @intFromBool(member_fn),
args_len,
},
);
errdefer msg.destroy(sema.gpa);
if (maybe_func_inst) |func_inst| {
try sema.errNote(.{
.base_node_inst = func_inst,
.offset = LazySrcLoc.Offset.nodeOffset(.zero),
}, msg, "function declared here", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn callBuiltin(
sema: *Sema,
block: *Block,
call_src: LazySrcLoc,
builtin_fn: Air.Inst.Ref,
modifier: std.builtin.CallModifier,
args: []const Air.Inst.Ref,
operation: CallOperation,
) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const callee_ty = sema.typeOf(builtin_fn);
const func_ty: Type = func_ty: {
switch (callee_ty.zigTypeTag(zcu)) {
.@"fn" => break :func_ty callee_ty,
.pointer => {
const ptr_info = callee_ty.ptrInfo(zcu);
if (ptr_info.flags.size == .one and Type.fromInterned(ptr_info.child).zigTypeTag(zcu) == .@"fn") {
break :func_ty .fromInterned(ptr_info.child);
}
},
else => {},
}
std.debug.panic("type '{f}' is not a function calling builtin fn", .{callee_ty.fmt(pt)});
};
const func_ty_info = zcu.typeToFunc(func_ty).?;
const fn_params_len = func_ty_info.param_types.len;
if (args.len != fn_params_len or (func_ty_info.is_var_args and args.len < fn_params_len)) {
std.debug.panic("parameter count mismatch calling builtin fn, expected {d}, found {d}", .{ fn_params_len, args.len });
}
_ = try sema.analyzeCall(
block,
builtin_fn,
func_ty,
call_src,
call_src,
modifier,
false,
.{ .resolved = .{ .src = call_src, .args = args } },
null,
operation,
);
}
const CallOperation = enum {
call,
@"@call",
@"@panic",
@"safety check",
@"error return",
};
const CallArgsInfo = union(enum) {
/// The full list of resolved (but uncoerced) arguments is known ahead of time.
resolved: struct {
src: LazySrcLoc,
args: []const Air.Inst.Ref,
},
/// The list of resolved (but uncoerced) arguments is known ahead of time, but
/// originated from a usage of the @call builtin at the given node offset.
call_builtin: struct {
call_node_offset: std.zig.Ast.Node.Offset,
args: []const Air.Inst.Ref,
},
/// This call corresponds to a ZIR call instruction. The arguments have not yet been
/// resolved. They must be resolved by `analyzeCall` so that argument resolution and
/// generic instantiation may be interleaved. This is required for RLS to work on
/// generic parameters.
zir_call: struct {
/// This may be `none`, in which case it is ignored. Otherwise, it is the
/// already-resolved value of the first argument, from method call syntax.
bound_arg: Air.Inst.Ref,
/// The source location of `bound_arg` if it is not `null`. Otherwise `undefined`.
bound_arg_src: LazySrcLoc,
/// The ZIR call instruction. The parameter type is placed at this index while
/// analyzing arguments.
call_inst: Zir.Inst.Index,
/// The node offset of `call_inst`.
call_node_offset: std.zig.Ast.Node.Offset,
/// The number of arguments to this call, not including `bound_arg`.
num_args: u32,
/// The ZIR corresponding to all function arguments (other than `bound_arg`, if it
/// is not `none`). Format is precisely the same as trailing data of ZIR `call`.
args_body: []const Zir.Inst.Index,
/// This bool will be set to true if any argument evaluated turns out to have an error set or error union type.
/// This is used by the caller to restore the error return trace when necessary.
any_arg_is_error: *bool,
},
fn count(cai: CallArgsInfo) usize {
return switch (cai) {
inline .resolved, .call_builtin => |resolved| resolved.args.len,
.zir_call => |zir_call| zir_call.num_args + @intFromBool(zir_call.bound_arg != .none),
};
}
fn argSrc(cai: CallArgsInfo, block: *Block, arg_index: usize) LazySrcLoc {
return switch (cai) {
.resolved => |resolved| resolved.src,
.call_builtin => |call_builtin| block.src(.{ .call_arg = .{
.call_node_offset = call_builtin.call_node_offset,
.arg_index = @intCast(arg_index),
} }),
.zir_call => |zir_call| if (arg_index == 0 and zir_call.bound_arg != .none) {
return zir_call.bound_arg_src;
} else block.src(.{ .call_arg = .{
.call_node_offset = zir_call.call_node_offset,
.arg_index = @intCast(arg_index - @intFromBool(zir_call.bound_arg != .none)),
} }),
};
}
/// Analyzes the arg at `arg_index` and coerces it to `param_ty`.
/// `param_ty` may be `generic_poison`. A value of `null` indicates a varargs parameter.
/// `func_ty_info` may be the type before instantiation, even if a generic instantiation is in progress.
/// Emits a compile error if the argument is not comptime-known despite either `block.isComptime()` or
/// the parameter being marked `comptime`.
fn analyzeArg(
cai: CallArgsInfo,
sema: *Sema,
block: *Block,
arg_index: usize,
maybe_param_ty: ?Type,
func_ty_info: InternPool.Key.FuncType,
func_inst: Air.Inst.Ref,
maybe_func_src_inst: ?InternPool.TrackedInst.Index,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const param_count = func_ty_info.param_types.len;
const uncoerced_arg: Air.Inst.Ref = switch (cai) {
inline .resolved, .call_builtin => |resolved| resolved.args[arg_index],
.zir_call => |zir_call| arg_val: {
const has_bound_arg = zir_call.bound_arg != .none;
if (arg_index == 0 and has_bound_arg) {
break :arg_val zir_call.bound_arg;
}
const real_arg_idx = arg_index - @intFromBool(has_bound_arg);
const arg_body = if (real_arg_idx == 0) blk: {
const start = zir_call.num_args;
const end = @intFromEnum(zir_call.args_body[0]);
break :blk zir_call.args_body[start..end];
} else blk: {
const start = @intFromEnum(zir_call.args_body[real_arg_idx - 1]);
const end = @intFromEnum(zir_call.args_body[real_arg_idx]);
break :blk zir_call.args_body[start..end];
};
// Generate args to comptime params in comptime block
const parent_comptime = block.comptime_reason;
defer block.comptime_reason = parent_comptime;
// Note that we are indexing into parameters, not arguments, so use `arg_index` instead of `real_arg_idx`
if (std.math.cast(u5, arg_index)) |i| {
if (i < param_count and func_ty_info.paramIsComptime(i)) {
block.comptime_reason = .{
.reason = .{
.src = cai.argSrc(block, arg_index),
.r = .{
.comptime_param = .{
.comptime_src = if (maybe_func_src_inst) |src_inst| .{
.base_node_inst = src_inst,
.offset = .{ .func_decl_param_comptime = @intCast(arg_index) },
} else unreachable, // should be non-null because the function is generic
},
},
},
};
}
}
// Give the arg its result type
const provide_param_ty: Type = maybe_param_ty orelse .generic_poison;
sema.inst_map.putAssumeCapacity(zir_call.call_inst, Air.internedToRef(provide_param_ty.toIntern()));
// Resolve the arg!
const uncoerced_arg = try sema.resolveInlineBody(block, arg_body, zir_call.call_inst);
if (block.isComptime() and !try sema.isComptimeKnown(uncoerced_arg)) {
return sema.failWithNeededComptime(block, cai.argSrc(block, arg_index), null);
}
if (sema.typeOf(uncoerced_arg).zigTypeTag(zcu) == .noreturn) {
// This terminates resolution of arguments. The caller should
// propagate this.
return uncoerced_arg;
}
if (sema.typeOf(uncoerced_arg).isError(zcu)) {
zir_call.any_arg_is_error.* = true;
}
break :arg_val uncoerced_arg;
},
};
const param_ty = maybe_param_ty orelse {
return sema.coerceVarArgParam(block, uncoerced_arg, cai.argSrc(block, arg_index));
};
switch (param_ty.toIntern()) {
.generic_poison_type => return uncoerced_arg,
else => return sema.coerceExtra(
block,
param_ty,
uncoerced_arg,
cai.argSrc(block, arg_index),
.{ .param_src = .{
.func_inst = func_inst,
.param_i = @intCast(arg_index),
} },
) catch |err| switch (err) {
error.NotCoercible => unreachable,
else => |e| return e,
},
}
}
};
fn analyzeCall(
sema: *Sema,
block: *Block,
callee: Air.Inst.Ref,
func_ty: Type,
func_src: LazySrcLoc,
call_src: LazySrcLoc,
modifier: std.builtin.CallModifier,
ensure_result_used: bool,
args_info: CallArgsInfo,
call_dbg_node: ?Zir.Inst.Index,
operation: CallOperation,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const arena = sema.arena;
const maybe_func_inst = try sema.funcDeclSrcInst(callee);
const func_ret_ty_src: LazySrcLoc = if (maybe_func_inst) |fn_decl_inst| .{
.base_node_inst = fn_decl_inst,
.offset = .{ .node_offset_fn_type_ret_ty = .zero },
} else func_src;
const func_ty_info = zcu.typeToFunc(func_ty).?;
if (!callConvIsCallable(func_ty_info.cc)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(
func_src,
"unable to call function with calling convention '{s}'",
.{@tagName(func_ty_info.cc)},
);
errdefer msg.destroy(gpa);
if (maybe_func_inst) |func_inst| try sema.errNote(.{
.base_node_inst = func_inst,
.offset = .nodeOffset(.zero),
}, msg, "function declared here", .{});
break :msg msg;
});
}
// We need this value in a few code paths.
const callee_val = try sema.resolveDefinedValue(block, call_src, callee);
// If the callee is a comptime-known *non-extern* function, `func_val` is populated.
// If it is a comptime-known extern function, `func_is_extern` is set instead.
// If it is not comptime-known, neither is set.
const func_val: ?Value, const func_is_extern: bool = if (callee_val) |c| switch (ip.indexToKey(c.toIntern())) {
.func => .{ c, false },
.ptr => switch (try sema.pointerDerefExtra(block, func_src, c)) {
.runtime_load, .needed_well_defined, .out_of_bounds => .{ null, false },
.val => |pointee| switch (ip.indexToKey(pointee.toIntern())) {
.func => .{ pointee, false },
.@"extern" => .{ null, true },
else => unreachable,
},
},
.@"extern" => .{ null, true },
else => unreachable,
} else .{ null, false };
if (func_ty_info.is_generic and func_val == null) {
return sema.failWithNeededComptime(block, func_src, .{ .simple = .generic_call_target });
}
const inline_requested = func_ty_info.cc == .@"inline" or modifier == .always_inline;
// If the modifier is `.compile_time`, or if the return type is non-generic and comptime-only,
// then we need to enter a comptime scope *now* to make sure the args are comptime-eval'd.
const old_block_comptime_reason = block.comptime_reason;
defer block.comptime_reason = old_block_comptime_reason;
if (!block.isComptime()) {
if (modifier == .compile_time) {
block.comptime_reason = .{ .reason = .{
.src = call_src,
.r = .{ .simple = .comptime_call_modifier },
} };
} else if (!inline_requested and try Type.fromInterned(func_ty_info.return_type).comptimeOnlySema(pt)) {
block.comptime_reason = .{
.reason = .{
.src = call_src,
.r = .{
.comptime_only_ret_ty = .{
.ty = .fromInterned(func_ty_info.return_type),
.is_generic_inst = false,
.ret_ty_src = func_ret_ty_src,
},
},
},
};
}
}
// This is whether we already know this to be an inline call.
// If so, then comptime-known arguments are propagated when evaluating generic parameter/return types.
// We might still learn that this call is inline *after* evaluating the generic return type.
const early_known_inline = inline_requested or block.isComptime();
// These values are undefined if `func_val == null`.
const fn_nav: InternPool.Nav, const fn_zir: Zir, const fn_tracked_inst: InternPool.TrackedInst.Index, const fn_zir_inst: Zir.Inst.Index, const fn_zir_info: Zir.FnInfo = if (func_val) |f| b: {
const info = ip.indexToKey(f.toIntern()).func;
const nav = ip.getNav(info.owner_nav);
const resolved_func_inst = info.zir_body_inst.resolveFull(ip) orelse return error.AnalysisFail;
const file = zcu.fileByIndex(resolved_func_inst.file);
const zir_info = file.zir.?.getFnInfo(resolved_func_inst.inst);
break :b .{ nav, file.zir.?, info.zir_body_inst, resolved_func_inst.inst, zir_info };
} else .{ undefined, undefined, undefined, undefined, undefined };
// This is the `inst_map` used when evaluating generic parameters and return types.
var generic_inst_map: InstMap = .{};
defer generic_inst_map.deinit(gpa);
if (func_ty_info.is_generic) {
try generic_inst_map.ensureSpaceForInstructions(gpa, fn_zir_info.param_body);
}
// This exists so that `generic_block` below can include a "called from here" note back to this
// call site when analyzing generic parameter/return types.
var generic_inlining: Block.Inlining = if (func_ty_info.is_generic) .{
.call_block = block,
.call_src = call_src,
.func = func_val.?.toIntern(),
.is_generic_instantiation = true, // this allows the following fields to be `undefined`
.has_comptime_args = undefined,
.comptime_result = undefined,
.merges = undefined,
} else undefined;
// This is the block in which we evaluate generic function components: that is, generic parameter
// types and the generic return type. This must not be used if the function is not generic.
// `comptime_reason` is set as needed.
var generic_block: Block = if (func_ty_info.is_generic) .{
.parent = null,
.sema = sema,
.namespace = fn_nav.analysis.?.namespace,
.instructions = .{},
.inlining = &generic_inlining,
.src_base_inst = fn_nav.analysis.?.zir_index,
.type_name_ctx = fn_nav.fqn,
} else undefined;
defer if (func_ty_info.is_generic) generic_block.instructions.deinit(gpa);
if (func_ty_info.is_generic) {
// We certainly depend on the generic owner's signature!
try sema.declareDependency(.{ .src_hash = fn_tracked_inst });
}
const args = try arena.alloc(Air.Inst.Ref, args_info.count());
for (args, 0..) |*arg, arg_idx| {
const param_ty: ?Type = if (arg_idx < func_ty_info.param_types.len) ty: {
const raw = func_ty_info.param_types.get(ip)[arg_idx];
if (raw != .generic_poison_type) break :ty .fromInterned(raw);
// We must discover the generic parameter type.
assert(func_ty_info.is_generic);
const param_inst_idx = fn_zir_info.param_body[arg_idx];
const param_inst = fn_zir.instructions.get(@intFromEnum(param_inst_idx));
switch (param_inst.tag) {
.param_anytype, .param_anytype_comptime => break :ty .generic_poison,
.param, .param_comptime => {},
else => unreachable,
}
// Evaluate the generic parameter type. We need to switch out `sema.code` and `sema.inst_map`, because
// the function definition may be in a different file to the call site.
const old_code = sema.code;
const old_inst_map = sema.inst_map;
defer {
generic_inst_map = sema.inst_map;
sema.code = old_code;
sema.inst_map = old_inst_map;
}
sema.code = fn_zir;
sema.inst_map = generic_inst_map;
const extra = sema.code.extraData(Zir.Inst.Param, param_inst.data.pl_tok.payload_index);
const param_src = generic_block.tokenOffset(param_inst.data.pl_tok.src_tok);
const body = sema.code.bodySlice(extra.end, extra.data.type.body_len);
generic_block.comptime_reason = .{ .reason = .{
.r = .{ .simple = .fn_param_types },
.src = param_src,
} };
const ty_ref = try sema.resolveInlineBody(&generic_block, body, param_inst_idx);
const param_ty = try sema.analyzeAsType(&generic_block, param_src, ty_ref);
if (!param_ty.isValidParamType(zcu)) {
const opaque_str = if (param_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
return sema.fail(block, param_src, "parameter of {s}type '{f}' not allowed", .{
opaque_str, param_ty.fmt(pt),
});
}
break :ty param_ty;
} else null; // vararg
arg.* = try args_info.analyzeArg(sema, block, arg_idx, param_ty, func_ty_info, callee, maybe_func_inst);
const arg_ty = sema.typeOf(arg.*);
if (arg_ty.zigTypeTag(zcu) == .noreturn) {
return arg.*; // terminate analysis here
}
if (func_ty_info.is_generic) {
// We need to put the argument into `generic_inst_map` so that other parameters can refer to it.
const param_inst_idx = fn_zir_info.param_body[arg_idx];
const declared_comptime = if (std.math.cast(u5, arg_idx)) |i| func_ty_info.paramIsComptime(i) else false;
const param_is_comptime = declared_comptime or try arg_ty.comptimeOnlySema(pt);
// We allow comptime-known arguments to propagate to generic types not only for comptime
// parameters, but if the call is known to be inline.
if (param_is_comptime or early_known_inline) {
if (param_is_comptime and !try sema.isComptimeKnown(arg.*)) {
assert(!declared_comptime); // `analyzeArg` handles this
const arg_src = args_info.argSrc(block, arg_idx);
const param_ty_src: LazySrcLoc = .{
.base_node_inst = maybe_func_inst.?, // the function is generic
.offset = .{ .func_decl_param_ty = @intCast(arg_idx) },
};
return sema.failWithNeededComptime(
block,
arg_src,
.{ .comptime_only_param_ty = .{ .ty = arg_ty, .param_ty_src = param_ty_src } },
);
}
generic_inst_map.putAssumeCapacityNoClobber(param_inst_idx, arg.*);
} else {
// We need a dummy instruction with this type. It doesn't actually need to be in any block,
// since it will never be referenced at runtime!
const dummy: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{ .tag = .alloc, .data = .{ .ty = arg_ty } });
generic_inst_map.putAssumeCapacityNoClobber(param_inst_idx, dummy.toRef());
}
}
}
// This return type is never generic poison.
// However, if it has an IES, it is always associated with the callee value.
// This is not correct for inline calls (where it should be an ad-hoc IES), nor for generic
// calls (where it should be the IES of the instantiation). However, it's how we print this
// in error messages.
const resolved_ret_ty: Type = ret_ty: {
if (!func_ty_info.is_generic) break :ret_ty .fromInterned(func_ty_info.return_type);
const maybe_poison_bare = if (fn_zir_info.inferred_error_set) maybe_poison: {
break :maybe_poison ip.errorUnionPayload(func_ty_info.return_type);
} else func_ty_info.return_type;
if (maybe_poison_bare != .generic_poison_type) break :ret_ty .fromInterned(func_ty_info.return_type);
// Evaluate the generic return type. As with generic parameters, we switch out `sema.code` and `sema.inst_map`.
assert(func_ty_info.is_generic);
const old_code = sema.code;
const old_inst_map = sema.inst_map;
defer {
generic_inst_map = sema.inst_map;
sema.code = old_code;
sema.inst_map = old_inst_map;
}
sema.code = fn_zir;
sema.inst_map = generic_inst_map;
generic_block.comptime_reason = .{ .reason = .{
.r = .{ .simple = .fn_ret_ty },
.src = func_ret_ty_src,
} };
const bare_ty = if (fn_zir_info.ret_ty_ref != .none) bare: {
assert(fn_zir_info.ret_ty_body.len == 0);
break :bare try sema.resolveType(&generic_block, func_ret_ty_src, fn_zir_info.ret_ty_ref);
} else bare: {
assert(fn_zir_info.ret_ty_body.len != 0);
const ty_ref = try sema.resolveInlineBody(&generic_block, fn_zir_info.ret_ty_body, fn_zir_inst);
break :bare try sema.analyzeAsType(&generic_block, func_ret_ty_src, ty_ref);
};
assert(bare_ty.toIntern() != .generic_poison_type);
const full_ty = if (fn_zir_info.inferred_error_set) full: {
try sema.validateErrorUnionPayloadType(block, bare_ty, func_ret_ty_src);
const set = ip.errorUnionSet(func_ty_info.return_type);
break :full try pt.errorUnionType(.fromInterned(set), bare_ty);
} else bare_ty;
if (!full_ty.isValidReturnType(zcu)) {
const opaque_str = if (full_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
return sema.fail(block, func_ret_ty_src, "{s}return type '{f}' not allowed", .{
opaque_str, full_ty.fmt(pt),
});
}
break :ret_ty full_ty;
};
// If we've discovered after evaluating arguments that a generic function instantiation is
// comptime-only, then we can mark the block as comptime *now*.
if (!inline_requested and !block.isComptime() and try resolved_ret_ty.comptimeOnlySema(pt)) {
block.comptime_reason = .{
.reason = .{
.src = call_src,
.r = .{
.comptime_only_ret_ty = .{
.ty = resolved_ret_ty,
.is_generic_inst = true,
.ret_ty_src = func_ret_ty_src,
},
},
},
};
}
if (call_dbg_node) |some| try sema.zirDbgStmt(block, some);
const is_inline_call = block.isComptime() or inline_requested;
if (!is_inline_call) {
if (sema.func_is_naked) return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(call_src, "runtime {s} not allowed in naked function", .{@tagName(operation)});
errdefer msg.destroy(gpa);
switch (operation) {
.call, .@"@call", .@"@panic", .@"error return" => {},
.@"safety check" => try sema.errNote(call_src, msg, "use @setRuntimeSafety to disable runtime safety", .{}),
}
break :msg msg;
});
if (func_ty_info.cc == .auto) {
switch (sema.owner.unwrap()) {
.@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
.func => |owner_func| ip.funcSetHasErrorTrace(owner_func, true),
}
}
for (args, 0..) |arg, arg_idx| {
try sema.validateRuntimeValue(block, args_info.argSrc(block, arg_idx), arg);
}
const runtime_func: Air.Inst.Ref, const runtime_args: []const Air.Inst.Ref = func: {
if (!func_ty_info.is_generic) break :func .{ callee, args };
// Instantiate the generic function!
// This may be an overestimate, but it's definitely sufficient.
const max_runtime_args = args_info.count() - @popCount(func_ty_info.comptime_bits);
var runtime_args: std.ArrayList(Air.Inst.Ref) = try .initCapacity(arena, max_runtime_args);
var runtime_param_tys: std.ArrayList(InternPool.Index) = try .initCapacity(arena, max_runtime_args);
const comptime_args = try arena.alloc(InternPool.Index, args_info.count());
var noalias_bits: u32 = 0;
for (args, comptime_args, 0..) |arg, *comptime_arg, arg_idx| {
const arg_ty = sema.typeOf(arg);
const is_comptime = c: {
if (std.math.cast(u5, arg_idx)) |i| {
if (func_ty_info.paramIsComptime(i)) {
break :c true;
}
}
break :c try arg_ty.comptimeOnlySema(pt);
};
const is_noalias = if (std.math.cast(u5, arg_idx)) |i| func_ty_info.paramIsNoalias(i) else false;
if (is_comptime) {
// We already emitted an error if the argument isn't comptime-known.
comptime_arg.* = (try sema.resolveValue(arg)).?.toIntern();
} else {
comptime_arg.* = .none;
if (is_noalias) {
const runtime_idx = runtime_args.items.len;
noalias_bits |= @as(u32, 1) << @intCast(runtime_idx);
}
runtime_args.appendAssumeCapacity(arg);
runtime_param_tys.appendAssumeCapacity(arg_ty.toIntern());
}
}
const bare_ret_ty = if (fn_zir_info.inferred_error_set) t: {
break :t resolved_ret_ty.errorUnionPayload(zcu);
} else resolved_ret_ty;
// We now need to actually create the function instance.
const func_instance = try ip.getFuncInstance(gpa, pt.tid, .{
.param_types = runtime_param_tys.items,
.noalias_bits = noalias_bits,
.bare_return_type = bare_ret_ty.toIntern(),
.is_noinline = func_ty_info.is_noinline,
.inferred_error_set = fn_zir_info.inferred_error_set,
.generic_owner = func_val.?.toIntern(),
.comptime_args = comptime_args,
});
if (zcu.comp.debugIncremental()) {
const nav = ip.indexToKey(func_instance).func.owner_nav;
const gop = try zcu.incremental_debug_state.navs.getOrPut(gpa, nav);
if (!gop.found_existing) gop.value_ptr.* = zcu.generation;
}
// This call is problematic as it breaks guarantees about order-independency of semantic analysis.
// These guarantees are necessary for incremental compilation and parallel semantic analysis.
// See: #22410
zcu.funcInfo(func_instance).maxBranchQuota(ip, sema.branch_quota);
break :func .{ Air.internedToRef(func_instance), runtime_args.items };
};
ref_func: {
const runtime_func_val = try sema.resolveValue(runtime_func) orelse break :ref_func;
if (!ip.isFuncBody(runtime_func_val.toIntern())) break :ref_func;
const orig_fn_index = ip.unwrapCoercedFunc(runtime_func_val.toIntern());
try sema.addReferenceEntry(block, call_src, .wrap(.{ .func = orig_fn_index }));
try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
}
const call_tag: Air.Inst.Tag = switch (modifier) {
.auto, .no_suspend => .call,
.never_tail => .call_never_tail,
.never_inline => .call_never_inline,
.always_tail => .call_always_tail,
.always_inline,
.compile_time,
=> unreachable,
};
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).@"struct".fields.len + runtime_args.len);
const maybe_opv = try block.addInst(.{
.tag = call_tag,
.data = .{ .pl_op = .{
.operand = runtime_func,
.payload = sema.addExtraAssumeCapacity(Air.Call{
.args_len = @intCast(runtime_args.len),
}),
} },
});
sema.appendRefsAssumeCapacity(runtime_args);
if (ensure_result_used) {
try sema.ensureResultUsed(block, sema.typeOf(maybe_opv), call_src);
}
if (call_tag == .call_always_tail) {
const func_or_ptr_ty = sema.typeOf(runtime_func);
const runtime_func_ty = switch (func_or_ptr_ty.zigTypeTag(zcu)) {
.@"fn" => func_or_ptr_ty,
.pointer => func_or_ptr_ty.childType(zcu),
else => unreachable,
};
return sema.handleTailCall(block, call_src, runtime_func_ty, maybe_opv);
}
if (ip.isNoReturn(resolved_ret_ty.toIntern())) {
const want_check = c: {
if (!block.wantSafety()) break :c false;
if (func_val != null) break :c false;
break :c true;
};
if (want_check) {
try sema.safetyPanic(block, call_src, .noreturn_returned);
} else {
_ = try block.addNoOp(.unreach);
}
return .unreachable_value;
}
const result: Air.Inst.Ref = if (try sema.typeHasOnePossibleValue(sema.typeOf(maybe_opv))) |opv|
.fromValue(opv)
else
maybe_opv;
return result;
}
// This is an inline call. The function must be comptime-known. We will analyze its body directly using this `Sema`.
if (zcu.comp.time_report) |*tr| {
if (!block.isComptime()) {
tr.stats.n_inline_calls += 1;
}
}
if (func_ty_info.is_noinline and !block.isComptime()) {
return sema.fail(block, call_src, "inline call of noinline function", .{});
}
const call_type: []const u8 = if (block.isComptime()) "comptime" else "inline";
if (modifier == .never_inline) {
const msg, const fail_block = msg: {
const msg = try sema.errMsg(call_src, "cannot perform {s} call with 'never_inline' modifier", .{call_type});
errdefer msg.destroy(gpa);
const fail_block = if (block.isComptime()) b: {
break :b try block.explainWhyBlockIsComptime(msg);
} else block;
break :msg .{ msg, fail_block };
};
return sema.failWithOwnedErrorMsg(fail_block, msg);
}
if (func_ty_info.is_var_args) {
const msg, const fail_block = msg: {
const msg = try sema.errMsg(call_src, "{s} call of variadic function", .{call_type});
errdefer msg.destroy(gpa);
const fail_block = if (block.isComptime()) b: {
break :b try block.explainWhyBlockIsComptime(msg);
} else block;
break :msg .{ msg, fail_block };
};
return sema.failWithOwnedErrorMsg(fail_block, msg);
}
if (func_val == null) {
if (func_is_extern) {
const msg, const fail_block = msg: {
const msg = try sema.errMsg(call_src, "{s} call of extern function", .{call_type});
errdefer msg.destroy(gpa);
const fail_block = if (block.isComptime()) b: {
break :b try block.explainWhyBlockIsComptime(msg);
} else block;
break :msg .{ msg, fail_block };
};
return sema.failWithOwnedErrorMsg(fail_block, msg);
}
return sema.failWithNeededComptime(
block,
func_src,
if (block.isComptime()) null else .{ .simple = .inline_call_target },
);
}
if (block.isComptime()) {
for (args, 0..) |arg, arg_idx| {
if (!try sema.isComptimeKnown(arg)) {
const arg_src = args_info.argSrc(block, arg_idx);
return sema.failWithNeededComptime(block, arg_src, null);
}
}
}
// For an inline call, we depend on the source code of the whole function definition.
try sema.declareDependency(.{ .src_hash = fn_nav.analysis.?.zir_index });
try sema.emitBackwardBranch(block, call_src);
const want_memoize = m: {
// TODO: comptime call memoization is currently not supported under incremental compilation
// since dependencies are not marked on callers. If we want to keep this around (we should
// check that it's worthwhile first!), each memoized call needs an `AnalUnit`.
if (zcu.comp.config.incremental) break :m false;
if (!block.isComptime()) break :m false;
for (args) |a| {
const val = (try sema.resolveValue(a)).?;
if (val.canMutateComptimeVarState(zcu)) break :m false;
}
break :m true;
};
const memoized_arg_values: []const InternPool.Index = if (want_memoize) arg_vals: {
const vals = try sema.arena.alloc(InternPool.Index, args.len);
for (vals, args) |*v, a| v.* = (try sema.resolveValue(a)).?.toIntern();
break :arg_vals vals;
} else undefined;
if (want_memoize) memoize: {
const memoized_call_index = ip.getIfExists(.{
.memoized_call = .{
.func = func_val.?.toIntern(),
.arg_values = memoized_arg_values,
.result = undefined, // ignored by hash+eql
.branch_count = undefined, // ignored by hash+eql
},
}) orelse break :memoize;
const memoized_call = ip.indexToKey(memoized_call_index).memoized_call;
if (sema.branch_count + memoized_call.branch_count > sema.branch_quota) {
// Let the call play out se we get the correct source location for the
// "evaluation exceeded X backwards branches" error.
break :memoize;
}
sema.branch_count += memoized_call.branch_count;
const result = Air.internedToRef(memoized_call.result);
if (ensure_result_used) {
try sema.ensureResultUsed(block, sema.typeOf(result), call_src);
}
return result;
}
var new_ies: InferredErrorSet = .{ .func = .none };
const old_inst_map = sema.inst_map;
const old_code = sema.code;
const old_func_index = sema.func_index;
const old_fn_ret_ty = sema.fn_ret_ty;
const old_fn_ret_ty_ies = sema.fn_ret_ty_ies;
const old_error_return_trace_index_on_fn_entry = sema.error_return_trace_index_on_fn_entry;
defer {
sema.inst_map.deinit(gpa);
sema.inst_map = old_inst_map;
sema.code = old_code;
sema.func_index = old_func_index;
sema.fn_ret_ty = old_fn_ret_ty;
sema.fn_ret_ty_ies = old_fn_ret_ty_ies;
sema.error_return_trace_index_on_fn_entry = old_error_return_trace_index_on_fn_entry;
}
sema.inst_map = .{};
sema.code = fn_zir;
sema.func_index = func_val.?.toIntern();
sema.fn_ret_ty = if (fn_zir_info.inferred_error_set) try pt.errorUnionType(
.fromInterned(.adhoc_inferred_error_set_type),
resolved_ret_ty.errorUnionPayload(zcu),
) else resolved_ret_ty;
sema.fn_ret_ty_ies = if (fn_zir_info.inferred_error_set) &new_ies else null;
try sema.inst_map.ensureSpaceForInstructions(gpa, fn_zir_info.param_body);
for (args, 0..) |arg, arg_idx| {
sema.inst_map.putAssumeCapacityNoClobber(fn_zir_info.param_body[arg_idx], arg);
}
const need_debug_scope = !block.isComptime() and !block.is_typeof and !block.ownerModule().strip;
const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{
.tag = if (need_debug_scope) .dbg_inline_block else .block,
.data = undefined,
});
var inlining: Block.Inlining = .{
.call_block = block,
.call_src = call_src,
.func = func_val.?.toIntern(),
.is_generic_instantiation = false,
.has_comptime_args = for (args) |a| {
if (try sema.isComptimeKnown(a)) break true;
} else false,
.comptime_result = undefined,
.merges = .{
.block_inst = block_inst,
.results = .empty,
.br_list = .empty,
.src_locs = .empty,
},
};
var child_block: Block = .{
.parent = null,
.sema = sema,
.namespace = fn_nav.analysis.?.namespace,
.instructions = .{},
.inlining = &inlining,
.is_typeof = block.is_typeof,
.comptime_reason = if (block.isComptime()) .inlining_parent else null,
.error_return_trace_index = block.error_return_trace_index,
.runtime_cond = block.runtime_cond,
.runtime_loop = block.runtime_loop,
.runtime_index = block.runtime_index,
.src_base_inst = fn_nav.analysis.?.zir_index,
.type_name_ctx = fn_nav.fqn,
};
defer child_block.instructions.deinit(gpa);
defer inlining.merges.deinit(gpa);
if (!inlining.has_comptime_args) {
var block_it = block;
while (block_it.inlining) |parent_inlining| {
if (!parent_inlining.is_generic_instantiation and
!parent_inlining.has_comptime_args and
parent_inlining.func == func_val.?.toIntern())
{
return sema.fail(block, call_src, "inline call is recursive", .{});
}
block_it = parent_inlining.call_block;
}
}
if (!block.isComptime() and !block.is_typeof) {
const zir_tags = sema.code.instructions.items(.tag);
const zir_datas = sema.code.instructions.items(.data);
for (fn_zir_info.param_body) |inst| switch (zir_tags[@intFromEnum(inst)]) {
.param, .param_comptime => {
const extra = sema.code.extraData(Zir.Inst.Param, zir_datas[@intFromEnum(inst)].pl_tok.payload_index);
const param_name = sema.code.nullTerminatedString(extra.data.name);
const air_inst = sema.inst_map.get(inst).?;
try sema.addDbgVar(&child_block, air_inst, .dbg_arg_inline, param_name);
},
.param_anytype, .param_anytype_comptime => {
const param_name = zir_datas[@intFromEnum(inst)].str_tok.get(sema.code);
const air_inst = sema.inst_map.get(inst).?;
try sema.addDbgVar(&child_block, air_inst, .dbg_arg_inline, param_name);
},
else => {},
};
}
child_block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(&child_block);
// Save the error trace as our first action in the function
// to match the behavior of runtime function calls.
const error_return_trace_index_on_parent_fn_entry = sema.error_return_trace_index_on_fn_entry;
sema.error_return_trace_index_on_fn_entry = child_block.error_return_trace_index;
defer sema.error_return_trace_index_on_fn_entry = error_return_trace_index_on_parent_fn_entry;
// We temporarily set `allow_memoize` to `true` to track this comptime call.
// It is restored after the call finishes analysis, so that a caller may
// know whether an in-progress call (containing this call) may be memoized.
const old_allow_memoize = sema.allow_memoize;
defer sema.allow_memoize = old_allow_memoize and sema.allow_memoize;
sema.allow_memoize = true;
// Store the current eval branch count so we can find out how many eval branches
// the comptime call caused.
const old_branch_count = sema.branch_count;
const result_raw: Air.Inst.Ref = result: {
sema.analyzeFnBody(&child_block, fn_zir_info.body) catch |err| switch (err) {
error.ComptimeReturn => break :result inlining.comptime_result,
else => |e| return e,
};
break :result try sema.resolveAnalyzedBlock(block, call_src, &child_block, &inlining.merges, need_debug_scope);
};
const maybe_opv: Air.Inst.Ref = if (try sema.resolveValue(result_raw)) |result_val| r: {
const val_resolved = try sema.resolveAdHocInferredErrorSet(block, call_src, result_val.toIntern());
break :r Air.internedToRef(val_resolved);
} else r: {
const resolved_ty = try sema.resolveAdHocInferredErrorSetTy(block, call_src, sema.typeOf(result_raw).toIntern());
if (resolved_ty == .none) break :r result_raw;
// TODO: mutate in place the previous instruction if possible
// rather than adding a bitcast instruction.
break :r try block.addBitCast(.fromInterned(resolved_ty), result_raw);
};
if (block.isComptime()) {
const result_val = (try sema.resolveValue(maybe_opv)).?;
if (want_memoize and sema.allow_memoize and !result_val.canMutateComptimeVarState(zcu)) {
_ = try pt.intern(.{ .memoized_call = .{
.func = func_val.?.toIntern(),
.arg_values = memoized_arg_values,
.result = result_val.toIntern(),
.branch_count = sema.branch_count - old_branch_count,
} });
}
}
if (ensure_result_used) {
try sema.ensureResultUsed(block, sema.typeOf(maybe_opv), call_src);
}
return maybe_opv;
}
fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const target = zcu.getTarget();
const backend = zcu.comp.getZigBackend();
if (!target_util.supportsTailCall(target, backend)) {
return sema.fail(block, call_src, "unable to perform tail call: compiler backend '{s}' does not support tail calls on target architecture '{s}' with the selected CPU feature flags", .{
@tagName(backend), @tagName(target.cpu.arch),
});
}
const owner_func_ty: Type = .fromInterned(zcu.funcInfo(sema.owner.unwrap().func).ty);
if (owner_func_ty.toIntern() != func_ty.toIntern()) {
return sema.fail(block, call_src, "unable to perform tail call: type of function being called '{f}' does not match type of calling function '{f}'", .{
func_ty.fmt(pt), owner_func_ty.fmt(pt),
});
}
_ = try block.addUnOp(.ret, result);
return .unreachable_value;
}
fn zirIntType(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const int_type = sema.code.instructions.items(.data)[@intFromEnum(inst)].int_type;
const ty = try sema.pt.intType(int_type.signedness, int_type.bit_count);
return Air.internedToRef(ty.toIntern());
}
fn zirOptionalType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.src(.{ .node_offset_un_op = inst_data.src_node });
const child_type = try sema.resolveType(block, operand_src, inst_data.operand);
if (child_type.zigTypeTag(zcu) == .@"opaque") {
return sema.fail(block, operand_src, "opaque type '{f}' cannot be optional", .{child_type.fmt(pt)});
} else if (child_type.zigTypeTag(zcu) == .null) {
return sema.fail(block, operand_src, "type '{f}' cannot be optional", .{child_type.fmt(pt)});
}
const opt_type = try pt.optionalType(child_type.toIntern());
return Air.internedToRef(opt_type.toIntern());
}
fn zirArrayInitElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const bin = sema.code.instructions.items(.data)[@intFromEnum(inst)].bin;
const maybe_wrapped_indexable_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, bin.lhs) orelse return .generic_poison_type;
const indexable_ty = maybe_wrapped_indexable_ty.optEuBaseType(zcu);
try indexable_ty.resolveFields(pt);
assert(indexable_ty.isIndexable(zcu)); // validated by a previous instruction
if (indexable_ty.zigTypeTag(zcu) == .@"struct") {
const elem_type = indexable_ty.fieldType(@intFromEnum(bin.rhs), zcu);
return Air.internedToRef(elem_type.toIntern());
} else {
const elem_type = indexable_ty.elemType2(zcu);
return Air.internedToRef(elem_type.toIntern());
}
}
fn zirElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const maybe_wrapped_ptr_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, un_node.operand) orelse return .generic_poison_type;
const ptr_ty = maybe_wrapped_ptr_ty.optEuBaseType(zcu);
assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
const elem_ty = ptr_ty.childType(zcu);
if (elem_ty.toIntern() == .anyopaque_type) {
// The pointer's actual child type is effectively unknown, so it makes
// sense to represent it with a generic poison.
return .generic_poison_type;
}
return Air.internedToRef(ptr_ty.childType(zcu).toIntern());
}
fn zirIndexablePtrElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(un_node.src_node);
const ptr_ty = try sema.resolveTypeOrPoison(block, src, un_node.operand) orelse return .generic_poison_type;
try sema.checkMemOperand(block, src, ptr_ty);
const elem_ty = switch (ptr_ty.ptrSize(zcu)) {
.slice, .many, .c => ptr_ty.childType(zcu),
.one => ptr_ty.childType(zcu).childType(zcu),
};
return Air.internedToRef(elem_ty.toIntern());
}
fn zirSplatOpResultType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const un_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const raw_ty = try sema.resolveTypeOrPoison(block, LazySrcLoc.unneeded, un_node.operand) orelse return .generic_poison_type;
const vec_ty = raw_ty.optEuBaseType(zcu);
switch (vec_ty.zigTypeTag(zcu)) {
.array, .vector => {},
else => return sema.fail(block, block.nodeOffset(un_node.src_node), "expected array or vector type, found '{f}'", .{vec_ty.fmt(pt)}),
}
return Air.internedToRef(vec_ty.childType(zcu).toIntern());
}
fn zirVectorType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const len_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const elem_type_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const len: u32 = @intCast(try sema.resolveInt(block, len_src, extra.lhs, .u32, .{ .simple = .vector_length }));
const elem_type = try sema.resolveType(block, elem_type_src, extra.rhs);
try sema.checkVectorElemType(block, elem_type_src, elem_type);
const vector_type = try sema.pt.vectorType(.{
.len = len,
.child = elem_type.toIntern(),
});
return Air.internedToRef(vector_type.toIntern());
}
fn zirArrayType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const len_src = block.src(.{ .node_offset_array_type_len = inst_data.src_node });
const elem_src = block.src(.{ .node_offset_array_type_elem = inst_data.src_node });
const len = try sema.resolveInt(block, len_src, extra.lhs, .usize, .{ .simple = .array_length });
const elem_type = try sema.resolveType(block, elem_src, extra.rhs);
try sema.validateArrayElemType(block, elem_type, elem_src);
const array_ty = try sema.pt.arrayType(.{
.len = len,
.child = elem_type.toIntern(),
});
return Air.internedToRef(array_ty.toIntern());
}
fn zirArrayTypeSentinel(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.ArrayTypeSentinel, inst_data.payload_index).data;
const len_src = block.src(.{ .node_offset_array_type_len = inst_data.src_node });
const sentinel_src = block.src(.{ .node_offset_array_type_sentinel = inst_data.src_node });
const elem_src = block.src(.{ .node_offset_array_type_elem = inst_data.src_node });
const len = try sema.resolveInt(block, len_src, extra.len, .usize, .{ .simple = .array_length });
const elem_type = try sema.resolveType(block, elem_src, extra.elem_type);
try sema.validateArrayElemType(block, elem_type, elem_src);
const uncasted_sentinel = try sema.resolveInst(extra.sentinel);
const sentinel = try sema.coerce(block, elem_type, uncasted_sentinel, sentinel_src);
const sentinel_val = try sema.resolveConstDefinedValue(block, sentinel_src, sentinel, .{ .simple = .array_sentinel });
if (sentinel_val.canMutateComptimeVarState(zcu)) {
const sentinel_name = try ip.getOrPutString(sema.gpa, pt.tid, "sentinel", .no_embedded_nulls);
return sema.failWithContainsReferenceToComptimeVar(block, sentinel_src, sentinel_name, "sentinel", sentinel_val);
}
const array_ty = try pt.arrayType(.{
.len = len,
.sentinel = sentinel_val.toIntern(),
.child = elem_type.toIntern(),
});
try sema.checkSentinelType(block, sentinel_src, elem_type);
return Air.internedToRef(array_ty.toIntern());
}
fn validateArrayElemType(sema: *Sema, block: *Block, elem_type: Type, elem_src: LazySrcLoc) !void {
const pt = sema.pt;
const zcu = pt.zcu;
if (elem_type.zigTypeTag(zcu) == .@"opaque") {
return sema.fail(block, elem_src, "array of opaque type '{f}' not allowed", .{elem_type.fmt(pt)});
} else if (elem_type.zigTypeTag(zcu) == .noreturn) {
return sema.fail(block, elem_src, "array of 'noreturn' not allowed", .{});
}
}
fn zirAnyframeType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
if (true) {
return sema.failWithUseOfAsync(block, block.nodeOffset(inst_data.src_node));
}
const zcu = sema.zcu;
const operand_src = block.src(.{ .node_offset_anyframe_type = inst_data.src_node });
const return_type = try sema.resolveType(block, operand_src, inst_data.operand);
const anyframe_type = try zcu.anyframeType(return_type);
return Air.internedToRef(anyframe_type.toIntern());
}
fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const error_set = try sema.resolveType(block, lhs_src, extra.lhs);
const payload = try sema.resolveType(block, rhs_src, extra.rhs);
if (error_set.zigTypeTag(zcu) != .error_set) {
return sema.fail(block, lhs_src, "expected error set type, found '{f}'", .{
error_set.fmt(pt),
});
}
try sema.validateErrorUnionPayloadType(block, payload, rhs_src);
const err_union_ty = try pt.errorUnionType(error_set, payload);
return Air.internedToRef(err_union_ty.toIntern());
}
fn validateErrorUnionPayloadType(sema: *Sema, block: *Block, payload_ty: Type, payload_src: LazySrcLoc) !void {
const pt = sema.pt;
const zcu = pt.zcu;
if (payload_ty.zigTypeTag(zcu) == .@"opaque") {
return sema.fail(block, payload_src, "error union with payload of opaque type '{f}' not allowed", .{
payload_ty.fmt(pt),
});
} else if (payload_ty.zigTypeTag(zcu) == .error_set) {
return sema.fail(block, payload_src, "error union with payload of error set type '{f}' not allowed", .{
payload_ty.fmt(pt),
});
}
}
fn zirErrorValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const pt = sema.pt;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const name = try pt.zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
inst_data.get(sema.code),
.no_embedded_nulls,
);
_ = try pt.getErrorValue(name);
// Create an error set type with only this error value, and return the value.
const error_set_type = try pt.singleErrorSetType(name);
return Air.internedToRef((try pt.intern(.{ .err = .{
.ty = error_set_type.toIntern(),
.name = name,
} })));
}
fn zirIntFromError(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const operand_src = block.builtinCallArgSrc(extra.node, 0);
const uncasted_operand = try sema.resolveInst(extra.operand);
const operand = try sema.coerce(block, .anyerror, uncasted_operand, operand_src);
const err_int_ty = try pt.errorIntType();
if (try sema.resolveValue(operand)) |val| {
if (val.isUndef(zcu)) {
return pt.undefRef(err_int_ty);
}
const err_name = ip.indexToKey(val.toIntern()).err.name;
return Air.internedToRef((try pt.intValue(
err_int_ty,
try pt.getErrorValue(err_name),
)).toIntern());
}
const op_ty = sema.typeOf(uncasted_operand);
switch (try sema.resolveInferredErrorSetTy(block, src, op_ty.toIntern())) {
.anyerror_type => {},
else => |err_set_ty_index| {
const names = ip.indexToKey(err_set_ty_index).error_set_type.names;
switch (names.len) {
0 => return Air.internedToRef((try pt.intValue(err_int_ty, 0)).toIntern()),
1 => return pt.intRef(err_int_ty, ip.getErrorValueIfExists(names.get(ip)[0]).?),
else => {},
}
},
}
try sema.requireRuntimeBlock(block, src, operand_src);
return block.addBitCast(err_int_ty, operand);
}
fn zirErrorFromInt(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const operand_src = block.builtinCallArgSrc(extra.node, 0);
const uncasted_operand = try sema.resolveInst(extra.operand);
const err_int_ty = try pt.errorIntType();
const operand = try sema.coerce(block, err_int_ty, uncasted_operand, operand_src);
if (try sema.resolveDefinedValue(block, operand_src, operand)) |value| {
const int = try sema.usizeCast(block, operand_src, try value.toUnsignedIntSema(pt));
if (int > len: {
const mutate = &ip.global_error_set.mutate;
mutate.map.mutex.lock();
defer mutate.map.mutex.unlock();
break :len mutate.names.len;
} or int == 0)
return sema.fail(block, operand_src, "integer value '{d}' represents no error", .{int});
return Air.internedToRef((try pt.intern(.{ .err = .{
.ty = .anyerror_type,
.name = ip.global_error_set.shared.names.acquire().view().items(.@"0")[int - 1],
} })));
}
try sema.requireRuntimeBlock(block, src, operand_src);
if (block.wantSafety()) {
const is_lt_len = try block.addUnOp(.cmp_lt_errors_len, operand);
const zero_val = Air.internedToRef((try pt.intValue(err_int_ty, 0)).toIntern());
const is_non_zero = try block.addBinOp(.cmp_neq, operand, zero_val);
const ok = try block.addBinOp(.bool_and, is_lt_len, is_non_zero);
try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
}
return block.addInst(.{
.tag = .bitcast,
.data = .{ .ty_op = .{
.ty = .anyerror_type,
.operand = operand,
} },
});
}
fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
if (sema.typeOf(lhs).zigTypeTag(zcu) == .bool and sema.typeOf(rhs).zigTypeTag(zcu) == .bool) {
const msg = msg: {
const msg = try sema.errMsg(lhs_src, "expected error set type, found 'bool'", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "'||' merges error sets; 'or' performs boolean OR", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const lhs_ty = try sema.analyzeAsType(block, lhs_src, lhs);
const rhs_ty = try sema.analyzeAsType(block, rhs_src, rhs);
if (lhs_ty.zigTypeTag(zcu) != .error_set)
return sema.fail(block, lhs_src, "expected error set type, found '{f}'", .{lhs_ty.fmt(pt)});
if (rhs_ty.zigTypeTag(zcu) != .error_set)
return sema.fail(block, rhs_src, "expected error set type, found '{f}'", .{rhs_ty.fmt(pt)});
// Anything merged with anyerror is anyerror.
if (lhs_ty.toIntern() == .anyerror_type or rhs_ty.toIntern() == .anyerror_type) {
return .anyerror_type;
}
if (ip.isInferredErrorSetType(lhs_ty.toIntern())) {
switch (try sema.resolveInferredErrorSet(block, src, lhs_ty.toIntern())) {
// isAnyError might have changed from a false negative to a true
// positive after resolution.
.anyerror_type => return .anyerror_type,
else => {},
}
}
if (ip.isInferredErrorSetType(rhs_ty.toIntern())) {
switch (try sema.resolveInferredErrorSet(block, src, rhs_ty.toIntern())) {
// isAnyError might have changed from a false negative to a true
// positive after resolution.
.anyerror_type => return .anyerror_type,
else => {},
}
}
const err_set_ty = try sema.errorSetMerge(lhs_ty, rhs_ty);
return Air.internedToRef(err_set_ty.toIntern());
}
fn zirEnumLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const name = inst_data.get(sema.code);
return Air.internedToRef((try pt.intern(.{
.enum_literal = try zcu.intern_pool.getOrPutString(sema.gpa, pt.tid, name, .no_embedded_nulls),
})));
}
fn zirDeclLiteral(sema: *Sema, block: *Block, inst: Zir.Inst.Index, do_coerce: bool) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
const name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
sema.code.nullTerminatedString(extra.field_name_start),
.no_embedded_nulls,
);
const orig_ty: Type = try sema.resolveTypeOrPoison(block, src, extra.lhs) orelse .generic_poison;
const uncoerced_result = res: {
if (orig_ty.toIntern() == .generic_poison_type) {
// Treat this as a normal enum literal.
break :res Air.internedToRef(try pt.intern(.{ .enum_literal = name }));
}
var ty = orig_ty;
while (true) switch (ty.zigTypeTag(zcu)) {
.error_union => ty = ty.errorUnionPayload(zcu),
.optional => ty = ty.optionalChild(zcu),
.pointer => ty = if (ty.isSinglePointer(zcu)) ty.childType(zcu) else break,
.enum_literal, .error_set => {
// Treat this as a normal enum literal.
break :res Air.internedToRef(try pt.intern(.{ .enum_literal = name }));
},
else => break,
};
break :res try sema.fieldVal(block, src, Air.internedToRef(ty.toIntern()), name, src);
};
// Decl literals cannot lookup runtime `var`s.
if (!try sema.isComptimeKnown(uncoerced_result)) {
return sema.fail(block, src, "decl literal must be comptime-known", .{});
}
if (do_coerce) {
return sema.coerce(block, orig_ty, uncoerced_result, src);
} else {
return uncoerced_result;
}
}
fn zirIntFromEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const enum_tag: Air.Inst.Ref = switch (operand_ty.zigTypeTag(zcu)) {
.@"enum" => operand,
.@"union" => blk: {
try operand_ty.resolveFields(pt);
const tag_ty = operand_ty.unionTagType(zcu) orelse {
return sema.fail(
block,
operand_src,
"untagged union '{f}' cannot be converted to integer",
.{operand_ty.fmt(pt)},
);
};
break :blk try sema.unionToTag(block, tag_ty, operand, operand_src);
},
else => {
return sema.fail(block, operand_src, "expected enum or tagged union, found '{f}'", .{
operand_ty.fmt(pt),
});
},
};
const enum_tag_ty = sema.typeOf(enum_tag);
const int_tag_ty = enum_tag_ty.intTagType(zcu);
// TODO: use correct solution
// https://github.com/ziglang/zig/issues/15909
if (enum_tag_ty.enumFieldCount(zcu) == 0 and !enum_tag_ty.isNonexhaustiveEnum(zcu)) {
return sema.fail(block, operand_src, "cannot use @intFromEnum on empty enum '{f}'", .{
enum_tag_ty.fmt(pt),
});
}
if (try sema.typeHasOnePossibleValue(enum_tag_ty)) |opv| {
return Air.internedToRef((try pt.getCoerced(opv, int_tag_ty)).toIntern());
}
if (try sema.resolveValue(enum_tag)) |enum_tag_val| {
if (enum_tag_val.isUndef(zcu)) {
return pt.undefRef(int_tag_ty);
}
const val = try enum_tag_val.intFromEnum(enum_tag_ty, pt);
return Air.internedToRef(val.toIntern());
}
try sema.requireRuntimeBlock(block, src, operand_src);
return block.addBitCast(int_tag_ty, enum_tag);
}
fn zirEnumFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@enumFromInt");
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
if (dest_ty.zigTypeTag(zcu) != .@"enum") {
return sema.fail(block, src, "expected enum, found '{f}'", .{dest_ty.fmt(pt)});
}
_ = try sema.checkIntType(block, operand_src, operand_ty);
if (try sema.resolveValue(operand)) |int_val| {
if (dest_ty.isNonexhaustiveEnum(zcu)) {
const int_tag_ty = dest_ty.intTagType(zcu);
if (try sema.intFitsInType(int_val, int_tag_ty, null)) {
return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern());
}
return sema.fail(block, src, "int value '{f}' out of range of non-exhaustive enum '{f}'", .{
int_val.fmtValueSema(pt, sema), dest_ty.fmt(pt),
});
}
if (int_val.isUndef(zcu)) {
return sema.failWithUseOfUndef(block, operand_src, null);
}
if (!(try sema.enumHasInt(dest_ty, int_val))) {
return sema.fail(block, src, "enum '{f}' has no tag with value '{f}'", .{
dest_ty.fmt(pt), int_val.fmtValueSema(pt, sema),
});
}
return Air.internedToRef((try pt.getCoerced(int_val, dest_ty)).toIntern());
}
if (dest_ty.intTagType(zcu).zigTypeTag(zcu) == .comptime_int) {
return sema.failWithNeededComptime(block, operand_src, .{ .simple = .casted_to_comptime_enum });
}
if (try sema.typeHasOnePossibleValue(dest_ty)) |opv| {
if (block.wantSafety()) {
// The operand is runtime-known but the result is comptime-known. In
// this case we still need a safety check.
const expect_int_val = switch (zcu.intern_pool.indexToKey(opv.toIntern())) {
.enum_tag => |enum_tag| enum_tag.int,
else => unreachable,
};
const expect_int_coerced = try pt.getCoerced(.fromInterned(expect_int_val), operand_ty);
const ok = try block.addBinOp(.cmp_eq, operand, Air.internedToRef(expect_int_coerced.toIntern()));
try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
}
return Air.internedToRef(opv.toIntern());
}
try sema.requireRuntimeBlock(block, src, operand_src);
if (block.wantSafety()) {
try sema.preparePanicId(src, .invalid_enum_value);
return block.addTyOp(.intcast_safe, dest_ty, operand);
}
return block.addTyOp(.intcast, dest_ty, operand);
}
/// Pointer in, pointer out.
fn zirOptionalPayloadPtr(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
safety_check: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const optional_ptr = try sema.resolveInst(inst_data.operand);
const src = block.nodeOffset(inst_data.src_node);
return sema.analyzeOptionalPayloadPtr(block, src, optional_ptr, safety_check, false);
}
fn analyzeOptionalPayloadPtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
optional_ptr: Air.Inst.Ref,
safety_check: bool,
initializing: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const optional_ptr_ty = sema.typeOf(optional_ptr);
assert(optional_ptr_ty.zigTypeTag(zcu) == .pointer);
const opt_type = optional_ptr_ty.childType(zcu);
if (opt_type.zigTypeTag(zcu) != .optional) {
return sema.failWithExpectedOptionalType(block, src, opt_type);
}
const child_type = opt_type.optionalChild(zcu);
const child_pointer = try pt.ptrTypeSema(.{
.child = child_type.toIntern(),
.flags = .{
.is_const = optional_ptr_ty.isConstPtr(zcu),
.address_space = optional_ptr_ty.ptrAddressSpace(zcu),
},
});
if (try sema.resolveDefinedValue(block, src, optional_ptr)) |ptr_val| {
if (initializing) {
if (sema.isComptimeMutablePtr(ptr_val)) {
// Set the optional to non-null at comptime.
// If the payload is OPV, we must use that value instead of undef.
const payload_val = try sema.typeHasOnePossibleValue(child_type) orelse try pt.undefValue(child_type);
const opt_val = try pt.intern(.{ .opt = .{
.ty = opt_type.toIntern(),
.val = payload_val.toIntern(),
} });
try sema.storePtrVal(block, src, ptr_val, Value.fromInterned(opt_val), opt_type);
} else {
// Emit runtime instructions to set the optional non-null bit.
const opt_payload_ptr = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr);
try sema.checkKnownAllocPtr(block, optional_ptr, opt_payload_ptr);
}
return Air.internedToRef((try ptr_val.ptrOptPayload(pt)).toIntern());
}
if (try sema.pointerDeref(block, src, ptr_val, optional_ptr_ty)) |val| {
if (val.isNull(zcu)) {
return sema.fail(block, src, "unable to unwrap null", .{});
}
return Air.internedToRef((try ptr_val.ptrOptPayload(pt)).toIntern());
}
}
try sema.requireRuntimeBlock(block, src, null);
if (safety_check and block.wantSafety()) {
const is_non_null = try block.addUnOp(.is_non_null_ptr, optional_ptr);
try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
}
if (initializing) {
const opt_payload_ptr = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr);
try sema.checkKnownAllocPtr(block, optional_ptr, opt_payload_ptr);
return opt_payload_ptr;
} else {
return block.addTyOp(.optional_payload_ptr, child_pointer, optional_ptr);
}
}
/// Value in, value out.
fn zirOptionalPayload(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
safety_check: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const result_ty = switch (operand_ty.zigTypeTag(zcu)) {
.optional => operand_ty.optionalChild(zcu),
.pointer => t: {
if (operand_ty.ptrSize(zcu) != .c) {
return sema.failWithExpectedOptionalType(block, src, operand_ty);
}
// TODO https://github.com/ziglang/zig/issues/6597
if (true) break :t operand_ty;
const ptr_info = operand_ty.ptrInfo(zcu);
break :t try pt.ptrTypeSema(.{
.child = ptr_info.child,
.flags = .{
.alignment = ptr_info.flags.alignment,
.is_const = ptr_info.flags.is_const,
.is_volatile = ptr_info.flags.is_volatile,
.is_allowzero = ptr_info.flags.is_allowzero,
.address_space = ptr_info.flags.address_space,
},
});
},
else => return sema.failWithExpectedOptionalType(block, src, operand_ty),
};
if (try sema.resolveDefinedValue(block, src, operand)) |val| {
if (val.optionalValue(zcu)) |payload| return Air.internedToRef(payload.toIntern());
if (block.isComptime()) return sema.fail(block, src, "unable to unwrap null", .{});
if (safety_check and block.wantSafety()) {
try sema.safetyPanic(block, src, .unwrap_null);
} else {
_ = try block.addNoOp(.unreach);
}
return .unreachable_value;
}
try sema.requireRuntimeBlock(block, src, null);
if (safety_check and block.wantSafety()) {
const is_non_null = try block.addUnOp(.is_non_null, operand);
try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
}
return block.addTyOp(.optional_payload, result_ty, operand);
}
/// Value in, value out
fn zirErrUnionPayload(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_src = src;
const err_union_ty = sema.typeOf(operand);
if (err_union_ty.zigTypeTag(zcu) != .error_union) {
return sema.fail(block, operand_src, "expected error union type, found '{f}'", .{
err_union_ty.fmt(pt),
});
}
return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, false);
}
fn analyzeErrUnionPayload(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
err_union_ty: Type,
operand: Air.Inst.Ref,
operand_src: LazySrcLoc,
safety_check: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const payload_ty = err_union_ty.errorUnionPayload(zcu);
if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| {
if (val.getErrorName(zcu).unwrap()) |name| {
return sema.failWithComptimeErrorRetTrace(block, src, name);
}
return Air.internedToRef(zcu.intern_pool.indexToKey(val.toIntern()).error_union.val.payload);
}
try sema.requireRuntimeBlock(block, src, null);
// If the error set has no fields then no safety check is needed.
if (safety_check and block.wantSafety() and
!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu))
{
try sema.addSafetyCheckUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err);
}
if (try sema.typeHasOnePossibleValue(payload_ty)) |payload_only_value| {
return Air.internedToRef(payload_only_value.toIntern());
}
return block.addTyOp(.unwrap_errunion_payload, payload_ty, operand);
}
/// Pointer in, pointer out.
fn zirErrUnionPayloadPtr(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand = try sema.resolveInst(inst_data.operand);
const src = block.nodeOffset(inst_data.src_node);
return sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false);
}
fn analyzeErrUnionPayloadPtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
safety_check: bool,
initializing: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
assert(operand_ty.zigTypeTag(zcu) == .pointer);
if (operand_ty.childType(zcu).zigTypeTag(zcu) != .error_union) {
return sema.fail(block, src, "expected error union type, found '{f}'", .{
operand_ty.childType(zcu).fmt(pt),
});
}
const err_union_ty = operand_ty.childType(zcu);
const payload_ty = err_union_ty.errorUnionPayload(zcu);
const operand_pointer_ty = try pt.ptrTypeSema(.{
.child = payload_ty.toIntern(),
.flags = .{
.is_const = operand_ty.isConstPtr(zcu),
.address_space = operand_ty.ptrAddressSpace(zcu),
},
});
if (try sema.resolveDefinedValue(block, src, operand)) |ptr_val| {
if (initializing) {
if (sema.isComptimeMutablePtr(ptr_val)) {
// Set the error union to non-error at comptime.
// If the payload is OPV, we must use that value instead of undef.
const payload_val = try sema.typeHasOnePossibleValue(payload_ty) orelse try pt.undefValue(payload_ty);
const eu_val = try pt.intern(.{ .error_union = .{
.ty = err_union_ty.toIntern(),
.val = .{ .payload = payload_val.toIntern() },
} });
try sema.storePtrVal(block, src, ptr_val, Value.fromInterned(eu_val), err_union_ty);
} else {
// Emit runtime instructions to set the error union error code.
try sema.requireRuntimeBlock(block, src, null);
const eu_payload_ptr = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand);
try sema.checkKnownAllocPtr(block, operand, eu_payload_ptr);
}
return Air.internedToRef((try ptr_val.ptrEuPayload(pt)).toIntern());
}
if (try sema.pointerDeref(block, src, ptr_val, operand_ty)) |val| {
if (val.getErrorName(zcu).unwrap()) |name| {
return sema.failWithComptimeErrorRetTrace(block, src, name);
}
return Air.internedToRef((try ptr_val.ptrEuPayload(pt)).toIntern());
}
}
try sema.requireRuntimeBlock(block, src, null);
// If the error set has no fields then no safety check is needed.
if (safety_check and block.wantSafety() and
!err_union_ty.errorUnionSet(zcu).errorSetIsEmpty(zcu))
{
try sema.addSafetyCheckUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr);
}
if (initializing) {
const eu_payload_ptr = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand);
try sema.checkKnownAllocPtr(block, operand, eu_payload_ptr);
return eu_payload_ptr;
} else {
return block.addTyOp(.unwrap_errunion_payload_ptr, operand_pointer_ty, operand);
}
}
/// Value in, value out
fn zirErrUnionCode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
return sema.analyzeErrUnionCode(block, src, operand);
}
/// If `operand` is comptime-known, asserts that it is an error value rather than a payload value.
fn analyzeErrUnionCode(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Inst.Ref) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
if (operand_ty.zigTypeTag(zcu) != .error_union) {
return sema.fail(block, src, "expected error union type, found '{f}'", .{
operand_ty.fmt(pt),
});
}
const result_ty = operand_ty.errorUnionSet(zcu);
if (try sema.resolveDefinedValue(block, src, operand)) |val| {
return Air.internedToRef((try pt.intern(.{ .err = .{
.ty = result_ty.toIntern(),
.name = zcu.intern_pool.indexToKey(val.toIntern()).error_union.val.err_name,
} })));
}
try sema.requireRuntimeBlock(block, src, null);
return block.addTyOp(.unwrap_errunion_err, result_ty, operand);
}
/// Pointer in, value out
fn zirErrUnionCodePtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
return sema.analyzeErrUnionCodePtr(block, src, operand);
}
fn analyzeErrUnionCodePtr(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Inst.Ref) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
assert(operand_ty.zigTypeTag(zcu) == .pointer);
if (operand_ty.childType(zcu).zigTypeTag(zcu) != .error_union) {
return sema.fail(block, src, "expected error union type, found '{f}'", .{
operand_ty.childType(zcu).fmt(pt),
});
}
const result_ty = operand_ty.childType(zcu).errorUnionSet(zcu);
if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
if (try sema.pointerDeref(block, src, pointer_val, operand_ty)) |val| {
assert(val.getErrorName(zcu) != .none);
return Air.internedToRef((try pt.intern(.{ .err = .{
.ty = result_ty.toIntern(),
.name = zcu.intern_pool.indexToKey(val.toIntern()).error_union.val.err_name,
} })));
}
}
try sema.requireRuntimeBlock(block, src, null);
return block.addTyOp(.unwrap_errunion_err_ptr, result_ty, operand);
}
fn zirFunc(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
inferred_error_set: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index);
const target = zcu.getTarget();
const ret_ty_src = block.src(.{ .node_offset_fn_type_ret_ty = inst_data.src_node });
const src = block.nodeOffset(inst_data.src_node);
var extra_index = extra.end;
const ret_ty: Type = if (extra.data.ret_ty.is_generic)
.generic_poison
else switch (extra.data.ret_ty.body_len) {
0 => .void,
1 => blk: {
const ret_ty_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
break :blk try sema.resolveType(block, ret_ty_src, ret_ty_ref);
},
else => blk: {
const ret_ty_body = sema.code.bodySlice(extra_index, extra.data.ret_ty.body_len);
extra_index += ret_ty_body.len;
const ret_ty_val = try sema.resolveGenericBody(block, ret_ty_src, ret_ty_body, inst, .type, .{ .simple = .fn_ret_ty });
break :blk ret_ty_val.toType();
},
};
var src_locs: Zir.Inst.Func.SrcLocs = undefined;
const has_body = extra.data.body_len != 0;
if (has_body) {
extra_index += extra.data.body_len;
src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data;
}
// If this instruction has a body, then it's a function declaration, and we decide
// the callconv based on whether it is exported. Otherwise, the callconv defaults
// to `.auto`.
const cc: std.builtin.CallingConvention = if (has_body) cc: {
const func_decl_nav = sema.owner.unwrap().nav_val;
const fn_is_exported = exported: {
const decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(ip) orelse return error.AnalysisFail;
const zir_decl = sema.code.getDeclaration(decl_inst);
break :exported zir_decl.linkage == .@"export";
};
if (fn_is_exported) {
break :cc target.cCallingConvention() orelse {
// This target has no default C calling convention. We sometimes trigger a similar
// error by trying to evaluate `std.builtin.CallingConvention.c`, so for consistency,
// let's eval that now and just get the transitive error. (It's guaranteed to error
// because it does the exact `cCallingConvention` call we just did.)
const cc_type = try sema.getBuiltinType(src, .CallingConvention);
_ = try sema.namespaceLookupVal(
block,
LazySrcLoc.unneeded,
cc_type.getNamespaceIndex(zcu),
try ip.getOrPutString(sema.gpa, pt.tid, "c", .no_embedded_nulls),
);
// The above should have errored.
@panic("std.builtin is corrupt");
};
} else {
break :cc .auto;
}
} else .auto;
return sema.funcCommon(
block,
inst_data.src_node,
inst,
cc,
ret_ty,
false,
inferred_error_set,
has_body,
src_locs,
0,
false,
);
}
fn resolveGenericBody(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
body: []const Zir.Inst.Index,
func_inst: Zir.Inst.Index,
dest_ty: Type,
reason: ComptimeReason,
) !Value {
assert(body.len != 0);
// Make sure any nested param instructions don't clobber our work.
const prev_params = block.params;
block.params = .{};
defer {
block.params = prev_params;
}
const uncasted = try sema.resolveInlineBody(block, body, func_inst);
const result = try sema.coerce(block, dest_ty, uncasted, src);
return sema.resolveConstDefinedValue(block, src, result, reason);
}
/// Given a library name, examines if the library name should end up in
/// `link.File.Options.windows_libs` table (for example, libc is always
/// specified via dedicated flag `link_libc` instead),
/// and puts it there if it doesn't exist.
/// It also dupes the library name which can then be saved as part of the
/// respective `Decl` (either `ExternFn` or `Var`).
/// The liveness of the duped library name is tied to liveness of `Zcu`.
/// To deallocate, call `deinit` on the respective `Decl` (`ExternFn` or `Var`).
pub fn handleExternLibName(
sema: *Sema,
block: *Block,
src_loc: LazySrcLoc,
lib_name: []const u8,
) CompileError!void {
blk: {
const pt = sema.pt;
const zcu = pt.zcu;
const comp = zcu.comp;
const target = zcu.getTarget();
log.debug("extern fn symbol expected in lib '{s}'", .{lib_name});
if (std.zig.target.isLibCLibName(target, lib_name)) {
if (!comp.config.link_libc) {
return sema.fail(
block,
src_loc,
"dependency on libc must be explicitly specified in the build command",
.{},
);
}
break :blk;
}
if (std.zig.target.isLibCxxLibName(target, lib_name)) {
if (!comp.config.link_libcpp) return sema.fail(
block,
src_loc,
"dependency on libc++ must be explicitly specified in the build command",
.{},
);
break :blk;
}
if (mem.eql(u8, lib_name, "unwind")) {
if (!comp.config.link_libunwind) return sema.fail(
block,
src_loc,
"dependency on libunwind must be explicitly specified in the build command",
.{},
);
break :blk;
}
if (!target.cpu.arch.isWasm() and !block.ownerModule().pic) {
return sema.fail(
block,
src_loc,
"dependency on dynamic library '{s}' requires enabling Position Independent Code; fixed by '-l{s}' or '-fPIC'",
.{ lib_name, lib_name },
);
}
comp.addLinkLib(lib_name) catch |err| {
return sema.fail(block, src_loc, "unable to add link lib '{s}': {s}", .{
lib_name, @errorName(err),
});
};
}
}
/// These are calling conventions that are confirmed to work with variadic functions.
/// Any calling conventions not included here are either not yet verified to work with variadic
/// functions or there are no more other calling conventions that support variadic functions.
const calling_conventions_supporting_var_args = [_]std.builtin.CallingConvention.Tag{
.x86_16_cdecl,
.x86_64_sysv,
.x86_64_x32,
.x86_64_win,
.x86_sysv,
.x86_win,
.aarch64_aapcs,
.aarch64_aapcs_darwin,
.aarch64_aapcs_win,
.aarch64_vfabi,
.aarch64_vfabi_sve,
.alpha_osf,
.arm_aapcs,
.arm_aapcs_vfp,
.microblaze_std,
.mips64_n64,
.mips64_n32,
.mips_o32,
.riscv64_lp64,
.riscv64_lp64_v,
.riscv32_ilp32,
.riscv32_ilp32_v,
.sparc64_sysv,
.sparc_sysv,
.powerpc64_elf,
.powerpc64_elf_altivec,
.powerpc64_elf_v2,
.powerpc_sysv,
.powerpc_sysv_altivec,
.powerpc_aix,
.powerpc_aix_altivec,
.wasm_mvp,
.arc_sysv,
.avr_gnu,
.bpf_std,
.csky_sysv,
.hexagon_sysv,
.hexagon_sysv_hvx,
.hppa_elf,
.hppa64_elf,
.kvx_lp64,
.kvx_ilp32,
.lanai_sysv,
.loongarch64_lp64,
.loongarch32_ilp32,
.m68k_sysv,
.m68k_gnu,
.m68k_rtd,
.msp430_eabi,
.or1k_sysv,
.s390x_sysv,
.s390x_sysv_vx,
.sh_gnu,
.sh_renesas,
.ve_sysv,
.xcore_xs1,
.xcore_xs2,
.xtensa_call0,
.xtensa_windowed,
};
fn callConvSupportsVarArgs(cc: std.builtin.CallingConvention.Tag) bool {
return for (calling_conventions_supporting_var_args) |supported_cc| {
if (cc == supported_cc) return true;
} else false;
}
fn checkCallConvSupportsVarArgs(sema: *Sema, block: *Block, src: LazySrcLoc, cc: std.builtin.CallingConvention.Tag) CompileError!void {
const CallingConventionsSupportingVarArgsList = struct {
arch: std.Target.Cpu.Arch,
pub fn format(ctx: @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
var first = true;
for (calling_conventions_supporting_var_args) |cc_inner| {
for (std.Target.Cpu.Arch.fromCallingConvention(cc_inner)) |supported_arch| {
if (supported_arch == ctx.arch) break;
} else continue; // callconv not supported by this arch
if (!first) {
try w.writeAll(", ");
}
first = false;
try w.print("'{s}'", .{@tagName(cc_inner)});
}
}
};
if (!callConvSupportsVarArgs(cc)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "variadic function does not support '{s}' calling convention", .{@tagName(cc)});
errdefer msg.destroy(sema.gpa);
const target = sema.pt.zcu.getTarget();
try sema.errNote(src, msg, "supported calling conventions: {f}", .{CallingConventionsSupportingVarArgsList{ .arch = target.cpu.arch }});
break :msg msg;
});
}
}
fn checkParamTypeCommon(
sema: *Sema,
block: *Block,
param_idx: u32,
param_ty: Type,
param_is_noalias: bool,
param_src: LazySrcLoc,
cc: std.builtin.CallingConvention,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const target = zcu.getTarget();
if (!param_ty.isValidParamType(zcu)) {
const opaque_str = if (param_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
return sema.fail(block, param_src, "parameter of {s}type '{f}' not allowed", .{
opaque_str, param_ty.fmt(pt),
});
}
if (!param_ty.isGenericPoison() and
!target_util.fnCallConvAllowsZigTypes(cc) and
!try sema.validateExternType(param_ty, .param_ty))
{
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(param_src, "parameter of type '{f}' not allowed in function with calling convention '{s}'", .{
param_ty.fmt(pt), @tagName(cc),
});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, param_src, param_ty, .param_ty);
try sema.addDeclaredHereNote(msg, param_ty);
break :msg msg;
});
}
switch (cc) {
.x86_64_interrupt, .x86_interrupt => {
const err_code_size = target.ptrBitWidth();
switch (param_idx) {
0 => if (param_ty.zigTypeTag(zcu) != .pointer) return sema.fail(block, param_src, "first parameter of function with '{s}' calling convention must be a pointer type", .{@tagName(cc)}),
1 => if (param_ty.bitSize(zcu) != err_code_size) return sema.fail(block, param_src, "second parameter of function with '{s}' calling convention must be a {d}-bit integer", .{ @tagName(cc), err_code_size }),
else => return sema.fail(block, param_src, "'{s}' calling convention supports up to 2 parameters, found {d}", .{ @tagName(cc), param_idx + 1 }),
}
},
.arc_interrupt,
.arm_interrupt,
.microblaze_interrupt,
.mips64_interrupt,
.mips_interrupt,
.riscv64_interrupt,
.riscv32_interrupt,
.sh_interrupt,
.avr_interrupt,
.csky_interrupt,
.m68k_interrupt,
.msp430_interrupt,
.avr_signal,
=> return sema.fail(block, param_src, "parameters are not allowed with '{s}' calling convention", .{@tagName(cc)}),
else => {},
}
if (param_is_noalias and !param_ty.isGenericPoison() and !param_ty.isPtrAtRuntime(zcu) and !param_ty.isSliceAtRuntime(zcu)) {
return sema.fail(block, param_src, "non-pointer parameter declared noalias", .{});
}
}
fn checkReturnTypeAndCallConvCommon(
sema: *Sema,
block: *Block,
bare_ret_ty: Type,
ret_ty_src: LazySrcLoc,
@"callconv": std.builtin.CallingConvention,
callconv_src: LazySrcLoc,
/// non-`null` only if the function is varargs.
opt_varargs_src: ?LazySrcLoc,
inferred_error_set: bool,
is_noinline: bool,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
if (opt_varargs_src) |varargs_src| {
try sema.checkCallConvSupportsVarArgs(block, varargs_src, @"callconv");
}
if (inferred_error_set and !bare_ret_ty.isGenericPoison()) {
try sema.validateErrorUnionPayloadType(block, bare_ret_ty, ret_ty_src);
}
const ies_ret_ty_prefix: []const u8 = if (inferred_error_set) "!" else "";
if (!bare_ret_ty.isValidReturnType(zcu)) {
const opaque_str = if (bare_ret_ty.zigTypeTag(zcu) == .@"opaque") "opaque " else "";
return sema.fail(block, ret_ty_src, "{s}return type '{s}{f}' not allowed", .{
opaque_str, ies_ret_ty_prefix, bare_ret_ty.fmt(pt),
});
}
if (!bare_ret_ty.isGenericPoison() and
!target_util.fnCallConvAllowsZigTypes(@"callconv") and
(inferred_error_set or !try sema.validateExternType(bare_ret_ty, .ret_ty)))
{
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(ret_ty_src, "return type '{s}{f}' not allowed in function with calling convention '{s}'", .{
ies_ret_ty_prefix, bare_ret_ty.fmt(pt), @tagName(@"callconv"),
});
errdefer msg.destroy(gpa);
if (!inferred_error_set) {
try sema.explainWhyTypeIsNotExtern(msg, ret_ty_src, bare_ret_ty, .ret_ty);
try sema.addDeclaredHereNote(msg, bare_ret_ty);
}
break :msg msg;
});
}
validate_incoming_stack_align: {
const a: u64 = switch (@"callconv") {
inline else => |payload| if (@TypeOf(payload) != void and @hasField(@TypeOf(payload), "incoming_stack_alignment"))
payload.incoming_stack_alignment orelse break :validate_incoming_stack_align
else
break :validate_incoming_stack_align,
};
if (!std.math.isPowerOfTwo(a)) {
return sema.fail(block, callconv_src, "calling convention incoming stack alignment '{d}' is not a power of two", .{a});
}
}
switch (@"callconv") {
.x86_64_interrupt,
.x86_interrupt,
.arm_interrupt,
.mips64_interrupt,
.mips_interrupt,
.riscv64_interrupt,
.riscv32_interrupt,
.sh_interrupt,
.arc_interrupt,
.avr_interrupt,
.csky_interrupt,
.m68k_interrupt,
.microblaze_interrupt,
.msp430_interrupt,
.avr_signal,
=> {
const ret_ok = !inferred_error_set and switch (bare_ret_ty.toIntern()) {
.void_type, .noreturn_type => true,
else => false,
};
if (!ret_ok) {
return sema.fail(block, ret_ty_src, "function with calling convention '{s}' must return 'void' or 'noreturn'", .{@tagName(@"callconv")});
}
},
.@"inline" => if (is_noinline) {
return sema.fail(block, callconv_src, "'noinline' function cannot have calling convention 'inline'", .{});
},
else => {},
}
switch (zcu.callconvSupported(@"callconv")) {
.ok => {},
.bad_arch => |allowed_archs| {
const ArchListFormatter = struct {
archs: []const std.Target.Cpu.Arch,
pub fn format(formatter: @This(), w: *std.Io.Writer) std.Io.Writer.Error!void {
for (formatter.archs, 0..) |arch, i| {
if (i != 0)
try w.writeAll(", ");
try w.print("'{s}'", .{@tagName(arch)});
}
}
};
return sema.fail(block, callconv_src, "calling convention '{s}' only available on architectures {f}", .{
@tagName(@"callconv"),
ArchListFormatter{ .archs = allowed_archs },
});
},
.bad_backend => |bad_backend| return sema.fail(block, callconv_src, "calling convention '{s}' not supported by compiler backend '{s}'", .{
@tagName(@"callconv"),
@tagName(bad_backend),
}),
}
}
fn callConvIsCallable(cc: std.builtin.CallingConvention.Tag) bool {
return switch (cc) {
.naked,
.arc_interrupt,
.arm_interrupt,
.avr_interrupt,
.avr_signal,
.csky_interrupt,
.m68k_interrupt,
.microblaze_interrupt,
.mips_interrupt,
.mips64_interrupt,
.msp430_interrupt,
.riscv32_interrupt,
.riscv64_interrupt,
.sh_interrupt,
.x86_interrupt,
.x86_64_interrupt,
.amdgcn_kernel,
.nvptx_kernel,
.spirv_kernel,
.spirv_fragment,
.spirv_vertex,
=> false,
else => true,
};
}
fn checkMergeAllowed(sema: *Sema, block: *Block, src: LazySrcLoc, peer_ty: Type) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const target = zcu.getTarget();
if (!peer_ty.isPtrAtRuntime(zcu)) {
return;
}
const as = peer_ty.ptrAddressSpace(zcu);
if (!target_util.shouldBlockPointerOps(target, as)) {
return;
}
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "value with non-mergable pointer type '{f}' depends on runtime control flow", .{peer_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
const runtime_src = block.runtime_cond orelse block.runtime_loop.?;
try sema.errNote(runtime_src, msg, "runtime control flow here", .{});
const backend = target_util.zigBackend(target, zcu.comp.config.use_llvm);
try sema.errNote(src, msg, "pointers with address space '{s}' cannot be returned from a branch on target {s}-{s} by compiler backend {s}", .{
@tagName(as),
@tagName(target.cpu.arch.family()),
@tagName(target.os.tag),
@tagName(backend),
});
break :msg msg;
});
}
const Section = union(enum) {
generic,
default,
explicit: InternPool.NullTerminatedString,
};
fn funcCommon(
sema: *Sema,
block: *Block,
src_node_offset: std.zig.Ast.Node.Offset,
func_inst: Zir.Inst.Index,
cc: std.builtin.CallingConvention,
/// this might be Type.generic_poison
bare_return_type: Type,
var_args: bool,
inferred_error_set: bool,
has_body: bool,
src_locs: Zir.Inst.Func.SrcLocs,
noalias_bits: u32,
is_noinline: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const ret_ty_src = block.src(.{ .node_offset_fn_type_ret_ty = src_node_offset });
const cc_src = block.src(.{ .node_offset_fn_type_cc = src_node_offset });
const func_src = block.nodeOffset(src_node_offset);
const ret_ty_requires_comptime = try bare_return_type.comptimeOnlySema(pt);
var is_generic = bare_return_type.isGenericPoison() or ret_ty_requires_comptime;
var comptime_bits: u32 = 0;
for (block.params.items(.ty), block.params.items(.is_comptime), 0..) |param_ty_ip, param_is_comptime, i| {
const param_ty: Type = .fromInterned(param_ty_ip);
const is_noalias = blk: {
const index = std.math.cast(u5, i) orelse break :blk false;
break :blk @as(u1, @truncate(noalias_bits >> index)) != 0;
};
const param_src = block.src(.{ .fn_proto_param = .{
.fn_proto_node_offset = src_node_offset,
.param_index = @intCast(i),
} });
const param_ty_comptime = try param_ty.comptimeOnlySema(pt);
const param_ty_generic = param_ty.isGenericPoison();
if (param_is_comptime or param_ty_comptime or param_ty_generic) {
is_generic = true;
}
if (param_is_comptime) {
comptime_bits |= @as(u32, 1) << @intCast(i); // TODO: handle cast error
}
if (param_is_comptime and !target_util.fnCallConvAllowsZigTypes(cc)) {
return sema.fail(block, param_src, "comptime parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
}
if (param_ty_generic and !target_util.fnCallConvAllowsZigTypes(cc)) {
return sema.fail(block, param_src, "generic parameters not allowed in function with calling convention '{s}'", .{@tagName(cc)});
}
try sema.checkParamTypeCommon(
block,
@intCast(i),
param_ty,
is_noalias,
param_src,
cc,
);
if (param_ty_comptime and !param_is_comptime and has_body and !block.isComptime()) {
const msg = msg: {
const msg = try sema.errMsg(param_src, "parameter of type '{f}' must be declared comptime", .{
param_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(msg, param_src, param_ty);
try sema.addDeclaredHereNote(msg, param_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
if (var_args and is_generic) {
return sema.fail(block, func_src, "generic function cannot be variadic", .{});
}
try sema.checkReturnTypeAndCallConvCommon(
block,
bare_return_type,
ret_ty_src,
cc,
cc_src,
if (var_args) block.src(.{ .fn_proto_param = .{
.fn_proto_node_offset = src_node_offset,
.param_index = @intCast(block.params.len),
} }) else null,
inferred_error_set,
is_noinline,
);
// If the return type is comptime-only but not dependent on parameters then
// all parameter types also need to be comptime.
if (has_body and ret_ty_requires_comptime and !block.isComptime()) comptime_check: {
for (block.params.items(.is_comptime)) |is_comptime| {
if (!is_comptime) break;
} else break :comptime_check;
const ies_ret_ty_prefix: []const u8 = if (inferred_error_set) "!" else "";
const msg = try sema.errMsg(
ret_ty_src,
"function with comptime-only return type '{s}{f}' requires all parameters to be comptime",
.{ ies_ret_ty_prefix, bare_return_type.fmt(pt) },
);
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(msg, ret_ty_src, bare_return_type);
const tags = sema.code.instructions.items(.tag);
const data = sema.code.instructions.items(.data);
const param_body = sema.code.getParamBody(func_inst);
for (
block.params.items(.is_comptime),
block.params.items(.name),
param_body[0..block.params.len],
) |is_comptime, name_nts, param_index| {
if (!is_comptime) {
const param_src = block.tokenOffset(switch (tags[@intFromEnum(param_index)]) {
.param => data[@intFromEnum(param_index)].pl_tok.src_tok,
.param_anytype => data[@intFromEnum(param_index)].str_tok.src_tok,
else => unreachable,
});
const name = sema.code.nullTerminatedString(name_nts);
if (name.len != 0) {
try sema.errNote(param_src, msg, "param '{s}' is required to be comptime", .{name});
} else {
try sema.errNote(param_src, msg, "param is required to be comptime", .{});
}
}
}
return sema.failWithOwnedErrorMsg(block, msg);
}
const param_types = block.params.items(.ty);
if (inferred_error_set) {
assert(has_body);
return .fromIntern(try ip.getFuncDeclIes(gpa, pt.tid, .{
.owner_nav = sema.owner.unwrap().nav_val,
.param_types = param_types,
.noalias_bits = noalias_bits,
.comptime_bits = comptime_bits,
.bare_return_type = bare_return_type.toIntern(),
.cc = cc,
.is_var_args = var_args,
.is_generic = is_generic,
.is_noinline = is_noinline,
.zir_body_inst = try block.trackZir(func_inst),
.lbrace_line = src_locs.lbrace_line,
.rbrace_line = src_locs.rbrace_line,
.lbrace_column = @as(u16, @truncate(src_locs.columns)),
.rbrace_column = @as(u16, @truncate(src_locs.columns >> 16)),
}));
}
const func_ty = try ip.getFuncType(gpa, pt.tid, .{
.param_types = param_types,
.noalias_bits = noalias_bits,
.comptime_bits = comptime_bits,
.return_type = bare_return_type.toIntern(),
.cc = cc,
.is_var_args = var_args,
.is_generic = is_generic,
.is_noinline = is_noinline,
});
if (has_body) {
return .fromIntern(try ip.getFuncDecl(gpa, pt.tid, .{
.owner_nav = sema.owner.unwrap().nav_val,
.ty = func_ty,
.cc = cc,
.is_noinline = is_noinline,
.zir_body_inst = try block.trackZir(func_inst),
.lbrace_line = src_locs.lbrace_line,
.rbrace_line = src_locs.rbrace_line,
.lbrace_column = @as(u16, @truncate(src_locs.columns)),
.rbrace_column = @as(u16, @truncate(src_locs.columns >> 16)),
}));
}
return .fromIntern(func_ty);
}
fn zirParam(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
comptime_syntax: bool,
) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
const src = block.tokenOffset(inst_data.src_tok);
const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index);
const param_name: Zir.NullTerminatedString = extra.data.name;
const body = sema.code.bodySlice(extra.end, extra.data.type.body_len);
const param_ty: Type = if (extra.data.type.is_generic) .generic_poison else ty: {
// Make sure any nested param instructions don't clobber our work.
const prev_params = block.params;
block.params = .{};
defer {
block.params = prev_params;
}
const param_ty_inst = try sema.resolveInlineBody(block, body, inst);
break :ty try sema.analyzeAsType(block, src, param_ty_inst);
};
try block.params.append(sema.arena, .{
.ty = param_ty.toIntern(),
.is_comptime = comptime_syntax,
.name = param_name,
});
}
fn zirParamAnytype(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
comptime_syntax: bool,
) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const param_name: Zir.NullTerminatedString = inst_data.start;
try block.params.append(sema.arena, .{
.ty = .generic_poison_type,
.is_comptime = comptime_syntax,
.name = param_name,
});
}
fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data;
return sema.analyzeAs(block, src, extra.dest_type, extra.operand, false);
}
fn zirAsShiftOperand(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.As, inst_data.payload_index).data;
return sema.analyzeAs(block, src, extra.dest_type, extra.operand, true);
}
fn analyzeAs(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_dest_type: Zir.Inst.Ref,
zir_operand: Zir.Inst.Ref,
no_cast_to_comptime_int: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand = try sema.resolveInst(zir_operand);
const dest_ty = try sema.resolveTypeOrPoison(block, src, zir_dest_type) orelse return operand;
switch (dest_ty.zigTypeTag(zcu)) {
.@"opaque" => return sema.fail(block, src, "cannot cast to opaque type '{f}'", .{dest_ty.fmt(pt)}),
.noreturn => return sema.fail(block, src, "cannot cast to noreturn", .{}),
else => {},
}
const is_ret = if (zir_dest_type.toIndex()) |ptr_index|
sema.code.instructions.items(.tag)[@intFromEnum(ptr_index)] == .ret_type
else
false;
return sema.coerceExtra(block, dest_ty, operand, src, .{ .is_ret = is_ret, .no_cast_to_comptime_int = no_cast_to_comptime_int }) catch |err| switch (err) {
error.NotCoercible => unreachable,
else => |e| return e,
};
}
fn zirIntFromPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const ptr_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const ptr_ty = operand_ty.scalarType(zcu);
const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
if (!ptr_ty.isPtrAtRuntime(zcu)) {
return sema.fail(block, ptr_src, "expected pointer, found '{f}'", .{ptr_ty.fmt(pt)});
}
const pointee_ty = ptr_ty.childType(zcu);
if (try ptr_ty.comptimeOnlySema(pt)) {
const msg = msg: {
const msg = try sema.errMsg(ptr_src, "comptime-only type '{f}' has no pointer address", .{pointee_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(msg, ptr_src, pointee_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const len = if (is_vector) operand_ty.vectorLen(zcu) else undefined;
const dest_ty: Type = if (is_vector) try pt.vectorType(.{ .child = .usize_type, .len = len }) else .usize;
if (try sema.resolveValue(operand)) |operand_val| ct: {
if (!is_vector) {
if (operand_val.isUndef(zcu)) {
return .undef_usize;
}
const addr = try operand_val.getUnsignedIntSema(pt) orelse {
// Wasn't an integer pointer. This is a runtime operation.
break :ct;
};
return Air.internedToRef((try pt.intValue(
.usize,
addr,
)).toIntern());
}
const new_elems = try sema.arena.alloc(InternPool.Index, len);
for (new_elems, 0..) |*new_elem, i| {
const ptr_val = try operand_val.elemValue(pt, i);
if (ptr_val.isUndef(zcu)) {
new_elem.* = .undef_usize;
continue;
}
const addr = try ptr_val.getUnsignedIntSema(pt) orelse {
// A vector element wasn't an integer pointer. This is a runtime operation.
break :ct;
};
new_elem.* = (try pt.intValue(
.usize,
addr,
)).toIntern();
}
return Air.internedToRef((try pt.aggregateValue(dest_ty, new_elems)).toIntern());
}
try sema.requireRuntimeBlock(block, block.nodeOffset(inst_data.src_node), ptr_src);
try sema.validateRuntimeValue(block, ptr_src, operand);
try sema.checkLogicalPtrOperation(block, ptr_src, ptr_ty);
return block.addBitCast(dest_ty, operand);
}
fn zirFieldPtrLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const field_name_src = block.src(.{ .node_offset_field_name = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
const field_name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
sema.code.nullTerminatedString(extra.field_name_start),
.no_embedded_nulls,
);
const object_ptr = try sema.resolveInst(extra.lhs);
return fieldPtrLoad(sema, block, src, object_ptr, field_name, field_name_src);
}
fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const field_name_src = block.src(.{ .node_offset_field_name = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
const field_name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
sema.code.nullTerminatedString(extra.field_name_start),
.no_embedded_nulls,
);
const object_ptr = try sema.resolveInst(extra.lhs);
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false);
}
fn zirStructInitFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const field_name_src = block.src(.{ .node_offset_field_name_init = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data;
const field_name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
sema.code.nullTerminatedString(extra.field_name_start),
.no_embedded_nulls,
);
const object_ptr = try sema.resolveInst(extra.lhs);
const struct_ty = sema.typeOf(object_ptr).childType(zcu);
switch (struct_ty.zigTypeTag(zcu)) {
.@"struct", .@"union" => {
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, true);
},
else => {
return sema.failWithStructInitNotSupported(block, src, struct_ty);
},
}
}
fn zirFieldPtrNamedLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const field_name_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
const object_ptr = try sema.resolveInst(extra.lhs);
const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.field_name, .{ .simple = .field_name });
return fieldPtrLoad(sema, block, src, object_ptr, field_name, field_name_src);
}
fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const field_name_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data;
const object_ptr = try sema.resolveInst(extra.lhs);
const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.field_name, .{ .simple = .field_name });
return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false);
}
fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intCast");
const operand = try sema.resolveInst(extra.rhs);
return sema.intCast(block, block.nodeOffset(inst_data.src_node), dest_ty, src, operand, operand_src);
}
fn intCast(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
dest_ty: Type,
dest_ty_src: LazySrcLoc,
operand: Air.Inst.Ref,
operand_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
const dest_scalar_ty = try sema.checkIntOrVectorAllowComptime(block, dest_ty, dest_ty_src);
const operand_scalar_ty = try sema.checkIntOrVectorAllowComptime(block, operand_ty, operand_src);
if (try sema.isComptimeKnown(operand)) {
return sema.coerce(block, dest_ty, operand, operand_src);
} else if (dest_scalar_ty.zigTypeTag(zcu) == .comptime_int) {
return sema.fail(block, operand_src, "unable to cast runtime value to 'comptime_int'", .{});
}
try sema.checkVectorizableBinaryOperands(block, operand_src, dest_ty, operand_ty, dest_ty_src, operand_src);
const is_vector = dest_ty.zigTypeTag(zcu) == .vector;
if ((try sema.typeHasOnePossibleValue(dest_ty))) |opv| {
// requirement: intCast(u0, input) iff input == 0
if (block.wantSafety()) {
try sema.requireRuntimeBlock(block, src, operand_src);
const wanted_info = dest_scalar_ty.intInfo(zcu);
const wanted_bits = wanted_info.bits;
if (wanted_bits == 0) {
const ok = if (is_vector) ok: {
const zeros = try sema.splat(operand_ty, try pt.intValue(operand_scalar_ty, 0));
const zero_inst = Air.internedToRef(zeros.toIntern());
const is_in_range = try block.addCmpVector(operand, zero_inst, .eq);
const all_in_range = try block.addReduce(is_in_range, .And);
break :ok all_in_range;
} else ok: {
const zero_inst = Air.internedToRef((try pt.intValue(operand_ty, 0)).toIntern());
const is_in_range = try block.addBinOp(.cmp_lte, operand, zero_inst);
break :ok is_in_range;
};
try sema.addSafetyCheck(block, src, ok, .integer_out_of_bounds);
}
}
return Air.internedToRef(opv.toIntern());
}
try sema.requireRuntimeBlock(block, src, operand_src);
if (block.wantSafety()) {
try sema.preparePanicId(src, .integer_out_of_bounds);
return block.addTyOp(.intcast_safe, dest_ty, operand);
}
return block.addTyOp(.intcast, dest_ty, operand);
}
fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@bitCast");
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
switch (dest_ty.zigTypeTag(zcu)) {
.@"anyframe",
.comptime_float,
.comptime_int,
.enum_literal,
.error_set,
.error_union,
.@"fn",
.frame,
.noreturn,
.null,
.@"opaque",
.optional,
.type,
.undefined,
.void,
=> return sema.fail(block, src, "cannot @bitCast to '{f}'", .{dest_ty.fmt(pt)}),
.@"enum" => {
const msg = msg: {
const msg = try sema.errMsg(src, "cannot @bitCast to '{f}'", .{dest_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
switch (operand_ty.zigTypeTag(zcu)) {
.int, .comptime_int => try sema.errNote(src, msg, "use @enumFromInt to cast from '{f}'", .{operand_ty.fmt(pt)}),
else => {},
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
.pointer => {
const msg = msg: {
const msg = try sema.errMsg(src, "cannot @bitCast to '{f}'", .{dest_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
switch (operand_ty.zigTypeTag(zcu)) {
.int, .comptime_int => try sema.errNote(src, msg, "use @ptrFromInt to cast from '{f}'", .{operand_ty.fmt(pt)}),
.pointer => try sema.errNote(src, msg, "use @ptrCast to cast from '{f}'", .{operand_ty.fmt(pt)}),
else => {},
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
.@"struct", .@"union" => if (dest_ty.containerLayout(zcu) == .auto) {
const container = switch (dest_ty.zigTypeTag(zcu)) {
.@"struct" => "struct",
.@"union" => "union",
else => unreachable,
};
return sema.fail(block, src, "cannot @bitCast to '{f}'; {s} does not have a guaranteed in-memory layout", .{
dest_ty.fmt(pt), container,
});
},
.array => {
const elem_ty = dest_ty.childType(zcu);
if (!elem_ty.hasWellDefinedLayout(zcu)) {
const msg = msg: {
const msg = try sema.errMsg(src, "cannot @bitCast to '{f}'", .{dest_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "array element type '{f}' does not have a guaranteed in-memory layout", .{elem_ty.fmt(pt)});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
},
.bool,
.float,
.int,
.vector,
=> {},
}
switch (operand_ty.zigTypeTag(zcu)) {
.@"anyframe",
.comptime_float,
.comptime_int,
.enum_literal,
.error_set,
.error_union,
.@"fn",
.frame,
.noreturn,
.null,
.@"opaque",
.optional,
.type,
.undefined,
.void,
=> return sema.fail(block, operand_src, "cannot @bitCast from '{f}'", .{operand_ty.fmt(pt)}),
.@"enum" => {
const msg = msg: {
const msg = try sema.errMsg(operand_src, "cannot @bitCast from '{f}'", .{operand_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
switch (dest_ty.zigTypeTag(zcu)) {
.int, .comptime_int => try sema.errNote(operand_src, msg, "use @intFromEnum to cast to '{f}'", .{dest_ty.fmt(pt)}),
else => {},
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
.pointer => {
const msg = msg: {
const msg = try sema.errMsg(operand_src, "cannot @bitCast from '{f}'", .{operand_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
switch (dest_ty.zigTypeTag(zcu)) {
.int, .comptime_int => try sema.errNote(operand_src, msg, "use @intFromPtr to cast to '{f}'", .{dest_ty.fmt(pt)}),
.pointer => try sema.errNote(operand_src, msg, "use @ptrCast to cast to '{f}'", .{dest_ty.fmt(pt)}),
else => {},
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
.@"struct", .@"union" => if (operand_ty.containerLayout(zcu) == .auto) {
const container = switch (operand_ty.zigTypeTag(zcu)) {
.@"struct" => "struct",
.@"union" => "union",
else => unreachable,
};
return sema.fail(block, operand_src, "cannot @bitCast from '{f}'; {s} does not have a guaranteed in-memory layout", .{
operand_ty.fmt(pt), container,
});
},
.array => {
const elem_ty = operand_ty.childType(zcu);
if (!elem_ty.hasWellDefinedLayout(zcu)) {
const msg = msg: {
const msg = try sema.errMsg(src, "cannot @bitCast from '{f}'", .{operand_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "array element type '{f}' does not have a guaranteed in-memory layout", .{elem_ty.fmt(pt)});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
},
.bool,
.float,
.int,
.vector,
=> {},
}
return sema.bitCast(block, dest_ty, operand, block.nodeOffset(inst_data.src_node), operand_src);
}
fn zirFloatCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@floatCast");
const dest_scalar_ty = dest_ty.scalarType(zcu);
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
const operand_scalar_ty = operand_ty.scalarType(zcu);
try sema.checkVectorizableBinaryOperands(block, operand_src, dest_ty, operand_ty, src, operand_src);
const is_vector = dest_ty.zigTypeTag(zcu) == .vector;
const target = zcu.getTarget();
const dest_is_comptime_float = switch (dest_scalar_ty.zigTypeTag(zcu)) {
.comptime_float => true,
.float => false,
else => return sema.fail(
block,
src,
"expected float or vector type, found '{f}'",
.{dest_ty.fmt(pt)},
),
};
switch (operand_scalar_ty.zigTypeTag(zcu)) {
.comptime_float, .float, .comptime_int => {},
else => return sema.fail(
block,
operand_src,
"expected float or vector type, found '{f}'",
.{operand_ty.fmt(pt)},
),
}
if (try sema.resolveValue(operand)) |operand_val| {
if (!is_vector) {
return Air.internedToRef((try operand_val.floatCast(dest_ty, pt)).toIntern());
}
const vec_len = operand_ty.vectorLen(zcu);
const new_elems = try sema.arena.alloc(InternPool.Index, vec_len);
for (new_elems, 0..) |*new_elem, i| {
const old_elem = try operand_val.elemValue(pt, i);
new_elem.* = (try old_elem.floatCast(dest_scalar_ty, pt)).toIntern();
}
return Air.internedToRef((try pt.aggregateValue(dest_ty, new_elems)).toIntern());
}
if (dest_is_comptime_float) {
return sema.fail(block, operand_src, "unable to cast runtime value to 'comptime_float'", .{});
}
try sema.requireRuntimeBlock(block, block.nodeOffset(inst_data.src_node), operand_src);
const src_bits = operand_scalar_ty.floatBits(target);
const dst_bits = dest_scalar_ty.floatBits(target);
if (dst_bits >= src_bits) {
return sema.coerce(block, dest_ty, operand, operand_src);
}
return block.addTyOp(.fptrunc, dest_ty, operand);
}
fn zirElemVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const array = try sema.resolveInst(extra.lhs);
const elem_index = try sema.resolveInst(extra.rhs);
return sema.elemVal(block, src, array, elem_index, src, false);
}
fn zirElemPtrLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const elem_index_src = block.src(.{ .node_offset_array_access_index = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.lhs);
const uncoerced_elem_index = try sema.resolveInst(extra.rhs);
if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| {
const array_ptr_ty = sema.typeOf(array_ptr);
if (try sema.pointerDeref(block, src, array_ptr_val, array_ptr_ty)) |array_val| {
const array: Air.Inst.Ref = .fromValue(array_val);
return elemVal(sema, block, src, array, uncoerced_elem_index, elem_index_src, true);
}
}
const elem_index = try sema.coerce(block, .usize, uncoerced_elem_index, elem_index_src);
const elem_ptr = try elemPtr(sema, block, src, array_ptr, elem_index, elem_index_src, false, true);
return analyzeLoad(sema, block, src, elem_ptr, elem_index_src);
}
fn zirElemValImm(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].elem_val_imm;
const array = try sema.resolveInst(inst_data.operand);
const elem_index = try sema.pt.intRef(.usize, inst_data.idx);
return sema.elemVal(block, LazySrcLoc.unneeded, array, elem_index, LazySrcLoc.unneeded, false);
}
fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.lhs);
const elem_index = try sema.resolveInst(extra.rhs);
const indexable_ty = sema.typeOf(array_ptr);
if (indexable_ty.zigTypeTag(zcu) != .pointer) {
const capture_src = block.src(.{ .for_capture_from_input = inst_data.src_node });
const msg = msg: {
const msg = try sema.errMsg(capture_src, "pointer capture of non pointer type '{f}'", .{
indexable_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
if (indexable_ty.isIndexable(zcu)) {
try sema.errNote(src, msg, "consider using '&' here", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
return sema.elemPtrOneLayerOnly(block, src, array_ptr, elem_index, src, false, false);
}
fn zirElemPtrNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const elem_index_src = block.src(.{ .node_offset_array_access_index = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.lhs);
const uncoerced_elem_index = try sema.resolveInst(extra.rhs);
const elem_index = try sema.coerce(block, .usize, uncoerced_elem_index, elem_index_src);
return sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src, false, true);
}
fn zirArrayInitElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.ElemPtrImm, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.ptr);
const elem_index = try pt.intRef(.usize, extra.index);
const array_ty = sema.typeOf(array_ptr).childType(zcu);
switch (array_ty.zigTypeTag(zcu)) {
.array, .vector => {},
else => if (!array_ty.isTuple(zcu)) {
return sema.failWithArrayInitNotSupported(block, src, array_ty);
},
}
return sema.elemPtr(block, src, array_ptr, elem_index, src, true, true);
}
fn zirSliceStart(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.SliceStart, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.lhs);
const start = try sema.resolveInst(extra.start);
const ptr_src = block.src(.{ .node_offset_slice_ptr = inst_data.src_node });
const start_src = block.src(.{ .node_offset_slice_start = inst_data.src_node });
const end_src = block.src(.{ .node_offset_slice_end = inst_data.src_node });
return sema.analyzeSlice(block, src, array_ptr, start, .none, .none, LazySrcLoc.unneeded, ptr_src, start_src, end_src, false);
}
fn zirSliceEnd(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.SliceEnd, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.lhs);
const start = try sema.resolveInst(extra.start);
const end = try sema.resolveInst(extra.end);
const ptr_src = block.src(.{ .node_offset_slice_ptr = inst_data.src_node });
const start_src = block.src(.{ .node_offset_slice_start = inst_data.src_node });
const end_src = block.src(.{ .node_offset_slice_end = inst_data.src_node });
return sema.analyzeSlice(block, src, array_ptr, start, end, .none, LazySrcLoc.unneeded, ptr_src, start_src, end_src, false);
}
fn zirSliceSentinel(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const sentinel_src = block.src(.{ .node_offset_slice_sentinel = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.SliceSentinel, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.lhs);
const start = try sema.resolveInst(extra.start);
const end: Air.Inst.Ref = if (extra.end == .none) .none else try sema.resolveInst(extra.end);
const sentinel = try sema.resolveInst(extra.sentinel);
const ptr_src = block.src(.{ .node_offset_slice_ptr = inst_data.src_node });
const start_src = block.src(.{ .node_offset_slice_start = inst_data.src_node });
const end_src = block.src(.{ .node_offset_slice_end = inst_data.src_node });
return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src, ptr_src, start_src, end_src, false);
}
fn zirSliceLength(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.SliceLength, inst_data.payload_index).data;
const array_ptr = try sema.resolveInst(extra.lhs);
const start = try sema.resolveInst(extra.start);
const len = try sema.resolveInst(extra.len);
const sentinel = if (extra.sentinel == .none) .none else try sema.resolveInst(extra.sentinel);
const ptr_src = block.src(.{ .node_offset_slice_ptr = inst_data.src_node });
const start_src = block.src(.{ .node_offset_slice_start = extra.start_src_node_offset });
const end_src = block.src(.{ .node_offset_slice_end = inst_data.src_node });
const sentinel_src: LazySrcLoc = if (sentinel == .none)
LazySrcLoc.unneeded
else
block.src(.{ .node_offset_slice_sentinel = inst_data.src_node });
return sema.analyzeSlice(block, src, array_ptr, start, len, sentinel, sentinel_src, ptr_src, start_src, end_src, true);
}
fn zirSliceSentinelTy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ptr_src = block.src(.{ .node_offset_slice_ptr = inst_data.src_node });
const sentinel_src = block.src(.{ .node_offset_slice_sentinel = inst_data.src_node });
// This is like the logic in `analyzeSlice`; since we've evaluated the LHS as an lvalue, we will
// have a double pointer if it was already a pointer.
const lhs_ptr_ty = sema.typeOf(try sema.resolveInst(inst_data.operand));
const lhs_ty = switch (lhs_ptr_ty.zigTypeTag(zcu)) {
.pointer => lhs_ptr_ty.childType(zcu),
else => return sema.fail(block, ptr_src, "expected pointer, found '{f}'", .{lhs_ptr_ty.fmt(pt)}),
};
const sentinel_ty: Type = switch (lhs_ty.zigTypeTag(zcu)) {
.array => lhs_ty.childType(zcu),
.pointer => switch (lhs_ty.ptrSize(zcu)) {
.many, .c, .slice => lhs_ty.childType(zcu),
.one => s: {
const lhs_elem_ty = lhs_ty.childType(zcu);
break :s switch (lhs_elem_ty.zigTypeTag(zcu)) {
.array => lhs_elem_ty.childType(zcu), // array element type
else => return sema.fail(block, sentinel_src, "slice of single-item pointer cannot have sentinel", .{}),
};
},
},
else => return sema.fail(block, src, "slice of non-array type '{f}'", .{lhs_ty.fmt(pt)}),
};
return Air.internedToRef(sentinel_ty.toIntern());
}
/// Holds common data used when analyzing or resolving switch prong bodies,
/// including setting up captures.
const SwitchProngAnalysis = struct {
sema: *Sema,
/// The block containing the `switch_block` itself.
parent_block: *Block,
operand: Operand,
/// If this switch is on an error set, this is the type to assign to the
/// `else` prong. If `null`, the prong should be unreachable.
else_error_ty: ?Type,
/// The index of the `switch_block` instruction itself.
switch_block_inst: Zir.Inst.Index,
/// The dummy index into which inline tag captures should be placed. May be
/// undefined if no prong has a tag capture.
tag_capture_inst: Zir.Inst.Index,
const Operand = union(enum) {
/// This switch will be dispatched only once, with the given operand.
simple: struct {
/// The raw switch operand value. Always defined.
by_val: Air.Inst.Ref,
/// The switch operand *pointer*. Defined only if there is a prong
/// with a by-ref capture.
by_ref: Air.Inst.Ref,
/// The switch condition value. For unions, `operand` is the union
/// and `cond` is its enum tag value.
cond: Air.Inst.Ref,
},
/// This switch may be dispatched multiple times with `continue` syntax.
/// As such, the operand is stored in an alloc if needed.
loop: struct {
/// The `alloc` containing the `switch` operand for the active dispatch.
/// Each prong must load from this `alloc` to get captures.
/// If there are no captures, this may be undefined.
operand_alloc: Air.Inst.Ref,
/// Whether `operand_alloc` contains a by-val operand or a by-ref
/// operand.
operand_is_ref: bool,
/// The switch condition value for the *initial* dispatch. For
/// unions, this is the enum tag value.
init_cond: Air.Inst.Ref,
},
};
/// Resolve a switch prong which is determined at comptime to have no peers.
/// Uses `resolveBlockBody`. Sets up captures as needed.
fn resolveProngComptime(
spa: SwitchProngAnalysis,
child_block: *Block,
prong_type: enum { normal, special },
prong_body: []const Zir.Inst.Index,
capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
/// Must use the `switch_capture` field in `offset`.
capture_src: LazySrcLoc,
/// The set of all values which can reach this prong. May be undefined
/// if the prong is special or contains ranges.
case_vals: []const Air.Inst.Ref,
/// The inline capture of this prong. If this is not an inline prong,
/// this is `.none`.
inline_case_capture: Air.Inst.Ref,
/// Whether this prong has an inline tag capture. If `true`, then
/// `inline_case_capture` cannot be `.none`.
has_tag_capture: bool,
merges: *Block.Merges,
) CompileError!Air.Inst.Ref {
const sema = spa.sema;
const src = spa.parent_block.nodeOffset(
sema.code.instructions.items(.data)[@intFromEnum(spa.switch_block_inst)].pl_node.src_node,
);
// We can propagate `.cold` hints from this branch since it's comptime-known
// to be taken from the parent branch.
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
if (has_tag_capture) {
const tag_ref = try spa.analyzeTagCapture(child_block, capture_src, inline_case_capture);
sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref);
}
defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst));
switch (capture) {
.none => {
return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);
},
.by_val, .by_ref => {
const capture_ref = try spa.analyzeCapture(
child_block,
capture == .by_ref,
prong_type == .special,
capture_src,
case_vals,
inline_case_capture,
);
if (sema.typeOf(capture_ref).isNoReturn(sema.pt.zcu)) {
// This prong should be unreachable!
return .unreachable_value;
}
sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);
defer assert(sema.inst_map.remove(spa.switch_block_inst));
return sema.resolveBlockBody(spa.parent_block, src, child_block, prong_body, spa.switch_block_inst, merges);
},
}
}
/// Analyze a switch prong which may have peers at runtime.
/// Uses `analyzeBodyRuntimeBreak`. Sets up captures as needed.
/// Returns the `BranchHint` for the prong.
fn analyzeProngRuntime(
spa: SwitchProngAnalysis,
case_block: *Block,
prong_type: enum { normal, special },
prong_body: []const Zir.Inst.Index,
capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
/// Must use the `switch_capture` field in `offset`.
capture_src: LazySrcLoc,
/// The set of all values which can reach this prong. May be undefined
/// if the prong is special or contains ranges.
case_vals: []const Air.Inst.Ref,
/// The inline capture of this prong. If this is not an inline prong,
/// this is `.none`.
inline_case_capture: Air.Inst.Ref,
/// Whether this prong has an inline tag capture. If `true`, then
/// `inline_case_capture` cannot be `.none`.
has_tag_capture: bool,
) CompileError!std.builtin.BranchHint {
const sema = spa.sema;
if (has_tag_capture) {
const tag_ref = try spa.analyzeTagCapture(case_block, capture_src, inline_case_capture);
sema.inst_map.putAssumeCapacity(spa.tag_capture_inst, tag_ref);
}
defer if (has_tag_capture) assert(sema.inst_map.remove(spa.tag_capture_inst));
switch (capture) {
.none => {
return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
},
.by_val, .by_ref => {
const capture_ref = try spa.analyzeCapture(
case_block,
capture == .by_ref,
prong_type == .special,
capture_src,
case_vals,
inline_case_capture,
);
if (sema.typeOf(capture_ref).isNoReturn(sema.pt.zcu)) {
// No need to analyze any further, the prong is unreachable
return .none;
}
sema.inst_map.putAssumeCapacity(spa.switch_block_inst, capture_ref);
defer assert(sema.inst_map.remove(spa.switch_block_inst));
return sema.analyzeBodyRuntimeBreak(case_block, prong_body);
},
}
}
fn analyzeTagCapture(
spa: SwitchProngAnalysis,
block: *Block,
capture_src: LazySrcLoc,
inline_case_capture: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const sema = spa.sema;
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = switch (spa.operand) {
.simple => |s| sema.typeOf(s.by_val),
.loop => |l| ty: {
const alloc_ty = sema.typeOf(l.operand_alloc);
const alloc_child = alloc_ty.childType(zcu);
if (l.operand_is_ref) break :ty alloc_child.childType(zcu);
break :ty alloc_child;
},
};
if (operand_ty.zigTypeTag(zcu) != .@"union") {
const tag_capture_src: LazySrcLoc = .{
.base_node_inst = capture_src.base_node_inst,
.offset = .{ .switch_tag_capture = capture_src.offset.switch_capture },
};
return sema.fail(block, tag_capture_src, "cannot capture tag of non-union type '{f}'", .{
operand_ty.fmt(pt),
});
}
assert(inline_case_capture != .none);
return inline_case_capture;
}
fn analyzeCapture(
spa: SwitchProngAnalysis,
block: *Block,
capture_byref: bool,
is_special_prong: bool,
capture_src: LazySrcLoc,
case_vals: []const Air.Inst.Ref,
inline_case_capture: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const sema = spa.sema;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const zir_datas = sema.code.instructions.items(.data);
const switch_node_offset = zir_datas[@intFromEnum(spa.switch_block_inst)].pl_node.src_node;
const operand_src = block.src(.{ .node_offset_switch_operand = switch_node_offset });
const operand_val, const operand_ptr = switch (spa.operand) {
.simple => |s| .{ s.by_val, s.by_ref },
.loop => |l| op: {
const loaded = try sema.analyzeLoad(block, operand_src, l.operand_alloc, operand_src);
if (l.operand_is_ref) {
const by_val = try sema.analyzeLoad(block, operand_src, loaded, operand_src);
break :op .{ by_val, loaded };
} else {
break :op .{ loaded, undefined };
}
},
};
const operand_ty = sema.typeOf(operand_val);
const operand_ptr_ty = if (capture_byref) sema.typeOf(operand_ptr) else undefined;
if (inline_case_capture != .none) {
const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inline_case_capture, undefined) catch unreachable;
if (operand_ty.zigTypeTag(zcu) == .@"union") {
const field_index: u32 = @intCast(operand_ty.unionTagFieldIndex(item_val, zcu).?);
const union_obj = zcu.typeToUnion(operand_ty).?;
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
if (capture_byref) {
const ptr_field_ty = try pt.ptrTypeSema(.{
.child = field_ty.toIntern(),
.flags = .{
.is_const = !operand_ptr_ty.ptrIsMutable(zcu),
.is_volatile = operand_ptr_ty.isVolatilePtr(zcu),
.address_space = operand_ptr_ty.ptrAddressSpace(zcu),
},
});
if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |union_ptr| {
return Air.internedToRef((try union_ptr.ptrField(field_index, pt)).toIntern());
}
return block.addStructFieldPtr(operand_ptr, field_index, ptr_field_ty);
} else {
if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |union_val| {
const tag_and_val = ip.indexToKey(union_val.toIntern()).un;
return Air.internedToRef(tag_and_val.val);
}
return block.addStructFieldVal(operand_val, field_index, field_ty);
}
} else if (capture_byref) {
return sema.uavRef(item_val.toIntern());
} else {
return inline_case_capture;
}
}
if (is_special_prong) {
if (capture_byref) {
return operand_ptr;
}
switch (operand_ty.zigTypeTag(zcu)) {
.error_set => if (spa.else_error_ty) |ty| {
return sema.bitCast(block, ty, operand_val, operand_src, null);
} else {
try sema.analyzeUnreachable(block, operand_src, false);
return .unreachable_value;
},
else => return operand_val,
}
}
switch (operand_ty.zigTypeTag(zcu)) {
.@"union" => {
const union_obj = zcu.typeToUnion(operand_ty).?;
const first_item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
const first_field_index: u32 = zcu.unionTagFieldIndex(union_obj, first_item_val).?;
const first_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_field_index]);
const field_indices = try sema.arena.alloc(u32, case_vals.len);
for (case_vals, field_indices) |item, *field_idx| {
const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
field_idx.* = zcu.unionTagFieldIndex(union_obj, item_val).?;
}
// Fast path: if all the operands are the same type already, we don't need to hit
// PTR! This will also allow us to emit simpler code.
const same_types = for (field_indices[1..]) |field_idx| {
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
if (!field_ty.eql(first_field_ty, zcu)) break false;
} else true;
const capture_ty = if (same_types) first_field_ty else capture_ty: {
// We need values to run PTR on, so make a bunch of undef constants.
const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
for (dummy_captures, field_indices) |*dummy, field_idx| {
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
dummy.* = try pt.undefRef(field_ty);
}
const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
for (case_srcs, 0..) |*case_src, i| {
case_src.* = .{
.base_node_inst = capture_src.base_node_inst,
.offset = .{ .switch_case_item = .{
.switch_node_offset = switch_node_offset,
.case_idx = capture_src.offset.switch_capture.case_idx,
.item_idx = .{ .kind = .single, .index = @intCast(i) },
} },
};
}
break :capture_ty sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) {
error.AnalysisFail => {
const msg = sema.err orelse return error.AnalysisFail;
try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
return error.AnalysisFail;
},
else => |e| return e,
};
};
// By-reference captures have some further restrictions which make them easier to emit
if (capture_byref) {
const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu);
const capture_ptr_ty = resolve: {
// By-ref captures of hetereogeneous types are only allowed if all field
// pointer types are peer resolvable to each other.
// We need values to run PTR on, so make a bunch of undef constants.
const dummy_captures = try sema.arena.alloc(Air.Inst.Ref, case_vals.len);
for (field_indices, dummy_captures) |field_idx, *dummy| {
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
const field_ptr_ty = try pt.ptrTypeSema(.{
.child = field_ty.toIntern(),
.flags = .{
.is_const = operand_ptr_info.flags.is_const,
.is_volatile = operand_ptr_info.flags.is_volatile,
.address_space = operand_ptr_info.flags.address_space,
.alignment = union_obj.fieldAlign(ip, field_idx),
},
});
dummy.* = try pt.undefRef(field_ptr_ty);
}
const case_srcs = try sema.arena.alloc(?LazySrcLoc, case_vals.len);
for (case_srcs, 0..) |*case_src, i| {
case_src.* = .{
.base_node_inst = capture_src.base_node_inst,
.offset = .{ .switch_case_item = .{
.switch_node_offset = switch_node_offset,
.case_idx = capture_src.offset.switch_capture.case_idx,
.item_idx = .{ .kind = .single, .index = @intCast(i) },
} },
};
}
break :resolve sema.resolvePeerTypes(block, capture_src, dummy_captures, .{ .override = case_srcs }) catch |err| switch (err) {
error.AnalysisFail => {
const msg = sema.err orelse return error.AnalysisFail;
try sema.errNote(capture_src, msg, "this coercion is only possible when capturing by value", .{});
try sema.reparentOwnedErrorMsg(capture_src, msg, "capture group with incompatible types", .{});
return error.AnalysisFail;
},
else => |e| return e,
};
};
if (try sema.resolveDefinedValue(block, operand_src, operand_ptr)) |op_ptr_val| {
if (op_ptr_val.isUndef(zcu)) return pt.undefRef(capture_ptr_ty);
const field_ptr_val = try op_ptr_val.ptrField(first_field_index, pt);
return Air.internedToRef((try pt.getCoerced(field_ptr_val, capture_ptr_ty)).toIntern());
}
try sema.requireRuntimeBlock(block, operand_src, null);
return block.addStructFieldPtr(operand_ptr, first_field_index, capture_ptr_ty);
}
if (try sema.resolveDefinedValue(block, operand_src, operand_val)) |operand_val_val| {
if (operand_val_val.isUndef(zcu)) return pt.undefRef(capture_ty);
const union_val = ip.indexToKey(operand_val_val.toIntern()).un;
if (Value.fromInterned(union_val.tag).isUndef(zcu)) return pt.undefRef(capture_ty);
const uncoerced = Air.internedToRef(union_val.val);
return sema.coerce(block, capture_ty, uncoerced, operand_src);
}
try sema.requireRuntimeBlock(block, operand_src, null);
if (same_types) {
return block.addStructFieldVal(operand_val, first_field_index, capture_ty);
}
// We may have to emit a switch block which coerces the operand to the capture type.
// If we can, try to avoid that using in-memory coercions.
const first_non_imc = in_mem: {
for (field_indices, 0..) |field_idx, i| {
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), LazySrcLoc.unneeded, LazySrcLoc.unneeded, null)) {
break :in_mem i;
}
}
// All fields are in-memory coercible to the resolved type!
// Just take the first field and bitcast the result.
const uncoerced = try block.addStructFieldVal(operand_val, first_field_index, first_field_ty);
return block.addBitCast(capture_ty, uncoerced);
};
// By-val capture with heterogeneous types which are not all in-memory coercible to
// the resolved capture type. We finally have to fall back to the ugly method.
// However, let's first track which operands are in-memory coercible. There may well
// be several, and we can squash all of these cases into the same switch prong using
// a simple bitcast. We'll make this the 'else' prong.
var in_mem_coercible = try std.DynamicBitSet.initFull(sema.arena, field_indices.len);
in_mem_coercible.unset(first_non_imc);
{
const next = first_non_imc + 1;
for (field_indices[next..], next..) |field_idx, i| {
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
if (.ok != try sema.coerceInMemoryAllowed(block, capture_ty, field_ty, false, zcu.getTarget(), LazySrcLoc.unneeded, LazySrcLoc.unneeded, null)) {
in_mem_coercible.unset(i);
}
}
}
const capture_block_inst = try block.addInstAsIndex(.{
.tag = .block,
.data = .{
.ty_pl = .{
.ty = Air.internedToRef(capture_ty.toIntern()),
.payload = undefined, // updated below
},
},
});
const prong_count = field_indices.len - in_mem_coercible.count();
const estimated_extra = prong_count * 6 + (prong_count / 10); // 2 for Case, 1 item, probably 3 insts; plus hints
var cases_extra = try std.array_list.Managed(u32).initCapacity(sema.gpa, estimated_extra);
defer cases_extra.deinit();
{
// All branch hints are `.none`, so just add zero elems.
comptime assert(@intFromEnum(std.builtin.BranchHint.none) == 0);
const need_elems = std.math.divCeil(usize, prong_count + 1, 10) catch unreachable;
try cases_extra.appendNTimes(0, need_elems);
}
{
// Non-bitcast cases
var it = in_mem_coercible.iterator(.{ .kind = .unset });
while (it.next()) |idx| {
var coerce_block = block.makeSubBlock();
defer coerce_block.instructions.deinit(sema.gpa);
const case_src: LazySrcLoc = .{
.base_node_inst = capture_src.base_node_inst,
.offset = .{ .switch_case_item = .{
.switch_node_offset = switch_node_offset,
.case_idx = capture_src.offset.switch_capture.case_idx,
.item_idx = .{ .kind = .single, .index = @intCast(idx) },
} },
};
const field_idx = field_indices[idx];
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_idx]);
const uncoerced = try coerce_block.addStructFieldVal(operand_val, field_idx, field_ty);
const coerced = try sema.coerce(&coerce_block, capture_ty, uncoerced, case_src);
_ = try coerce_block.addBr(capture_block_inst, coerced);
try cases_extra.ensureUnusedCapacity(@typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
coerce_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(coerce_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(case_vals[idx])); // item
cases_extra.appendSliceAssumeCapacity(@ptrCast(coerce_block.instructions.items)); // body
}
}
const else_body_len = len: {
// 'else' prong uses a bitcast
var coerce_block = block.makeSubBlock();
defer coerce_block.instructions.deinit(sema.gpa);
const first_imc_item_idx = in_mem_coercible.findFirstSet().?;
const first_imc_field_idx = field_indices[first_imc_item_idx];
const first_imc_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[first_imc_field_idx]);
const uncoerced = try coerce_block.addStructFieldVal(operand_val, first_imc_field_idx, first_imc_field_ty);
const coerced = try coerce_block.addBitCast(capture_ty, uncoerced);
_ = try coerce_block.addBr(capture_block_inst, coerced);
try cases_extra.appendSlice(@ptrCast(coerce_block.instructions.items));
break :len coerce_block.instructions.items.len;
};
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
cases_extra.items.len +
@typeInfo(Air.Block).@"struct".fields.len +
1);
const switch_br_inst: u32 = @intCast(sema.air_instructions.len);
try sema.air_instructions.append(sema.gpa, .{
.tag = .switch_br,
.data = .{
.pl_op = .{
.operand = undefined, // set by switch below
.payload = sema.addExtraAssumeCapacity(Air.SwitchBr{
.cases_len = @intCast(prong_count),
.else_body_len = @intCast(else_body_len),
}),
},
},
});
sema.air_extra.appendSliceAssumeCapacity(cases_extra.items);
// Set up block body
switch (spa.operand) {
.simple => |s| {
const air_datas = sema.air_instructions.items(.data);
air_datas[switch_br_inst].pl_op.operand = s.cond;
air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = 1,
});
sema.air_extra.appendAssumeCapacity(switch_br_inst);
},
.loop => {
// The block must first extract the tag from the loaded union.
const tag_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(sema.gpa, .{
.tag = .get_union_tag,
.data = .{ .ty_op = .{
.ty = Air.internedToRef(union_obj.enum_tag_ty),
.operand = operand_val,
} },
});
const air_datas = sema.air_instructions.items(.data);
air_datas[switch_br_inst].pl_op.operand = tag_inst.toRef();
air_datas[@intFromEnum(capture_block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = 2,
});
sema.air_extra.appendAssumeCapacity(@intFromEnum(tag_inst));
sema.air_extra.appendAssumeCapacity(switch_br_inst);
},
}
return capture_block_inst.toRef();
},
.error_set => {
if (capture_byref) {
return sema.fail(
block,
capture_src,
"error set cannot be captured by reference",
.{},
);
}
if (case_vals.len == 1) {
const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, case_vals[0], undefined) catch unreachable;
const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?);
return sema.bitCast(block, item_ty, operand_val, operand_src, null);
}
var names: InferredErrorSet.NameMap = .{};
try names.ensureUnusedCapacity(sema.arena, case_vals.len);
for (case_vals) |err| {
const err_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, err, undefined) catch unreachable;
names.putAssumeCapacityNoClobber(err_val.getErrorName(zcu).unwrap().?, {});
}
const error_ty = try pt.errorSetFromUnsortedNames(names.keys());
return sema.bitCast(block, error_ty, operand_val, operand_src, null);
},
else => {
// In this case the capture value is just the passed-through value
// of the switch condition.
if (capture_byref) {
return operand_ptr;
} else {
return operand_val;
}
},
}
}
};
fn switchCond(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
switch (operand_ty.zigTypeTag(zcu)) {
.type,
.void,
.bool,
.int,
.float,
.comptime_float,
.comptime_int,
.enum_literal,
.pointer,
.@"fn",
.error_set,
.@"enum",
=> {
if (operand_ty.isSlice(zcu)) {
return sema.fail(block, src, "switch on type '{f}'", .{operand_ty.fmt(pt)});
}
if ((try sema.typeHasOnePossibleValue(operand_ty))) |opv| {
return Air.internedToRef(opv.toIntern());
}
return operand;
},
.@"union" => {
try operand_ty.resolveFields(pt);
const enum_ty = operand_ty.unionTagType(zcu) orelse {
const msg = msg: {
const msg = try sema.errMsg(src, "switch on union with no attached enum", .{});
errdefer msg.destroy(sema.gpa);
if (operand_ty.srcLocOrNull(zcu)) |union_src| {
try sema.errNote(union_src, msg, "consider 'union(enum)' here", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
};
return sema.unionToTag(block, enum_ty, operand, src);
},
.error_union,
.noreturn,
.array,
.@"struct",
.undefined,
.null,
.optional,
.@"opaque",
.vector,
.frame,
.@"anyframe",
=> return sema.fail(block, src, "switch on type '{f}'", .{operand_ty.fmt(pt)}),
}
}
const SwitchErrorSet = std.AutoHashMap(InternPool.NullTerminatedString, LazySrcLoc);
fn zirSwitchBlockErrUnion(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const switch_src = block.nodeOffset(inst_data.src_node);
const switch_src_node_offset = inst_data.src_node;
const switch_operand_src = block.src(.{ .node_offset_switch_operand = switch_src_node_offset });
const else_prong_src = block.src(.{ .node_offset_switch_else_prong = switch_src_node_offset });
const extra = sema.code.extraData(Zir.Inst.SwitchBlockErrUnion, inst_data.payload_index);
const main_operand_src = block.src(.{ .node_offset_if_cond = extra.data.main_src_node_offset });
const main_src = block.src(.{ .node_offset_main_token = extra.data.main_src_node_offset });
const raw_operand_val = try sema.resolveInst(extra.data.operand);
// AstGen guarantees that the instruction immediately preceding
// switch_block_err_union is a dbg_stmt
const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1);
var header_extra_index: usize = extra.end;
const scalar_cases_len = extra.data.bits.scalar_cases_len;
const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
const multi_cases_len = sema.code.extra[header_extra_index];
header_extra_index += 1;
break :blk multi_cases_len;
} else 0;
const err_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_uses_err_capture) blk: {
const err_capture_inst: Zir.Inst.Index = @enumFromInt(sema.code.extra[header_extra_index]);
header_extra_index += 1;
// SwitchProngAnalysis wants inst_map to have space for the tag capture.
// Note that the normal capture is referred to via the switch block
// index, which there is already necessarily space for.
try sema.inst_map.ensureSpaceForInstructions(gpa, &.{err_capture_inst});
break :blk err_capture_inst;
} else undefined;
var case_vals = try std.ArrayList(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);
defer case_vals.deinit(gpa);
const NonError = struct {
body: []const Zir.Inst.Index,
end: usize,
capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
};
const non_error_case: NonError = non_error: {
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]);
const extra_body_start = header_extra_index + 1;
break :non_error .{
.body = sema.code.bodySlice(extra_body_start, info.body_len),
.end = extra_body_start + info.body_len,
.capture = info.capture,
};
};
const Else = struct {
body: []const Zir.Inst.Index,
end: usize,
is_inline: bool,
has_capture: bool,
};
const else_case: Else = if (!extra.data.bits.has_else) .{
.body = &.{},
.end = non_error_case.end,
.is_inline = false,
.has_capture = false,
} else special: {
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[non_error_case.end]);
const extra_body_start = non_error_case.end + 1;
assert(info.capture != .by_ref);
assert(!info.has_tag_capture);
break :special .{
.body = sema.code.bodySlice(extra_body_start, info.body_len),
.end = extra_body_start + info.body_len,
.is_inline = info.is_inline,
.has_capture = info.capture != .none,
};
};
var seen_errors = SwitchErrorSet.init(gpa);
defer seen_errors.deinit();
const operand_ty = sema.typeOf(raw_operand_val);
const operand_err_set = if (extra.data.bits.payload_is_ref)
operand_ty.childType(zcu)
else
operand_ty;
if (operand_err_set.zigTypeTag(zcu) != .error_union) {
return sema.fail(block, switch_src, "expected error union type, found '{f}'", .{
operand_ty.fmt(pt),
});
}
const operand_err_set_ty = operand_err_set.errorUnionSet(zcu);
const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{
.tag = .block,
.data = undefined,
});
var label: Block.Label = .{
.zir_block = inst,
.merges = .{
.src_locs = .{},
.results = .{},
.br_list = .{},
.block_inst = block_inst,
},
};
var child_block: Block = .{
.parent = block,
.sema = sema,
.namespace = block.namespace,
.instructions = .{},
.label = &label,
.inlining = block.inlining,
.comptime_reason = block.comptime_reason,
.is_typeof = block.is_typeof,
.c_import_buf = block.c_import_buf,
.runtime_cond = block.runtime_cond,
.runtime_loop = block.runtime_loop,
.runtime_index = block.runtime_index,
.error_return_trace_index = block.error_return_trace_index,
.want_safety = block.want_safety,
.src_base_inst = block.src_base_inst,
.type_name_ctx = block.type_name_ctx,
};
const merges = &child_block.label.?.merges;
defer child_block.instructions.deinit(gpa);
defer merges.deinit(gpa);
const resolved_err_set = try sema.resolveInferredErrorSetTy(block, main_src, operand_err_set_ty.toIntern());
if (Type.fromInterned(resolved_err_set).errorSetIsEmpty(zcu)) {
return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);
}
const else_error_ty: ?Type = try validateErrSetSwitch(
sema,
block,
&seen_errors,
&case_vals,
operand_err_set_ty,
inst_data,
scalar_cases_len,
multi_cases_len,
.{ .body = else_case.body, .end = else_case.end, .src = else_prong_src },
extra.data.bits.has_else,
);
var spa: SwitchProngAnalysis = .{
.sema = sema,
.parent_block = block,
.operand = .{
.simple = .{
.by_val = undefined, // must be set to the unwrapped error code before use
.by_ref = undefined,
.cond = raw_operand_val,
},
},
.else_error_ty = else_error_ty,
.switch_block_inst = inst,
.tag_capture_inst = undefined,
};
if (try sema.resolveDefinedValue(&child_block, main_src, raw_operand_val)) |ov| {
const operand_val = if (extra.data.bits.payload_is_ref)
(try sema.pointerDeref(&child_block, main_src, ov, operand_ty)).?
else
ov;
if (operand_val.errorUnionIsPayload(zcu)) {
return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);
} else {
const err_val = Value.fromInterned(try pt.intern(.{
.err = .{
.ty = operand_err_set_ty.toIntern(),
.name = operand_val.getErrorName(zcu).unwrap().?,
},
}));
spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
try sema.analyzeErrUnionCodePtr(block, switch_operand_src, raw_operand_val)
else
try sema.analyzeErrUnionCode(block, switch_operand_src, raw_operand_val);
if (extra.data.bits.any_uses_err_capture) {
sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
}
defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
return resolveSwitchComptime(
sema,
spa,
&child_block,
try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
err_val,
operand_err_set_ty,
switch_src_node_offset,
null,
.{
.body = else_case.body,
.end = else_case.end,
.capture = if (else_case.has_capture) .by_val else .none,
.is_inline = else_case.is_inline,
.has_tag_capture = false,
},
false,
case_vals,
scalar_cases_len,
multi_cases_len,
true,
false,
);
}
}
if (scalar_cases_len + multi_cases_len == 0) {
if (else_error_ty) |ty| if (ty.errorSetIsEmpty(zcu)) {
return sema.resolveBlockBody(block, main_operand_src, &child_block, non_error_case.body, inst, merges);
};
}
if (child_block.isComptime()) {
_ = try sema.resolveConstDefinedValue(&child_block, main_operand_src, raw_operand_val, null);
unreachable;
}
const cond = if (extra.data.bits.payload_is_ref) blk: {
try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val).elemType2(zcu));
const loaded = try sema.analyzeLoad(block, main_src, raw_operand_val, main_src);
break :blk try sema.analyzeIsNonErr(block, main_src, loaded);
} else blk: {
try sema.checkErrorType(block, main_src, sema.typeOf(raw_operand_val));
break :blk try sema.analyzeIsNonErr(block, main_src, raw_operand_val);
};
var sub_block = child_block.makeSubBlock();
sub_block.runtime_loop = null;
sub_block.runtime_cond = main_operand_src;
sub_block.runtime_index.increment();
sub_block.need_debug_scope = null; // this body is emitted regardless
defer sub_block.instructions.deinit(gpa);
const non_error_hint = try sema.analyzeBodyRuntimeBreak(&sub_block, non_error_case.body);
const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);
defer gpa.free(true_instructions);
spa.operand.simple.by_val = if (extra.data.bits.payload_is_ref)
try sema.analyzeErrUnionCodePtr(&sub_block, switch_operand_src, raw_operand_val)
else
try sema.analyzeErrUnionCode(&sub_block, switch_operand_src, raw_operand_val);
if (extra.data.bits.any_uses_err_capture) {
sema.inst_map.putAssumeCapacity(err_capture_inst, spa.operand.simple.by_val);
}
defer if (extra.data.bits.any_uses_err_capture) assert(sema.inst_map.remove(err_capture_inst));
_ = try sema.analyzeSwitchRuntimeBlock(
spa,
&sub_block,
switch_src,
try sema.switchCond(block, switch_operand_src, spa.operand.simple.by_val),
operand_err_set_ty,
switch_operand_src,
case_vals,
.{
.body = else_case.body,
.end = else_case.end,
.capture = if (else_case.has_capture) .by_val else .none,
.is_inline = else_case.is_inline,
.has_tag_capture = false,
},
scalar_cases_len,
multi_cases_len,
false,
undefined,
true,
switch_src_node_offset,
else_prong_src,
false,
undefined,
seen_errors,
undefined,
undefined,
undefined,
cond_dbg_node_index,
true,
null,
undefined,
&.{},
&.{},
);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +
true_instructions.len + sub_block.instructions.items.len);
_ = try child_block.addInst(.{
.tag = .cond_br,
.data = .{
.pl_op = .{
.operand = cond,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(true_instructions.len),
.else_body_len = @intCast(sub_block.instructions.items.len),
.branch_hints = .{
.true = non_error_hint,
.false = .none,
// Code coverage is desired for error handling.
.then_cov = .poi,
.else_cov = .poi,
},
}),
},
},
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
return sema.resolveAnalyzedBlock(block, main_src, &child_block, merges, false);
}
fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index, operand_is_ref: bool) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const gpa = sema.gpa;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const src_node_offset = inst_data.src_node;
const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset });
const under_prong_src = block.src(.{ .node_offset_switch_under_prong = src_node_offset });
const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
const operand: SwitchProngAnalysis.Operand, const raw_operand_ty: Type = op: {
const maybe_ptr = try sema.resolveInst(extra.data.operand);
const val, const ref = if (operand_is_ref)
.{ try sema.analyzeLoad(block, src, maybe_ptr, operand_src), maybe_ptr }
else
.{ maybe_ptr, undefined };
const init_cond = try sema.switchCond(block, operand_src, val);
const operand_ty = sema.typeOf(val);
if (extra.data.bits.has_continue and !block.isComptime()) {
// Even if the operand is comptime-known, this `switch` is runtime.
if (try operand_ty.comptimeOnlySema(pt)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(operand_src, "operand of switch loop has comptime-only type '{f}'", .{operand_ty.fmt(pt)});
errdefer msg.destroy(gpa);
try sema.errNote(operand_src, msg, "switch loops are evaluated at runtime outside of comptime scopes", .{});
break :msg msg;
});
}
try sema.validateRuntimeValue(block, operand_src, maybe_ptr);
const operand_alloc = if (extra.data.bits.any_non_inline_capture) a: {
const operand_ptr_ty = try pt.singleMutPtrType(sema.typeOf(maybe_ptr));
const operand_alloc = try block.addTy(.alloc, operand_ptr_ty);
_ = try block.addBinOp(.store, operand_alloc, maybe_ptr);
break :a operand_alloc;
} else undefined;
break :op .{
.{ .loop = .{
.operand_alloc = operand_alloc,
.operand_is_ref = operand_is_ref,
.init_cond = init_cond,
} },
operand_ty,
};
}
// We always use `simple` in the comptime case, because as far as the dispatching logic
// is concerned, it really is dispatching a single prong. `resolveSwitchComptime` will
// be resposible for recursively resolving different prongs as needed.
break :op .{
.{ .simple = .{
.by_val = val,
.by_ref = ref,
.cond = init_cond,
} },
operand_ty,
};
};
const union_originally = raw_operand_ty.zigTypeTag(zcu) == .@"union";
const err_set = raw_operand_ty.zigTypeTag(zcu) == .error_set;
const cond_ty = switch (raw_operand_ty.zigTypeTag(zcu)) {
.@"union" => raw_operand_ty.unionTagType(zcu).?, // validated by `switchCond` above
else => raw_operand_ty,
};
// AstGen guarantees that the instruction immediately preceding
// switch_block(_ref) is a dbg_stmt
const cond_dbg_node_index: Zir.Inst.Index = @enumFromInt(@intFromEnum(inst) - 1);
var header_extra_index: usize = extra.end;
const scalar_cases_len = extra.data.bits.scalar_cases_len;
const multi_cases_len = if (extra.data.bits.has_multi_cases) blk: {
const multi_cases_len = sema.code.extra[header_extra_index];
header_extra_index += 1;
break :blk multi_cases_len;
} else 0;
const tag_capture_inst: Zir.Inst.Index = if (extra.data.bits.any_has_tag_capture) blk: {
const tag_capture_inst: Zir.Inst.Index = @enumFromInt(sema.code.extra[header_extra_index]);
header_extra_index += 1;
// SwitchProngAnalysis wants inst_map to have space for the tag capture.
// Note that the normal capture is referred to via the switch block
// index, which there is already necessarily space for.
try sema.inst_map.ensureSpaceForInstructions(gpa, &.{tag_capture_inst});
break :blk tag_capture_inst;
} else undefined;
var case_vals = try std.ArrayList(Air.Inst.Ref).initCapacity(gpa, scalar_cases_len + 2 * multi_cases_len);
defer case_vals.deinit(gpa);
var single_absorbed_item: Zir.Inst.Ref = .none;
var absorbed_items: []const Zir.Inst.Ref = &.{};
var absorbed_ranges: []const Zir.Inst.Ref = &.{};
const special_prongs = extra.data.bits.special_prongs;
const has_else = special_prongs.hasElse();
const has_under = special_prongs.hasUnder();
const special_else: SpecialProng = if (has_else) blk: {
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[header_extra_index]);
const extra_body_start = header_extra_index + 1;
break :blk .{
.body = sema.code.bodySlice(extra_body_start, info.body_len),
.end = extra_body_start + info.body_len,
.capture = info.capture,
.is_inline = info.is_inline,
.has_tag_capture = info.has_tag_capture,
};
} else .{
.body = &.{},
.end = header_extra_index,
.capture = .none,
.is_inline = false,
.has_tag_capture = false,
};
const special_under: SpecialProng = if (has_under) blk: {
var extra_index = special_else.end;
var trailing_items_len: usize = 0;
if (special_prongs.hasOneAdditionalItem()) {
single_absorbed_item = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
absorbed_items = @ptrCast(&single_absorbed_item);
} else if (special_prongs.hasManyAdditionalItems()) {
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
absorbed_items = sema.code.refSlice(extra_index + 1, items_len);
absorbed_ranges = sema.code.refSlice(extra_index + 1 + items_len, ranges_len * 2);
trailing_items_len = items_len + ranges_len * 2;
}
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + trailing_items_len;
break :blk .{
.body = sema.code.bodySlice(extra_index, info.body_len),
.end = extra_index + info.body_len,
.capture = info.capture,
.is_inline = info.is_inline,
.has_tag_capture = info.has_tag_capture,
};
} else .{
.body = &.{},
.end = special_else.end,
.capture = .none,
.is_inline = false,
.has_tag_capture = false,
};
const special_end = special_under.end;
// Duplicate checking variables later also used for `inline else`.
var seen_enum_fields: []?LazySrcLoc = &.{};
var seen_errors = SwitchErrorSet.init(gpa);
var range_set = RangeSet.init(gpa, zcu);
var true_count: u8 = 0;
var false_count: u8 = 0;
defer {
range_set.deinit();
gpa.free(seen_enum_fields);
seen_errors.deinit();
}
var empty_enum = false;
var else_error_ty: ?Type = null;
// Validate usage of '_' prongs.
if (has_under and !raw_operand_ty.isNonexhaustiveEnum(zcu)) {
const msg = msg: {
const msg = try sema.errMsg(
src,
"'_' prong only allowed when switching on non-exhaustive enums",
.{},
);
errdefer msg.destroy(gpa);
try sema.errNote(
under_prong_src,
msg,
"'_' prong here",
.{},
);
try sema.errNote(
src,
msg,
"consider using 'else'",
.{},
);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
// Validate for duplicate items, missing else prong, and invalid range.
switch (cond_ty.zigTypeTag(zcu)) {
.@"union" => unreachable, // handled in `switchCond`
.@"enum" => {
seen_enum_fields = try gpa.alloc(?LazySrcLoc, cond_ty.enumFieldCount(zcu));
empty_enum = seen_enum_fields.len == 0 and !cond_ty.isNonexhaustiveEnum(zcu);
@memset(seen_enum_fields, null);
// `range_set` is used for non-exhaustive enum values that do not correspond to any tags.
for (absorbed_items, 0..) |item_ref, item_i| {
_ = try sema.validateSwitchItemEnum(
block,
seen_enum_fields,
&range_set,
item_ref,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .special_under,
.item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} }),
);
}
try sema.validateSwitchNoRange(block, @intCast(absorbed_ranges.len), cond_ty, src_node_offset);
var extra_index: usize = special_end;
{
var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + info.body_len;
case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
block,
seen_enum_fields,
&range_set,
item_ref,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
.item_idx = .{ .kind = .single, .index = 0 },
} }),
));
}
}
{
var multi_i: u32 = 0;
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + info.body_len;
try case_vals.ensureUnusedCapacity(gpa, items.len);
for (items, 0..) |item_ref, item_i| {
case_vals.appendAssumeCapacity(try sema.validateSwitchItemEnum(
block,
seen_enum_fields,
&range_set,
item_ref,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} }),
));
}
try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
}
}
const all_tags_handled = for (seen_enum_fields) |seen_src| {
if (seen_src == null) break false;
} else true;
if (has_else) {
if (all_tags_handled) {
if (cond_ty.isNonexhaustiveEnum(zcu)) {
if (has_under) return sema.fail(
block,
else_prong_src,
"unreachable else prong; all explicit cases already handled",
.{},
);
} else return sema.fail(
block,
else_prong_src,
"unreachable else prong; all cases already handled",
.{},
);
}
} else if (!all_tags_handled) {
const msg = msg: {
const msg = try sema.errMsg(
src,
"switch must handle all possibilities",
.{},
);
errdefer msg.destroy(sema.gpa);
for (seen_enum_fields, 0..) |seen_src, i| {
if (seen_src != null) continue;
const field_name = cond_ty.enumFieldName(i, zcu);
try sema.addFieldErrNote(
cond_ty,
i,
msg,
"unhandled enumeration value: '{f}'",
.{field_name.fmt(ip)},
);
}
try sema.errNote(
cond_ty.srcLoc(zcu),
msg,
"enum '{f}' declared here",
.{cond_ty.fmt(pt)},
);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
} else if (special_prongs == .none and cond_ty.isNonexhaustiveEnum(zcu) and !union_originally) {
return sema.fail(
block,
src,
"switch on non-exhaustive enum must include 'else' or '_' prong or both",
.{},
);
}
},
.error_set => else_error_ty = try validateErrSetSwitch(
sema,
block,
&seen_errors,
&case_vals,
cond_ty,
inst_data,
scalar_cases_len,
multi_cases_len,
.{ .body = special_else.body, .end = special_else.end, .src = else_prong_src },
has_else,
),
.int, .comptime_int => {
var extra_index: usize = special_end;
{
var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + info.body_len;
case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
block,
&range_set,
item_ref,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
.item_idx = .{ .kind = .single, .index = 0 },
} }),
));
}
}
{
var multi_i: u32 = 0;
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len;
try case_vals.ensureUnusedCapacity(gpa, items.len);
for (items, 0..) |item_ref, item_i| {
case_vals.appendAssumeCapacity(try sema.validateSwitchItemInt(
block,
&range_set,
item_ref,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} }),
));
}
try case_vals.ensureUnusedCapacity(gpa, 2 * ranges_len);
var range_i: u32 = 0;
while (range_i < ranges_len) : (range_i += 1) {
const item_first: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const item_last: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const vals = try sema.validateSwitchRange(
block,
&range_set,
item_first,
item_last,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .range, .index = @intCast(range_i) },
} }),
);
case_vals.appendAssumeCapacity(vals[0]);
case_vals.appendAssumeCapacity(vals[1]);
}
extra_index += info.body_len;
}
}
check_range: {
if (cond_ty.zigTypeTag(zcu) == .int) {
const min_int = try cond_ty.minInt(pt, cond_ty);
const max_int = try cond_ty.maxInt(pt, cond_ty);
if (try range_set.spans(min_int.toIntern(), max_int.toIntern())) {
if (has_else) {
return sema.fail(
block,
else_prong_src,
"unreachable else prong; all cases already handled",
.{},
);
}
break :check_range;
}
}
if (special_prongs == .none) {
return sema.fail(
block,
src,
"switch must handle all possibilities",
.{},
);
}
}
},
.bool => {
var extra_index: usize = special_end;
{
var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + info.body_len;
case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
block,
&true_count,
&false_count,
item_ref,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
.item_idx = .{ .kind = .single, .index = 0 },
} }),
));
}
}
{
var multi_i: u32 = 0;
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + info.body_len;
try case_vals.ensureUnusedCapacity(gpa, items.len);
for (items, 0..) |item_ref, item_i| {
case_vals.appendAssumeCapacity(try sema.validateSwitchItemBool(
block,
&true_count,
&false_count,
item_ref,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} }),
));
}
try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
}
}
if (has_else) {
if (true_count + false_count == 2) {
return sema.fail(
block,
else_prong_src,
"unreachable else prong; all cases already handled",
.{},
);
}
} else {
if (true_count + false_count < 2) {
return sema.fail(
block,
src,
"switch must handle all possibilities",
.{},
);
}
}
},
.enum_literal, .void, .@"fn", .pointer, .type => {
if (!has_else) {
return sema.fail(
block,
src,
"else prong required when switching on type '{f}'",
.{cond_ty.fmt(pt)},
);
}
var seen_values = ValueSrcMap{};
defer seen_values.deinit(gpa);
var extra_index: usize = special_end;
{
var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
extra_index += info.body_len;
case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
block,
&seen_values,
item_ref,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
.item_idx = .{ .kind = .single, .index = 0 },
} }),
));
}
}
{
var multi_i: u32 = 0;
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + info.body_len;
try case_vals.ensureUnusedCapacity(gpa, items.len);
for (items, 0..) |item_ref, item_i| {
case_vals.appendAssumeCapacity(try sema.validateSwitchItemSparse(
block,
&seen_values,
item_ref,
cond_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} }),
));
}
try sema.validateSwitchNoRange(block, ranges_len, cond_ty, src_node_offset);
}
}
},
.error_union,
.noreturn,
.array,
.@"struct",
.undefined,
.null,
.optional,
.@"opaque",
.vector,
.frame,
.@"anyframe",
.comptime_float,
.float,
=> return sema.fail(block, operand_src, "invalid switch operand type '{f}'", .{
raw_operand_ty.fmt(pt),
}),
}
var special_members_only: ?SpecialProng = null;
var special_members_only_src: LazySrcLoc = undefined;
const special_generic, const special_generic_src = if (has_under) b: {
if (has_else) {
special_members_only = special_else;
special_members_only_src = else_prong_src;
}
break :b .{ special_under, under_prong_src };
} else .{ special_else, else_prong_src };
const spa: SwitchProngAnalysis = .{
.sema = sema,
.parent_block = block,
.operand = operand,
.else_error_ty = else_error_ty,
.switch_block_inst = inst,
.tag_capture_inst = tag_capture_inst,
};
const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{
.tag = .block,
.data = undefined,
});
var label: Block.Label = .{
.zir_block = inst,
.merges = .{
.src_locs = .{},
.results = .{},
.br_list = .{},
.block_inst = block_inst,
},
};
var child_block: Block = .{
.parent = block,
.sema = sema,
.namespace = block.namespace,
.instructions = .{},
.label = &label,
.inlining = block.inlining,
.comptime_reason = block.comptime_reason,
.is_typeof = block.is_typeof,
.c_import_buf = block.c_import_buf,
.runtime_cond = block.runtime_cond,
.runtime_loop = block.runtime_loop,
.runtime_index = block.runtime_index,
.want_safety = block.want_safety,
.error_return_trace_index = block.error_return_trace_index,
.src_base_inst = block.src_base_inst,
.type_name_ctx = block.type_name_ctx,
};
const merges = &child_block.label.?.merges;
defer child_block.instructions.deinit(gpa);
defer merges.deinit(gpa);
if (scalar_cases_len + multi_cases_len == 0 and
special_members_only == null and
!special_generic.is_inline)
{
if (empty_enum) {
return .void_value;
}
if (special_prongs == .none) {
return sema.fail(block, src, "switch must handle all possibilities", .{});
}
const init_cond = switch (operand) {
.simple => |s| s.cond,
.loop => |l| l.init_cond,
};
if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
raw_operand_ty.zigTypeTag(zcu) == .@"enum" and !raw_operand_ty.isNonexhaustiveEnum(zcu))
{
try sema.zirDbgStmt(block, cond_dbg_node_index);
const ok = try block.addUnOp(.is_named_enum_value, init_cond);
try sema.addSafetyCheck(block, src, ok, .corrupt_switch);
}
if (err_set and try sema.maybeErrorUnwrap(block, special_generic.body, init_cond, operand_src, false)) {
return .unreachable_value;
}
}
switch (operand) {
.loop => {}, // always runtime; evaluation in comptime scope uses `simple`
.simple => |s| {
if (try sema.resolveDefinedValue(&child_block, src, s.cond)) |cond_val| {
return resolveSwitchComptimeLoop(
sema,
spa,
&child_block,
if (operand_is_ref)
sema.typeOf(s.by_ref)
else
raw_operand_ty,
cond_ty,
cond_val,
src_node_offset,
special_members_only,
special_generic,
has_under,
case_vals,
scalar_cases_len,
multi_cases_len,
err_set,
empty_enum,
operand_is_ref,
);
}
if (scalar_cases_len + multi_cases_len == 0 and
special_members_only == null and
!special_generic.is_inline and
!extra.data.bits.has_continue)
{
return spa.resolveProngComptime(
&child_block,
.special,
special_generic.body,
special_generic.capture,
block.src(.{ .switch_capture = .{
.switch_node_offset = src_node_offset,
.case_idx = if (has_under) .special_under else .special_else,
} }),
undefined, // case_vals may be undefined for special prongs
.none,
false,
merges,
);
}
},
}
if (child_block.isComptime()) {
_ = try sema.resolveConstDefinedValue(&child_block, operand_src, operand.simple.cond, null);
unreachable;
}
var extra_case_vals: struct {
items: std.ArrayList(Air.Inst.Ref),
ranges: std.ArrayList([2]Air.Inst.Ref),
} = .{ .items = .empty, .ranges = .empty };
defer {
extra_case_vals.items.deinit(gpa);
extra_case_vals.ranges.deinit(gpa);
}
// Runtime switch, if we have a special_members_only prong we need to unroll
// it to a prong with explicit items.
// Although this is potentially the same as `inline else` it does not count
// towards the backward branch quota because it's an implementation detail.
if (special_members_only != null) gen: {
assert(cond_ty.isNonexhaustiveEnum(zcu));
var min_i: usize = math.maxInt(usize);
var max_i: usize = 0;
var seen_field_count: usize = 0;
for (seen_enum_fields, 0..) |seen, enum_i| {
if (seen != null) {
seen_field_count += 1;
} else {
min_i = @min(min_i, enum_i);
max_i = @max(max_i, enum_i);
}
}
if (min_i == max_i) {
seen_enum_fields[min_i] = special_members_only_src;
const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i));
const item_ref = Air.internedToRef(item_val.toIntern());
try extra_case_vals.items.append(gpa, item_ref);
break :gen;
}
const missing_field_count = seen_enum_fields.len - seen_field_count;
extra_case_vals.items = try .initCapacity(gpa, missing_field_count / 2);
extra_case_vals.ranges = try .initCapacity(gpa, missing_field_count / 4);
const int_ty = cond_ty.intTagType(zcu);
var last_val = try pt.enumValueFieldIndex(cond_ty, @intCast(min_i));
var first_ref = Air.internedToRef(last_val.toIntern());
seen_enum_fields[min_i] = special_members_only_src;
for (seen_enum_fields[(min_i + 1)..(max_i + 1)], (min_i + 1)..) |seen, enum_i| {
if (seen != null) continue;
seen_enum_fields[enum_i] = special_members_only_src;
const item_val = try pt.enumValueFieldIndex(cond_ty, @intCast(enum_i));
const item_ref = Air.internedToRef(item_val.toIntern());
const is_next = is_next: {
const prev_int = ip.indexToKey(last_val.toIntern()).enum_tag.int;
const result = try arith.incrementDefinedInt(sema, int_ty, .fromInterned(prev_int));
if (result.overflow) break :is_next false;
const item_int = ip.indexToKey(item_val.toIntern()).enum_tag.int;
break :is_next try sema.valuesEqual(.fromInterned(item_int), result.val, int_ty);
};
if (is_next) {
last_val = item_val;
} else {
const last_ref = Air.internedToRef(last_val.toIntern());
if (first_ref == last_ref) {
try extra_case_vals.items.append(gpa, first_ref);
} else {
try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref });
}
first_ref = item_ref;
last_val = item_val;
}
}
const last_ref = Air.internedToRef(last_val.toIntern());
if (first_ref == last_ref) {
try extra_case_vals.items.append(gpa, first_ref);
} else {
try extra_case_vals.ranges.append(gpa, .{ first_ref, last_ref });
}
}
const air_switch_ref = try sema.analyzeSwitchRuntimeBlock(
spa,
&child_block,
src,
switch (operand) {
.simple => |s| s.cond,
.loop => |l| l.init_cond,
},
cond_ty,
operand_src,
case_vals,
special_generic,
scalar_cases_len,
multi_cases_len,
union_originally,
raw_operand_ty,
err_set,
src_node_offset,
special_generic_src,
has_under,
seen_enum_fields,
seen_errors,
range_set,
true_count,
false_count,
cond_dbg_node_index,
false,
special_members_only,
special_members_only_src,
extra_case_vals.items.items,
extra_case_vals.ranges.items,
);
for (merges.extra_insts.items, merges.extra_src_locs.items) |placeholder_inst, dispatch_src| {
var replacement_block = block.makeSubBlock();
defer replacement_block.instructions.deinit(gpa);
assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br);
const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand;
if (extra.data.bits.any_non_inline_capture) {
_ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref);
}
const new_operand_val = if (operand_is_ref)
try sema.analyzeLoad(&replacement_block, dispatch_src, new_operand_maybe_ref, dispatch_src)
else
new_operand_maybe_ref;
const new_cond = try sema.switchCond(&replacement_block, dispatch_src, new_operand_val);
if (zcu.backendSupportsFeature(.is_named_enum_value) and block.wantSafety() and
cond_ty.zigTypeTag(zcu) == .@"enum" and !cond_ty.isNonexhaustiveEnum(zcu) and
!try sema.isComptimeKnown(new_cond))
{
const ok = try replacement_block.addUnOp(.is_named_enum_value, new_cond);
try sema.addSafetyCheck(&replacement_block, src, ok, .corrupt_switch);
}
_ = try replacement_block.addInst(.{
.tag = .switch_dispatch,
.data = .{ .br = .{
.block_inst = air_switch_ref.toIndex().?,
.operand = new_cond,
} },
});
if (replacement_block.instructions.items.len == 1) {
// Optimization: we don't need a block!
sema.air_instructions.set(
@intFromEnum(placeholder_inst),
sema.air_instructions.get(@intFromEnum(replacement_block.instructions.items[0])),
);
continue;
}
// Replace placeholder with a block.
// No `br` is needed as the block is a switch dispatch so necessarily `noreturn`.
try sema.air_extra.ensureUnusedCapacity(
gpa,
@typeInfo(Air.Block).@"struct".fields.len + replacement_block.instructions.items.len,
);
sema.air_instructions.set(@intFromEnum(placeholder_inst), .{
.tag = .block,
.data = .{ .ty_pl = .{
.ty = .noreturn_type,
.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = @intCast(replacement_block.instructions.items.len),
}),
} },
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(replacement_block.instructions.items));
}
return sema.resolveAnalyzedBlock(block, src, &child_block, merges, false);
}
const SpecialProng = struct {
body: []const Zir.Inst.Index,
end: usize,
capture: Zir.Inst.SwitchBlock.ProngInfo.Capture,
is_inline: bool,
has_tag_capture: bool,
};
fn analyzeSwitchRuntimeBlock(
sema: *Sema,
spa: SwitchProngAnalysis,
child_block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
operand_ty: Type,
operand_src: LazySrcLoc,
case_vals: std.ArrayList(Air.Inst.Ref),
else_prong: SpecialProng,
scalar_cases_len: usize,
multi_cases_len: usize,
union_originally: bool,
maybe_union_ty: Type,
err_set: bool,
switch_node_offset: std.zig.Ast.Node.Offset,
else_prong_src: LazySrcLoc,
else_prong_is_underscore: bool,
seen_enum_fields: []?LazySrcLoc,
seen_errors: SwitchErrorSet,
range_set: RangeSet,
true_count: u8,
false_count: u8,
cond_dbg_node_index: Zir.Inst.Index,
allow_err_code_unwrap: bool,
extra_prong: ?SpecialProng,
/// May be `undefined` if `extra_prong` is `null`
extra_prong_src: LazySrcLoc,
extra_prong_items: []const Air.Inst.Ref,
extra_prong_ranges: []const [2]Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const block = child_block.parent.?;
const estimated_cases_extra = (scalar_cases_len + multi_cases_len) *
@typeInfo(Air.SwitchBr.Case).@"struct".fields.len + 2;
var cases_extra = try std.ArrayList(u32).initCapacity(gpa, estimated_cases_extra);
defer cases_extra.deinit(gpa);
var branch_hints = try std.ArrayList(std.builtin.BranchHint).initCapacity(gpa, scalar_cases_len);
defer branch_hints.deinit(gpa);
var case_block = child_block.makeSubBlock();
case_block.runtime_loop = null;
case_block.runtime_cond = operand_src;
case_block.runtime_index.increment();
case_block.need_debug_scope = null; // this body is emitted regardless
defer case_block.instructions.deinit(gpa);
var extra_index: usize = else_prong.end;
var scalar_i: usize = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
const body = sema.code.bodySlice(extra_index, info.body_len);
extra_index += info.body_len;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
const item = case_vals.items[scalar_i];
// `item` is already guaranteed to be constant known.
const analyze_body = if (union_originally) blk: {
const unresolved_item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
const item_val = sema.resolveLazyValue(unresolved_item_val) catch unreachable;
const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
break :blk field_ty.zigTypeTag(zcu) != .noreturn;
} else true;
const prong_hint: std.builtin.BranchHint = if (err_set and
try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
h: {
// nothing to do here. weight against error branch
break :h .unlikely;
} else if (analyze_body) h: {
break :h try spa.analyzeProngRuntime(
&case_block,
.normal,
body,
info.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
} }),
&.{item},
if (info.is_inline) item else .none,
info.has_tag_capture,
);
} else h: {
_ = try case_block.addNoOp(.unreach);
break :h .none;
};
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(item));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
var cases_len = scalar_cases_len;
var case_val_idx: usize = scalar_cases_len;
const multi_cases_len_with_extra_prong = multi_cases_len + @intFromBool(extra_prong != null);
var multi_i: u32 = 0;
while (multi_i < multi_cases_len_with_extra_prong) : (multi_i += 1) {
const is_extra_prong = multi_i == multi_cases_len;
var items: []const Air.Inst.Ref = undefined;
var info: Zir.Inst.SwitchBlock.ProngInfo = undefined;
var ranges: []const [2]Air.Inst.Ref = undefined;
var body: []const Zir.Inst.Index = undefined;
if (is_extra_prong) {
const prong = extra_prong.?;
items = extra_prong_items;
ranges = extra_prong_ranges;
body = prong.body;
info = .{
.body_len = undefined,
.capture = prong.capture,
.is_inline = prong.is_inline,
.has_tag_capture = prong.has_tag_capture,
};
} else {
@branchHint(.likely);
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
info = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + items_len + ranges_len * 2;
items = case_vals.items[case_val_idx..][0..items_len];
case_val_idx += items_len;
ranges = @ptrCast(case_vals.items[case_val_idx..][0 .. ranges_len * 2]);
case_val_idx += ranges_len * 2;
body = sema.code.bodySlice(extra_index, info.body_len);
extra_index += info.body_len;
}
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
// Generate all possible cases as scalar prongs.
if (info.is_inline) {
var emit_bb = false;
for (ranges, 0..) |range_items, range_i| {
var item = sema.resolveConstDefinedValue(block, .unneeded, range_items[0], undefined) catch unreachable;
const item_last = sema.resolveConstDefinedValue(block, .unneeded, range_items[1], undefined) catch unreachable;
while (item.compareScalar(.lte, item_last, operand_ty, zcu)) : ({
// Previous validation has resolved any possible lazy values.
const int_val: Value, const int_ty: Type = switch (operand_ty.zigTypeTag(zcu)) {
.int => .{ item, operand_ty },
.@"enum" => b: {
const int_val = Value.fromInterned(ip.indexToKey(item.toIntern()).enum_tag.int);
break :b .{ int_val, int_val.typeOf(zcu) };
},
else => unreachable,
};
const result = try arith.incrementDefinedInt(sema, int_ty, int_val);
assert(!result.overflow);
item = switch (operand_ty.zigTypeTag(zcu)) {
.int => result.val,
.@"enum" => .fromInterned(try pt.intern(.{ .enum_tag = .{
.ty = operand_ty.toIntern(),
.int = result.val.toIntern(),
} })),
else => unreachable,
};
}) {
cases_len += 1;
const item_ref = Air.internedToRef(item.toIntern());
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
if (emit_bb) {
const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .range, .index = @intCast(range_i) },
} });
try sema.emitBackwardBranch(block, bb_src);
}
emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime(
&case_block,
.normal,
body,
info.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
} }),
undefined, // case_vals may be undefined for ranges
item_ref,
info.has_tag_capture,
);
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
if (item.compareScalar(.eq, item_last, operand_ty, zcu)) break;
}
}
for (items, 0..) |item, item_i| {
cases_len += 1;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
const analyze_body = if (union_originally) blk: {
const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
break :blk field_ty.zigTypeTag(zcu) != .noreturn;
} else true;
if (emit_bb) {
const bb_src = if (is_extra_prong) extra_prong_src else block.src(.{ .switch_case_item = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} });
try sema.emitBackwardBranch(block, bb_src);
}
emit_bb = true;
const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
break :h try spa.analyzeProngRuntime(
&case_block,
.normal,
body,
info.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
} }),
&.{item},
item,
info.has_tag_capture,
);
} else h: {
_ = try case_block.addNoOp(.unreach);
break :h .none;
};
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(item));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
continue;
}
cases_len += 1;
const analyze_body = if (union_originally)
for (items) |item| {
const item_val = sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
} else false
else
true;
const prong_hint: std.builtin.BranchHint = if (err_set and
try sema.maybeErrorUnwrap(&case_block, body, operand, operand_src, allow_err_code_unwrap))
h: {
// nothing to do here. weight against error branch
break :h .unlikely;
} else if (analyze_body) h: {
break :h try spa.analyzeProngRuntime(
&case_block,
.normal,
body,
info.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
} }),
items,
.none,
false,
);
} else h: {
_ = try case_block.addNoOp(.unreach);
break :h .none;
};
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
items.len + ranges.len * 2 +
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = @intCast(items.len),
.ranges_len = @intCast(ranges.len),
.body_len = @intCast(case_block.instructions.items.len),
}));
for (items) |item| {
cases_extra.appendAssumeCapacity(@intFromEnum(item));
}
for (ranges) |range| {
cases_extra.appendSliceAssumeCapacity(&.{
@intFromEnum(range[0]),
@intFromEnum(range[1]),
});
}
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
const else_body: []const Air.Inst.Index = if (else_prong.body.len != 0 or case_block.wantSafety()) else_body: {
var emit_bb = false;
// If this is true we must have a 'true' else prong and not an underscore because
// underscore prongs can never be inlined. We've already checked for this.
if (else_prong.is_inline) switch (operand_ty.zigTypeTag(zcu)) {
.@"enum" => {
if (operand_ty.isNonexhaustiveEnum(zcu) and !union_originally) {
return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
operand_ty.fmt(pt),
});
}
for (seen_enum_fields, 0..) |f, i| {
if (f != null) continue;
cases_len += 1;
const item_val = try pt.enumValueFieldIndex(operand_ty, @intCast(i));
const item_ref = Air.internedToRef(item_val.toIntern());
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
const analyze_body = if (union_originally) blk: {
const field_ty = maybe_union_ty.unionFieldType(item_val, zcu).?;
break :blk field_ty.zigTypeTag(zcu) != .noreturn;
} else true;
if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
emit_bb = true;
const prong_hint: std.builtin.BranchHint = if (analyze_body) h: {
break :h try spa.analyzeProngRuntime(
&case_block,
.special,
else_prong.body,
else_prong.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .special_else,
} }),
&.{item_ref},
item_ref,
else_prong.has_tag_capture,
);
} else h: {
_ = try case_block.addNoOp(.unreach);
break :h .none;
};
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
},
.error_set => {
if (operand_ty.isAnyError(zcu)) {
return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
operand_ty.fmt(pt),
});
}
const error_names = operand_ty.errorSetNames(zcu);
for (0..error_names.len) |name_index| {
const error_name = error_names.get(ip)[name_index];
if (seen_errors.contains(error_name)) continue;
cases_len += 1;
const item_val = try pt.intern(.{ .err = .{
.ty = operand_ty.toIntern(),
.name = error_name,
} });
const item_ref = Air.internedToRef(item_val);
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime(
&case_block,
.special,
else_prong.body,
else_prong.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .special_else,
} }),
&.{item_ref},
item_ref,
else_prong.has_tag_capture,
);
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
},
.int => {
var it = try RangeSetUnhandledIterator.init(sema, operand_ty, range_set);
while (try it.next()) |cur| {
cases_len += 1;
const item_ref = Air.internedToRef(cur);
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime(
&case_block,
.special,
else_prong.body,
else_prong.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .special_else,
} }),
&.{item_ref},
item_ref,
else_prong.has_tag_capture,
);
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(item_ref));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
},
.bool => {
if (true_count == 0) {
cases_len += 1;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime(
&case_block,
.special,
else_prong.body,
else_prong.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .special_else,
} }),
&.{.bool_true},
.bool_true,
else_prong.has_tag_capture,
);
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_true));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
if (false_count == 0) {
cases_len += 1;
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
if (emit_bb) try sema.emitBackwardBranch(block, else_prong_src);
emit_bb = true;
const prong_hint = try spa.analyzeProngRuntime(
&case_block,
.special,
else_prong.body,
else_prong.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .special_else,
} }),
&.{.bool_false},
.bool_false,
else_prong.has_tag_capture,
);
try branch_hints.append(gpa, prong_hint);
try cases_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr.Case).@"struct".fields.len +
1 + // `item`, no ranges
case_block.instructions.items.len);
cases_extra.appendSliceAssumeCapacity(&payloadToExtraItems(Air.SwitchBr.Case{
.items_len = 1,
.ranges_len = 0,
.body_len = @intCast(case_block.instructions.items.len),
}));
cases_extra.appendAssumeCapacity(@intFromEnum(Air.Inst.Ref.bool_false));
cases_extra.appendSliceAssumeCapacity(@ptrCast(case_block.instructions.items));
}
},
else => return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{
operand_ty.fmt(pt),
}),
};
case_block.instructions.shrinkRetainingCapacity(0);
case_block.error_return_trace_index = child_block.error_return_trace_index;
if (zcu.backendSupportsFeature(.is_named_enum_value) and
else_prong.body.len != 0 and block.wantSafety() and
operand_ty.zigTypeTag(zcu) == .@"enum" and
(!operand_ty.isNonexhaustiveEnum(zcu) or union_originally))
{
try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
const ok = try case_block.addUnOp(.is_named_enum_value, operand);
try sema.addSafetyCheck(&case_block, src, ok, .corrupt_switch);
}
const else_src_idx: LazySrcLoc.Offset.SwitchCaseIndex = if (else_prong_is_underscore)
.special_under
else
.special_else;
const analyze_body = if (union_originally and !else_prong.is_inline)
for (seen_enum_fields, 0..) |seen_field, index| {
if (seen_field != null) continue;
const union_obj = zcu.typeToUnion(maybe_union_ty).?;
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[index]);
if (field_ty.zigTypeTag(zcu) != .noreturn) break true;
} else false
else
true;
const else_hint: std.builtin.BranchHint = if (else_prong.body.len != 0 and err_set and
try sema.maybeErrorUnwrap(&case_block, else_prong.body, operand, operand_src, allow_err_code_unwrap))
h: {
// nothing to do here. weight against error branch
break :h .unlikely;
} else if (else_prong.body.len != 0 and analyze_body and !else_prong.is_inline) h: {
break :h try spa.analyzeProngRuntime(
&case_block,
.special,
else_prong.body,
else_prong.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = else_src_idx,
} }),
undefined, // case_vals may be undefined for special prongs
.none,
false,
);
} else h: {
// We still need a terminator in this block, but we have proven
// that it is unreachable.
if (case_block.wantSafety()) {
try sema.zirDbgStmt(&case_block, cond_dbg_node_index);
try sema.safetyPanic(&case_block, src, .corrupt_switch);
} else {
_ = try case_block.addNoOp(.unreach);
}
// Safety check / unreachable branches are cold.
break :h .cold;
};
try branch_hints.append(gpa, else_hint);
break :else_body case_block.instructions.items;
} else else_body: {
try branch_hints.append(gpa, .none);
break :else_body &.{};
};
assert(branch_hints.items.len == cases_len + 1);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.SwitchBr).@"struct".fields.len +
cases_extra.items.len + else_body.len +
(std.math.divCeil(usize, branch_hints.items.len, 10) catch unreachable)); // branch hints
const payload_index = sema.addExtraAssumeCapacity(Air.SwitchBr{
.cases_len = @intCast(cases_len),
.else_body_len = @intCast(else_body.len),
});
{
// Add branch hints.
var cur_bag: u32 = 0;
for (branch_hints.items, 0..) |hint, idx| {
const idx_in_bag = idx % 10;
cur_bag |= @as(u32, @intFromEnum(hint)) << @intCast(idx_in_bag * 3);
if (idx_in_bag == 9) {
sema.air_extra.appendAssumeCapacity(cur_bag);
cur_bag = 0;
}
}
if (branch_hints.items.len % 10 != 0) {
sema.air_extra.appendAssumeCapacity(cur_bag);
}
}
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(cases_extra.items));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_body));
const has_any_continues = spa.operand == .loop and child_block.label.?.merges.extra_insts.items.len > 0;
return try child_block.addInst(.{
.tag = if (has_any_continues) .loop_switch_br else .switch_br,
.data = .{ .pl_op = .{
.operand = operand,
.payload = payload_index,
} },
});
}
fn resolveSwitchComptimeLoop(
sema: *Sema,
init_spa: SwitchProngAnalysis,
child_block: *Block,
maybe_ptr_operand_ty: Type,
cond_ty: Type,
init_cond_val: Value,
switch_node_offset: std.zig.Ast.Node.Offset,
special_members_only: ?SpecialProng,
special_generic: SpecialProng,
special_generic_is_under: bool,
case_vals: std.ArrayList(Air.Inst.Ref),
scalar_cases_len: u32,
multi_cases_len: u32,
err_set: bool,
empty_enum: bool,
operand_is_ref: bool,
) CompileError!Air.Inst.Ref {
var spa = init_spa;
var cond_val = init_cond_val;
while (true) {
if (resolveSwitchComptime(
sema,
spa,
child_block,
spa.operand.simple.cond,
cond_val,
cond_ty,
switch_node_offset,
special_members_only,
special_generic,
special_generic_is_under,
case_vals,
scalar_cases_len,
multi_cases_len,
err_set,
empty_enum,
)) |result| {
return result;
} else |err| switch (err) {
error.ComptimeBreak => {
const break_inst = sema.code.instructions.get(@intFromEnum(sema.comptime_break_inst));
if (break_inst.tag != .switch_continue) return error.ComptimeBreak;
const extra = sema.code.extraData(Zir.Inst.Break, break_inst.data.@"break".payload_index).data;
if (extra.block_inst != spa.switch_block_inst) return error.ComptimeBreak;
// This is a `switch_continue` targeting this block. Change the operand and start over.
const src = child_block.nodeOffset(extra.operand_src_node.unwrap().?);
const new_operand_uncoerced = try sema.resolveInst(break_inst.data.@"break".operand);
const new_operand = try sema.coerce(child_block, maybe_ptr_operand_ty, new_operand_uncoerced, src);
try sema.emitBackwardBranch(child_block, src);
const val, const ref = if (operand_is_ref)
.{ try sema.analyzeLoad(child_block, src, new_operand, src), new_operand }
else
.{ new_operand, undefined };
const cond_ref = try sema.switchCond(child_block, src, val);
cond_val = try sema.resolveConstDefinedValue(child_block, src, cond_ref, null);
spa.operand = .{ .simple = .{
.by_val = val,
.by_ref = ref,
.cond = cond_ref,
} };
},
else => |e| return e,
}
}
}
fn resolveSwitchComptime(
sema: *Sema,
spa: SwitchProngAnalysis,
child_block: *Block,
cond_operand: Air.Inst.Ref,
operand_val: Value,
operand_ty: Type,
switch_node_offset: std.zig.Ast.Node.Offset,
special_members_only: ?SpecialProng,
special_generic: SpecialProng,
special_generic_is_under: bool,
case_vals: std.ArrayList(Air.Inst.Ref),
scalar_cases_len: u32,
multi_cases_len: u32,
err_set: bool,
empty_enum: bool,
) CompileError!Air.Inst.Ref {
const zcu = sema.pt.zcu;
const merges = &child_block.label.?.merges;
const resolved_operand_val = try sema.resolveLazyValue(operand_val);
var extra_index: usize = special_generic.end;
{
var scalar_i: usize = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
const body = sema.code.bodySlice(extra_index, info.body_len);
extra_index += info.body_len;
const item = case_vals.items[scalar_i];
const item_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
if (operand_val.eql(item_val, operand_ty, sema.pt.zcu)) {
if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
return spa.resolveProngComptime(
child_block,
.normal,
body,
info.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
} }),
&.{item},
if (info.is_inline) cond_operand else .none,
info.has_tag_capture,
merges,
);
}
}
}
{
var multi_i: usize = 0;
var case_val_idx: usize = scalar_cases_len;
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + items_len;
const body = sema.code.bodySlice(extra_index + 2 * ranges_len, info.body_len);
const items = case_vals.items[case_val_idx..][0..items_len];
case_val_idx += items_len;
for (items) |item| {
// Validation above ensured these will succeed.
const item_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, item, undefined) catch unreachable;
if (operand_val.eql(item_val, operand_ty, sema.pt.zcu)) {
if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
return spa.resolveProngComptime(
child_block,
.normal,
body,
info.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
} }),
items,
if (info.is_inline) cond_operand else .none,
info.has_tag_capture,
merges,
);
}
}
var range_i: usize = 0;
while (range_i < ranges_len) : (range_i += 1) {
const range_items = case_vals.items[case_val_idx..][0..2];
extra_index += 2;
case_val_idx += 2;
// Validation above ensured these will succeed.
const first_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, range_items[0], undefined) catch unreachable;
const last_val = sema.resolveConstDefinedValue(child_block, LazySrcLoc.unneeded, range_items[1], undefined) catch unreachable;
if ((try sema.compareAll(resolved_operand_val, .gte, first_val, operand_ty)) and
(try sema.compareAll(resolved_operand_val, .lte, last_val, operand_ty)))
{
if (err_set) try sema.maybeErrorUnwrapComptime(child_block, body, cond_operand);
return spa.resolveProngComptime(
child_block,
.normal,
body,
info.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
} }),
undefined, // case_vals may be undefined for ranges
if (info.is_inline) cond_operand else .none,
info.has_tag_capture,
merges,
);
}
}
extra_index += info.body_len;
}
}
if (err_set) try sema.maybeErrorUnwrapComptime(child_block, special_generic.body, cond_operand);
if (empty_enum) {
return .void_value;
}
if (special_members_only) |special| {
assert(operand_ty.isNonexhaustiveEnum(zcu));
if (operand_ty.enumTagFieldIndex(operand_val, zcu)) |_| {
return spa.resolveProngComptime(
child_block,
.special,
special.body,
special.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = .special_else,
} }),
undefined, // case_vals may be undefined for special prongs
if (special.is_inline) cond_operand else .none,
special.has_tag_capture,
merges,
);
}
}
return spa.resolveProngComptime(
child_block,
.special,
special_generic.body,
special_generic.capture,
child_block.src(.{ .switch_capture = .{
.switch_node_offset = switch_node_offset,
.case_idx = if (special_generic_is_under)
.special_under
else
.special_else,
} }),
undefined, // case_vals may be undefined for special prongs
if (special_generic.is_inline) cond_operand else .none,
special_generic.has_tag_capture,
merges,
);
}
const RangeSetUnhandledIterator = struct {
pt: Zcu.PerThread,
cur: ?InternPool.Index,
max: InternPool.Index,
range_i: usize,
ranges: []const RangeSet.Range,
limbs: []math.big.Limb,
const preallocated_limbs = math.big.int.calcTwosCompLimbCount(128);
fn init(sema: *Sema, ty: Type, range_set: RangeSet) !RangeSetUnhandledIterator {
const pt = sema.pt;
const int_type = pt.zcu.intern_pool.indexToKey(ty.toIntern()).int_type;
const needed_limbs = math.big.int.calcTwosCompLimbCount(int_type.bits);
return .{
.pt = pt,
.cur = (try ty.minInt(pt, ty)).toIntern(),
.max = (try ty.maxInt(pt, ty)).toIntern(),
.range_i = 0,
.ranges = range_set.ranges.items,
.limbs = if (needed_limbs > preallocated_limbs)
try sema.arena.alloc(math.big.Limb, needed_limbs)
else
&.{},
};
}
fn addOne(it: *const RangeSetUnhandledIterator, val: InternPool.Index) !?InternPool.Index {
if (val == it.max) return null;
const int = it.pt.zcu.intern_pool.indexToKey(val).int;
switch (int.storage) {
inline .u64, .i64 => |val_int| {
const next_int = @addWithOverflow(val_int, 1);
if (next_int[1] == 0)
return (try it.pt.intValue(.fromInterned(int.ty), next_int[0])).toIntern();
},
.big_int => {},
.lazy_align, .lazy_size => unreachable,
}
var val_space: InternPool.Key.Int.Storage.BigIntSpace = undefined;
const val_bigint = int.storage.toBigInt(&val_space);
var result_limbs: [preallocated_limbs]math.big.Limb = undefined;
var result_bigint = math.big.int.Mutable.init(
if (it.limbs.len > 0) it.limbs else &result_limbs,
0,
);
result_bigint.addScalar(val_bigint, 1);
return (try it.pt.intValue_big(.fromInterned(int.ty), result_bigint.toConst())).toIntern();
}
fn next(it: *RangeSetUnhandledIterator) !?InternPool.Index {
var cur = it.cur orelse return null;
while (it.range_i < it.ranges.len and cur == it.ranges[it.range_i].first) {
defer it.range_i += 1;
cur = (try it.addOne(it.ranges[it.range_i].last)) orelse {
it.cur = null;
return null;
};
}
it.cur = try it.addOne(cur);
return cur;
}
};
const ResolvedSwitchItem = struct {
ref: Air.Inst.Ref,
val: InternPool.Index,
};
fn resolveSwitchItemVal(
sema: *Sema,
block: *Block,
item_ref: Zir.Inst.Ref,
/// Coerce `item_ref` to this type.
coerce_ty: Type,
item_src: LazySrcLoc,
) CompileError!ResolvedSwitchItem {
const uncoerced_item = try sema.resolveInst(item_ref);
// Constructing a LazySrcLoc is costly because we only have the switch AST node.
// Only if we know for sure we need to report a compile error do we resolve the
// full source locations.
const item = try sema.coerce(block, coerce_ty, uncoerced_item, item_src);
const maybe_lazy = try sema.resolveConstDefinedValue(block, item_src, item, .{ .simple = .switch_item });
const val = try sema.resolveLazyValue(maybe_lazy);
const new_item = if (val.toIntern() != maybe_lazy.toIntern()) blk: {
break :blk Air.internedToRef(val.toIntern());
} else item;
return .{ .ref = new_item, .val = val.toIntern() };
}
fn validateErrSetSwitch(
sema: *Sema,
block: *Block,
seen_errors: *SwitchErrorSet,
case_vals: *std.ArrayList(Air.Inst.Ref),
operand_ty: Type,
inst_data: @FieldType(Zir.Inst.Data, "pl_node"),
scalar_cases_len: u32,
multi_cases_len: u32,
else_case: struct { body: []const Zir.Inst.Index, end: usize, src: LazySrcLoc },
has_else: bool,
) CompileError!?Type {
const gpa = sema.gpa;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const src_node_offset = inst_data.src_node;
const src = block.nodeOffset(src_node_offset);
var extra_index: usize = else_case.end;
{
var scalar_i: u32 = 0;
while (scalar_i < scalar_cases_len) : (scalar_i += 1) {
const item_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1 + info.body_len;
case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
block,
seen_errors,
item_ref,
operand_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .scalar, .index = @intCast(scalar_i) },
.item_idx = .{ .kind = .single, .index = 0 },
} }),
));
}
}
{
var multi_i: u32 = 0;
while (multi_i < multi_cases_len) : (multi_i += 1) {
const items_len = sema.code.extra[extra_index];
extra_index += 1;
const ranges_len = sema.code.extra[extra_index];
extra_index += 1;
const info: Zir.Inst.SwitchBlock.ProngInfo = @bitCast(sema.code.extra[extra_index]);
extra_index += 1;
const items = sema.code.refSlice(extra_index, items_len);
extra_index += items_len + info.body_len;
try case_vals.ensureUnusedCapacity(gpa, items.len);
for (items, 0..) |item_ref, item_i| {
case_vals.appendAssumeCapacity(try sema.validateSwitchItemError(
block,
seen_errors,
item_ref,
operand_ty,
block.src(.{ .switch_case_item = .{
.switch_node_offset = src_node_offset,
.case_idx = .{ .kind = .multi, .index = @intCast(multi_i) },
.item_idx = .{ .kind = .single, .index = @intCast(item_i) },
} }),
));
}
try sema.validateSwitchNoRange(block, ranges_len, operand_ty, src_node_offset);
}
}
switch (try sema.resolveInferredErrorSetTy(block, src, operand_ty.toIntern())) {
.anyerror_type => {
if (!has_else) {
return sema.fail(
block,
src,
"else prong required when switching on type 'anyerror'",
.{},
);
}
return .anyerror;
},
else => |err_set_ty_index| else_validation: {
const error_names = ip.indexToKey(err_set_ty_index).error_set_type.names;
var maybe_msg: ?*Zcu.ErrorMsg = null;
errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa);
for (error_names.get(ip)) |error_name| {
if (!seen_errors.contains(error_name) and !has_else) {
const msg = maybe_msg orelse blk: {
maybe_msg = try sema.errMsg(
src,
"switch must handle all possibilities",
.{},
);
break :blk maybe_msg.?;
};
try sema.errNote(
src,
msg,
"unhandled error value: 'error.{f}'",
.{error_name.fmt(ip)},
);
}
}
if (maybe_msg) |msg| {
maybe_msg = null;
try sema.addDeclaredHereNote(msg, operand_ty);
return sema.failWithOwnedErrorMsg(block, msg);
}
if (has_else and seen_errors.count() == error_names.len) {
// In order to enable common patterns for generic code allow simple else bodies
// else => unreachable,
// else => return,
// else => |e| return e,
// even if all the possible errors were already handled.
const tags = sema.code.instructions.items(.tag);
const datas = sema.code.instructions.items(.data);
for (else_case.body) |else_inst| switch (tags[@intFromEnum(else_inst)]) {
.dbg_stmt,
.dbg_var_val,
.ret_type,
.as_node,
.ret_node,
.@"unreachable",
.@"defer",
.defer_err_code,
.err_union_code,
.ret_err_value_code,
.save_err_ret_index,
.restore_err_ret_index_unconditional,
.restore_err_ret_index_fn_entry,
.is_non_err,
.ret_is_non_err,
.condbr,
=> {},
.extended => switch (datas[@intFromEnum(else_inst)].extended.opcode) {
.restore_err_ret_index => {},
else => break,
},
else => break,
} else break :else_validation;
return sema.fail(
block,
else_case.src,
"unreachable else prong; all cases already handled",
.{},
);
}
var names: InferredErrorSet.NameMap = .{};
try names.ensureUnusedCapacity(sema.arena, error_names.len);
for (error_names.get(ip)) |error_name| {
if (seen_errors.contains(error_name)) continue;
names.putAssumeCapacityNoClobber(error_name, {});
}
// No need to keep the hash map metadata correct; here we
// extract the (sorted) keys only.
return try pt.errorSetFromUnsortedNames(names.keys());
},
}
return null;
}
fn validateSwitchRange(
sema: *Sema,
block: *Block,
range_set: *RangeSet,
first_ref: Zir.Inst.Ref,
last_ref: Zir.Inst.Ref,
operand_ty: Type,
item_src: LazySrcLoc,
) CompileError![2]Air.Inst.Ref {
const first_src: LazySrcLoc = .{
.base_node_inst = item_src.base_node_inst,
.offset = .{ .switch_case_item_range_first = item_src.offset.switch_case_item },
};
const last_src: LazySrcLoc = .{
.base_node_inst = item_src.base_node_inst,
.offset = .{ .switch_case_item_range_last = item_src.offset.switch_case_item },
};
const first = try sema.resolveSwitchItemVal(block, first_ref, operand_ty, first_src);
const last = try sema.resolveSwitchItemVal(block, last_ref, operand_ty, last_src);
if (try Value.fromInterned(first.val).compareAll(.gt, Value.fromInterned(last.val), operand_ty, sema.pt)) {
return sema.fail(block, item_src, "range start value is greater than the end value", .{});
}
const maybe_prev_src = try range_set.add(first.val, last.val, item_src);
try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
return .{ first.ref, last.ref };
}
fn validateSwitchItemInt(
sema: *Sema,
block: *Block,
range_set: *RangeSet,
item_ref: Zir.Inst.Ref,
operand_ty: Type,
item_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);
const maybe_prev_src = try range_set.add(item.val, item.val, item_src);
try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
return item.ref;
}
fn validateSwitchItemEnum(
sema: *Sema,
block: *Block,
seen_fields: []?LazySrcLoc,
range_set: *RangeSet,
item_ref: Zir.Inst.Ref,
operand_ty: Type,
item_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const ip = &sema.pt.zcu.intern_pool;
const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);
const int = ip.indexToKey(item.val).enum_tag.int;
const field_index = ip.loadEnumType(ip.typeOf(item.val)).tagValueIndex(ip, int) orelse {
const maybe_prev_src = try range_set.add(int, int, item_src);
try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
return item.ref;
};
const maybe_prev_src = seen_fields[field_index];
seen_fields[field_index] = item_src;
try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
return item.ref;
}
fn validateSwitchItemError(
sema: *Sema,
block: *Block,
seen_errors: *SwitchErrorSet,
item_ref: Zir.Inst.Ref,
operand_ty: Type,
item_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);
const error_name = sema.pt.zcu.intern_pool.indexToKey(item.val).err.name;
const maybe_prev_src = if (try seen_errors.fetchPut(error_name, item_src)) |prev|
prev.value
else
null;
try sema.validateSwitchDupe(block, maybe_prev_src, item_src);
return item.ref;
}
fn validateSwitchDupe(
sema: *Sema,
block: *Block,
maybe_prev_src: ?LazySrcLoc,
item_src: LazySrcLoc,
) CompileError!void {
const prev_item_src = maybe_prev_src orelse return;
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(
item_src,
"duplicate switch value",
.{},
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(
prev_item_src,
msg,
"previous value here",
.{},
);
break :msg msg;
});
}
fn validateSwitchItemBool(
sema: *Sema,
block: *Block,
true_count: *u8,
false_count: *u8,
item_ref: Zir.Inst.Ref,
item_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const item = try sema.resolveSwitchItemVal(block, item_ref, .bool, item_src);
if (Value.fromInterned(item.val).toBool()) {
true_count.* += 1;
} else {
false_count.* += 1;
}
if (true_count.* > 1 or false_count.* > 1) {
return sema.fail(block, item_src, "duplicate switch value", .{});
}
return item.ref;
}
const ValueSrcMap = std.AutoHashMapUnmanaged(InternPool.Index, LazySrcLoc);
fn validateSwitchItemSparse(
sema: *Sema,
block: *Block,
seen_values: *ValueSrcMap,
item_ref: Zir.Inst.Ref,
operand_ty: Type,
item_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const item = try sema.resolveSwitchItemVal(block, item_ref, operand_ty, item_src);
const kv = try seen_values.fetchPut(sema.gpa, item.val, item_src) orelse return item.ref;
try sema.validateSwitchDupe(block, kv.value, item_src);
unreachable;
}
fn validateSwitchNoRange(
sema: *Sema,
block: *Block,
ranges_len: u32,
operand_ty: Type,
src_node_offset: std.zig.Ast.Node.Offset,
) CompileError!void {
if (ranges_len == 0)
return;
const operand_src = block.src(.{ .node_offset_switch_operand = src_node_offset });
const range_src = block.src(.{ .node_offset_switch_range = src_node_offset });
const msg = msg: {
const msg = try sema.errMsg(
operand_src,
"ranges not allowed when switching on type '{f}'",
.{operand_ty.fmt(sema.pt)},
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(
range_src,
msg,
"range here",
.{},
);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn maybeErrorUnwrap(
sema: *Sema,
block: *Block,
body: []const Zir.Inst.Index,
operand: Air.Inst.Ref,
operand_src: LazySrcLoc,
allow_err_code_inst: bool,
) !bool {
const pt = sema.pt;
const zcu = pt.zcu;
const tags = sema.code.instructions.items(.tag);
for (body) |inst| {
switch (tags[@intFromEnum(inst)]) {
.@"unreachable" => if (!block.wantSafety()) return false,
.err_union_code => if (!allow_err_code_inst) return false,
.save_err_ret_index,
.dbg_stmt,
.str,
.as_node,
.panic,
=> {},
else => return false,
}
}
for (body) |inst| {
const air_inst = switch (tags[@intFromEnum(inst)]) {
.err_union_code => continue,
.dbg_stmt => {
try sema.zirDbgStmt(block, inst);
continue;
},
.save_err_ret_index => {
try sema.zirSaveErrRetIndex(block, inst);
continue;
},
.str => try sema.zirStr(inst),
.as_node => try sema.zirAsNode(block, inst),
.@"unreachable" => {
try safetyPanicUnwrapError(sema, block, operand_src, operand);
return true;
},
.panic => {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const msg_inst = try sema.resolveInst(inst_data.operand);
const panic_fn = try getBuiltin(sema, operand_src, .@"panic.call");
const args: [2]Air.Inst.Ref = .{ msg_inst, .null_value };
try sema.callBuiltin(block, operand_src, Air.internedToRef(panic_fn), .auto, &args, .@"safety check");
return true;
},
else => unreachable,
};
if (sema.typeOf(air_inst).isNoReturn(zcu))
return true;
sema.inst_map.putAssumeCapacity(inst, air_inst);
}
unreachable;
}
fn maybeErrorUnwrapCondbr(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, cond: Zir.Inst.Ref, cond_src: LazySrcLoc) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const index = cond.toIndex() orelse return;
if (sema.code.instructions.items(.tag)[@intFromEnum(index)] != .is_non_err) return;
const err_inst_data = sema.code.instructions.items(.data)[@intFromEnum(index)].un_node;
const err_operand = try sema.resolveInst(err_inst_data.operand);
const operand_ty = sema.typeOf(err_operand);
if (operand_ty.zigTypeTag(zcu) == .error_set) {
try sema.maybeErrorUnwrapComptime(block, body, err_operand);
return;
}
if (try sema.resolveDefinedValue(block, cond_src, err_operand)) |val| {
if (!operand_ty.isError(zcu)) return;
if (val.getErrorName(zcu) == .none) return;
try sema.maybeErrorUnwrapComptime(block, body, err_operand);
}
}
fn maybeErrorUnwrapComptime(sema: *Sema, block: *Block, body: []const Zir.Inst.Index, operand: Air.Inst.Ref) !void {
const tags = sema.code.instructions.items(.tag);
const inst = for (body) |inst| {
switch (tags[@intFromEnum(inst)]) {
.dbg_stmt,
.save_err_ret_index,
=> {},
.@"unreachable" => break inst,
else => return,
}
} else return;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"unreachable";
const src = block.nodeOffset(inst_data.src_node);
if (try sema.resolveDefinedValue(block, src, operand)) |val| {
if (val.getErrorName(sema.pt.zcu).unwrap()) |name| {
return sema.failWithComptimeErrorRetTrace(block, src, name);
}
}
}
fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const name_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const ty = try sema.resolveType(block, ty_src, extra.lhs);
const field_name = try sema.resolveConstStringIntern(block, name_src, extra.rhs, .{ .simple = .field_name });
try ty.resolveFields(pt);
const ip = &zcu.intern_pool;
const has_field = hf: {
switch (ip.indexToKey(ty.toIntern())) {
.ptr_type => |ptr_type| switch (ptr_type.flags.size) {
.slice => {
if (field_name.eqlSlice("ptr", ip)) break :hf true;
if (field_name.eqlSlice("len", ip)) break :hf true;
break :hf false;
},
else => {},
},
.tuple_type => |tuple| {
const field_index = field_name.toUnsigned(ip) orelse break :hf false;
break :hf field_index < tuple.types.len;
},
.struct_type => {
break :hf ip.loadStructType(ty.toIntern()).nameIndex(ip, field_name) != null;
},
.union_type => {
const union_type = ip.loadUnionType(ty.toIntern());
break :hf union_type.loadTagType(ip).nameIndex(ip, field_name) != null;
},
.enum_type => {
break :hf ip.loadEnumType(ty.toIntern()).nameIndex(ip, field_name) != null;
},
.array_type => break :hf field_name.eqlSlice("len", ip),
else => {},
}
return sema.fail(block, ty_src, "type '{f}' does not support '@hasField'", .{
ty.fmt(pt),
});
};
return if (has_field) .bool_true else .bool_false;
}
fn zirHasDecl(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const container_type = try sema.resolveType(block, lhs_src, extra.lhs);
const decl_name = try sema.resolveConstStringIntern(block, rhs_src, extra.rhs, .{ .simple = .decl_name });
try sema.checkNamespaceType(block, lhs_src, container_type);
const namespace = container_type.getNamespace(zcu).unwrap() orelse return .bool_false;
if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {
if (lookup.accessible) {
return .bool_true;
}
}
return .bool_false;
}
fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_tok;
const extra = sema.code.extraData(Zir.Inst.Import, inst_data.payload_index).data;
const operand_src = block.tokenOffset(inst_data.src_tok);
const operand = sema.code.nullTerminatedString(extra.path);
const result = pt.doImport(block.getFileScope(zcu), operand) catch |err| switch (err) {
error.ModuleNotFound => return sema.fail(block, operand_src, "no module named '{s}' available within module '{s}'", .{
operand, block.getFileScope(zcu).mod.?.fully_qualified_name,
}),
error.IllegalZigImport => unreachable, // caught before semantic analysis
error.OutOfMemory => |e| return e,
};
const file_index = result.file;
const file = zcu.fileByIndex(file_index);
switch (file.getMode()) {
.zig => {
try pt.ensureFileAnalyzed(file_index);
const ty = zcu.fileRootType(file_index);
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(operand_src, ty);
return Air.internedToRef(ty);
},
.zon => {
const res_ty: InternPool.Index = b: {
if (extra.res_ty == .none) break :b .none;
const res_ty_inst = try sema.resolveInst(extra.res_ty);
const res_ty = try sema.analyzeAsType(block, operand_src, res_ty_inst);
if (res_ty.isGenericPoison()) break :b .none;
break :b res_ty.toIntern();
};
try sema.declareDependency(.{ .zon_file = file_index });
const interned = try LowerZon.run(
sema,
file,
file_index,
res_ty,
operand_src,
block,
);
return Air.internedToRef(interned);
},
}
}
fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const name = try sema.resolveConstString(block, operand_src, inst_data.operand, .{ .simple = .operand_embedFile });
if (name.len == 0) {
return sema.fail(block, operand_src, "file path name cannot be empty", .{});
}
const ef_idx = pt.embedFile(block.getFileScope(zcu), name) catch |err| switch (err) {
error.ImportOutsideModulePath => {
return sema.fail(block, operand_src, "embed of file outside package path: '{s}'", .{name});
},
error.CurrentWorkingDirectoryUnlinked => {
// TODO: this should be some kind of retryable failure, in case the cwd is put back
return sema.fail(block, operand_src, "unable to resolve '{s}': working directory has been unlinked", .{name});
},
error.OutOfMemory => |e| return e,
error.Canceled => |e| return e,
};
try sema.declareDependency(.{ .embed_file = ef_idx });
const result = ef_idx.get(zcu);
if (result.val == .none) {
return sema.fail(block, operand_src, "unable to open '{s}': {s}", .{ name, @errorName(result.err.?) });
}
return Air.internedToRef(result.val);
}
fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
inst_data.get(sema.code),
.no_embedded_nulls,
);
_ = try pt.getErrorValue(name);
const error_set_type = try pt.singleErrorSetType(name);
return Air.internedToRef((try pt.intern(.{ .err = .{
.ty = error_set_type.toIntern(),
.name = name,
} })));
}
fn zirShl(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
air_tag: Air.Inst.Tag,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const src = block.nodeOffset(inst_data.src_node);
const lhs_src, const rhs_src = switch (air_tag) {
.shl, .shl_sat => .{
block.src(.{ .node_offset_bin_lhs = inst_data.src_node }),
block.src(.{ .node_offset_bin_rhs = inst_data.src_node }),
},
.shl_exact => .{
block.builtinCallArgSrc(inst_data.src_node, 0),
block.builtinCallArgSrc(inst_data.src_node, 1),
},
else => unreachable,
};
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
const scalar_ty = lhs_ty.scalarType(zcu);
const scalar_rhs_ty = rhs_ty.scalarType(zcu);
// AstGen currently forces the rhs of `<<` to coerce to the correct type before the `.shl` instruction, so
// we already know `scalar_rhs_ty` is valid for `.shl` -- we only need to validate for `.shl_sat`.
if (air_tag == .shl_sat) _ = try sema.checkIntType(block, rhs_src, scalar_rhs_ty);
const maybe_lhs_val = try sema.resolveValueResolveLazy(lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(rhs);
const runtime_src = rs: {
if (maybe_rhs_val) |rhs_val| {
if (maybe_lhs_val) |lhs_val| {
return .fromValue(try arith.shl(sema, block, lhs_ty, lhs_val, rhs_val, src, lhs_src, rhs_src, switch (air_tag) {
.shl => .shl,
.shl_sat => .shl_sat,
.shl_exact => .shl_exact,
else => unreachable,
}));
}
if (rhs_val.isUndef(zcu)) switch (air_tag) {
.shl_sat => return pt.undefRef(lhs_ty),
.shl, .shl_exact => return sema.failWithUseOfUndef(block, rhs_src, null),
else => unreachable,
};
const bits = scalar_ty.intInfo(zcu).bits;
switch (rhs_ty.zigTypeTag(zcu)) {
.int, .comptime_int => {
switch (try rhs_val.orderAgainstZeroSema(pt)) {
.gt => {
if (air_tag != .shl_sat) {
var rhs_space: Value.BigIntSpace = undefined;
const rhs_bigint = try rhs_val.toBigIntSema(&rhs_space, pt);
if (rhs_bigint.orderAgainstScalar(bits) != .lt) {
return sema.failWithTooLargeShiftAmount(block, lhs_ty, rhs_val, rhs_src, null);
}
}
},
.eq => return lhs,
.lt => return sema.failWithNegativeShiftAmount(block, rhs_src, rhs_val, null),
}
},
.vector => {
var any_positive: bool = false;
for (0..rhs_ty.vectorLen(zcu)) |elem_idx| {
const rhs_elem = try rhs_val.elemValue(pt, elem_idx);
if (rhs_elem.isUndef(zcu)) switch (air_tag) {
.shl_sat => continue,
.shl, .shl_exact => return sema.failWithUseOfUndef(block, rhs_src, elem_idx),
else => unreachable,
};
switch (try rhs_elem.orderAgainstZeroSema(pt)) {
.gt => {
if (air_tag != .shl_sat) {
var rhs_elem_space: Value.BigIntSpace = undefined;
const rhs_elem_bigint = try rhs_elem.toBigIntSema(&rhs_elem_space, pt);
if (rhs_elem_bigint.orderAgainstScalar(bits) != .lt) {
return sema.failWithTooLargeShiftAmount(block, lhs_ty, rhs_elem, rhs_src, elem_idx);
}
}
any_positive = true;
},
.eq => {},
.lt => return sema.failWithNegativeShiftAmount(block, rhs_src, rhs_elem, elem_idx),
}
}
if (!any_positive) return lhs;
},
else => unreachable,
}
break :rs lhs_src;
} else {
if (air_tag == .shl_sat and scalar_rhs_ty.isSignedInt(zcu)) {
return sema.fail(block, rhs_src, "shift by signed type '{f}'", .{rhs_ty.fmt(pt)});
}
if (scalar_ty.toIntern() == .comptime_int_type) {
return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be comptime-known", .{});
}
if (maybe_lhs_val) |lhs_val| {
switch (air_tag) {
.shl_sat => if (lhs_val.isUndef(zcu)) return pt.undefRef(lhs_ty),
.shl, .shl_exact => try sema.checkAllScalarsDefined(block, lhs_src, lhs_val),
else => unreachable,
}
if (try lhs_val.compareAllWithZeroSema(.eq, pt)) return lhs;
}
}
break :rs rhs_src;
};
const rt_rhs: Air.Inst.Ref = switch (air_tag) {
else => unreachable,
.shl, .shl_exact => rhs,
// The backend can handle a large runtime rhs better than we can, but
// we can limit a large comptime rhs better here. This also has the
// necessary side effect of preventing rhs from being a `comptime_int`.
.shl_sat => if (maybe_rhs_val) |rhs_val| .fromValue(rt_rhs: {
const bit_count = scalar_ty.intInfo(zcu).bits;
const rt_rhs_scalar_ty = try pt.smallestUnsignedInt(bit_count);
if (!rhs_ty.isVector(zcu)) break :rt_rhs try pt.intValue(
rt_rhs_scalar_ty,
@min(try rhs_val.getUnsignedIntSema(pt) orelse bit_count, bit_count),
);
const rhs_len = rhs_ty.vectorLen(zcu);
const rhs_elems = try sema.arena.alloc(InternPool.Index, rhs_len);
for (rhs_elems, 0..) |*rhs_elem, i| rhs_elem.* = (try pt.intValue(
rt_rhs_scalar_ty,
@min(try (try rhs_val.elemValue(pt, i)).getUnsignedIntSema(pt) orelse bit_count, bit_count),
)).toIntern();
break :rt_rhs try pt.aggregateValue(try pt.vectorType(.{
.len = rhs_len,
.child = rt_rhs_scalar_ty.toIntern(),
}), rhs_elems);
}) else rhs,
};
try sema.requireRuntimeBlock(block, src, runtime_src);
if (block.wantSafety()) {
const bit_count = scalar_ty.intInfo(zcu).bits;
if (air_tag != .shl_sat and !std.math.isPowerOfTwo(bit_count)) {
const bit_count_val = try pt.intValue(scalar_rhs_ty, bit_count);
const ok = if (rhs_ty.zigTypeTag(zcu) == .vector) ok: {
const bit_count_inst = Air.internedToRef((try sema.splat(rhs_ty, bit_count_val)).toIntern());
const lt = try block.addCmpVector(rhs, bit_count_inst, .lt);
break :ok try block.addReduce(lt, .And);
} else ok: {
const bit_count_inst = Air.internedToRef(bit_count_val.toIntern());
break :ok try block.addBinOp(.cmp_lt, rhs, bit_count_inst);
};
try sema.addSafetyCheck(block, src, ok, .shift_rhs_too_big);
}
if (air_tag == .shl_exact) {
const op_ov_tuple_ty = try pt.overflowArithmeticTupleType(lhs_ty);
const op_ov = try block.addInst(.{
.tag = .shl_with_overflow,
.data = .{ .ty_pl = .{
.ty = .fromIntern(op_ov_tuple_ty.toIntern()),
.payload = try sema.addExtra(Air.Bin{
.lhs = lhs,
.rhs = rhs,
}),
} },
});
const ov_bit = try sema.tupleFieldValByIndex(block, op_ov, 1, op_ov_tuple_ty);
const any_ov_bit = if (lhs_ty.zigTypeTag(zcu) == .vector)
try block.addReduce(ov_bit, .Or)
else
ov_bit;
const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, .zero_u1);
try sema.addSafetyCheck(block, src, no_ov, .shl_overflow);
return sema.tupleFieldValByIndex(block, op_ov, 0, op_ov_tuple_ty);
}
}
return block.addBinOp(air_tag, lhs, rt_rhs);
}
fn zirShr(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
air_tag: Air.Inst.Tag,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const src = block.nodeOffset(inst_data.src_node);
const lhs_src = switch (air_tag) {
.shr => block.src(.{ .node_offset_bin_lhs = inst_data.src_node }),
.shr_exact => block.builtinCallArgSrc(inst_data.src_node, 0),
else => unreachable,
};
const rhs_src = switch (air_tag) {
.shr => block.src(.{ .node_offset_bin_rhs = inst_data.src_node }),
.shr_exact => block.builtinCallArgSrc(inst_data.src_node, 1),
else => unreachable,
};
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
const scalar_ty = lhs_ty.scalarType(zcu);
const maybe_lhs_val = try sema.resolveValueResolveLazy(lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(rhs);
const runtime_src = rs: {
if (maybe_rhs_val) |rhs_val| {
if (maybe_lhs_val) |lhs_val| {
return .fromValue(try arith.shr(sema, block, lhs_ty, rhs_ty, lhs_val, rhs_val, src, lhs_src, rhs_src, switch (air_tag) {
.shr => .shr,
.shr_exact => .shr_exact,
else => unreachable,
}));
}
if (rhs_val.isUndef(zcu)) {
return sema.failWithUseOfUndef(block, rhs_src, null);
}
const bits = scalar_ty.intInfo(zcu).bits;
switch (rhs_ty.zigTypeTag(zcu)) {
.int, .comptime_int => {
switch (try rhs_val.orderAgainstZeroSema(pt)) {
.gt => {
var rhs_space: Value.BigIntSpace = undefined;
const rhs_bigint = try rhs_val.toBigIntSema(&rhs_space, pt);
if (rhs_bigint.orderAgainstScalar(bits) != .lt) {
return sema.failWithTooLargeShiftAmount(block, lhs_ty, rhs_val, rhs_src, null);
}
},
.eq => return lhs,
.lt => return sema.failWithNegativeShiftAmount(block, rhs_src, rhs_val, null),
}
},
.vector => {
var any_positive: bool = false;
for (0..rhs_ty.vectorLen(zcu)) |elem_idx| {
const rhs_elem = try rhs_val.elemValue(pt, elem_idx);
if (rhs_elem.isUndef(zcu)) {
return sema.failWithUseOfUndef(block, rhs_src, elem_idx);
}
switch (try rhs_elem.orderAgainstZeroSema(pt)) {
.gt => {
var rhs_elem_space: Value.BigIntSpace = undefined;
const rhs_elem_bigint = try rhs_elem.toBigIntSema(&rhs_elem_space, pt);
if (rhs_elem_bigint.orderAgainstScalar(bits) != .lt) {
return sema.failWithTooLargeShiftAmount(block, lhs_ty, rhs_elem, rhs_src, elem_idx);
}
any_positive = true;
},
.eq => {},
.lt => return sema.failWithNegativeShiftAmount(block, rhs_src, rhs_elem, elem_idx),
}
}
if (!any_positive) return lhs;
},
else => unreachable,
}
break :rs lhs_src;
} else {
if (scalar_ty.toIntern() == .comptime_int_type) {
return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be comptime-known", .{});
}
if (maybe_lhs_val) |lhs_val| {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
if (try lhs_val.compareAllWithZeroSema(.eq, pt)) return lhs;
}
}
break :rs rhs_src;
};
try sema.requireRuntimeBlock(block, src, runtime_src);
const result = try block.addBinOp(air_tag, lhs, rhs);
if (block.wantSafety()) {
const bit_count = scalar_ty.intInfo(zcu).bits;
if (!std.math.isPowerOfTwo(bit_count)) {
const bit_count_val = try pt.intValue(rhs_ty.scalarType(zcu), bit_count);
const ok = if (rhs_ty.zigTypeTag(zcu) == .vector) ok: {
const bit_count_inst = Air.internedToRef((try sema.splat(rhs_ty, bit_count_val)).toIntern());
const lt = try block.addCmpVector(rhs, bit_count_inst, .lt);
break :ok try block.addReduce(lt, .And);
} else ok: {
const bit_count_inst = Air.internedToRef(bit_count_val.toIntern());
break :ok try block.addBinOp(.cmp_lt, rhs, bit_count_inst);
};
try sema.addSafetyCheck(block, src, ok, .shift_rhs_too_big);
}
if (air_tag == .shr_exact) {
const back = try block.addBinOp(.shl, result, rhs);
const ok = if (rhs_ty.zigTypeTag(zcu) == .vector) ok: {
const eql = try block.addCmpVector(lhs, back, .eq);
break :ok try block.addReduce(eql, .And);
} else try block.addBinOp(.cmp_eq, lhs, back);
try sema.addSafetyCheck(block, src, ok, .shr_overflow);
}
}
return result;
}
fn zirBitwise(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
air_tag: Air.Inst.Tag,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]?LazySrcLoc{ lhs_src, rhs_src } });
const scalar_type = resolved_type.scalarType(zcu);
const scalar_tag = scalar_type.zigTypeTag(zcu);
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const is_int_or_bool = scalar_tag == .int or scalar_tag == .comptime_int or scalar_tag == .bool;
if (!is_int_or_bool) {
return sema.fail(block, src, "invalid operands to binary bitwise expression: '{s}' and '{s}'", .{ @tagName(lhs_ty.zigTypeTag(zcu)), @tagName(rhs_ty.zigTypeTag(zcu)) });
}
const runtime_src = runtime: {
// TODO: ask the linker what kind of relocations are available, and
// in some cases emit a Value that means "this decl's address AND'd with this operand".
if (try sema.resolveValueResolveLazy(casted_lhs)) |lhs_val| {
if (try sema.resolveValueResolveLazy(casted_rhs)) |rhs_val| {
const result_val = switch (air_tag) {
// zig fmt: off
.bit_and => try arith.bitwiseBin(sema, resolved_type, lhs_val, rhs_val, .@"and"),
.bit_or => try arith.bitwiseBin(sema, resolved_type, lhs_val, rhs_val, .@"or"),
.xor => try arith.bitwiseBin(sema, resolved_type, lhs_val, rhs_val, .xor),
else => unreachable,
// zig fmt: on
};
return Air.internedToRef(result_val.toIntern());
} else {
break :runtime rhs_src;
}
} else {
break :runtime lhs_src;
}
};
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.src(.{ .node_offset_un_op = inst_data.src_node });
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const scalar_ty = operand_ty.scalarType(zcu);
const scalar_tag = scalar_ty.zigTypeTag(zcu);
if (scalar_tag != .int and scalar_tag != .bool)
return sema.fail(block, operand_src, "bitwise not operation on type '{f}'", .{operand_ty.fmt(pt)});
return analyzeBitNot(sema, block, operand, src);
}
fn analyzeBitNot(
sema: *Sema,
block: *Block,
operand: Air.Inst.Ref,
src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const operand_ty = sema.typeOf(operand);
if (try sema.resolveValue(operand)) |operand_val| {
const result_val = try arith.bitwiseNot(sema, operand_ty, operand_val);
return Air.internedToRef(result_val.toIntern());
}
try sema.requireRuntimeBlock(block, src, null);
return block.addTyOp(.not, operand_ty, operand);
}
fn analyzeTupleCat(
sema: *Sema,
block: *Block,
src_node: std.zig.Ast.Node.Offset,
lhs: Air.Inst.Ref,
rhs: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const src = block.nodeOffset(src_node);
const lhs_len = lhs_ty.structFieldCount(zcu);
const rhs_len = rhs_ty.structFieldCount(zcu);
const dest_fields = lhs_len + rhs_len;
if (dest_fields == 0) {
return .empty_tuple;
}
if (lhs_len == 0) {
return rhs;
}
if (rhs_len == 0) {
return lhs;
}
const final_len = try sema.usizeCast(block, src, dest_fields);
const types = try sema.arena.alloc(InternPool.Index, final_len);
const values = try sema.arena.alloc(InternPool.Index, final_len);
const opt_runtime_src = rs: {
var runtime_src: ?LazySrcLoc = null;
var i: u32 = 0;
while (i < lhs_len) : (i += 1) {
types[i] = lhs_ty.fieldType(i, zcu).toIntern();
const default_val = lhs_ty.structFieldDefaultValue(i, zcu);
values[i] = default_val.toIntern();
const operand_src = block.src(.{ .array_cat_lhs = .{
.array_cat_offset = src_node,
.elem_index = i,
} });
if (default_val.toIntern() == .unreachable_value) {
runtime_src = operand_src;
values[i] = .none;
}
}
i = 0;
while (i < rhs_len) : (i += 1) {
types[i + lhs_len] = rhs_ty.fieldType(i, zcu).toIntern();
const default_val = rhs_ty.structFieldDefaultValue(i, zcu);
values[i + lhs_len] = default_val.toIntern();
const operand_src = block.src(.{ .array_cat_rhs = .{
.array_cat_offset = src_node,
.elem_index = i,
} });
if (default_val.toIntern() == .unreachable_value) {
runtime_src = operand_src;
values[i + lhs_len] = .none;
}
}
break :rs runtime_src;
};
const tuple_ty: Type = .fromInterned(try zcu.intern_pool.getTupleType(zcu.gpa, pt.tid, .{
.types = types,
.values = values,
}));
const runtime_src = opt_runtime_src orelse {
const tuple_val = try pt.aggregateValue(tuple_ty, values);
return Air.internedToRef(tuple_val.toIntern());
};
try sema.requireRuntimeBlock(block, src, runtime_src);
const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len);
var i: u32 = 0;
while (i < lhs_len) : (i += 1) {
element_refs[i] = try sema.tupleFieldValByIndex(block, lhs, i, lhs_ty);
}
i = 0;
while (i < rhs_len) : (i += 1) {
element_refs[i + lhs_len] =
try sema.tupleFieldValByIndex(block, rhs, i, rhs_ty);
}
return block.addAggregateInit(tuple_ty, element_refs);
}
fn zirArrayCat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const src = block.nodeOffset(inst_data.src_node);
const lhs_is_tuple = lhs_ty.isTuple(zcu);
const rhs_is_tuple = rhs_ty.isTuple(zcu);
if (lhs_is_tuple and rhs_is_tuple) {
return sema.analyzeTupleCat(block, inst_data.src_node, lhs, rhs);
}
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const lhs_info = try sema.getArrayCatInfo(block, lhs_src, lhs, rhs_ty) orelse lhs_info: {
if (lhs_is_tuple) break :lhs_info undefined;
return sema.fail(block, lhs_src, "expected indexable; found '{f}'", .{lhs_ty.fmt(pt)});
};
const rhs_info = try sema.getArrayCatInfo(block, rhs_src, rhs, lhs_ty) orelse {
assert(!rhs_is_tuple);
return sema.fail(block, rhs_src, "expected indexable; found '{f}'", .{rhs_ty.fmt(pt)});
};
const resolved_elem_ty = t: {
var trash_block = block.makeSubBlock();
trash_block.comptime_reason = null;
defer trash_block.instructions.deinit(sema.gpa);
const instructions = [_]Air.Inst.Ref{
try trash_block.addBitCast(lhs_info.elem_type, .void_value),
try trash_block.addBitCast(rhs_info.elem_type, .void_value),
};
break :t try sema.resolvePeerTypes(block, src, &instructions, .{
.override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
});
};
// When there is a sentinel mismatch, no sentinel on the result.
// Otherwise, use the sentinel value provided by either operand,
// coercing it to the peer-resolved element type.
const res_sent_val: ?Value = s: {
if (lhs_info.sentinel) |lhs_sent_val| {
const lhs_sent = Air.internedToRef(lhs_sent_val.toIntern());
if (rhs_info.sentinel) |rhs_sent_val| {
const rhs_sent = Air.internedToRef(rhs_sent_val.toIntern());
const lhs_sent_casted = try sema.coerce(block, resolved_elem_ty, lhs_sent, lhs_src);
const rhs_sent_casted = try sema.coerce(block, resolved_elem_ty, rhs_sent, rhs_src);
const lhs_sent_casted_val = (try sema.resolveDefinedValue(block, lhs_src, lhs_sent_casted)).?;
const rhs_sent_casted_val = (try sema.resolveDefinedValue(block, rhs_src, rhs_sent_casted)).?;
if (try sema.valuesEqual(lhs_sent_casted_val, rhs_sent_casted_val, resolved_elem_ty)) {
break :s lhs_sent_casted_val;
} else {
break :s null;
}
} else {
const lhs_sent_casted = try sema.coerce(block, resolved_elem_ty, lhs_sent, lhs_src);
const lhs_sent_casted_val = (try sema.resolveDefinedValue(block, lhs_src, lhs_sent_casted)).?;
break :s lhs_sent_casted_val;
}
} else {
if (rhs_info.sentinel) |rhs_sent_val| {
const rhs_sent = Air.internedToRef(rhs_sent_val.toIntern());
const rhs_sent_casted = try sema.coerce(block, resolved_elem_ty, rhs_sent, rhs_src);
const rhs_sent_casted_val = (try sema.resolveDefinedValue(block, rhs_src, rhs_sent_casted)).?;
break :s rhs_sent_casted_val;
} else {
break :s null;
}
}
};
const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len);
const rhs_len = try sema.usizeCast(block, rhs_src, rhs_info.len);
const result_len = std.math.add(usize, lhs_len, rhs_len) catch |err| switch (err) {
error.Overflow => return sema.fail(
block,
src,
"concatenating arrays of length {d} and {d} produces an array too large for this compiler implementation to handle",
.{ lhs_len, rhs_len },
),
};
const result_ty = try pt.arrayType(.{
.len = result_len,
.sentinel = if (res_sent_val) |v| v.toIntern() else .none,
.child = resolved_elem_ty.toIntern(),
});
const ptr_addrspace = p: {
if (lhs_ty.zigTypeTag(zcu) == .pointer) break :p lhs_ty.ptrAddressSpace(zcu);
if (rhs_ty.zigTypeTag(zcu) == .pointer) break :p rhs_ty.ptrAddressSpace(zcu);
break :p null;
};
const runtime_src = if (switch (lhs_ty.zigTypeTag(zcu)) {
.array, .@"struct" => try sema.resolveValue(lhs),
.pointer => try sema.resolveDefinedValue(block, lhs_src, lhs),
else => unreachable,
}) |lhs_val| rs: {
if (switch (rhs_ty.zigTypeTag(zcu)) {
.array, .@"struct" => try sema.resolveValue(rhs),
.pointer => try sema.resolveDefinedValue(block, rhs_src, rhs),
else => unreachable,
}) |rhs_val| {
const lhs_sub_val = if (lhs_ty.isSinglePointer(zcu))
try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :rs lhs_src
else if (lhs_ty.isSlice(zcu))
try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val) orelse break :rs lhs_src
else
lhs_val;
const rhs_sub_val = if (rhs_ty.isSinglePointer(zcu))
try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty) orelse break :rs rhs_src
else if (rhs_ty.isSlice(zcu))
try sema.maybeDerefSliceAsArray(block, rhs_src, rhs_val) orelse break :rs rhs_src
else
rhs_val;
const element_vals = try sema.arena.alloc(InternPool.Index, result_len);
var elem_i: u32 = 0;
while (elem_i < lhs_len) : (elem_i += 1) {
const lhs_elem_i = elem_i;
const elem_default_val = if (lhs_is_tuple) lhs_ty.structFieldDefaultValue(lhs_elem_i, zcu) else Value.@"unreachable";
const elem_val = if (elem_default_val.toIntern() == .unreachable_value) try lhs_sub_val.elemValue(pt, lhs_elem_i) else elem_default_val;
const elem_val_inst = Air.internedToRef(elem_val.toIntern());
const operand_src = block.src(.{ .array_cat_lhs = .{
.array_cat_offset = inst_data.src_node,
.elem_index = elem_i,
} });
const coerced_elem_val_inst = try sema.coerce(block, resolved_elem_ty, elem_val_inst, operand_src);
const coerced_elem_val = try sema.resolveConstValue(block, operand_src, coerced_elem_val_inst, undefined);
element_vals[elem_i] = coerced_elem_val.toIntern();
}
while (elem_i < result_len) : (elem_i += 1) {
const rhs_elem_i = elem_i - lhs_len;
const elem_default_val = if (rhs_is_tuple) rhs_ty.structFieldDefaultValue(rhs_elem_i, zcu) else Value.@"unreachable";
const elem_val = if (elem_default_val.toIntern() == .unreachable_value) try rhs_sub_val.elemValue(pt, rhs_elem_i) else elem_default_val;
const elem_val_inst = Air.internedToRef(elem_val.toIntern());
const operand_src = block.src(.{ .array_cat_rhs = .{
.array_cat_offset = inst_data.src_node,
.elem_index = @intCast(rhs_elem_i),
} });
const coerced_elem_val_inst = try sema.coerce(block, resolved_elem_ty, elem_val_inst, operand_src);
const coerced_elem_val = try sema.resolveConstValue(block, operand_src, coerced_elem_val_inst, undefined);
element_vals[elem_i] = coerced_elem_val.toIntern();
}
return sema.addConstantMaybeRef(
(try pt.aggregateValue(result_ty, element_vals)).toIntern(),
ptr_addrspace != null,
);
} else break :rs rhs_src;
} else lhs_src;
try sema.requireRuntimeBlock(block, src, runtime_src);
if (ptr_addrspace) |ptr_as| {
const constant_alloc_ty = try pt.ptrTypeSema(.{
.child = result_ty.toIntern(),
.flags = .{
.address_space = ptr_as,
.is_const = true,
},
});
const alloc_ty = try pt.ptrTypeSema(.{
.child = result_ty.toIntern(),
.flags = .{ .address_space = ptr_as },
});
const elem_ptr_ty = try pt.ptrTypeSema(.{
.child = resolved_elem_ty.toIntern(),
.flags = .{ .address_space = ptr_as },
});
const mutable_alloc = try block.addTy(.alloc, alloc_ty);
// if both the source and destination are arrays
// we can hotpath via a memcpy.
if (lhs_ty.zigTypeTag(zcu) == .pointer and
rhs_ty.zigTypeTag(zcu) == .pointer)
{
const slice_ty = try pt.ptrTypeSema(.{
.child = resolved_elem_ty.toIntern(),
.flags = .{
.size = .slice,
.address_space = ptr_as,
},
});
const many_ty = slice_ty.slicePtrFieldType(zcu);
const many_alloc = try block.addBitCast(many_ty, mutable_alloc);
// lhs_dest_slice = dest[0..lhs.len]
const slice_ty_ref = Air.internedToRef(slice_ty.toIntern());
const lhs_len_ref = try pt.intRef(.usize, lhs_len);
const lhs_dest_slice = try block.addInst(.{
.tag = .slice,
.data = .{ .ty_pl = .{
.ty = slice_ty_ref,
.payload = try sema.addExtra(Air.Bin{
.lhs = many_alloc,
.rhs = lhs_len_ref,
}),
} },
});
_ = try block.addBinOp(.memcpy, lhs_dest_slice, lhs);
// rhs_dest_slice = dest[lhs.len..][0..rhs.len]
const rhs_len_ref = try pt.intRef(.usize, rhs_len);
const rhs_dest_offset = try block.addInst(.{
.tag = .ptr_add,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(many_ty.toIntern()),
.payload = try sema.addExtra(Air.Bin{
.lhs = many_alloc,
.rhs = lhs_len_ref,
}),
} },
});
const rhs_dest_slice = try block.addInst(.{
.tag = .slice,
.data = .{ .ty_pl = .{
.ty = slice_ty_ref,
.payload = try sema.addExtra(Air.Bin{
.lhs = rhs_dest_offset,
.rhs = rhs_len_ref,
}),
} },
});
_ = try block.addBinOp(.memcpy, rhs_dest_slice, rhs);
if (res_sent_val) |sent_val| {
const elem_index = try pt.intRef(.usize, result_len);
const elem_ptr = try block.addPtrElemPtr(mutable_alloc, elem_index, elem_ptr_ty);
const init = Air.internedToRef((try pt.getCoerced(sent_val, lhs_info.elem_type)).toIntern());
try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store);
}
return block.addBitCast(constant_alloc_ty, mutable_alloc);
}
var elem_i: u32 = 0;
while (elem_i < lhs_len) : (elem_i += 1) {
const elem_index = try pt.intRef(.usize, elem_i);
const elem_ptr = try block.addPtrElemPtr(mutable_alloc, elem_index, elem_ptr_ty);
const operand_src = block.src(.{ .array_cat_lhs = .{
.array_cat_offset = inst_data.src_node,
.elem_index = elem_i,
} });
const init = try sema.elemVal(block, operand_src, lhs, elem_index, src, true);
try sema.storePtr2(block, src, elem_ptr, src, init, operand_src, .store);
}
while (elem_i < result_len) : (elem_i += 1) {
const rhs_elem_i = elem_i - lhs_len;
const elem_index = try pt.intRef(.usize, elem_i);
const rhs_index = try pt.intRef(.usize, rhs_elem_i);
const elem_ptr = try block.addPtrElemPtr(mutable_alloc, elem_index, elem_ptr_ty);
const operand_src = block.src(.{ .array_cat_rhs = .{
.array_cat_offset = inst_data.src_node,
.elem_index = @intCast(rhs_elem_i),
} });
const init = try sema.elemVal(block, operand_src, rhs, rhs_index, src, true);
try sema.storePtr2(block, src, elem_ptr, src, init, operand_src, .store);
}
if (res_sent_val) |sent_val| {
const elem_index = try pt.intRef(.usize, result_len);
const elem_ptr = try block.addPtrElemPtr(mutable_alloc, elem_index, elem_ptr_ty);
const init = Air.internedToRef((try pt.getCoerced(sent_val, lhs_info.elem_type)).toIntern());
try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store);
}
return block.addBitCast(constant_alloc_ty, mutable_alloc);
}
const element_refs = try sema.arena.alloc(Air.Inst.Ref, result_len);
{
var elem_i: u32 = 0;
while (elem_i < lhs_len) : (elem_i += 1) {
const index = try pt.intRef(.usize, elem_i);
const operand_src = block.src(.{ .array_cat_lhs = .{
.array_cat_offset = inst_data.src_node,
.elem_index = elem_i,
} });
const init = try sema.elemVal(block, operand_src, lhs, index, src, true);
element_refs[elem_i] = try sema.coerce(block, resolved_elem_ty, init, operand_src);
}
while (elem_i < result_len) : (elem_i += 1) {
const rhs_elem_i = elem_i - lhs_len;
const index = try pt.intRef(.usize, rhs_elem_i);
const operand_src = block.src(.{ .array_cat_rhs = .{
.array_cat_offset = inst_data.src_node,
.elem_index = @intCast(rhs_elem_i),
} });
const init = try sema.elemVal(block, operand_src, rhs, index, src, true);
element_refs[elem_i] = try sema.coerce(block, resolved_elem_ty, init, operand_src);
}
}
return block.addAggregateInit(result_ty, element_refs);
}
fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Inst.Ref, peer_ty: Type) !?Type.ArrayInfo {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
switch (operand_ty.zigTypeTag(zcu)) {
.array => return operand_ty.arrayInfo(zcu),
.pointer => {
const ptr_info = operand_ty.ptrInfo(zcu);
switch (ptr_info.flags.size) {
.slice => {
const val = try sema.resolveConstDefinedValue(block, src, operand, .{ .simple = .slice_cat_operand });
return .{
.elem_type = .fromInterned(ptr_info.child),
.sentinel = switch (ptr_info.sentinel) {
.none => null,
else => Value.fromInterned(ptr_info.sentinel),
},
.len = try val.sliceLen(pt),
};
},
.one => {
if (Type.fromInterned(ptr_info.child).zigTypeTag(zcu) == .array) {
return Type.fromInterned(ptr_info.child).arrayInfo(zcu);
}
},
.c, .many => {},
}
},
.@"struct" => {
if (operand_ty.isTuple(zcu) and peer_ty.isIndexable(zcu)) {
assert(!peer_ty.isTuple(zcu));
return .{
.elem_type = peer_ty.elemType2(zcu),
.sentinel = null,
.len = operand_ty.arrayLen(zcu),
};
}
},
else => {},
}
return null;
}
fn analyzeTupleMul(
sema: *Sema,
block: *Block,
src_node: std.zig.Ast.Node.Offset,
operand: Air.Inst.Ref,
factor: usize,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
const src = block.nodeOffset(src_node);
const len_src = block.src(.{ .node_offset_bin_rhs = src_node });
const tuple_len = operand_ty.structFieldCount(zcu);
const final_len = std.math.mul(usize, tuple_len, factor) catch
return sema.fail(block, len_src, "operation results in overflow", .{});
if (final_len == 0) {
return .empty_tuple;
}
const types = try sema.arena.alloc(InternPool.Index, final_len);
const values = try sema.arena.alloc(InternPool.Index, final_len);
const opt_runtime_src = rs: {
var runtime_src: ?LazySrcLoc = null;
for (0..tuple_len) |i| {
types[i] = operand_ty.fieldType(i, zcu).toIntern();
values[i] = operand_ty.structFieldDefaultValue(i, zcu).toIntern();
const operand_src = block.src(.{ .array_cat_lhs = .{
.array_cat_offset = src_node,
.elem_index = @intCast(i),
} });
if (values[i] == .unreachable_value) {
runtime_src = operand_src;
values[i] = .none; // TODO don't treat unreachable_value as special
}
}
for (0..factor) |i| {
@memmove(types[tuple_len * i ..][0..tuple_len], types[0..tuple_len]);
@memmove(values[tuple_len * i ..][0..tuple_len], values[0..tuple_len]);
}
break :rs runtime_src;
};
const tuple_ty: Type = .fromInterned(try zcu.intern_pool.getTupleType(zcu.gpa, pt.tid, .{
.types = types,
.values = values,
}));
const runtime_src = opt_runtime_src orelse {
const tuple_val = try pt.aggregateValue(tuple_ty, values);
return Air.internedToRef(tuple_val.toIntern());
};
try sema.requireRuntimeBlock(block, src, runtime_src);
const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len);
var i: u32 = 0;
while (i < tuple_len) : (i += 1) {
element_refs[i] = try sema.tupleFieldValByIndex(block, operand, @intCast(i), operand_ty);
}
i = 1;
while (i < factor) : (i += 1) {
@memcpy(element_refs[tuple_len * i ..][0..tuple_len], element_refs[0..tuple_len]);
}
return block.addAggregateInit(tuple_ty, element_refs);
}
fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.ArrayMul, inst_data.payload_index).data;
const uncoerced_lhs = try sema.resolveInst(extra.lhs);
const uncoerced_lhs_ty = sema.typeOf(uncoerced_lhs);
const src: LazySrcLoc = block.nodeOffset(inst_data.src_node);
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const operator_src = block.src(.{ .node_offset_main_token = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const lhs, const lhs_ty = coerced_lhs: {
// If we have a result type, we might be able to do this more efficiently
// by coercing the LHS first. Specifically, if we want an array or vector
// and have a tuple, coerce the tuple immediately.
no_coerce: {
if (extra.res_ty == .none) break :no_coerce;
const res_ty = try sema.resolveTypeOrPoison(block, src, extra.res_ty) orelse break :no_coerce;
if (!uncoerced_lhs_ty.isTuple(zcu)) break :no_coerce;
const lhs_len = uncoerced_lhs_ty.structFieldCount(zcu);
const lhs_dest_ty = switch (res_ty.zigTypeTag(zcu)) {
else => break :no_coerce,
.array => try pt.arrayType(.{
.child = res_ty.childType(zcu).toIntern(),
.len = lhs_len,
.sentinel = if (res_ty.sentinel(zcu)) |s| s.toIntern() else .none,
}),
.vector => try pt.vectorType(.{
.child = res_ty.childType(zcu).toIntern(),
.len = lhs_len,
}),
};
// Attempt to coerce to this type, but don't emit an error if it fails. Instead,
// just exit out of this path and let the usual error happen later, so that error
// messages are consistent.
const coerced = sema.coerceExtra(block, lhs_dest_ty, uncoerced_lhs, lhs_src, .{ .report_err = false }) catch |err| switch (err) {
error.NotCoercible => break :no_coerce,
else => |e| return e,
};
break :coerced_lhs .{ coerced, lhs_dest_ty };
}
break :coerced_lhs .{ uncoerced_lhs, uncoerced_lhs_ty };
};
if (lhs_ty.isTuple(zcu)) {
// In `**` rhs must be comptime-known, but lhs can be runtime-known
const factor = try sema.resolveInt(block, rhs_src, extra.rhs, .usize, .{ .simple = .array_mul_factor });
const factor_casted = try sema.usizeCast(block, rhs_src, factor);
return sema.analyzeTupleMul(block, inst_data.src_node, lhs, factor_casted);
}
// Analyze the lhs first, to catch the case that someone tried to do exponentiation
const lhs_info = try sema.getArrayCatInfo(block, lhs_src, lhs, lhs_ty) orelse {
const msg = msg: {
const msg = try sema.errMsg(lhs_src, "expected indexable; found '{f}'", .{lhs_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
switch (lhs_ty.zigTypeTag(zcu)) {
.int, .float, .comptime_float, .comptime_int, .vector => {
try sema.errNote(operator_src, msg, "this operator multiplies arrays; use std.math.pow for exponentiation", .{});
},
else => {},
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
};
// In `**` rhs must be comptime-known, but lhs can be runtime-known
const factor = try sema.resolveInt(block, rhs_src, extra.rhs, .usize, .{ .simple = .array_mul_factor });
const result_len_u64 = std.math.mul(u64, lhs_info.len, factor) catch
return sema.fail(block, rhs_src, "operation results in overflow", .{});
const result_len = try sema.usizeCast(block, src, result_len_u64);
const result_ty = try pt.arrayType(.{
.len = result_len,
.sentinel = if (lhs_info.sentinel) |s| s.toIntern() else .none,
.child = lhs_info.elem_type.toIntern(),
});
const ptr_addrspace = if (lhs_ty.zigTypeTag(zcu) == .pointer) lhs_ty.ptrAddressSpace(zcu) else null;
const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len);
if (try sema.resolveValue(lhs)) |lhs_val| ct: {
const lhs_sub_val = if (lhs_ty.isSinglePointer(zcu))
try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty) orelse break :ct
else if (lhs_ty.isSlice(zcu))
try sema.maybeDerefSliceAsArray(block, lhs_src, lhs_val) orelse break :ct
else
lhs_val;
const val = v: {
// Optimization for the common pattern of a single element repeated N times, such
// as zero-filling a byte array.
if (lhs_len == 1 and lhs_info.sentinel == null) {
const elem_val = try lhs_sub_val.elemValue(pt, 0);
break :v try pt.aggregateSplatValue(result_ty, elem_val);
}
const element_vals = try sema.arena.alloc(InternPool.Index, result_len);
var elem_i: usize = 0;
while (elem_i < result_len) {
var lhs_i: usize = 0;
while (lhs_i < lhs_len) : (lhs_i += 1) {
const elem_val = try lhs_sub_val.elemValue(pt, lhs_i);
element_vals[elem_i] = elem_val.toIntern();
elem_i += 1;
}
}
break :v try pt.aggregateValue(result_ty, element_vals);
};
return sema.addConstantMaybeRef(val.toIntern(), ptr_addrspace != null);
}
try sema.requireRuntimeBlock(block, src, lhs_src);
// Grab all the LHS values ahead of time, rather than repeatedly emitting instructions
// to get the same elem values.
const lhs_vals = try sema.arena.alloc(Air.Inst.Ref, lhs_len);
for (lhs_vals, 0..) |*lhs_val, idx| {
const idx_ref = try pt.intRef(.usize, idx);
lhs_val.* = try sema.elemVal(block, lhs_src, lhs, idx_ref, src, false);
}
if (ptr_addrspace) |ptr_as| {
const alloc_ty = try pt.ptrTypeSema(.{
.child = result_ty.toIntern(),
.flags = .{
.address_space = ptr_as,
.is_const = true,
},
});
const alloc = try block.addTy(.alloc, alloc_ty);
const elem_ptr_ty = try pt.ptrTypeSema(.{
.child = lhs_info.elem_type.toIntern(),
.flags = .{ .address_space = ptr_as },
});
var elem_i: usize = 0;
while (elem_i < result_len) {
for (lhs_vals) |lhs_val| {
const elem_index = try pt.intRef(.usize, elem_i);
const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty);
try sema.storePtr2(block, src, elem_ptr, src, lhs_val, lhs_src, .store);
elem_i += 1;
}
}
if (lhs_info.sentinel) |sent_val| {
const elem_index = try pt.intRef(.usize, result_len);
const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty);
const init = Air.internedToRef(sent_val.toIntern());
try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store);
}
return alloc;
}
const element_refs = try sema.arena.alloc(Air.Inst.Ref, result_len);
for (0..try sema.usizeCast(block, rhs_src, factor)) |i| {
@memcpy(element_refs[i * lhs_len ..][0..lhs_len], lhs_vals);
}
return block.addAggregateInit(result_ty, element_refs);
}
fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const lhs_src = src;
const rhs_src = block.src(.{ .node_offset_un_op = inst_data.src_node });
const rhs = try sema.resolveInst(inst_data.operand);
const rhs_ty = sema.typeOf(rhs);
const rhs_scalar_ty = rhs_ty.scalarType(zcu);
if (rhs_scalar_ty.isUnsignedInt(zcu) or switch (rhs_scalar_ty.zigTypeTag(zcu)) {
.int, .comptime_int, .float, .comptime_float => false,
else => true,
}) {
return sema.fail(block, src, "negation of type '{f}'", .{rhs_ty.fmt(pt)});
}
if (rhs_scalar_ty.isAnyFloat()) {
// We handle float negation here to ensure negative zero is represented in the bits.
if (try sema.resolveValue(rhs)) |rhs_val| {
const result = try arith.negateFloat(sema, rhs_ty, rhs_val);
return Air.internedToRef(result.toIntern());
}
try sema.requireRuntimeBlock(block, src, null);
return block.addUnOp(if (block.float_mode == .optimized) .neg_optimized else .neg, rhs);
}
const lhs = Air.internedToRef((try sema.splat(rhs_ty, try pt.intValue(rhs_scalar_ty, 0))).toIntern());
return sema.analyzeArithmetic(block, .sub, lhs, rhs, src, lhs_src, rhs_src, true);
}
fn zirNegateWrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const lhs_src = src;
const rhs_src = block.src(.{ .node_offset_un_op = inst_data.src_node });
const rhs = try sema.resolveInst(inst_data.operand);
const rhs_ty = sema.typeOf(rhs);
const rhs_scalar_ty = rhs_ty.scalarType(zcu);
switch (rhs_scalar_ty.zigTypeTag(zcu)) {
.int, .comptime_int, .float, .comptime_float => {},
else => return sema.fail(block, src, "negation of type '{f}'", .{rhs_ty.fmt(pt)}),
}
const lhs = Air.internedToRef((try sema.splat(rhs_ty, try pt.intValue(rhs_scalar_ty, 0))).toIntern());
return sema.analyzeArithmetic(block, .subwrap, lhs, rhs, src, lhs_src, rhs_src, true);
}
fn zirArithmetic(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
zir_tag: Zir.Inst.Tag,
safety: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, src, lhs_src, rhs_src, safety);
}
fn zirDiv(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div);
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
if ((lhs_ty.zigTypeTag(zcu) == .comptime_float and rhs_ty.zigTypeTag(zcu) == .comptime_int) or
(lhs_ty.zigTypeTag(zcu) == .comptime_int and rhs_ty.zigTypeTag(zcu) == .comptime_float))
{
// If it makes a difference whether we coerce to ints or floats before doing the division, error.
// If lhs % rhs is 0, it doesn't matter.
const lhs_val = maybe_lhs_val orelse unreachable;
const rhs_val = maybe_rhs_val orelse unreachable;
const rem = arith.modRem(sema, block, resolved_type, lhs_val, rhs_val, lhs_src, rhs_src, .rem) catch unreachable;
if (!rem.compareAllWithZero(.eq, zcu)) {
return sema.fail(
block,
src,
"ambiguous coercion of division operands '{f}' and '{f}'; non-zero remainder '{f}'",
.{ lhs_ty.fmt(pt), rhs_ty.fmt(pt), rem.fmtValueSema(pt, sema) },
);
}
}
// TODO: emit compile error when .div is used on integers and there would be an
// ambiguous result between div_floor and div_trunc.
// The rules here are like those in `analyzeArithmetic`:
//
// * If both operands are comptime-known, call the corresponding function in `arith`.
// Inputs which would be IB at runtime are compile errors.
//
// * Otherwise, if one operand is comptime-known `undefined`, we either trigger a compile error
// or return `undefined`, depending on whether this operator can trigger IB.
//
// * No other comptime operand determines a comptime result, so remaining cases are runtime ops.
const allow_div_zero = !is_int and
resolved_type.toIntern() != .comptime_float_type and
block.float_mode == .strict;
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
return .fromValue(try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div));
}
if (allow_div_zero) {
if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
}
} else if (maybe_rhs_val) |rhs_val| {
if (allow_div_zero) {
if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
}
}
if (block.wantSafety()) {
try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
}
const air_tag: Air.Inst.Tag = if (is_int) blk: {
if (lhs_ty.isSignedInt(zcu) or rhs_ty.isSignedInt(zcu)) {
return sema.fail(
block,
src,
"division with '{f}' and '{f}': signed integers must use @divTrunc, @divFloor, or @divExact",
.{ lhs_ty.fmt(pt), rhs_ty.fmt(pt) },
);
}
break :blk .div_trunc;
} else switch (block.float_mode) {
.optimized => .div_float_optimized,
.strict => .div_float,
};
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
fn zirDivExact(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_exact);
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
// Because `@divExact` can trigger Illegal Behavior, undefined operands trigger Illegal Behavior.
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div_exact);
return Air.internedToRef(result.toIntern());
}
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
} else if (maybe_rhs_val) |rhs_val| {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
}
// Depending on whether safety is enabled, we will have a slightly different strategy
// here. The `div_exact` AIR instruction causes illegal behavior if a remainder
// is produced, so in the safety check case, it cannot be used. Instead we do a
// div_trunc and check for remainder.
if (block.wantSafety()) {
try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
const result = try block.addBinOp(.div_trunc, casted_lhs, casted_rhs);
const ok = if (!is_int) ok: {
const floored = try block.addUnOp(.floor, result);
if (resolved_type.zigTypeTag(zcu) == .vector) {
const eql = try block.addCmpVector(result, floored, .eq);
break :ok try block.addReduce(eql, .And);
} else {
const is_in_range = try block.addBinOp(switch (block.float_mode) {
.strict => .cmp_eq,
.optimized => .cmp_eq_optimized,
}, result, floored);
break :ok is_in_range;
}
} else ok: {
const remainder = try block.addBinOp(.rem, casted_lhs, casted_rhs);
const scalar_zero = switch (scalar_tag) {
.comptime_float, .float => try pt.floatValue(resolved_type.scalarType(zcu), 0.0),
.comptime_int, .int => try pt.intValue(resolved_type.scalarType(zcu), 0),
else => unreachable,
};
if (resolved_type.zigTypeTag(zcu) == .vector) {
const zero_val = try sema.splat(resolved_type, scalar_zero);
const zero = Air.internedToRef(zero_val.toIntern());
const eql = try block.addCmpVector(remainder, zero, .eq);
break :ok try block.addReduce(eql, .And);
} else {
const zero = Air.internedToRef(scalar_zero.toIntern());
const is_in_range = try block.addBinOp(.cmp_eq, remainder, zero);
break :ok is_in_range;
}
};
try sema.addSafetyCheck(block, src, ok, .exact_division_remainder);
return result;
}
return block.addBinOp(airTag(block, is_int, .div_exact, .div_exact_optimized), casted_lhs, casted_rhs);
}
fn zirDivFloor(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_floor);
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
const allow_div_zero = !is_int and
resolved_type.toIntern() != .comptime_float_type and
block.float_mode == .strict;
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div_floor);
return Air.internedToRef(result.toIntern());
}
if (allow_div_zero) {
if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
}
} else if (maybe_rhs_val) |rhs_val| {
if (allow_div_zero) {
if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
}
}
if (block.wantSafety()) {
try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
}
return block.addBinOp(airTag(block, is_int, .div_floor, .div_floor_optimized), casted_lhs, casted_rhs);
}
fn zirDivTrunc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .div_trunc);
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
const allow_div_zero = !is_int and
resolved_type.toIntern() != .comptime_float_type and
block.float_mode == .strict;
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
const result = try arith.div(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src, .div_trunc);
return Air.internedToRef(result.toIntern());
}
if (allow_div_zero) {
if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
}
} else if (maybe_rhs_val) |rhs_val| {
if (allow_div_zero) {
if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
}
}
if (block.wantSafety()) {
try sema.addDivIntOverflowSafety(block, src, resolved_type, lhs_scalar_ty, maybe_lhs_val, maybe_rhs_val, casted_lhs, casted_rhs, is_int);
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
}
return block.addBinOp(airTag(block, is_int, .div_trunc, .div_trunc_optimized), casted_lhs, casted_rhs);
}
fn addDivIntOverflowSafety(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
resolved_type: Type,
lhs_scalar_ty: Type,
maybe_lhs_val: ?Value,
maybe_rhs_val: ?Value,
casted_lhs: Air.Inst.Ref,
casted_rhs: Air.Inst.Ref,
is_int: bool,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
if (!is_int) return;
// If the LHS is unsigned, it cannot cause overflow.
if (!lhs_scalar_ty.isSignedInt(zcu)) return;
// If the LHS is widened to a larger integer type, no overflow is possible.
if (lhs_scalar_ty.intInfo(zcu).bits < resolved_type.intInfo(zcu).bits) {
return;
}
const min_int = try resolved_type.minInt(pt, resolved_type);
const neg_one_scalar = try pt.intValue(lhs_scalar_ty, -1);
const neg_one = try sema.splat(resolved_type, neg_one_scalar);
// If the LHS is comptime-known to be not equal to the min int,
// no overflow is possible.
if (maybe_lhs_val) |lhs_val| {
if (try lhs_val.compareAll(.neq, min_int, resolved_type, pt)) return;
}
// If the RHS is comptime-known to not be equal to -1, no overflow is possible.
if (maybe_rhs_val) |rhs_val| {
if (try rhs_val.compareAll(.neq, neg_one, resolved_type, pt)) return;
}
if (resolved_type.zigTypeTag(zcu) == .vector) {
const vec_len = resolved_type.vectorLen(zcu);
// This is a bool vector whose elements are true if the LHS element does NOT equal `min_int`.
const lhs_ok: Air.Inst.Ref = if (maybe_lhs_val) |lhs_val| ok: {
// The operand is comptime-known; intern a constant bool vector for the potentially unsafe elements.
const min_int_scalar = try min_int.elemValue(pt, 0);
const elems_ok = try sema.arena.alloc(InternPool.Index, vec_len);
for (elems_ok, 0..) |*elem_ok, elem_idx| {
const elem_val = try lhs_val.elemValue(pt, elem_idx);
elem_ok.* = if (elem_val.eqlScalarNum(min_int_scalar, zcu)) .bool_false else .bool_true;
}
break :ok .fromValue(try pt.aggregateValue(try pt.vectorType(.{
.len = vec_len,
.child = .bool_type,
}), elems_ok));
} else ok: {
// The operand isn't comptime-known; add a runtime comparison.
const min_int_ref = Air.internedToRef(min_int.toIntern());
break :ok try block.addCmpVector(casted_lhs, min_int_ref, .neq);
};
// This is a bool vector whose elements are true if the RHS element does NOT equal -1.
const rhs_ok: Air.Inst.Ref = if (maybe_rhs_val) |rhs_val| ok: {
// The operand is comptime-known; intern a constant bool vector for the potentially unsafe elements.
const elems_ok = try sema.arena.alloc(InternPool.Index, vec_len);
for (elems_ok, 0..) |*elem_ok, elem_idx| {
const elem_val = try rhs_val.elemValue(pt, elem_idx);
elem_ok.* = if (elem_val.eqlScalarNum(neg_one_scalar, zcu)) .bool_false else .bool_true;
}
break :ok .fromValue(try pt.aggregateValue(try pt.vectorType(.{
.len = vec_len,
.child = .bool_type,
}), elems_ok));
} else ok: {
// The operand isn't comptime-known; add a runtime comparison.
const neg_one_ref = Air.internedToRef(neg_one.toIntern());
break :ok try block.addCmpVector(casted_rhs, neg_one_ref, .neq);
};
const ok = try block.addReduce(try block.addBinOp(.bool_or, lhs_ok, rhs_ok), .And);
try sema.addSafetyCheck(block, src, ok, .integer_overflow);
} else {
const lhs_ok: Air.Inst.Ref = if (maybe_lhs_val == null) ok: {
const min_int_ref = Air.internedToRef(min_int.toIntern());
break :ok try block.addBinOp(.cmp_neq, casted_lhs, min_int_ref);
} else .none; // means false
const rhs_ok: Air.Inst.Ref = if (maybe_rhs_val == null) ok: {
const neg_one_ref = Air.internedToRef(neg_one.toIntern());
break :ok try block.addBinOp(.cmp_neq, casted_rhs, neg_one_ref);
} else .none; // means false
const ok = if (lhs_ok != .none and rhs_ok != .none)
try block.addBinOp(.bool_or, lhs_ok, rhs_ok)
else if (lhs_ok != .none)
lhs_ok
else if (rhs_ok != .none)
rhs_ok
else
unreachable;
try sema.addSafetyCheck(block, src, ok, .integer_overflow);
}
}
fn addDivByZeroSafety(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
resolved_type: Type,
maybe_rhs_val: ?Value,
casted_rhs: Air.Inst.Ref,
is_int: bool,
) CompileError!void {
// Strict IEEE floats have well-defined division by zero.
if (!is_int and block.float_mode == .strict) return;
// If rhs was comptime-known to be zero a compile error would have been
// emitted above.
if (maybe_rhs_val != null) return;
const pt = sema.pt;
const zcu = pt.zcu;
const scalar_zero = if (is_int)
try pt.intValue(resolved_type.scalarType(zcu), 0)
else
try pt.floatValue(resolved_type.scalarType(zcu), 0.0);
const ok = if (resolved_type.zigTypeTag(zcu) == .vector) ok: {
const zero_val = try sema.splat(resolved_type, scalar_zero);
const zero = Air.internedToRef(zero_val.toIntern());
const ok = try block.addCmpVector(casted_rhs, zero, .neq);
break :ok try block.addReduce(ok, .And);
} else ok: {
const zero = Air.internedToRef(scalar_zero.toIntern());
break :ok try block.addBinOp(if (is_int) .cmp_neq else .cmp_neq_optimized, casted_rhs, zero);
};
try sema.addSafetyCheck(block, src, ok, .divide_by_zero);
}
fn airTag(block: *Block, is_int: bool, normal: Air.Inst.Tag, optimized: Air.Inst.Tag) Air.Inst.Tag {
if (is_int) return normal;
return switch (block.float_mode) {
.strict => normal,
.optimized => optimized,
};
}
fn zirModRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const lhs_scalar_ty = lhs_ty.scalarType(zcu);
const rhs_scalar_ty = rhs_ty.scalarType(zcu);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .mod_rem);
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
const lhs_maybe_negative = a: {
if (lhs_scalar_ty.isUnsignedInt(zcu)) break :a false;
const lhs_val = maybe_lhs_val orelse break :a true;
if (lhs_val.compareAllWithZero(.gte, zcu)) break :a false;
break :a true;
};
const rhs_maybe_negative = a: {
if (rhs_scalar_ty.isUnsignedInt(zcu)) break :a false;
const rhs_val = maybe_rhs_val orelse break :a true;
if (rhs_val.compareAllWithZero(.gte, zcu)) break :a false;
break :a true;
};
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
const result = try arith.modRem(sema, block, resolved_type, lhs_val, rhs_val, lhs_src, rhs_src, .rem);
if (lhs_maybe_negative or rhs_maybe_negative) {
if (!result.compareAllWithZero(.eq, zcu)) {
// Non-zero result means ambiguity between mod and rem
return sema.failWithModRemNegative(block, src: {
if (lhs_maybe_negative) break :src lhs_src;
if (rhs_maybe_negative) break :src rhs_src;
unreachable;
}, lhs_ty, rhs_ty);
}
}
return Air.internedToRef(result.toIntern());
}
}
// Result not comptime-known, so floats and signed integers are illegal due to mod/rem ambiguity
if (lhs_maybe_negative or rhs_maybe_negative) {
return sema.failWithModRemNegative(block, src: {
if (lhs_maybe_negative) break :src lhs_src;
if (rhs_maybe_negative) break :src rhs_src;
unreachable;
}, lhs_ty, rhs_ty);
}
const allow_div_zero = !is_int and
resolved_type.toIntern() != .comptime_float_type and
block.float_mode == .strict;
if (maybe_lhs_val) |lhs_val| {
if (allow_div_zero) {
if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
}
} else if (maybe_rhs_val) |rhs_val| {
if (allow_div_zero) {
if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
}
}
if (block.wantSafety()) {
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
}
const air_tag = airTag(block, is_int, .rem, .rem_optimized);
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
fn zirMod(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .mod);
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
const allow_div_zero = !is_int and
resolved_type.toIntern() != .comptime_float_type and
block.float_mode == .strict;
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
const result = try arith.modRem(sema, block, resolved_type, lhs_val, rhs_val, lhs_src, rhs_src, .mod);
return Air.internedToRef(result.toIntern());
}
if (allow_div_zero) {
if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
}
} else if (maybe_rhs_val) |rhs_val| {
if (allow_div_zero) {
if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
}
}
if (block.wantSafety()) {
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
}
const air_tag = airTag(block, is_int, .mod, .mod_optimized);
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
fn zirRem(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
try sema.checkInvalidPtrIntArithmetic(block, src, lhs_ty);
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const scalar_tag = resolved_type.scalarType(zcu).zigTypeTag(zcu);
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, .rem);
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
const allow_div_zero = !is_int and
resolved_type.toIntern() != .comptime_float_type and
block.float_mode == .strict;
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
const result = try arith.modRem(sema, block, resolved_type, lhs_val, rhs_val, lhs_src, rhs_src, .rem);
return Air.internedToRef(result.toIntern());
}
if (allow_div_zero) {
if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
}
} else if (maybe_rhs_val) |rhs_val| {
if (allow_div_zero) {
if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
} else {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
if (rhs_val.anyScalarIsZero(zcu)) return sema.failWithDivideByZero(block, rhs_src);
}
}
if (block.wantSafety()) {
try sema.addDivByZeroSafety(block, src, resolved_type, maybe_rhs_val, casted_rhs, is_int);
}
const air_tag = airTag(block, is_int, .rem, .rem_optimized);
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
fn zirOverflowArithmetic(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
zir_tag: Zir.Inst.Extended,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const lhs_src = block.builtinCallArgSrc(extra.node, 0);
const rhs_src = block.builtinCallArgSrc(extra.node, 1);
const uncasted_lhs = try sema.resolveInst(extra.lhs);
const uncasted_rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(uncasted_lhs);
const rhs_ty = sema.typeOf(uncasted_rhs);
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
const instructions = &[_]Air.Inst.Ref{ uncasted_lhs, uncasted_rhs };
const dest_ty = if (zir_tag == .shl_with_overflow)
lhs_ty
else
try sema.resolvePeerTypes(block, src, instructions, .{
.override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
});
const rhs_dest_ty = if (zir_tag == .shl_with_overflow)
try sema.log2IntType(block, lhs_ty, src)
else
dest_ty;
const lhs = try sema.coerce(block, dest_ty, uncasted_lhs, lhs_src);
const rhs = try sema.coerce(block, rhs_dest_ty, uncasted_rhs, rhs_src);
if (dest_ty.scalarType(zcu).zigTypeTag(zcu) != .int) {
return sema.fail(block, src, "expected vector of integers or integer tag type, found '{f}'", .{dest_ty.fmt(pt)});
}
const maybe_lhs_val = try sema.resolveValue(lhs);
const maybe_rhs_val = try sema.resolveValue(rhs);
const tuple_ty = try pt.overflowArithmeticTupleType(dest_ty);
const overflow_ty: Type = .fromInterned(ip.indexToKey(tuple_ty.toIntern()).tuple_type.types.get(ip)[1]);
var result: struct {
inst: Air.Inst.Ref = .none,
wrapped: Value = Value.@"unreachable",
overflow_bit: Value,
} = result: {
switch (zir_tag) {
.add_with_overflow => {
// If either of the arguments is zero, `false` is returned and the other is stored
// to the result, even if it is undefined..
// Otherwise, if either of the argument is undefined, undefined is returned.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef(zcu) and (try lhs_val.compareAllWithZeroSema(.eq, pt))) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = rhs };
}
}
if (maybe_rhs_val) |rhs_val| {
if (!rhs_val.isUndef(zcu) and (try rhs_val.compareAllWithZeroSema(.eq, pt))) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = lhs };
}
}
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
if (lhs_val.isUndef(zcu) or rhs_val.isUndef(zcu)) {
break :result .{ .overflow_bit = .undef, .wrapped = .undef };
}
const result = try arith.addWithOverflow(sema, dest_ty, lhs_val, rhs_val);
break :result .{ .overflow_bit = result.overflow_bit, .wrapped = result.wrapped_result };
}
}
},
.sub_with_overflow => {
// If the rhs is zero, then the result is lhs and no overflow occured.
// Otherwise, if either result is undefined, both results are undefined.
if (maybe_rhs_val) |rhs_val| {
if (rhs_val.isUndef(zcu)) {
break :result .{ .overflow_bit = .undef, .wrapped = .undef };
} else if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = lhs };
} else if (maybe_lhs_val) |lhs_val| {
if (lhs_val.isUndef(zcu)) {
break :result .{ .overflow_bit = .undef, .wrapped = .undef };
}
const result = try arith.subWithOverflow(sema, dest_ty, lhs_val, rhs_val);
break :result .{ .overflow_bit = result.overflow_bit, .wrapped = result.wrapped_result };
}
}
},
.mul_with_overflow => {
// If either of the arguments is zero, the result is zero and no overflow occured.
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef(zcu) and try lhs_val.compareAllWithZeroSema(.eq, pt)) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = lhs };
}
}
if (maybe_rhs_val) |rhs_val| {
if (!rhs_val.isUndef(zcu) and try rhs_val.compareAllWithZeroSema(.eq, pt)) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = rhs };
}
}
// If either of the arguments is one, the result is the other and no overflow occured.
const dest_scalar_ty = dest_ty.scalarType(zcu);
const dest_scalar_int = dest_scalar_ty.intInfo(zcu);
// We could still be working with i1, where '1' is not a legal value!
if (!(dest_scalar_int.bits == 1 and dest_scalar_int.signedness == .signed)) {
const scalar_one = try pt.intValue(dest_scalar_ty, 1);
const vec_one = try sema.splat(dest_ty, scalar_one);
if (maybe_lhs_val) |lhs_val| {
if (!lhs_val.isUndef(zcu) and try sema.compareAll(lhs_val, .eq, vec_one, dest_ty)) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = rhs };
}
}
if (maybe_rhs_val) |rhs_val| {
if (!rhs_val.isUndef(zcu) and try sema.compareAll(rhs_val, .eq, vec_one, dest_ty)) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = lhs };
}
}
}
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
if (lhs_val.isUndef(zcu) or rhs_val.isUndef(zcu)) {
break :result .{ .overflow_bit = .undef, .wrapped = .undef };
}
const result = try arith.mulWithOverflow(sema, dest_ty, lhs_val, rhs_val);
break :result .{ .overflow_bit = result.overflow_bit, .wrapped = result.wrapped_result };
}
}
},
.shl_with_overflow => {
// If either of the arguments is undefined, IB is possible and we return an error.
// If lhs is zero, the result is zero and no overflow occurred.
// If rhs is zero, the result is lhs and no overflow occurred.
const scalar_ty = lhs_ty.scalarType(zcu);
if (maybe_rhs_val) |rhs_val| {
if (maybe_lhs_val) |lhs_val| {
const result = try arith.shlWithOverflow(sema, block, lhs_ty, lhs_val, rhs_val, lhs_src, rhs_src);
break :result .{ .overflow_bit = result.overflow_bit, .wrapped = result.wrapped_result };
}
if (rhs_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src, null);
const bits = scalar_ty.intInfo(zcu).bits;
switch (rhs_ty.zigTypeTag(zcu)) {
.int, .comptime_int => {
switch (try rhs_val.orderAgainstZeroSema(pt)) {
.gt => {
var rhs_space: Value.BigIntSpace = undefined;
const rhs_bigint = try rhs_val.toBigIntSema(&rhs_space, pt);
if (rhs_bigint.orderAgainstScalar(bits) != .lt) {
return sema.failWithTooLargeShiftAmount(block, lhs_ty, rhs_val, rhs_src, null);
}
},
.eq => break :result .{ .overflow_bit = .zero_u1, .inst = lhs },
.lt => return sema.failWithNegativeShiftAmount(block, rhs_src, rhs_val, null),
}
},
.vector => {
var any_positive: bool = false;
for (0..rhs_ty.vectorLen(zcu)) |elem_idx| {
const rhs_elem = try rhs_val.elemValue(pt, elem_idx);
if (rhs_elem.isUndef(zcu)) return sema.failWithUseOfUndef(block, rhs_src, elem_idx);
switch (try rhs_elem.orderAgainstZeroSema(pt)) {
.gt => {
var rhs_elem_space: Value.BigIntSpace = undefined;
const rhs_elem_bigint = try rhs_elem.toBigIntSema(&rhs_elem_space, pt);
if (rhs_elem_bigint.orderAgainstScalar(bits) != .lt) {
return sema.failWithTooLargeShiftAmount(block, lhs_ty, rhs_elem, rhs_src, elem_idx);
}
any_positive = true;
},
.eq => {},
.lt => return sema.failWithNegativeShiftAmount(block, rhs_src, rhs_elem, elem_idx),
}
}
if (!any_positive) break :result .{ .overflow_bit = try pt.aggregateSplatValue(overflow_ty, .zero_u1), .inst = lhs };
},
else => unreachable,
}
if (try rhs_val.compareAllWithZeroSema(.eq, pt)) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = lhs };
}
} else {
if (scalar_ty.toIntern() == .comptime_int_type) {
return sema.fail(block, src, "LHS of shift must be a fixed-width integer type, or RHS must be comptime-known", .{});
}
if (maybe_lhs_val) |lhs_val| {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
if (try lhs_val.compareAllWithZeroSema(.eq, pt)) {
break :result .{ .overflow_bit = try sema.splat(overflow_ty, .zero_u1), .inst = lhs };
}
}
}
},
else => unreachable,
}
const air_tag: Air.Inst.Tag = switch (zir_tag) {
.add_with_overflow => .add_with_overflow,
.mul_with_overflow => .mul_with_overflow,
.sub_with_overflow => .sub_with_overflow,
.shl_with_overflow => .shl_with_overflow,
else => unreachable,
};
return block.addInst(.{
.tag = air_tag,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(tuple_ty.toIntern()),
.payload = try block.sema.addExtra(Air.Bin{
.lhs = lhs,
.rhs = rhs,
}),
} },
});
};
if (result.inst != .none) {
if (try sema.resolveValue(result.inst)) |some| {
result.wrapped = some;
result.inst = .none;
}
}
if (result.inst == .none) {
return Air.internedToRef((try pt.aggregateValue(tuple_ty, &.{
result.wrapped.toIntern(),
result.overflow_bit.toIntern(),
})).toIntern());
}
const element_refs = try sema.arena.alloc(Air.Inst.Ref, 2);
element_refs[0] = result.inst;
element_refs[1] = Air.internedToRef(result.overflow_bit.toIntern());
return block.addAggregateInit(tuple_ty, element_refs);
}
fn splat(sema: *Sema, ty: Type, val: Value) !Value {
const pt = sema.pt;
if (ty.zigTypeTag(pt.zcu) != .vector) return val;
return pt.aggregateSplatValue(ty, val);
}
fn analyzeArithmetic(
sema: *Sema,
block: *Block,
zir_tag: Zir.Inst.Tag,
lhs: Air.Inst.Ref,
rhs: Air.Inst.Ref,
src: LazySrcLoc,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
want_safety: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
if (lhs_zig_ty_tag == .pointer) {
if (rhs_zig_ty_tag == .pointer) {
if (lhs_ty.ptrSize(zcu) != .slice and rhs_ty.ptrSize(zcu) != .slice) {
if (zir_tag != .sub) {
return sema.failWithInvalidPtrArithmetic(block, src, "pointer-pointer", "subtraction");
}
if (!lhs_ty.elemType2(zcu).eql(rhs_ty.elemType2(zcu), zcu)) {
return sema.fail(block, src, "incompatible pointer arithmetic operands '{f}' and '{f}'", .{
lhs_ty.fmt(pt), rhs_ty.fmt(pt),
});
}
const elem_size = lhs_ty.elemType2(zcu).abiSize(zcu);
if (elem_size == 0) {
return sema.fail(block, src, "pointer arithmetic requires element type '{f}' to have runtime bits", .{
lhs_ty.elemType2(zcu).fmt(pt),
});
}
const runtime_src = runtime_src: {
if (try sema.resolveValue(lhs)) |lhs_value| {
if (try sema.resolveValue(rhs)) |rhs_value| {
const lhs_ptr = switch (zcu.intern_pool.indexToKey(lhs_value.toIntern())) {
.undef => return sema.failWithUseOfUndef(block, lhs_src, null),
.ptr => |ptr| ptr,
else => unreachable,
};
const rhs_ptr = switch (zcu.intern_pool.indexToKey(rhs_value.toIntern())) {
.undef => return sema.failWithUseOfUndef(block, rhs_src, null),
.ptr => |ptr| ptr,
else => unreachable,
};
// Make sure the pointers point to the same data.
if (!lhs_ptr.base_addr.eql(rhs_ptr.base_addr)) break :runtime_src src;
const address = std.math.sub(u64, lhs_ptr.byte_offset, rhs_ptr.byte_offset) catch
return sema.fail(block, src, "operation results in overflow", .{});
const result = address / elem_size;
return try pt.intRef(.usize, result);
} else {
break :runtime_src lhs_src;
}
} else {
break :runtime_src rhs_src;
}
};
try sema.requireRuntimeBlock(block, src, runtime_src);
try sema.checkLogicalPtrOperation(block, src, lhs_ty);
try sema.checkLogicalPtrOperation(block, src, rhs_ty);
const lhs_int = try block.addBitCast(.usize, lhs);
const rhs_int = try block.addBitCast(.usize, rhs);
const address = try block.addBinOp(.sub_wrap, lhs_int, rhs_int);
return try block.addBinOp(.div_exact, address, try pt.intRef(.usize, elem_size));
}
} else {
switch (lhs_ty.ptrSize(zcu)) {
.one, .slice => {},
.many, .c => {
const air_tag: Air.Inst.Tag = switch (zir_tag) {
.add => .ptr_add,
.sub => .ptr_sub,
else => return sema.failWithInvalidPtrArithmetic(block, src, "pointer-integer", "addition and subtraction"),
};
if (!try lhs_ty.elemType2(zcu).hasRuntimeBitsSema(pt)) {
return sema.fail(block, src, "pointer arithmetic requires element type '{f}' to have runtime bits", .{
lhs_ty.elemType2(zcu).fmt(pt),
});
}
return sema.analyzePtrArithmetic(block, src, lhs, rhs, air_tag, lhs_src, rhs_src);
},
}
}
}
const resolved_type = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{
.override = &.{ lhs_src, rhs_src },
});
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
const scalar_type = resolved_type.scalarType(zcu);
const scalar_tag = scalar_type.zigTypeTag(zcu);
try sema.checkArithmeticOp(block, src, scalar_tag, lhs_zig_ty_tag, rhs_zig_ty_tag, zir_tag);
// The rules we'll implement below are as follows:
//
// * If both operands are comptime-known, we call the corresponding function in `arith` to get
// the comptime-known result. Inputs which would be IB at runtime are compile errors.
//
// * Otherwise, if one operand is comptime-known `undefined`, we trigger a compile error if this
// operator can ever possibly trigger IB, or otherwise return comptime-known `undefined`.
//
// * No other comptime operand detemines a comptime result; e.g. `0 * x` isn't always `0` because
// of `undefined`. Therefore, the remaining cases all become runtime operations.
const is_int = switch (scalar_tag) {
.int, .comptime_int => true,
.float, .comptime_float => false,
else => unreachable,
};
const maybe_lhs_val = try sema.resolveValueResolveLazy(casted_lhs);
const maybe_rhs_val = try sema.resolveValueResolveLazy(casted_rhs);
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
const result_val = switch (zir_tag) {
.add, .add_unsafe => try arith.add(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src),
.addwrap => try arith.addWrap(sema, resolved_type, lhs_val, rhs_val),
.add_sat => try arith.addSat(sema, resolved_type, lhs_val, rhs_val),
.sub => try arith.sub(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src),
.subwrap => try arith.subWrap(sema, resolved_type, lhs_val, rhs_val),
.sub_sat => try arith.subSat(sema, resolved_type, lhs_val, rhs_val),
.mul => try arith.mul(sema, block, resolved_type, lhs_val, rhs_val, src, lhs_src, rhs_src),
.mulwrap => try arith.mulWrap(sema, resolved_type, lhs_val, rhs_val),
.mul_sat => try arith.mulSat(sema, resolved_type, lhs_val, rhs_val),
else => unreachable,
};
return Air.internedToRef(result_val.toIntern());
}
}
const air_tag: Air.Inst.Tag, const air_tag_safe: Air.Inst.Tag, const allow_undef: bool = switch (zir_tag) {
.add, .add_unsafe => .{ if (block.float_mode == .optimized) .add_optimized else .add, .add_safe, !is_int },
.addwrap => .{ .add_wrap, .add_wrap, true },
.add_sat => .{ .add_sat, .add_sat, true },
.sub => .{ if (block.float_mode == .optimized) .sub_optimized else .sub, .sub_safe, !is_int },
.subwrap => .{ .sub_wrap, .sub_wrap, true },
.sub_sat => .{ .sub_sat, .sub_sat, true },
.mul => .{ if (block.float_mode == .optimized) .mul_optimized else .mul, .mul_safe, !is_int },
.mulwrap => .{ .mul_wrap, .mul_wrap, true },
.mul_sat => .{ .mul_sat, .mul_sat, true },
else => unreachable,
};
if (allow_undef) {
if (maybe_lhs_val) |lhs_val| {
if (lhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
}
if (maybe_rhs_val) |rhs_val| {
if (rhs_val.isUndef(zcu)) return pt.undefRef(resolved_type);
}
} else {
if (maybe_lhs_val) |lhs_val| {
try sema.checkAllScalarsDefined(block, lhs_src, lhs_val);
}
if (maybe_rhs_val) |rhs_val| {
try sema.checkAllScalarsDefined(block, rhs_src, rhs_val);
}
}
if (block.wantSafety() and want_safety and scalar_tag == .int) {
if (air_tag != air_tag_safe) try sema.preparePanicId(src, .integer_overflow);
return block.addBinOp(air_tag_safe, casted_lhs, casted_rhs);
}
return block.addBinOp(air_tag, casted_lhs, casted_rhs);
}
fn analyzePtrArithmetic(
sema: *Sema,
block: *Block,
op_src: LazySrcLoc,
ptr: Air.Inst.Ref,
uncasted_offset: Air.Inst.Ref,
air_tag: Air.Inst.Tag,
ptr_src: LazySrcLoc,
offset_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
// TODO if the operand is comptime-known to be negative, or is a negative int,
// coerce to isize instead of usize.
const offset = try sema.coerce(block, .usize, uncasted_offset, offset_src);
const pt = sema.pt;
const zcu = pt.zcu;
const opt_ptr_val = try sema.resolveValue(ptr);
const opt_off_val = try sema.resolveDefinedValue(block, offset_src, offset);
const ptr_ty = sema.typeOf(ptr);
const ptr_info = ptr_ty.ptrInfo(zcu);
assert(ptr_info.flags.size == .many or ptr_info.flags.size == .c);
if ((try sema.typeHasOnePossibleValue(.fromInterned(ptr_info.child))) != null) {
// Offset will be multiplied by zero, so result is the same as the base pointer.
return ptr;
}
const new_ptr_ty = t: {
// Calculate the new pointer alignment.
// This code is duplicated in `Type.elemPtrType`.
if (ptr_info.flags.alignment == .none) {
// ABI-aligned pointer. Any pointer arithmetic maintains the same ABI-alignedness.
break :t ptr_ty;
}
// If the addend is not a comptime-known value we can still count on
// it being a multiple of the type size.
const elem_size = try Type.fromInterned(ptr_info.child).abiSizeSema(pt);
const addend = if (opt_off_val) |off_val| a: {
const off_int = try sema.usizeCast(block, offset_src, try off_val.toUnsignedIntSema(pt));
break :a elem_size * off_int;
} else elem_size;
// The resulting pointer is aligned to the lcd between the offset (an
// arbitrary number) and the alignment factor (always a power of two,
// non zero).
const new_align: Alignment = @enumFromInt(@min(
@ctz(addend),
@intFromEnum(ptr_info.flags.alignment),
));
assert(new_align != .none);
break :t try pt.ptrTypeSema(.{
.child = ptr_info.child,
.sentinel = ptr_info.sentinel,
.flags = .{
.size = ptr_info.flags.size,
.alignment = new_align,
.is_const = ptr_info.flags.is_const,
.is_volatile = ptr_info.flags.is_volatile,
.is_allowzero = ptr_info.flags.is_allowzero,
.address_space = ptr_info.flags.address_space,
},
});
};
const runtime_src = rs: {
if (opt_ptr_val) |ptr_val| {
if (opt_off_val) |offset_val| {
if (ptr_val.isUndef(zcu)) return pt.undefRef(new_ptr_ty);
const offset_int = try sema.usizeCast(block, offset_src, try offset_val.toUnsignedIntSema(pt));
if (offset_int == 0) return ptr;
if (air_tag == .ptr_sub) {
const elem_size = try Type.fromInterned(ptr_info.child).abiSizeSema(pt);
const new_ptr_val = try sema.ptrSubtract(block, op_src, ptr_val, offset_int * elem_size, new_ptr_ty);
return Air.internedToRef(new_ptr_val.toIntern());
} else {
const new_ptr_val = try pt.getCoerced(try ptr_val.ptrElem(offset_int, pt), new_ptr_ty);
return Air.internedToRef(new_ptr_val.toIntern());
}
} else break :rs offset_src;
} else break :rs ptr_src;
};
try sema.requireRuntimeBlock(block, op_src, runtime_src);
try sema.checkLogicalPtrOperation(block, op_src, ptr_ty);
return block.addInst(.{
.tag = air_tag,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(new_ptr_ty.toIntern()),
.payload = try sema.addExtra(Air.Bin{
.lhs = ptr,
.rhs = offset,
}),
} },
});
}
fn zirLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ptr_src = src; // TODO better source location
const ptr = try sema.resolveInst(inst_data.operand);
return sema.analyzeLoad(block, src, ptr, ptr_src);
}
fn zirAsm(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
tmpl_is_expr: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.Asm, extended.operand);
const src = block.nodeOffset(extra.data.src_node);
const ret_ty_src = block.src(.{ .node_offset_asm_ret_ty = extra.data.src_node });
const small: Zir.Inst.Asm.Small = @bitCast(extended.small);
const outputs_len = small.outputs_len;
const inputs_len = small.inputs_len;
const is_volatile = small.is_volatile;
const is_global_assembly = sema.func_index == .none;
const zir_tags = sema.code.instructions.items(.tag);
const asm_source: []const u8 = if (tmpl_is_expr) s: {
const tmpl: Zir.Inst.Ref = @enumFromInt(@intFromEnum(extra.data.asm_source));
break :s try sema.resolveConstString(block, src, tmpl, .{ .simple = .inline_assembly_code });
} else sema.code.nullTerminatedString(extra.data.asm_source);
if (is_global_assembly) {
assert(outputs_len == 0); // validated by AstGen
assert(inputs_len == 0); // validated by AstGen
assert(extra.data.clobbers == .none); // validated by AstGen
assert(!is_volatile); // validated by AstGen
try zcu.addGlobalAssembly(sema.owner, asm_source);
return .void_value;
}
try sema.requireRuntimeBlock(block, src, null);
var extra_i = extra.end;
var output_type_bits = extra.data.output_type_bits;
var needed_capacity: usize = @typeInfo(Air.Asm).@"struct".fields.len + outputs_len + inputs_len;
const ConstraintName = struct { c: []const u8, n: []const u8 };
const out_args = try sema.arena.alloc(Air.Inst.Ref, outputs_len);
const outputs = try sema.arena.alloc(ConstraintName, outputs_len);
var expr_ty = Air.Inst.Ref.void_type;
for (out_args, 0..) |*arg, out_i| {
const output = sema.code.extraData(Zir.Inst.Asm.Output, extra_i);
extra_i = output.end;
const is_type = @as(u1, @truncate(output_type_bits)) != 0;
output_type_bits >>= 1;
if (is_type) {
// Indicate the output is the asm instruction return value.
arg.* = .none;
const out_ty = try sema.resolveType(block, ret_ty_src, output.data.operand);
expr_ty = Air.internedToRef(out_ty.toIntern());
} else {
arg.* = try sema.resolveInst(output.data.operand);
}
const constraint = sema.code.nullTerminatedString(output.data.constraint);
const name = sema.code.nullTerminatedString(output.data.name);
needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;
if (output.data.operand.toIndex()) |index| {
if (zir_tags[@intFromEnum(index)] == .ref) {
// TODO: better error location; it would be even nicer if there were notes that pointed at the output and the variable definition
return sema.fail(block, src, "asm cannot output to const local '{s}'", .{name});
}
}
outputs[out_i] = .{ .c = constraint, .n = name };
}
const args = try sema.arena.alloc(Air.Inst.Ref, inputs_len);
const inputs = try sema.arena.alloc(ConstraintName, inputs_len);
for (args, 0..) |*arg, arg_i| {
const input = sema.code.extraData(Zir.Inst.Asm.Input, extra_i);
extra_i = input.end;
const uncasted_arg = try sema.resolveInst(input.data.operand);
const uncasted_arg_ty = sema.typeOf(uncasted_arg);
switch (uncasted_arg_ty.zigTypeTag(zcu)) {
.comptime_int => arg.* = try sema.coerce(block, .usize, uncasted_arg, src),
.comptime_float => arg.* = try sema.coerce(block, .f64, uncasted_arg, src),
else => {
arg.* = uncasted_arg;
},
}
const constraint = sema.code.nullTerminatedString(input.data.constraint);
const name = sema.code.nullTerminatedString(input.data.name);
needed_capacity += (constraint.len + name.len + (2 + 3)) / 4;
inputs[arg_i] = .{ .c = constraint, .n = name };
}
const clobbers = if (extra.data.clobbers == .none) empty: {
const clobbers_ty = try sema.getBuiltinType(src, .@"assembly.Clobbers");
break :empty try sema.structInitEmpty(block, clobbers_ty, src, src);
} else try sema.resolveInst(extra.data.clobbers); // Already coerced by AstGen.
const clobbers_val = try sema.resolveConstDefinedValue(block, src, clobbers, .{ .simple = .clobber });
needed_capacity += asm_source.len / 4 + 1;
const gpa = sema.gpa;
try sema.air_extra.ensureUnusedCapacity(gpa, needed_capacity);
const asm_air = try block.addInst(.{
.tag = .assembly,
.data = .{ .ty_pl = .{
.ty = expr_ty,
.payload = sema.addExtraAssumeCapacity(Air.Asm{
.source_len = @intCast(asm_source.len),
.inputs_len = @intCast(args.len),
.clobbers = clobbers_val.toIntern(),
.flags = .{
.is_volatile = is_volatile,
.outputs_len = outputs_len,
},
}),
} },
});
sema.appendRefsAssumeCapacity(out_args);
sema.appendRefsAssumeCapacity(args);
for (outputs) |o| {
const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
@memcpy(buffer[0..o.c.len], o.c);
buffer[o.c.len] = 0;
@memcpy(buffer[o.c.len + 1 ..][0..o.n.len], o.n);
buffer[o.c.len + 1 + o.n.len] = 0;
sema.air_extra.items.len += (o.c.len + o.n.len + (2 + 3)) / 4;
}
for (inputs) |input| {
const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
@memcpy(buffer[0..input.c.len], input.c);
buffer[input.c.len] = 0;
@memcpy(buffer[input.c.len + 1 ..][0..input.n.len], input.n);
buffer[input.c.len + 1 + input.n.len] = 0;
sema.air_extra.items.len += (input.c.len + input.n.len + (2 + 3)) / 4;
}
{
const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice());
@memcpy(buffer[0..asm_source.len], asm_source);
buffer[asm_source.len] = 0;
sema.air_extra.items.len += asm_source.len / 4 + 1;
}
return asm_air;
}
/// Only called for equality operators. See also `zirCmp`.
fn zirCmpEq(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
op: std.math.CompareOperator,
air_tag: Air.Inst.Tag,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src: LazySrcLoc = block.nodeOffset(inst_data.src_node);
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
const lhs_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_ty_tag = rhs_ty.zigTypeTag(zcu);
if (lhs_ty_tag == .null and rhs_ty_tag == .null) {
// null == null, null != null
return if (op == .eq) .bool_true else .bool_false;
}
// comparing null with optionals
if (lhs_ty_tag == .null and (rhs_ty_tag == .optional or rhs_ty.isCPtr(zcu))) {
return sema.analyzeIsNull(block, rhs, op == .neq);
}
if (rhs_ty_tag == .null and (lhs_ty_tag == .optional or lhs_ty.isCPtr(zcu))) {
return sema.analyzeIsNull(block, lhs, op == .neq);
}
if (lhs_ty_tag == .null or rhs_ty_tag == .null) {
const non_null_type = if (lhs_ty_tag == .null) rhs_ty else lhs_ty;
return sema.fail(block, src, "comparison of '{f}' with null", .{non_null_type.fmt(pt)});
}
if (lhs_ty_tag == .@"union" and (rhs_ty_tag == .enum_literal or rhs_ty_tag == .@"enum")) {
return sema.analyzeCmpUnionTag(block, src, lhs, lhs_src, rhs, rhs_src, op);
}
if (rhs_ty_tag == .@"union" and (lhs_ty_tag == .enum_literal or lhs_ty_tag == .@"enum")) {
return sema.analyzeCmpUnionTag(block, src, rhs, rhs_src, lhs, lhs_src, op);
}
if (lhs_ty_tag == .error_set and rhs_ty_tag == .error_set) {
const runtime_src: LazySrcLoc = src: {
if (try sema.resolveValue(lhs)) |lval| {
if (try sema.resolveValue(rhs)) |rval| {
if (lval.isUndef(zcu) or rval.isUndef(zcu)) return .undef_bool;
const lkey = zcu.intern_pool.indexToKey(lval.toIntern());
const rkey = zcu.intern_pool.indexToKey(rval.toIntern());
return if ((lkey.err.name == rkey.err.name) == (op == .eq))
.bool_true
else
.bool_false;
} else {
break :src rhs_src;
}
} else {
break :src lhs_src;
}
};
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addBinOp(air_tag, lhs, rhs);
}
if (lhs_ty_tag == .type and rhs_ty_tag == .type) {
const lhs_as_type = try sema.analyzeAsType(block, lhs_src, lhs);
const rhs_as_type = try sema.analyzeAsType(block, rhs_src, rhs);
return if (lhs_as_type.eql(rhs_as_type, zcu) == (op == .eq)) .bool_true else .bool_false;
}
return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, true);
}
fn analyzeCmpUnionTag(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
un: Air.Inst.Ref,
un_src: LazySrcLoc,
tag: Air.Inst.Ref,
tag_src: LazySrcLoc,
op: std.math.CompareOperator,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const union_ty = sema.typeOf(un);
try union_ty.resolveFields(pt);
const union_tag_ty = union_ty.unionTagType(zcu) orelse {
const msg = msg: {
const msg = try sema.errMsg(un_src, "comparison of union and enum literal is only valid for tagged union types", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(union_ty.srcLoc(zcu), msg, "union '{f}' is not a tagged union", .{union_ty.fmt(pt)});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
};
// Coerce both the union and the tag to the union's tag type, and then execute the
// enum comparison codepath.
const coerced_tag = try sema.coerce(block, union_tag_ty, tag, tag_src);
const coerced_union = try sema.coerce(block, union_tag_ty, un, un_src);
if (try sema.resolveValue(coerced_tag)) |enum_val| {
if (enum_val.isUndef(zcu)) return .undef_bool;
const field_ty = union_ty.unionFieldType(enum_val, zcu).?;
if (field_ty.zigTypeTag(zcu) == .noreturn) {
return .bool_false;
}
}
return sema.cmpSelf(block, src, coerced_union, coerced_tag, op, un_src, tag_src);
}
/// Only called for non-equality operators. See also `zirCmpEq`.
fn zirCmp(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
op: std.math.CompareOperator,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src: LazySrcLoc = block.nodeOffset(inst_data.src_node);
const lhs_src = block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
return sema.analyzeCmp(block, src, lhs, rhs, op, lhs_src, rhs_src, false);
}
fn analyzeCmp(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
lhs: Air.Inst.Ref,
rhs: Air.Inst.Ref,
op: std.math.CompareOperator,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
is_equality_cmp: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
if (lhs_ty.zigTypeTag(zcu) != .optional and rhs_ty.zigTypeTag(zcu) != .optional) {
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
}
if (lhs_ty.zigTypeTag(zcu) == .vector and rhs_ty.zigTypeTag(zcu) == .vector) {
return sema.cmpVector(block, src, lhs, rhs, op, lhs_src, rhs_src);
}
if (lhs_ty.isNumeric(zcu) and rhs_ty.isNumeric(zcu)) {
// This operation allows any combination of integer and float types, regardless of the
// signed-ness, comptime-ness, and bit-width. So peer type resolution is incorrect for
// numeric types.
return sema.cmpNumeric(block, src, lhs, rhs, op, lhs_src, rhs_src);
}
if (is_equality_cmp and lhs_ty.zigTypeTag(zcu) == .error_union and rhs_ty.zigTypeTag(zcu) == .error_set) {
if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| {
if (lhs_val.errorUnionIsPayload(zcu)) return .bool_false;
}
const casted_lhs = try sema.analyzeErrUnionCode(block, lhs_src, lhs);
return sema.cmpSelf(block, src, casted_lhs, rhs, op, lhs_src, rhs_src);
}
if (is_equality_cmp and lhs_ty.zigTypeTag(zcu) == .error_set and rhs_ty.zigTypeTag(zcu) == .error_union) {
if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| {
if (rhs_val.errorUnionIsPayload(zcu)) return .bool_false;
}
const casted_rhs = try sema.analyzeErrUnionCode(block, rhs_src, rhs);
return sema.cmpSelf(block, src, lhs, casted_rhs, op, lhs_src, rhs_src);
}
const instructions = &[_]Air.Inst.Ref{ lhs, rhs };
const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]?LazySrcLoc{ lhs_src, rhs_src } });
if (!resolved_type.isSelfComparable(zcu, is_equality_cmp)) {
return sema.fail(block, src, "operator {s} not allowed for type '{f}'", .{
compareOperatorName(op), resolved_type.fmt(pt),
});
}
const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_type, rhs, rhs_src);
return sema.cmpSelf(block, src, casted_lhs, casted_rhs, op, lhs_src, rhs_src);
}
fn compareOperatorName(comp: std.math.CompareOperator) []const u8 {
return switch (comp) {
.lt => "<",
.lte => "<=",
.eq => "==",
.gte => ">=",
.gt => ">",
.neq => "!=",
};
}
fn cmpSelf(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
casted_lhs: Air.Inst.Ref,
casted_rhs: Air.Inst.Ref,
op: std.math.CompareOperator,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const resolved_type = sema.typeOf(casted_lhs);
const maybe_lhs_val = try sema.resolveValue(casted_lhs);
const maybe_rhs_val = try sema.resolveValue(casted_rhs);
if (maybe_lhs_val) |v| if (v.isUndef(zcu)) return .undef_bool;
if (maybe_rhs_val) |v| if (v.isUndef(zcu)) return .undef_bool;
const runtime_src: LazySrcLoc = src: {
if (maybe_lhs_val) |lhs_val| {
if (maybe_rhs_val) |rhs_val| {
if (resolved_type.zigTypeTag(zcu) == .vector) {
const cmp_val = try sema.compareVector(lhs_val, op, rhs_val, resolved_type);
return Air.internedToRef(cmp_val.toIntern());
}
return if (try sema.compareAll(lhs_val, op, rhs_val, resolved_type))
.bool_true
else
.bool_false;
} else {
if (resolved_type.zigTypeTag(zcu) == .bool) {
// We can lower bool eq/neq more efficiently.
return sema.runtimeBoolCmp(block, src, op, casted_rhs, lhs_val.toBool(), rhs_src);
}
break :src rhs_src;
}
} else {
// For bools, we still check the other operand, because we can lower
// bool eq/neq more efficiently.
if (resolved_type.zigTypeTag(zcu) == .bool) {
if (maybe_rhs_val) |rhs_val| {
return sema.runtimeBoolCmp(block, src, op, casted_lhs, rhs_val.toBool(), lhs_src);
}
}
break :src lhs_src;
}
};
try sema.requireRuntimeBlock(block, src, runtime_src);
if (resolved_type.zigTypeTag(zcu) == .vector) {
return block.addCmpVector(casted_lhs, casted_rhs, op);
}
const tag = Air.Inst.Tag.fromCmpOp(op, block.float_mode == .optimized);
return block.addBinOp(tag, casted_lhs, casted_rhs);
}
/// cmp_eq (x, false) => not(x)
/// cmp_eq (x, true ) => x
/// cmp_neq(x, false) => x
/// cmp_neq(x, true ) => not(x)
fn runtimeBoolCmp(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
op: std.math.CompareOperator,
lhs: Air.Inst.Ref,
rhs: bool,
runtime_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
if ((op == .neq) == rhs) {
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addTyOp(.not, .bool, lhs);
} else {
return lhs;
}
}
fn zirSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const ty = try sema.resolveType(block, operand_src, inst_data.operand);
switch (ty.zigTypeTag(pt.zcu)) {
.@"fn",
.noreturn,
.undefined,
.null,
.@"opaque",
=> return sema.fail(block, operand_src, "no size available for type '{f}'", .{ty.fmt(pt)}),
.type,
.enum_literal,
.comptime_float,
.comptime_int,
.void,
=> return .zero,
.bool,
.int,
.float,
.pointer,
.array,
.@"struct",
.optional,
.error_union,
.error_set,
.@"enum",
.@"union",
.vector,
.frame,
.@"anyframe",
=> {},
}
const val = try ty.abiSizeLazy(pt);
return Air.internedToRef(val.toIntern());
}
fn zirBitSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand_ty = try sema.resolveType(block, operand_src, inst_data.operand);
switch (operand_ty.zigTypeTag(zcu)) {
.@"fn",
.noreturn,
.undefined,
.null,
.@"opaque",
=> return sema.fail(block, operand_src, "no size available for type '{f}'", .{operand_ty.fmt(pt)}),
.type,
.enum_literal,
.comptime_float,
.comptime_int,
.void,
=> return .zero,
.bool,
.int,
.float,
.pointer,
.array,
.@"struct",
.optional,
.error_union,
.error_set,
.@"enum",
.@"union",
.vector,
.frame,
.@"anyframe",
=> {},
}
const bit_size = try operand_ty.bitSizeSema(pt);
return pt.intRef(.comptime_int, bit_size);
}
fn zirThis(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
_ = extended;
const pt = sema.pt;
const zcu = pt.zcu;
const namespace = pt.zcu.namespacePtr(block.namespace);
switch (pt.zcu.intern_pool.indexToKey(namespace.owner_type)) {
.opaque_type => {
// Opaque types are never outdated since they don't undergo type resolution, so nothing to do!
return Air.internedToRef(namespace.owner_type);
},
.struct_type, .union_type => {
const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);
try sema.declareDependency(.{ .interned = new_ty });
return Air.internedToRef(new_ty);
},
.enum_type => {
const new_ty = try pt.ensureTypeUpToDate(namespace.owner_type);
try sema.declareDependency(.{ .interned = new_ty });
// Since this is an enum, it has to be resolved immediately.
// `ensureTypeUpToDate` has resolved the new type if necessary.
// We just need to check for resolution failures.
const ty_unit: AnalUnit = .wrap(.{ .type = new_ty });
if (zcu.failed_analysis.contains(ty_unit) or zcu.transitive_failed_analysis.contains(ty_unit)) {
return error.AnalysisFail;
}
return Air.internedToRef(new_ty);
},
else => unreachable,
}
}
fn zirClosureGet(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const captures = Type.fromInterned(zcu.namespacePtr(block.namespace).owner_type).getCaptures(zcu);
const src_node: std.zig.Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(extended.operand)));
const src = block.nodeOffset(src_node);
const capture_ty = switch (captures.get(ip)[extended.small].unwrap()) {
.@"comptime" => |index| return Air.internedToRef(index),
.runtime => |index| index,
.nav_val => |nav| return sema.analyzeNavVal(block, src, nav),
.nav_ref => |nav| return sema.analyzeNavRef(block, src, nav),
};
// The comptime case is handled already above. Runtime case below.
if (!block.is_typeof and sema.func_index == .none) {
const msg = msg: {
const name = name: {
// TODO: we should probably store this name in the ZIR to avoid this complexity.
const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;
const tree = file.getTree(zcu) catch |err| {
// In this case we emit a warning + a less precise source location.
log.warn("unable to load {f}: {s}", .{
file.path.fmt(zcu.comp), @errorName(err),
});
break :name null;
};
const node = src_node.toAbsolute(src_base_node);
const token = tree.nodeMainToken(node);
break :name tree.tokenSlice(token);
};
const msg = if (name) |some|
try sema.errMsg(src, "'{s}' not accessible outside function scope", .{some})
else
try sema.errMsg(src, "variable not accessible outside function scope", .{});
errdefer msg.destroy(sema.gpa);
// TODO add "declared here" note
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
if (!block.is_typeof and !block.isComptime() and sema.func_index != .none) {
const msg = msg: {
const name = name: {
const file, const src_base_node = Zcu.LazySrcLoc.resolveBaseNode(block.src_base_inst, zcu).?;
const tree = file.getTree(zcu) catch |err| {
// In this case we emit a warning + a less precise source location.
log.warn("unable to load {f}: {s}", .{
file.path.fmt(zcu.comp), @errorName(err),
});
break :name null;
};
const node = src_node.toAbsolute(src_base_node);
const token = tree.nodeMainToken(node);
break :name tree.tokenSlice(token);
};
const msg = if (name) |some|
try sema.errMsg(src, "'{s}' not accessible from inner function", .{some})
else
try sema.errMsg(src, "variable not accessible from inner function", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(block.nodeOffset(.zero), msg, "crossed function definition here", .{});
// TODO add "declared here" note
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
assert(block.is_typeof);
// We need a dummy runtime instruction with the correct type.
return block.addTy(.alloc, .fromInterned(capture_ty));
}
fn zirRetAddr(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
_ = sema;
_ = extended;
if (block.isComptime()) {
// TODO: we could give a meaningful lazy value here. #14938
return .zero_usize;
} else {
return block.addNoOp(.ret_addr);
}
}
fn zirFrameAddress(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const src_node: std.zig.Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(extended.operand)));
const src = block.nodeOffset(src_node);
try sema.requireRuntimeBlock(block, src, null);
return try block.addNoOp(.frame_addr);
}
fn zirBuiltinSrc(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.Src, extended.operand).data;
const fn_name = ip.getNav(zcu.funcInfo(sema.func_index).owner_nav).name;
const gpa = sema.gpa;
const file_scope = block.getFileScope(zcu);
const func_name_val = v: {
const func_name_len = fn_name.length(ip);
const array_ty = try pt.intern(.{ .array_type = .{
.len = func_name_len,
.sentinel = .zero_u8,
.child = .u8_type,
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.orig_ty = .slice_const_u8_sentinel_0_type,
.val = try pt.intern(.{ .aggregate = .{
.ty = array_ty,
.storage = .{ .bytes = fn_name.toString() },
} }),
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, func_name_len)).toIntern(),
} });
};
const module_name_val = v: {
const module_name = file_scope.mod.?.fully_qualified_name;
const array_ty = try pt.intern(.{ .array_type = .{
.len = module_name.len,
.sentinel = .zero_u8,
.child = .u8_type,
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.orig_ty = .slice_const_u8_sentinel_0_type,
.val = try pt.intern(.{ .aggregate = .{
.ty = array_ty,
.storage = .{
.bytes = try ip.getOrPutString(gpa, pt.tid, module_name, .maybe_embedded_nulls),
},
} }),
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, module_name.len)).toIntern(),
} });
};
const file_name_val = v: {
const file_name = file_scope.sub_file_path;
const array_ty = try pt.intern(.{ .array_type = .{
.len = file_name.len,
.sentinel = .zero_u8,
.child = .u8_type,
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.orig_ty = .slice_const_u8_sentinel_0_type,
.val = try pt.intern(.{ .aggregate = .{
.ty = array_ty,
.storage = .{
.bytes = try ip.getOrPutString(gpa, pt.tid, file_name, .maybe_embedded_nulls),
},
} }),
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, file_name.len)).toIntern(),
} });
};
const src_loc_ty = try sema.getBuiltinType(block.nodeOffset(.zero), .SourceLocation);
const fields = .{
// module: [:0]const u8,
module_name_val,
// file: [:0]const u8,
file_name_val,
// fn_name: [:0]const u8,
func_name_val,
// line: u32,
(try pt.intValue(.u32, extra.line + 1)).toIntern(),
// column: u32,
(try pt.intValue(.u32, extra.column + 1)).toIntern(),
};
return Air.internedToRef((try pt.aggregateValue(src_loc_ty, &fields)).toIntern());
}
fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ty = try sema.resolveType(block, src, inst_data.operand);
const type_info_ty = try sema.getBuiltinType(src, .Type);
const type_info_tag_ty = type_info_ty.unionTagType(zcu).?;
if (ty.typeDeclInst(zcu)) |type_decl_inst| {
try sema.declareDependency(.{ .namespace = type_decl_inst });
}
switch (ty.zigTypeTag(zcu)) {
.type,
.void,
.bool,
.noreturn,
.comptime_float,
.comptime_int,
.undefined,
.null,
.enum_literal,
=> |type_info_tag| return unionInitFromEnumTag(sema, block, src, type_info_ty, @intFromEnum(type_info_tag), .void_value),
.@"fn" => {
const fn_info_ty = try sema.getBuiltinType(src, .@"Type.Fn");
const param_info_ty = try sema.getBuiltinType(src, .@"Type.Fn.Param");
const func_ty_info = zcu.typeToFunc(ty).?;
const param_vals = try sema.arena.alloc(InternPool.Index, func_ty_info.param_types.len);
for (param_vals, 0..) |*param_val, i| {
const param_ty = func_ty_info.param_types.get(ip)[i];
const is_generic = param_ty == .generic_poison_type;
const param_ty_val = try pt.intern(.{ .opt = .{
.ty = try pt.intern(.{ .opt_type = .type_type }),
.val = if (is_generic) .none else param_ty,
} });
const is_noalias = blk: {
const index = std.math.cast(u5, i) orelse break :blk false;
break :blk @as(u1, @truncate(func_ty_info.noalias_bits >> index)) != 0;
};
const param_fields = .{
// is_generic: bool,
Value.makeBool(is_generic).toIntern(),
// is_noalias: bool,
Value.makeBool(is_noalias).toIntern(),
// type: ?type,
param_ty_val,
};
param_val.* = (try pt.aggregateValue(param_info_ty, &param_fields)).toIntern();
}
const args_val = v: {
const new_decl_ty = try pt.arrayType(.{
.len = param_vals.len,
.child = param_info_ty.toIntern(),
});
const new_decl_val = (try pt.aggregateValue(new_decl_ty, param_vals)).toIntern();
const slice_ty = (try pt.ptrTypeSema(.{
.child = param_info_ty.toIntern(),
.flags = .{
.size = .slice,
.is_const = true,
},
})).toIntern();
const manyptr_ty = Type.fromInterned(slice_ty).slicePtrFieldType(zcu).toIntern();
break :v try pt.intern(.{ .slice = .{
.ty = slice_ty,
.ptr = try pt.intern(.{ .ptr = .{
.ty = manyptr_ty,
.base_addr = .{ .uav = .{
.orig_ty = manyptr_ty,
.val = new_decl_val,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, param_vals.len)).toIntern(),
} });
};
const ret_ty_opt = try pt.intern(.{ .opt = .{
.ty = try pt.intern(.{ .opt_type = .type_type }),
.val = opt_val: {
const ret_ty: Type = .fromInterned(func_ty_info.return_type);
if (ret_ty.toIntern() == .generic_poison_type) break :opt_val .none;
if (ret_ty.zigTypeTag(zcu) == .error_union) {
if (ret_ty.errorUnionPayload(zcu).toIntern() == .generic_poison_type) {
break :opt_val .none;
}
}
break :opt_val ret_ty.toIntern();
},
} });
const callconv_ty = try sema.getBuiltinType(src, .CallingConvention);
const callconv_val = Value.uninterpret(func_ty_info.cc, callconv_ty, pt) catch |err| switch (err) {
error.TypeMismatch => @panic("std.builtin is corrupt"),
error.OutOfMemory => |e| return e,
};
const field_values: [5]InternPool.Index = .{
// calling_convention: CallingConvention,
callconv_val.toIntern(),
// is_generic: bool,
Value.makeBool(func_ty_info.is_generic).toIntern(),
// is_var_args: bool,
Value.makeBool(func_ty_info.is_var_args).toIntern(),
// return_type: ?type,
ret_ty_opt,
// args: []const Fn.Param,
args_val,
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"fn"))).toIntern(),
.val = (try pt.aggregateValue(fn_info_ty, &field_values)).toIntern(),
})));
},
.int => {
const int_info_ty = try sema.getBuiltinType(src, .@"Type.Int");
const signedness_ty = try sema.getBuiltinType(src, .Signedness);
const info = ty.intInfo(zcu);
const field_values = .{
// signedness: Signedness,
(try pt.enumValueFieldIndex(signedness_ty, @intFromEnum(info.signedness))).toIntern(),
// bits: u16,
(try pt.intValue(.u16, info.bits)).toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.int))).toIntern(),
.val = (try pt.aggregateValue(int_info_ty, &field_values)).toIntern(),
})));
},
.float => {
const float_info_ty = try sema.getBuiltinType(src, .@"Type.Float");
const field_vals = .{
// bits: u16,
(try pt.intValue(.u16, ty.bitSize(zcu))).toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.float))).toIntern(),
.val = (try pt.aggregateValue(float_info_ty, &field_vals)).toIntern(),
})));
},
.pointer => {
const info = ty.ptrInfo(zcu);
const alignment = if (info.flags.alignment.toByteUnits()) |alignment|
try pt.intValue(.comptime_int, alignment)
else
try Type.fromInterned(info.child).lazyAbiAlignment(pt);
const addrspace_ty = try sema.getBuiltinType(src, .AddressSpace);
const pointer_ty = try sema.getBuiltinType(src, .@"Type.Pointer");
const ptr_size_ty = try sema.getBuiltinType(src, .@"Type.Pointer.Size");
const field_values = .{
// size: Size,
(try pt.enumValueFieldIndex(ptr_size_ty, @intFromEnum(info.flags.size))).toIntern(),
// is_const: bool,
Value.makeBool(info.flags.is_const).toIntern(),
// is_volatile: bool,
Value.makeBool(info.flags.is_volatile).toIntern(),
// alignment: comptime_int,
alignment.toIntern(),
// address_space: AddressSpace
(try pt.enumValueFieldIndex(addrspace_ty, @intFromEnum(info.flags.address_space))).toIntern(),
// child: type,
info.child,
// is_allowzero: bool,
Value.makeBool(info.flags.is_allowzero).toIntern(),
// sentinel: ?*const anyopaque,
(try sema.optRefValue(switch (info.sentinel) {
.none => null,
else => Value.fromInterned(info.sentinel),
})).toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.pointer))).toIntern(),
.val = (try pt.aggregateValue(pointer_ty, &field_values)).toIntern(),
})));
},
.array => {
const array_field_ty = try sema.getBuiltinType(src, .@"Type.Array");
const info = ty.arrayInfo(zcu);
const field_values = .{
// len: comptime_int,
(try pt.intValue(.comptime_int, info.len)).toIntern(),
// child: type,
info.elem_type.toIntern(),
// sentinel: ?*const anyopaque,
(try sema.optRefValue(info.sentinel)).toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.array))).toIntern(),
.val = (try pt.aggregateValue(array_field_ty, &field_values)).toIntern(),
})));
},
.vector => {
const vector_field_ty = try sema.getBuiltinType(src, .@"Type.Vector");
const info = ty.arrayInfo(zcu);
const field_values = .{
// len: comptime_int,
(try pt.intValue(.comptime_int, info.len)).toIntern(),
// child: type,
info.elem_type.toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.vector))).toIntern(),
.val = (try pt.aggregateValue(vector_field_ty, &field_values)).toIntern(),
})));
},
.optional => {
const optional_field_ty = try sema.getBuiltinType(src, .@"Type.Optional");
const field_values = .{
// child: type,
ty.optionalChild(zcu).toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.optional))).toIntern(),
.val = (try pt.aggregateValue(optional_field_ty, &field_values)).toIntern(),
})));
},
.error_set => {
// Get the Error type
const error_field_ty = try sema.getBuiltinType(src, .@"Type.Error");
// Build our list of Error values
// Optional value is only null if anyerror
// Value can be zero-length slice otherwise
const error_field_vals = switch (try sema.resolveInferredErrorSetTy(block, src, ty.toIntern())) {
.anyerror_type => null,
else => |err_set_ty_index| blk: {
const names = ip.indexToKey(err_set_ty_index).error_set_type.names;
const vals = try sema.arena.alloc(InternPool.Index, names.len);
for (vals, 0..) |*field_val, error_index| {
const error_name = names.get(ip)[error_index];
const error_name_len = error_name.length(ip);
const error_name_val = v: {
const new_decl_ty = try pt.arrayType(.{
.len = error_name_len,
.sentinel = .zero_u8,
.child = .u8_type,
});
const new_decl_val = try pt.intern(.{ .aggregate = .{
.ty = new_decl_ty.toIntern(),
.storage = .{ .bytes = error_name.toString() },
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.val = new_decl_val,
.orig_ty = .slice_const_u8_sentinel_0_type,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, error_name_len)).toIntern(),
} });
};
const error_field_fields = .{
// name: [:0]const u8,
error_name_val,
};
field_val.* = (try pt.aggregateValue(error_field_ty, &error_field_fields)).toIntern();
}
break :blk vals;
},
};
// Build our ?[]const Error value
const slice_errors_ty = try pt.ptrTypeSema(.{
.child = error_field_ty.toIntern(),
.flags = .{
.size = .slice,
.is_const = true,
},
});
const opt_slice_errors_ty = try pt.optionalType(slice_errors_ty.toIntern());
const errors_payload_val: InternPool.Index = if (error_field_vals) |vals| v: {
const array_errors_ty = try pt.arrayType(.{
.len = vals.len,
.child = error_field_ty.toIntern(),
});
const new_decl_val = (try pt.aggregateValue(array_errors_ty, vals)).toIntern();
const manyptr_errors_ty = slice_errors_ty.slicePtrFieldType(zcu).toIntern();
break :v try pt.intern(.{ .slice = .{
.ty = slice_errors_ty.toIntern(),
.ptr = try pt.intern(.{ .ptr = .{
.ty = manyptr_errors_ty,
.base_addr = .{ .uav = .{
.orig_ty = manyptr_errors_ty,
.val = new_decl_val,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, vals.len)).toIntern(),
} });
} else .none;
const errors_val = try pt.intern(.{ .opt = .{
.ty = opt_slice_errors_ty.toIntern(),
.val = errors_payload_val,
} });
// Construct Type{ .error_set = errors_val }
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.error_set))).toIntern(),
.val = errors_val,
})));
},
.error_union => {
const error_union_field_ty = try sema.getBuiltinType(src, .@"Type.ErrorUnion");
const field_values = .{
// error_set: type,
ty.errorUnionSet(zcu).toIntern(),
// payload: type,
ty.errorUnionPayload(zcu).toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.error_union))).toIntern(),
.val = (try pt.aggregateValue(error_union_field_ty, &field_values)).toIntern(),
})));
},
.@"enum" => {
const is_exhaustive = Value.makeBool(ip.loadEnumType(ty.toIntern()).tag_mode != .nonexhaustive);
const enum_field_ty = try sema.getBuiltinType(src, .@"Type.EnumField");
const enum_field_vals = try sema.arena.alloc(InternPool.Index, ip.loadEnumType(ty.toIntern()).names.len);
for (enum_field_vals, 0..) |*field_val, tag_index| {
const enum_type = ip.loadEnumType(ty.toIntern());
const value_val = if (enum_type.values.len > 0)
try ip.getCoercedInts(
zcu.gpa,
pt.tid,
ip.indexToKey(enum_type.values.get(ip)[tag_index]).int,
.comptime_int_type,
)
else
(try pt.intValue(.comptime_int, tag_index)).toIntern();
// TODO: write something like getCoercedInts to avoid needing to dupe
const name_val = v: {
const tag_name = enum_type.names.get(ip)[tag_index];
const tag_name_len = tag_name.length(ip);
const new_decl_ty = try pt.arrayType(.{
.len = tag_name_len,
.sentinel = .zero_u8,
.child = .u8_type,
});
const new_decl_val = try pt.intern(.{ .aggregate = .{
.ty = new_decl_ty.toIntern(),
.storage = .{ .bytes = tag_name.toString() },
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.val = new_decl_val,
.orig_ty = .slice_const_u8_sentinel_0_type,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, tag_name_len)).toIntern(),
} });
};
const enum_field_fields = .{
// name: [:0]const u8,
name_val,
// value: comptime_int,
value_val,
};
field_val.* = (try pt.aggregateValue(enum_field_ty, &enum_field_fields)).toIntern();
}
const fields_val = v: {
const fields_array_ty = try pt.arrayType(.{
.len = enum_field_vals.len,
.child = enum_field_ty.toIntern(),
});
const new_decl_val = (try pt.aggregateValue(fields_array_ty, enum_field_vals)).toIntern();
const slice_ty = (try pt.ptrTypeSema(.{
.child = enum_field_ty.toIntern(),
.flags = .{
.size = .slice,
.is_const = true,
},
})).toIntern();
const manyptr_ty = Type.fromInterned(slice_ty).slicePtrFieldType(zcu).toIntern();
break :v try pt.intern(.{ .slice = .{
.ty = slice_ty,
.ptr = try pt.intern(.{ .ptr = .{
.ty = manyptr_ty,
.base_addr = .{ .uav = .{
.val = new_decl_val,
.orig_ty = manyptr_ty,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, enum_field_vals.len)).toIntern(),
} });
};
const decls_val = try sema.typeInfoDecls(src, ip.loadEnumType(ty.toIntern()).namespace.toOptional());
const type_enum_ty = try sema.getBuiltinType(src, .@"Type.Enum");
const field_values = .{
// tag_type: type,
ip.loadEnumType(ty.toIntern()).tag_ty,
// fields: []const EnumField,
fields_val,
// decls: []const Declaration,
decls_val,
// is_exhaustive: bool,
is_exhaustive.toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"enum"))).toIntern(),
.val = (try pt.aggregateValue(type_enum_ty, &field_values)).toIntern(),
})));
},
.@"union" => {
const type_union_ty = try sema.getBuiltinType(src, .@"Type.Union");
const union_field_ty = try sema.getBuiltinType(src, .@"Type.UnionField");
try ty.resolveLayout(pt); // Getting alignment requires type layout
const union_obj = zcu.typeToUnion(ty).?;
const tag_type = union_obj.loadTagType(ip);
const layout = union_obj.flagsUnordered(ip).layout;
const union_field_vals = try gpa.alloc(InternPool.Index, tag_type.names.len);
defer gpa.free(union_field_vals);
for (union_field_vals, 0..) |*field_val, field_index| {
const name_val = v: {
const field_name = tag_type.names.get(ip)[field_index];
const field_name_len = field_name.length(ip);
const new_decl_ty = try pt.arrayType(.{
.len = field_name_len,
.sentinel = .zero_u8,
.child = .u8_type,
});
const new_decl_val = try pt.intern(.{ .aggregate = .{
.ty = new_decl_ty.toIntern(),
.storage = .{ .bytes = field_name.toString() },
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.val = new_decl_val,
.orig_ty = .slice_const_u8_sentinel_0_type,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, field_name_len)).toIntern(),
} });
};
const alignment = switch (layout) {
.auto, .@"extern" => try ty.fieldAlignmentSema(field_index, pt),
.@"packed" => .none,
};
const field_ty = union_obj.field_types.get(ip)[field_index];
const union_field_fields = .{
// name: [:0]const u8,
name_val,
// type: type,
field_ty,
// alignment: comptime_int,
(try pt.intValue(.comptime_int, alignment.toByteUnits() orelse 0)).toIntern(),
};
field_val.* = (try pt.aggregateValue(union_field_ty, &union_field_fields)).toIntern();
}
const fields_val = v: {
const array_fields_ty = try pt.arrayType(.{
.len = union_field_vals.len,
.child = union_field_ty.toIntern(),
});
const new_decl_val = (try pt.aggregateValue(array_fields_ty, union_field_vals)).toIntern();
const slice_ty = (try pt.ptrTypeSema(.{
.child = union_field_ty.toIntern(),
.flags = .{
.size = .slice,
.is_const = true,
},
})).toIntern();
const manyptr_ty = Type.fromInterned(slice_ty).slicePtrFieldType(zcu).toIntern();
break :v try pt.intern(.{ .slice = .{
.ty = slice_ty,
.ptr = try pt.intern(.{ .ptr = .{
.ty = manyptr_ty,
.base_addr = .{ .uav = .{
.orig_ty = manyptr_ty,
.val = new_decl_val,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, union_field_vals.len)).toIntern(),
} });
};
const decls_val = try sema.typeInfoDecls(src, ty.getNamespaceIndex(zcu).toOptional());
const enum_tag_ty_val = try pt.intern(.{ .opt = .{
.ty = (try pt.optionalType(.type_type)).toIntern(),
.val = if (ty.unionTagType(zcu)) |tag_ty| tag_ty.toIntern() else .none,
} });
const container_layout_ty = try sema.getBuiltinType(src, .@"Type.ContainerLayout");
const field_values = .{
// layout: ContainerLayout,
(try pt.enumValueFieldIndex(container_layout_ty, @intFromEnum(layout))).toIntern(),
// tag_type: ?type,
enum_tag_ty_val,
// fields: []const UnionField,
fields_val,
// decls: []const Declaration,
decls_val,
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"union"))).toIntern(),
.val = (try pt.aggregateValue(type_union_ty, &field_values)).toIntern(),
})));
},
.@"struct" => {
const type_struct_ty = try sema.getBuiltinType(src, .@"Type.Struct");
const struct_field_ty = try sema.getBuiltinType(src, .@"Type.StructField");
try ty.resolveLayout(pt); // Getting alignment requires type layout
var struct_field_vals: []InternPool.Index = &.{};
defer gpa.free(struct_field_vals);
fv: {
const struct_type = switch (ip.indexToKey(ty.toIntern())) {
.tuple_type => |tuple_type| {
struct_field_vals = try gpa.alloc(InternPool.Index, tuple_type.types.len);
for (struct_field_vals, 0..) |*struct_field_val, field_index| {
const field_ty = tuple_type.types.get(ip)[field_index];
const field_val = tuple_type.values.get(ip)[field_index];
const name_val = v: {
const field_name = try ip.getOrPutStringFmt(gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
const field_name_len = field_name.length(ip);
const new_decl_ty = try pt.arrayType(.{
.len = field_name_len,
.sentinel = .zero_u8,
.child = .u8_type,
});
const new_decl_val = try pt.intern(.{ .aggregate = .{
.ty = new_decl_ty.toIntern(),
.storage = .{ .bytes = field_name.toString() },
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.val = new_decl_val,
.orig_ty = .slice_const_u8_sentinel_0_type,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, field_name_len)).toIntern(),
} });
};
try Type.fromInterned(field_ty).resolveLayout(pt);
const is_comptime = field_val != .none;
const opt_default_val = if (is_comptime) Value.fromInterned(field_val) else null;
const default_val_ptr = try sema.optRefValue(opt_default_val);
const struct_field_fields = .{
// name: [:0]const u8,
name_val,
// type: type,
field_ty,
// default_value: ?*const anyopaque,
default_val_ptr.toIntern(),
// is_comptime: bool,
Value.makeBool(is_comptime).toIntern(),
// alignment: comptime_int,
(try pt.intValue(.comptime_int, Type.fromInterned(field_ty).abiAlignment(zcu).toByteUnits() orelse 0)).toIntern(),
};
struct_field_val.* = (try pt.aggregateValue(struct_field_ty, &struct_field_fields)).toIntern();
}
break :fv;
},
.struct_type => ip.loadStructType(ty.toIntern()),
else => unreachable,
};
struct_field_vals = try gpa.alloc(InternPool.Index, struct_type.field_types.len);
try ty.resolveStructFieldInits(pt);
for (struct_field_vals, 0..) |*field_val, field_index| {
const field_name = struct_type.fieldName(ip, field_index);
const field_name_len = field_name.length(ip);
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
const field_init = struct_type.fieldInit(ip, field_index);
const field_is_comptime = struct_type.fieldIsComptime(ip, field_index);
const name_val = v: {
const new_decl_ty = try pt.arrayType(.{
.len = field_name_len,
.sentinel = .zero_u8,
.child = .u8_type,
});
const new_decl_val = try pt.intern(.{ .aggregate = .{
.ty = new_decl_ty.toIntern(),
.storage = .{ .bytes = field_name.toString() },
} });
break :v try pt.intern(.{ .slice = .{
.ty = .slice_const_u8_sentinel_0_type,
.ptr = try pt.intern(.{ .ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type,
.base_addr = .{ .uav = .{
.val = new_decl_val,
.orig_ty = .slice_const_u8_sentinel_0_type,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, field_name_len)).toIntern(),
} });
};
const opt_default_val = if (field_init == .none) null else Value.fromInterned(field_init);
const default_val_ptr = try sema.optRefValue(opt_default_val);
const alignment = switch (struct_type.layout) {
.@"packed" => .none,
else => try field_ty.structFieldAlignmentSema(
struct_type.fieldAlign(ip, field_index),
struct_type.layout,
pt,
),
};
const struct_field_fields = .{
// name: [:0]const u8,
name_val,
// type: type,
field_ty.toIntern(),
// default_value: ?*const anyopaque,
default_val_ptr.toIntern(),
// is_comptime: bool,
Value.makeBool(field_is_comptime).toIntern(),
// alignment: comptime_int,
(try pt.intValue(.comptime_int, alignment.toByteUnits() orelse 0)).toIntern(),
};
field_val.* = (try pt.aggregateValue(struct_field_ty, &struct_field_fields)).toIntern();
}
}
const fields_val = v: {
const array_fields_ty = try pt.arrayType(.{
.len = struct_field_vals.len,
.child = struct_field_ty.toIntern(),
});
const new_decl_val = (try pt.aggregateValue(array_fields_ty, struct_field_vals)).toIntern();
const slice_ty = (try pt.ptrTypeSema(.{
.child = struct_field_ty.toIntern(),
.flags = .{
.size = .slice,
.is_const = true,
},
})).toIntern();
const manyptr_ty = Type.fromInterned(slice_ty).slicePtrFieldType(zcu).toIntern();
break :v try pt.intern(.{ .slice = .{
.ty = slice_ty,
.ptr = try pt.intern(.{ .ptr = .{
.ty = manyptr_ty,
.base_addr = .{ .uav = .{
.orig_ty = manyptr_ty,
.val = new_decl_val,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, struct_field_vals.len)).toIntern(),
} });
};
const decls_val = try sema.typeInfoDecls(src, ty.getNamespace(zcu));
const backing_integer_val = try pt.intern(.{ .opt = .{
.ty = (try pt.optionalType(.type_type)).toIntern(),
.val = if (zcu.typeToPackedStruct(ty)) |packed_struct| val: {
assert(Type.fromInterned(packed_struct.backingIntTypeUnordered(ip)).isInt(zcu));
break :val packed_struct.backingIntTypeUnordered(ip);
} else .none,
} });
const container_layout_ty = try sema.getBuiltinType(src, .@"Type.ContainerLayout");
const layout = ty.containerLayout(zcu);
const field_values = [_]InternPool.Index{
// layout: ContainerLayout,
(try pt.enumValueFieldIndex(container_layout_ty, @intFromEnum(layout))).toIntern(),
// backing_integer: ?type,
backing_integer_val,
// fields: []const StructField,
fields_val,
// decls: []const Declaration,
decls_val,
// is_tuple: bool,
Value.makeBool(ty.isTuple(zcu)).toIntern(),
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"struct"))).toIntern(),
.val = (try pt.aggregateValue(type_struct_ty, &field_values)).toIntern(),
})));
},
.@"opaque" => {
const type_opaque_ty = try sema.getBuiltinType(src, .@"Type.Opaque");
try ty.resolveFields(pt);
const decls_val = try sema.typeInfoDecls(src, ty.getNamespace(zcu));
const field_values = .{
// decls: []const Declaration,
decls_val,
};
return Air.internedToRef((try pt.internUnion(.{
.ty = type_info_ty.toIntern(),
.tag = (try pt.enumValueFieldIndex(type_info_tag_ty, @intFromEnum(std.builtin.TypeId.@"opaque"))).toIntern(),
.val = (try pt.aggregateValue(type_opaque_ty, &field_values)).toIntern(),
})));
},
.frame => return sema.failWithUseOfAsync(block, src),
.@"anyframe" => return sema.failWithUseOfAsync(block, src),
}
}
fn typeInfoDecls(
sema: *Sema,
src: LazySrcLoc,
opt_namespace: InternPool.OptionalNamespaceIndex,
) CompileError!InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const declaration_ty = try sema.getBuiltinType(src, .@"Type.Declaration");
var decl_vals = std.array_list.Managed(InternPool.Index).init(gpa);
defer decl_vals.deinit();
var seen_namespaces = std.AutoHashMap(*Namespace, void).init(gpa);
defer seen_namespaces.deinit();
try sema.typeInfoNamespaceDecls(opt_namespace, declaration_ty, &decl_vals, &seen_namespaces);
const array_decl_ty = try pt.arrayType(.{
.len = decl_vals.items.len,
.child = declaration_ty.toIntern(),
});
const new_decl_val = (try pt.aggregateValue(array_decl_ty, decl_vals.items)).toIntern();
const slice_ty = (try pt.ptrTypeSema(.{
.child = declaration_ty.toIntern(),
.flags = .{
.size = .slice,
.is_const = true,
},
})).toIntern();
const manyptr_ty = Type.fromInterned(slice_ty).slicePtrFieldType(zcu).toIntern();
return try pt.intern(.{ .slice = .{
.ty = slice_ty,
.ptr = try pt.intern(.{ .ptr = .{
.ty = manyptr_ty,
.base_addr = .{ .uav = .{
.orig_ty = manyptr_ty,
.val = new_decl_val,
} },
.byte_offset = 0,
} }),
.len = (try pt.intValue(.usize, decl_vals.items.len)).toIntern(),
} });
}
fn typeInfoNamespaceDecls(
sema: *Sema,
opt_namespace_index: InternPool.OptionalNamespaceIndex,
declaration_ty: Type,
decl_vals: *std.array_list.Managed(InternPool.Index),
seen_namespaces: *std.AutoHashMap(*Namespace, void),
) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const namespace_index = opt_namespace_index.unwrap() orelse return;
try pt.ensureNamespaceUpToDate(namespace_index);
const namespace = zcu.namespacePtr(namespace_index);
const gop = try seen_namespaces.getOrPut(namespace);
if (gop.found_existing) return;
for (namespace.pub_decls.keys()) |nav| {
const name = ip.getNav(nav).name;
const name_val = name_val: {
const name_len = name.length(ip);
const array_ty = try pt.arrayType(.{
.len = name_len,
.sentinel = .zero_u8,
.child = .u8_type,
});
const array_val = try pt.intern(.{ .aggregate = .{
.ty = array_ty.toIntern(),
.storage = .{ .bytes = name.toString() },
} });
break :name_val try pt.intern(.{
.slice = .{
.ty = .slice_const_u8_sentinel_0_type, // [:0]const u8
.ptr = try pt.intern(.{
.ptr = .{
.ty = .manyptr_const_u8_sentinel_0_type, // [*:0]const u8
.base_addr = .{ .uav = .{
.orig_ty = .slice_const_u8_sentinel_0_type,
.val = array_val,
} },
.byte_offset = 0,
},
}),
.len = (try pt.intValue(.usize, name_len)).toIntern(),
},
});
};
const fields = [_]InternPool.Index{
// name: [:0]const u8,
name_val,
};
try decl_vals.append((try pt.aggregateValue(declaration_ty, &fields)).toIntern());
}
}
fn zirTypeof(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
_ = block;
const zir_datas = sema.code.instructions.items(.data);
const inst_data = zir_datas[@intFromEnum(inst)].un_node;
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
return Air.internedToRef(operand_ty.toIntern());
}
fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pl_node = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
var child_block: Block = .{
.parent = block,
.sema = sema,
.namespace = block.namespace,
.instructions = .{},
.inlining = block.inlining,
.comptime_reason = null,
.is_typeof = true,
.want_safety = false,
.error_return_trace_index = block.error_return_trace_index,
.src_base_inst = block.src_base_inst,
.type_name_ctx = block.type_name_ctx,
};
defer child_block.instructions.deinit(sema.gpa);
const operand = try sema.resolveInlineBody(&child_block, body, inst);
return Air.internedToRef(sema.typeOf(operand).toIntern());
}
fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const res_ty = try sema.log2IntType(block, operand_ty, src);
return Air.internedToRef(res_ty.toIntern());
}
fn log2IntType(sema: *Sema, block: *Block, operand: Type, src: LazySrcLoc) CompileError!Type {
const pt = sema.pt;
const zcu = pt.zcu;
switch (operand.zigTypeTag(zcu)) {
.comptime_int => return .comptime_int,
.int => {
const bits = operand.bitSize(zcu);
const count = if (bits == 0)
0
else blk: {
var count: u16 = 0;
var s = bits - 1;
while (s != 0) : (s >>= 1) {
count += 1;
}
break :blk count;
};
return pt.intType(.unsigned, count);
},
.vector => {
const elem_ty = operand.elemType2(zcu);
const log2_elem_ty = try sema.log2IntType(block, elem_ty, src);
return pt.vectorType(.{
.len = operand.vectorLen(zcu),
.child = log2_elem_ty.toIntern(),
});
},
else => {},
}
return sema.fail(
block,
src,
"bit shifting operation expected integer type, found '{f}'",
.{operand.fmt(pt)},
);
}
fn zirTypeofPeer(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const extra = sema.code.extraData(Zir.Inst.TypeOfPeer, extended.operand);
const src = block.nodeOffset(extra.data.src_node);
const body = sema.code.bodySlice(extra.data.body_index, extra.data.body_len);
var child_block: Block = .{
.parent = block,
.sema = sema,
.namespace = block.namespace,
.instructions = .{},
.inlining = block.inlining,
.comptime_reason = null,
.is_typeof = true,
.runtime_cond = block.runtime_cond,
.runtime_loop = block.runtime_loop,
.runtime_index = block.runtime_index,
.src_base_inst = block.src_base_inst,
.type_name_ctx = block.type_name_ctx,
};
defer child_block.instructions.deinit(sema.gpa);
// Ignore the result, we only care about the instructions in `args`.
_ = try sema.analyzeInlineBody(&child_block, body, inst);
const args = sema.code.refSlice(extra.end, extended.small);
const inst_list = try sema.gpa.alloc(Air.Inst.Ref, args.len);
defer sema.gpa.free(inst_list);
for (args, 0..) |arg_ref, i| {
inst_list[i] = try sema.resolveInst(arg_ref);
}
const result_type = try sema.resolvePeerTypes(block, src, inst_list, .{ .typeof_builtin_call_node_offset = extra.data.src_node });
return Air.internedToRef(result_type.toIntern());
}
fn zirBoolNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.src(.{ .node_offset_un_op = inst_data.src_node });
const uncasted_operand = try sema.resolveInst(inst_data.operand);
const uncasted_ty = sema.typeOf(uncasted_operand);
if (uncasted_ty.isVector(zcu)) {
if (uncasted_ty.scalarType(zcu).zigTypeTag(zcu) != .bool) {
return sema.fail(block, operand_src, "boolean not operation on type '{f}'", .{
uncasted_ty.fmt(pt),
});
}
return analyzeBitNot(sema, block, uncasted_operand, src);
}
const operand = try sema.coerce(block, .bool, uncasted_operand, operand_src);
if (try sema.resolveValue(operand)) |val| {
return if (val.isUndef(zcu)) .undef_bool else if (val.toBool()) .bool_false else .bool_true;
}
try sema.requireRuntimeBlock(block, src, null);
return block.addTyOp(.not, .bool, operand);
}
fn zirBoolBr(
sema: *Sema,
parent_block: *Block,
inst: Zir.Inst.Index,
is_bool_or: bool,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const datas = sema.code.instructions.items(.data);
const inst_data = datas[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.BoolBr, inst_data.payload_index);
const uncoerced_lhs = try sema.resolveInst(extra.data.lhs);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
const lhs_src = parent_block.src(.{ .node_offset_bin_lhs = inst_data.src_node });
const rhs_src = parent_block.src(.{ .node_offset_bin_rhs = inst_data.src_node });
const lhs = try sema.coerce(parent_block, .bool, uncoerced_lhs, lhs_src);
if (try sema.resolveDefinedValue(parent_block, lhs_src, lhs)) |lhs_val| {
if (is_bool_or and lhs_val.toBool()) {
return .bool_true;
} else if (!is_bool_or and !lhs_val.toBool()) {
return .bool_false;
}
// comptime-known left-hand side. No need for a block here; the result
// is simply the rhs expression. Here we rely on there only being 1
// break instruction (`break_inline`).
const rhs_result = try sema.resolveInlineBody(parent_block, body, inst);
if (sema.typeOf(rhs_result).isNoReturn(zcu)) {
return rhs_result;
}
return sema.coerce(parent_block, .bool, rhs_result, rhs_src);
}
const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
try sema.air_instructions.append(gpa, .{
.tag = .block,
.data = .{ .ty_pl = .{
.ty = .bool_type,
.payload = undefined,
} },
});
var child_block = parent_block.makeSubBlock();
child_block.runtime_loop = null;
child_block.runtime_cond = lhs_src;
child_block.runtime_index.increment();
defer child_block.instructions.deinit(gpa);
var then_block = child_block.makeSubBlock();
defer then_block.instructions.deinit(gpa);
var else_block = child_block.makeSubBlock();
defer else_block.instructions.deinit(gpa);
const lhs_block = if (is_bool_or) &then_block else &else_block;
const rhs_block = if (is_bool_or) &else_block else &then_block;
const lhs_result: Air.Inst.Ref = if (is_bool_or) .bool_true else .bool_false;
_ = try lhs_block.addBr(block_inst, lhs_result);
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint;
sema.branch_hint = null;
const rhs_result = try sema.resolveInlineBody(rhs_block, body, inst);
const rhs_noret = sema.typeOf(rhs_result).isNoReturn(zcu);
const coerced_rhs_result = if (!rhs_noret) rhs: {
const coerced_result = try sema.coerce(rhs_block, .bool, rhs_result, rhs_src);
_ = try rhs_block.addBr(block_inst, coerced_result);
break :rhs coerced_result;
} else rhs_result;
const rhs_hint = sema.branch_hint orelse .none;
const result = try sema.finishCondBr(
parent_block,
&child_block,
&then_block,
&else_block,
lhs,
block_inst,
if (is_bool_or) .{
.true = .none,
.false = rhs_hint,
.then_cov = .poi,
.else_cov = .poi,
} else .{
.true = rhs_hint,
.false = .none,
.then_cov = .poi,
.else_cov = .poi,
},
);
if (!rhs_noret) {
if (try sema.resolveDefinedValue(rhs_block, rhs_src, coerced_rhs_result)) |rhs_val| {
if (is_bool_or and rhs_val.toBool()) {
return .bool_true;
} else if (!is_bool_or and !rhs_val.toBool()) {
return .bool_false;
}
}
}
return result;
}
fn finishCondBr(
sema: *Sema,
parent_block: *Block,
child_block: *Block,
then_block: *Block,
else_block: *Block,
cond: Air.Inst.Ref,
block_inst: Air.Inst.Index,
branch_hints: Air.CondBr.BranchHints,
) !Air.Inst.Ref {
const gpa = sema.gpa;
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +
then_block.instructions.items.len + else_block.instructions.items.len +
@typeInfo(Air.Block).@"struct".fields.len + child_block.instructions.items.len + 1);
const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(then_block.instructions.items.len),
.else_body_len = @intCast(else_block.instructions.items.len),
.branch_hints = branch_hints,
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items));
_ = try child_block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
.operand = cond,
.payload = cond_br_payload,
} } });
sema.air_instructions.items(.data)[@intFromEnum(block_inst)].ty_pl.payload = sema.addExtraAssumeCapacity(
Air.Block{ .body_len = @intCast(child_block.instructions.items.len) },
);
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(child_block.instructions.items));
try parent_block.instructions.append(gpa, block_inst);
return block_inst.toRef();
}
fn checkNullableType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.optional, .null, .undefined => return,
.pointer => if (ty.isPtrLikeOptional(zcu)) return,
else => {},
}
return sema.failWithExpectedOptionalType(block, src, ty);
}
fn checkSentinelType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
const pt = sema.pt;
const zcu = pt.zcu;
if (!ty.isSelfComparable(zcu, true)) {
return sema.fail(block, src, "non-scalar sentinel type '{f}'", .{ty.fmt(pt)});
}
}
fn zirIsNonNull(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
try sema.checkNullableType(block, src, sema.typeOf(operand));
return sema.analyzeIsNull(block, operand, true);
}
fn zirIsNonNullPtr(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ptr = try sema.resolveInst(inst_data.operand);
const ptr_ty = sema.typeOf(ptr);
try sema.checkNullableType(block, src, sema.typeOf(ptr).elemType2(zcu));
if (try sema.resolveValue(ptr)) |ptr_val| {
if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |loaded_val| {
return sema.analyzeIsNull(block, Air.internedToRef(loaded_val.toIntern()), true);
}
}
if (ptr_ty.childType(zcu).isNullFromType(zcu)) |is_null| {
return if (is_null) .bool_false else .bool_true;
}
return block.addUnOp(.is_non_null_ptr, ptr);
}
fn checkErrorType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.error_set, .error_union, .undefined => return,
else => return sema.fail(block, src, "expected error union type, found '{f}'", .{
ty.fmt(pt),
}),
}
}
fn zirIsNonErr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
try sema.checkErrorType(block, src, sema.typeOf(operand));
return sema.analyzeIsNonErr(block, src, operand);
}
fn zirIsNonErrPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ptr = try sema.resolveInst(inst_data.operand);
try sema.checkErrorType(block, src, sema.typeOf(ptr).elemType2(zcu));
const loaded = try sema.analyzeLoad(block, src, ptr, src);
return sema.analyzeIsNonErr(block, src, loaded);
}
fn zirRetIsNonErr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
return sema.analyzeIsNonErr(block, src, operand);
}
fn zirCondbr(
sema: *Sema,
parent_block: *Block,
inst: Zir.Inst.Index,
) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const cond_src = parent_block.src(.{ .node_offset_if_cond = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.CondBr, inst_data.payload_index);
const then_body = sema.code.bodySlice(extra.end, extra.data.then_body_len);
const else_body = sema.code.bodySlice(extra.end + then_body.len, extra.data.else_body_len);
const uncasted_cond = try sema.resolveInst(extra.data.condition);
const cond = try sema.coerce(parent_block, .bool, uncasted_cond, cond_src);
if (try sema.resolveDefinedValue(parent_block, cond_src, cond)) |cond_val| {
const body = if (cond_val.toBool()) then_body else else_body;
// We can propagate `.cold` hints from this branch since it's comptime-known
// to be taken from the parent branch.
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
try sema.maybeErrorUnwrapCondbr(parent_block, body, extra.data.condition, cond_src);
// We use `analyzeBodyInner` since we want to propagate any comptime control flow to the caller.
return sema.analyzeBodyInner(parent_block, body);
}
const gpa = sema.gpa;
// We'll re-use the sub block to save on memory bandwidth, and yank out the
// instructions array in between using it for the then block and else block.
var sub_block = parent_block.makeSubBlock();
sub_block.runtime_loop = null;
sub_block.runtime_cond = cond_src;
sub_block.runtime_index.increment();
sub_block.need_debug_scope = null; // this body is emitted regardless
defer sub_block.instructions.deinit(gpa);
const true_hint = try sema.analyzeBodyRuntimeBreak(&sub_block, then_body);
const true_instructions = try sub_block.instructions.toOwnedSlice(gpa);
defer gpa.free(true_instructions);
const err_cond = blk: {
const index = extra.data.condition.toIndex() orelse break :blk null;
if (sema.code.instructions.items(.tag)[@intFromEnum(index)] != .is_non_err) break :blk null;
const err_inst_data = sema.code.instructions.items(.data)[@intFromEnum(index)].un_node;
const err_operand = try sema.resolveInst(err_inst_data.operand);
const operand_ty = sema.typeOf(err_operand);
assert(operand_ty.zigTypeTag(zcu) == .error_union);
const result_ty = operand_ty.errorUnionSet(zcu);
break :blk try sub_block.addTyOp(.unwrap_errunion_err, result_ty, err_operand);
};
const false_hint: std.builtin.BranchHint = if (err_cond != null and
try sema.maybeErrorUnwrap(&sub_block, else_body, err_cond.?, cond_src, false))
h: {
// nothing to do here. weight against error branch
break :h .unlikely;
} else try sema.analyzeBodyRuntimeBreak(&sub_block, else_body);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +
true_instructions.len + sub_block.instructions.items.len);
_ = try parent_block.addInst(.{
.tag = .cond_br,
.data = .{
.pl_op = .{
.operand = cond,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(true_instructions.len),
.else_body_len = @intCast(sub_block.instructions.items.len),
.branch_hints = .{
.true = true_hint,
.false = false_hint,
// Code coverage is desired for error handling.
.then_cov = .poi,
.else_cov = .poi,
},
}),
},
},
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(true_instructions));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
}
fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = parent_block.nodeOffset(inst_data.src_node);
const operand_src = parent_block.src(.{ .node_offset_try_operand = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
const err_union = try sema.resolveInst(extra.data.operand);
const err_union_ty = sema.typeOf(err_union);
const pt = sema.pt;
const zcu = pt.zcu;
if (err_union_ty.zigTypeTag(zcu) != .error_union) {
return sema.failWithOwnedErrorMsg(parent_block, msg: {
const msg = try sema.errMsg(operand_src, "expected error union type, found '{f}'", .{err_union_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, err_union_ty);
try sema.errNote(operand_src, msg, "consider omitting 'try'", .{});
break :msg msg;
});
}
const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union);
if (is_non_err != .none) {
// We can propagate `.cold` hints from this branch since it's comptime-known
// to be taken from the parent branch.
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
const is_non_err_val = (try sema.resolveDefinedValue(parent_block, operand_src, is_non_err)).?;
if (is_non_err_val.toBool()) {
return sema.analyzeErrUnionPayload(parent_block, src, err_union_ty, err_union, operand_src, false);
}
// We can analyze the body directly in the parent block because we know there are
// no breaks from the body possible, and that the body is noreturn.
try sema.analyzeBodyInner(parent_block, body);
return .unreachable_value;
}
var sub_block = parent_block.makeSubBlock();
defer sub_block.instructions.deinit(sema.gpa);
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint;
// This body is guaranteed to end with noreturn and has no breaks.
try sema.analyzeBodyInner(&sub_block, body);
// The only interesting hint here is `.cold`, which can come from e.g. `errdefer @panic`.
const is_cold = sema.branch_hint == .cold;
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Try).@"struct".fields.len +
sub_block.instructions.items.len);
const try_inst = try parent_block.addInst(.{
.tag = if (is_cold) .try_cold else .@"try",
.data = .{ .pl_op = .{
.operand = err_union,
.payload = sema.addExtraAssumeCapacity(Air.Try{
.body_len = @intCast(sub_block.instructions.items.len),
}),
} },
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
return try_inst;
}
fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = parent_block.nodeOffset(inst_data.src_node);
const operand_src = parent_block.src(.{ .node_offset_try_operand = inst_data.src_node });
const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
const operand = try sema.resolveInst(extra.data.operand);
const err_union = try sema.analyzeLoad(parent_block, src, operand, operand_src);
const err_union_ty = sema.typeOf(err_union);
const pt = sema.pt;
const zcu = pt.zcu;
if (err_union_ty.zigTypeTag(zcu) != .error_union) {
return sema.failWithOwnedErrorMsg(parent_block, msg: {
const msg = try sema.errMsg(operand_src, "expected error union type, found '{f}'", .{err_union_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, err_union_ty);
try sema.errNote(operand_src, msg, "consider omitting 'try'", .{});
break :msg msg;
});
}
const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union);
if (is_non_err != .none) {
// We can propagate `.cold` hints from this branch since it's comptime-known
// to be taken from the parent branch.
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint orelse if (sema.branch_hint == .cold) .cold else null;
const is_non_err_val = (try sema.resolveDefinedValue(parent_block, operand_src, is_non_err)).?;
if (is_non_err_val.toBool()) {
return sema.analyzeErrUnionPayloadPtr(parent_block, src, operand, false, false);
}
// We can analyze the body directly in the parent block because we know there are
// no breaks from the body possible, and that the body is noreturn.
try sema.analyzeBodyInner(parent_block, body);
return .unreachable_value;
}
var sub_block = parent_block.makeSubBlock();
defer sub_block.instructions.deinit(sema.gpa);
const parent_hint = sema.branch_hint;
defer sema.branch_hint = parent_hint;
// This body is guaranteed to end with noreturn and has no breaks.
try sema.analyzeBodyInner(&sub_block, body);
// The only interesting hint here is `.cold`, which can come from e.g. `errdefer @panic`.
const is_cold = sema.branch_hint == .cold;
const operand_ty = sema.typeOf(operand);
const ptr_info = operand_ty.ptrInfo(zcu);
const res_ty = try pt.ptrTypeSema(.{
.child = err_union_ty.errorUnionPayload(zcu).toIntern(),
.flags = .{
.is_const = ptr_info.flags.is_const,
.is_volatile = ptr_info.flags.is_volatile,
.is_allowzero = ptr_info.flags.is_allowzero,
.address_space = ptr_info.flags.address_space,
},
});
const res_ty_ref = Air.internedToRef(res_ty.toIntern());
try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.TryPtr).@"struct".fields.len +
sub_block.instructions.items.len);
const try_inst = try parent_block.addInst(.{
.tag = if (is_cold) .try_ptr_cold else .try_ptr,
.data = .{ .ty_pl = .{
.ty = res_ty_ref,
.payload = sema.addExtraAssumeCapacity(Air.TryPtr{
.ptr = operand,
.body_len = @intCast(sub_block.instructions.items.len),
}),
} },
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(sub_block.instructions.items));
return try_inst;
}
fn ensurePostHoc(sema: *Sema, block: *Block, dest_block: Zir.Inst.Index) !*LabeledBlock {
const gop = sema.inst_map.getOrPutAssumeCapacity(dest_block);
if (gop.found_existing) existing: {
// This may be a *result* from an earlier iteration of an inline loop.
// In this case, there will not be a post-hoc block entry, and we can
// continue with the logic below.
const new_block_inst = gop.value_ptr.*.toIndex() orelse break :existing;
return sema.post_hoc_blocks.get(new_block_inst) orelse break :existing;
}
try sema.post_hoc_blocks.ensureUnusedCapacity(sema.gpa, 1);
const new_block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
gop.value_ptr.* = new_block_inst.toRef();
try sema.air_instructions.append(sema.gpa, .{
.tag = .block,
.data = undefined,
});
const labeled_block = try sema.gpa.create(LabeledBlock);
labeled_block.* = .{
.label = .{
.zir_block = dest_block,
.merges = .{
.src_locs = .{},
.results = .{},
.br_list = .{},
.block_inst = new_block_inst,
},
},
.block = .{
.parent = block,
.sema = sema,
.namespace = block.namespace,
.instructions = .{},
.label = &labeled_block.label,
.inlining = block.inlining,
.comptime_reason = block.comptime_reason,
.src_base_inst = block.src_base_inst,
.type_name_ctx = block.type_name_ctx,
},
};
sema.post_hoc_blocks.putAssumeCapacityNoClobber(new_block_inst, labeled_block);
return labeled_block;
}
/// A `break` statement is inside a runtime condition, but trying to
/// break from an inline loop. In such case we must convert it to
/// a runtime break.
fn addRuntimeBreak(sema: *Sema, child_block: *Block, block_inst: Zir.Inst.Index, break_operand: Zir.Inst.Ref) !void {
const labeled_block = try sema.ensurePostHoc(child_block, block_inst);
const operand = try sema.resolveInst(break_operand);
const br_ref = try child_block.addBr(labeled_block.label.merges.block_inst, operand);
try labeled_block.label.merges.results.append(sema.gpa, operand);
try labeled_block.label.merges.br_list.append(sema.gpa, br_ref.toIndex().?);
try labeled_block.label.merges.src_locs.append(sema.gpa, null);
labeled_block.block.runtime_index.increment();
if (labeled_block.block.runtime_cond == null and labeled_block.block.runtime_loop == null) {
labeled_block.block.runtime_cond = child_block.runtime_cond orelse child_block.runtime_loop;
labeled_block.block.runtime_loop = child_block.runtime_loop;
}
}
fn zirUnreachable(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].@"unreachable";
const src = block.nodeOffset(inst_data.src_node);
if (block.isComptime()) {
return sema.fail(block, src, "reached unreachable code", .{});
}
// TODO Add compile error for @optimizeFor occurring too late in a scope.
sema.analyzeUnreachable(block, src, true) catch |err| switch (err) {
error.AnalysisFail => {
const msg = sema.err orelse return err;
if (!mem.eql(u8, msg.msg, "runtime safety check not allowed in naked function")) return err;
try sema.errNote(src, msg, "the end of a naked function is implicitly unreachable", .{});
return err;
},
else => |e| return e,
};
}
fn zirRetErrValue(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].str_tok;
const src = block.tokenOffset(inst_data.src_tok);
const err_name = try zcu.intern_pool.getOrPutString(
sema.gpa,
pt.tid,
inst_data.get(sema.code),
.no_embedded_nulls,
);
_ = try pt.getErrorValue(err_name);
// Return the error code from the function.
const error_set_type = try pt.singleErrorSetType(err_name);
const result_inst = Air.internedToRef((try pt.intern(.{ .err = .{
.ty = error_set_type.toIntern(),
.name = err_name,
} })));
return sema.analyzeRet(block, result_inst, src, src);
}
fn zirRetImplicit(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_tok;
const r_brace_src = block.tokenOffset(inst_data.src_tok);
if (block.inlining == null and sema.func_is_naked) {
assert(!block.isComptime());
if (block.wantSafety()) {
// Calling a safety function from a naked function would not be legal.
_ = try block.addNoOp(.trap);
} else {
try sema.analyzeUnreachable(block, r_brace_src, false);
}
return;
}
const operand = try sema.resolveInst(inst_data.operand);
const ret_ty_src = block.src(.{ .node_offset_fn_type_ret_ty = .zero });
const base_tag = sema.fn_ret_ty.baseZigTypeTag(zcu);
if (base_tag == .noreturn) {
const msg = msg: {
const msg = try sema.errMsg(ret_ty_src, "function declared '{f}' implicitly returns", .{
sema.fn_ret_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(r_brace_src, msg, "control flow reaches end of body here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
} else if (base_tag != .void) {
const msg = msg: {
const msg = try sema.errMsg(ret_ty_src, "function with non-void return type '{f}' implicitly returns", .{
sema.fn_ret_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(r_brace_src, msg, "control flow reaches end of body here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
return sema.analyzeRet(block, operand, r_brace_src, r_brace_src);
}
fn zirRetNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand = try sema.resolveInst(inst_data.operand);
const src = block.nodeOffset(inst_data.src_node);
return sema.analyzeRet(block, operand, src, block.src(.{ .node_offset_return_operand = inst_data.src_node }));
}
fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ret_ptr = try sema.resolveInst(inst_data.operand);
if (block.isComptime() or block.inlining != null or sema.func_is_naked) {
const operand = try sema.analyzeLoad(block, src, ret_ptr, src);
return sema.analyzeRet(block, operand, src, block.src(.{ .node_offset_return_operand = inst_data.src_node }));
}
if (sema.wantErrorReturnTracing(sema.fn_ret_ty)) {
const is_non_err = try sema.analyzePtrIsNonErr(block, src, ret_ptr);
return sema.retWithErrTracing(block, src, is_non_err, .ret_load, ret_ptr);
}
_ = try block.addUnOp(.ret_load, ret_ptr);
}
fn retWithErrTracing(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
is_non_err: Air.Inst.Ref,
ret_tag: Air.Inst.Tag,
operand: Air.Inst.Ref,
) CompileError!void {
const pt = sema.pt;
const need_check = switch (is_non_err) {
.bool_true => {
_ = try block.addUnOp(ret_tag, operand);
return;
},
.bool_false => false,
else => true,
};
// This means we're returning something that might be an error!
// This should only be possible with the `auto` cc, so we definitely have an error trace.
assert(pt.zcu.intern_pool.funcAnalysisUnordered(sema.owner.unwrap().func).has_error_trace);
const gpa = sema.gpa;
const return_err_fn = Air.internedToRef(try sema.getBuiltin(src, .returnError));
if (!need_check) {
try sema.callBuiltin(block, src, return_err_fn, .never_tail, &.{}, .@"error return");
_ = try block.addUnOp(ret_tag, operand);
return;
}
var then_block = block.makeSubBlock();
defer then_block.instructions.deinit(gpa);
_ = try then_block.addUnOp(ret_tag, operand);
var else_block = block.makeSubBlock();
defer else_block.instructions.deinit(gpa);
try sema.callBuiltin(&else_block, src, return_err_fn, .never_tail, &.{}, .@"error return");
_ = try else_block.addUnOp(ret_tag, operand);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).@"struct".fields.len +
then_block.instructions.items.len + else_block.instructions.items.len +
@typeInfo(Air.Block).@"struct".fields.len + 1);
const cond_br_payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = @intCast(then_block.instructions.items.len),
.else_body_len = @intCast(else_block.instructions.items.len),
.branch_hints = .{
// Weight against error branch.
.true = .likely,
.false = .unlikely,
// Code coverage is not valuable on either branch.
.then_cov = .none,
.else_cov = .none,
},
});
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(then_block.instructions.items));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(else_block.instructions.items));
_ = try block.addInst(.{ .tag = .cond_br, .data = .{ .pl_op = .{
.operand = is_non_err,
.payload = cond_br_payload,
} } });
}
fn wantErrorReturnTracing(sema: *Sema, fn_ret_ty: Type) bool {
const pt = sema.pt;
const zcu = pt.zcu;
return fn_ret_ty.isError(zcu) and zcu.comp.config.any_error_tracing;
}
fn zirSaveErrRetIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].save_err_ret_index;
if (!block.ownerModule().error_tracing) return;
// This is only relevant at runtime.
if (block.isComptime() or block.is_typeof) return;
const save_index = inst_data.operand == .none or b: {
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
break :b operand_ty.isError(zcu);
};
if (save_index)
block.error_return_trace_index = try sema.analyzeSaveErrRetIndex(block);
}
fn zirRestoreErrRetIndex(sema: *Sema, start_block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
const extra = sema.code.extraData(Zir.Inst.RestoreErrRetIndex, extended.operand).data;
return sema.restoreErrRetIndex(start_block, start_block.nodeOffset(extra.src_node), extra.block, extra.operand);
}
/// If `operand` is non-error (or is `none`), restores the error return trace to
/// its state at the point `block` was reached (or, if `block` is `none`, the
/// point this function began execution).
fn restoreErrRetIndex(sema: *Sema, start_block: *Block, src: LazySrcLoc, target_block: Zir.Inst.Ref, operand_zir: Zir.Inst.Ref) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const saved_index = if (target_block.toIndexAllowNone()) |zir_block| b: {
var block = start_block;
while (true) {
if (block.label) |label| {
if (label.zir_block == zir_block) {
const target_trace_index = if (block.parent) |parent_block| tgt: {
break :tgt parent_block.error_return_trace_index;
} else sema.error_return_trace_index_on_fn_entry;
if (start_block.error_return_trace_index != target_trace_index)
break :b target_trace_index;
return; // No need to restore
}
}
block = block.parent.?;
}
} else b: {
if (start_block.error_return_trace_index != sema.error_return_trace_index_on_fn_entry)
break :b sema.error_return_trace_index_on_fn_entry;
return; // No need to restore
};
const operand = try sema.resolveInstAllowNone(operand_zir);
if (start_block.isComptime() or start_block.is_typeof) {
const is_non_error = if (operand != .none) blk: {
const is_non_error_inst = try sema.analyzeIsNonErr(start_block, src, operand);
const cond_val = try sema.resolveDefinedValue(start_block, src, is_non_error_inst);
break :blk cond_val.?.toBool();
} else true; // no operand means pop unconditionally
if (is_non_error) return;
const saved_index_val = try sema.resolveDefinedValue(start_block, src, saved_index);
const saved_index_int = saved_index_val.?.toUnsignedInt(zcu);
assert(saved_index_int <= sema.comptime_err_ret_trace.items.len);
sema.comptime_err_ret_trace.items.len = @intCast(saved_index_int);
return;
}
if (!zcu.intern_pool.funcAnalysisUnordered(sema.owner.unwrap().func).has_error_trace) return;
if (!start_block.ownerModule().error_tracing) return;
assert(saved_index != .none); // The .error_return_trace_index field was dropped somewhere
return sema.popErrorReturnTrace(start_block, src, operand, saved_index);
}
fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(sema.fn_ret_ty.zigTypeTag(zcu) == .error_union);
const err_set_ty = sema.fn_ret_ty.errorUnionSet(zcu).toIntern();
switch (err_set_ty) {
.adhoc_inferred_error_set_type => {
const ies = sema.fn_ret_ty_ies.?;
assert(ies.func == .none);
try sema.addToInferredErrorSetPtr(ies, sema.typeOf(uncasted_operand));
},
else => if (ip.isInferredErrorSetType(err_set_ty)) {
const ies = sema.fn_ret_ty_ies.?;
assert(ies.func == sema.owner.unwrap().func);
try sema.addToInferredErrorSetPtr(ies, sema.typeOf(uncasted_operand));
},
}
}
fn addToInferredErrorSetPtr(sema: *Sema, ies: *InferredErrorSet, op_ty: Type) !void {
const arena = sema.arena;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
switch (op_ty.zigTypeTag(zcu)) {
.error_set => try ies.addErrorSet(op_ty, ip, arena),
.error_union => try ies.addErrorSet(op_ty.errorUnionSet(zcu), ip, arena),
else => {},
}
}
fn analyzeRet(
sema: *Sema,
block: *Block,
uncasted_operand: Air.Inst.Ref,
src: LazySrcLoc,
operand_src: LazySrcLoc,
) CompileError!void {
// Special case for returning an error to an inferred error set; we need to
// add the error tag to the inferred error set of the in-scope function, so
// that the coercion below works correctly.
const pt = sema.pt;
const zcu = pt.zcu;
if (sema.fn_ret_ty_ies != null and sema.fn_ret_ty.zigTypeTag(zcu) == .error_union) {
try sema.addToInferredErrorSet(uncasted_operand);
}
const operand = sema.coerceExtra(block, sema.fn_ret_ty, uncasted_operand, operand_src, .{ .is_ret = true }) catch |err| switch (err) {
error.NotCoercible => unreachable,
else => |e| return e,
};
if (block.inlining) |inlining| {
assert(!inlining.is_generic_instantiation); // can't `return` in a generic param/ret ty expr
if (block.isComptime()) {
const ret_val = try sema.resolveConstValue(block, operand_src, operand, null);
inlining.comptime_result = operand;
if (sema.fn_ret_ty.isError(zcu) and ret_val.getErrorName(zcu) != .none) {
try sema.comptime_err_ret_trace.append(src);
}
return error.ComptimeReturn;
}
// We are inlining a function call; rewrite the `ret` as a `break`.
const br_inst = try block.addBr(inlining.merges.block_inst, operand);
try inlining.merges.results.append(sema.gpa, operand);
try inlining.merges.br_list.append(sema.gpa, br_inst.toIndex().?);
try inlining.merges.src_locs.append(sema.gpa, operand_src);
return;
} else if (block.isComptime()) {
return sema.fail(block, src, "function called at runtime cannot return value at comptime", .{});
} else if (sema.func_is_naked) {
const msg = msg: {
const msg = try sema.errMsg(src, "cannot return from naked function", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "can only return using assembly", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
try sema.fn_ret_ty.resolveLayout(pt);
try sema.validateRuntimeValue(block, operand_src, operand);
const air_tag: Air.Inst.Tag = if (block.wantSafety()) .ret_safe else .ret;
if (sema.wantErrorReturnTracing(sema.fn_ret_ty)) {
// Avoid adding a frame to the error return trace in case the value is comptime-known
// to be not an error.
const is_non_err = try sema.analyzeIsNonErr(block, operand_src, operand);
return sema.retWithErrTracing(block, src, is_non_err, air_tag, operand);
}
_ = try block.addUnOp(air_tag, operand);
}
fn floatOpAllowed(tag: Zir.Inst.Tag) bool {
// extend this swich as additional operators are implemented
return switch (tag) {
.add, .sub, .mul, .div, .div_exact, .div_trunc, .div_floor, .mod, .rem, .mod_rem => true,
else => false,
};
}
fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].ptr_type;
const extra = sema.code.extraData(Zir.Inst.PtrType, inst_data.payload_index);
const elem_ty_src = block.src(.{ .node_offset_ptr_elem = extra.data.src_node });
const sentinel_src = block.src(.{ .node_offset_ptr_sentinel = extra.data.src_node });
const align_src = block.src(.{ .node_offset_ptr_align = extra.data.src_node });
const addrspace_src = block.src(.{ .node_offset_ptr_addrspace = extra.data.src_node });
const bitoffset_src = block.src(.{ .node_offset_ptr_bitoffset = extra.data.src_node });
const hostsize_src = block.src(.{ .node_offset_ptr_hostsize = extra.data.src_node });
const elem_ty = blk: {
const air_inst = try sema.resolveInst(extra.data.elem_type);
const ty = sema.analyzeAsType(block, elem_ty_src, air_inst) catch |err| {
if (err == error.AnalysisFail and sema.err != null and sema.typeOf(air_inst).isSinglePointer(zcu)) {
try sema.errNote(elem_ty_src, sema.err.?, "use '.*' to dereference pointer", .{});
}
return err;
};
assert(!ty.isGenericPoison());
break :blk ty;
};
if (elem_ty.zigTypeTag(zcu) == .noreturn)
return sema.fail(block, elem_ty_src, "pointer to noreturn not allowed", .{});
const target = zcu.getTarget();
var extra_i = extra.end;
const sentinel = if (inst_data.flags.has_sentinel) blk: {
const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);
extra_i += 1;
const coerced = try sema.coerce(block, elem_ty, try sema.resolveInst(ref), sentinel_src);
const val = try sema.resolveConstDefinedValue(block, sentinel_src, coerced, .{ .simple = .pointer_sentinel });
try checkSentinelType(sema, block, sentinel_src, elem_ty);
if (val.canMutateComptimeVarState(zcu)) {
const sentinel_name = try ip.getOrPutString(sema.gpa, pt.tid, "sentinel", .no_embedded_nulls);
return sema.failWithContainsReferenceToComptimeVar(block, sentinel_src, sentinel_name, "sentinel", val);
}
break :blk val.toIntern();
} else .none;
const abi_align: Alignment = if (inst_data.flags.has_align) blk: {
const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);
extra_i += 1;
const coerced = try sema.coerce(block, align_ty, try sema.resolveInst(ref), align_src);
const val = try sema.resolveConstDefinedValue(block, align_src, coerced, .{ .simple = .@"align" });
// Check if this happens to be the lazy alignment of our element type, in
// which case we can make this 0 without resolving it.
switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.int => |int| switch (int.storage) {
.lazy_align => |lazy_ty| if (lazy_ty == elem_ty.toIntern()) break :blk .none,
else => {},
},
else => {},
}
const align_bytes = (try val.getUnsignedIntSema(pt)).?;
break :blk try sema.validateAlign(block, align_src, align_bytes);
} else .none;
const address_space: std.builtin.AddressSpace = if (inst_data.flags.has_addrspace) blk: {
const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);
extra_i += 1;
break :blk try sema.resolveAddressSpace(block, addrspace_src, ref, .pointer);
} else if (elem_ty.zigTypeTag(zcu) == .@"fn" and target.cpu.arch == .avr) .flash else .generic;
const bit_offset: u16 = if (inst_data.flags.has_bit_range) blk: {
const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);
extra_i += 1;
const bit_offset = try sema.resolveInt(block, bitoffset_src, ref, .u16, .{ .simple = .type });
break :blk @intCast(bit_offset);
} else 0;
const host_size: u16 = if (inst_data.flags.has_bit_range) blk: {
const ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_i]);
extra_i += 1;
const host_size = try sema.resolveInt(block, hostsize_src, ref, .u16, .{ .simple = .type });
break :blk @intCast(host_size);
} else 0;
if (host_size != 0) {
if (bit_offset >= host_size * 8) {
return sema.fail(block, bitoffset_src, "packed type '{f}' at bit offset {d} starts {d} bits after the end of a {d} byte host integer", .{
elem_ty.fmt(pt), bit_offset, bit_offset - host_size * 8, host_size,
});
}
const elem_bit_size = try elem_ty.bitSizeSema(pt);
if (elem_bit_size > host_size * 8 - bit_offset) {
return sema.fail(block, bitoffset_src, "packed type '{f}' at bit offset {d} ends {d} bits after the end of a {d} byte host integer", .{
elem_ty.fmt(pt), bit_offset, elem_bit_size - (host_size * 8 - bit_offset), host_size,
});
}
}
if (elem_ty.zigTypeTag(zcu) == .@"fn") {
if (inst_data.size != .one) {
return sema.fail(block, elem_ty_src, "function pointers must be single pointers", .{});
}
} else if (inst_data.size != .one and elem_ty.zigTypeTag(zcu) == .@"opaque") {
return sema.fail(block, elem_ty_src, "indexable pointer to opaque type '{f}' not allowed", .{elem_ty.fmt(pt)});
} else if (inst_data.size == .c) {
if (!try sema.validateExternType(elem_ty, .other)) {
const msg = msg: {
const msg = try sema.errMsg(elem_ty_src, "C pointers cannot point to non-C-ABI-compatible type '{f}'", .{elem_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, elem_ty_src, elem_ty, .other);
try sema.addDeclaredHereNote(msg, elem_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
if (host_size != 0 and !try sema.validatePackedType(elem_ty)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(elem_ty_src, "bit-pointer cannot refer to value of type '{f}'", .{elem_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotPacked(msg, elem_ty_src, elem_ty);
break :msg msg;
});
}
const ty = try pt.ptrTypeSema(.{
.child = elem_ty.toIntern(),
.sentinel = sentinel,
.flags = .{
.alignment = abi_align,
.address_space = address_space,
.is_const = !inst_data.flags.is_mutable,
.is_allowzero = inst_data.flags.is_allowzero,
.is_volatile = inst_data.flags.is_volatile,
.size = inst_data.size,
},
.packed_offset = .{
.bit_offset = bit_offset,
.host_size = host_size,
},
});
return Air.internedToRef(ty.toIntern());
}
fn zirStructInitEmpty(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const ty_src = block.src(.{ .node_offset_init_ty = inst_data.src_node });
const obj_ty = try sema.resolveType(block, ty_src, inst_data.operand);
const pt = sema.pt;
const zcu = pt.zcu;
switch (obj_ty.zigTypeTag(zcu)) {
.@"struct" => return sema.structInitEmpty(block, obj_ty, src, src),
.array, .vector => return sema.arrayInitEmpty(block, src, obj_ty),
.void => return Air.internedToRef(Value.void.toIntern()),
.@"union" => return sema.fail(block, src, "union initializer must initialize one field", .{}),
else => return sema.failWithArrayInitNotSupported(block, src, obj_ty),
}
}
fn zirStructInitEmptyResult(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_byref: bool) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
// Generic poison means this is an untyped anonymous empty struct/array init
const ty_operand = try sema.resolveTypeOrPoison(block, src, inst_data.operand) orelse {
if (is_byref) {
return sema.uavRef(.empty_tuple);
} else {
return .empty_tuple;
}
};
const init_ty = if (is_byref) ty: {
const ptr_ty = ty_operand.optEuBaseType(zcu);
assert(ptr_ty.zigTypeTag(zcu) == .pointer); // validated by a previous instruction
switch (ptr_ty.ptrSize(zcu)) {
// Use a zero-length array for a slice or many-ptr result
.slice, .many => break :ty try pt.arrayType(.{
.len = 0,
.child = ptr_ty.childType(zcu).toIntern(),
.sentinel = if (ptr_ty.sentinel(zcu)) |s| s.toIntern() else .none,
}),
// Just use the child type for a single-pointer or C-pointer result
.one, .c => {
const child = ptr_ty.childType(zcu);
if (child.toIntern() == .anyopaque_type) {
// ...unless that child is anyopaque, in which case this is equivalent to an untyped init.
// `.{}` is an empty tuple.
if (is_byref) {
return sema.uavRef(.empty_tuple);
} else {
return .empty_tuple;
}
}
break :ty child;
},
}
if (!ptr_ty.isSlice(zcu)) {
break :ty ptr_ty.childType(zcu);
}
// To make `&.{}` a `[:s]T`, the init should be a `[0:s]T`.
break :ty try pt.arrayType(.{
.len = 0,
.sentinel = if (ptr_ty.sentinel(zcu)) |s| s.toIntern() else .none,
.child = ptr_ty.childType(zcu).toIntern(),
});
} else ty_operand;
const obj_ty = init_ty.optEuBaseType(zcu);
const empty_ref = switch (obj_ty.zigTypeTag(zcu)) {
.@"struct" => try sema.structInitEmpty(block, obj_ty, src, src),
.array, .vector => try sema.arrayInitEmpty(block, src, obj_ty),
.@"union" => return sema.fail(block, src, "union initializer must initialize one field", .{}),
else => return sema.failWithArrayInitNotSupported(block, src, obj_ty),
};
const init_ref = try sema.coerce(block, init_ty, empty_ref, src);
if (is_byref) {
const init_val = (try sema.resolveValue(init_ref)).?;
return sema.uavRef(init_val.toIntern());
} else {
return init_ref;
}
}
fn structInitEmpty(
sema: *Sema,
block: *Block,
struct_ty: Type,
dest_src: LazySrcLoc,
init_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
// This logic must be synchronized with that in `zirStructInit`.
try struct_ty.resolveFields(pt);
// The init values to use for the struct instance.
const field_inits = try gpa.alloc(Air.Inst.Ref, struct_ty.structFieldCount(zcu));
defer gpa.free(field_inits);
@memset(field_inits, .none);
// Maps field index in the struct declaration to the field index in the initialization expression.
const field_assign_idxs = try gpa.alloc(?usize, struct_ty.structFieldCount(zcu));
defer gpa.free(field_assign_idxs);
@memset(field_assign_idxs, null);
return sema.finishStructInit(block, init_src, dest_src, field_inits, field_assign_idxs, struct_ty, struct_ty, false);
}
fn arrayInitEmpty(sema: *Sema, block: *Block, src: LazySrcLoc, obj_ty: Type) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const arr_len = obj_ty.arrayLen(zcu);
if (arr_len != 0) {
if (obj_ty.zigTypeTag(zcu) == .array) {
return sema.fail(block, src, "expected {d} array elements; found 0", .{arr_len});
} else {
return sema.fail(block, src, "expected {d} vector elements; found 0", .{arr_len});
}
}
return .fromValue(try pt.aggregateValue(obj_ty, &.{}));
}
fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const field_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const init_src = block.builtinCallArgSrc(inst_data.src_node, 2);
const extra = sema.code.extraData(Zir.Inst.UnionInit, inst_data.payload_index).data;
const union_ty = try sema.resolveType(block, ty_src, extra.union_type);
if (union_ty.zigTypeTag(pt.zcu) != .@"union") {
return sema.fail(block, ty_src, "expected union type, found '{f}'", .{union_ty.fmt(pt)});
}
const field_name = try sema.resolveConstStringIntern(block, field_src, extra.field_name, .{ .simple = .union_field_names });
const init = try sema.resolveInst(extra.init);
return sema.unionInit(block, init, init_src, union_ty, ty_src, field_name, field_src);
}
fn unionInit(
sema: *Sema,
block: *Block,
uncasted_init: Air.Inst.Ref,
init_src: LazySrcLoc,
union_ty: Type,
union_ty_src: LazySrcLoc,
field_name: InternPool.NullTerminatedString,
field_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_src);
const field_ty: Type = .fromInterned(zcu.typeToUnion(union_ty).?.field_types.get(ip)[field_index]);
const init = try sema.coerce(block, field_ty, uncasted_init, init_src);
_ = union_ty_src;
return unionInitFromEnumTag(sema, block, init_src, union_ty, field_index, init);
}
fn unionInitFromEnumTag(
sema: *Sema,
block: *Block,
init_src: LazySrcLoc,
union_ty: Type,
field_index: u32,
init: Air.Inst.Ref,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
if (try sema.resolveValue(init)) |init_val| {
const tag_ty = union_ty.unionTagTypeHypothetical(zcu);
const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index);
return Air.internedToRef((try pt.internUnion(.{
.ty = union_ty.toIntern(),
.tag = tag_val.toIntern(),
.val = init_val.toIntern(),
})));
}
try sema.requireRuntimeBlock(block, init_src, null);
return block.addUnionInit(union_ty, field_index, init);
}
fn zirStructInit(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_ref: bool,
) CompileError!Air.Inst.Ref {
const gpa = sema.gpa;
const zir_datas = sema.code.instructions.items(.data);
const inst_data = zir_datas[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.StructInit, inst_data.payload_index);
const src = block.nodeOffset(inst_data.src_node);
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const first_item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end).data;
const first_field_type_data = zir_datas[@intFromEnum(first_item.field_type)].pl_node;
const first_field_type_extra = sema.code.extraData(Zir.Inst.FieldType, first_field_type_data.payload_index).data;
const result_ty = try sema.resolveTypeOrPoison(block, src, first_field_type_extra.container_type) orelse {
// The type wasn't actually known, so treat this as an anon struct init.
return sema.structInitAnon(block, src, inst, .typed_init, extra.data, extra.end, is_ref);
};
const resolved_ty = result_ty.optEuBaseType(zcu);
try resolved_ty.resolveLayout(pt);
if (resolved_ty.zigTypeTag(zcu) == .@"struct") {
// This logic must be synchronized with that in `zirStructInitEmpty`.
// Maps field index to field_type index of where it was already initialized.
// For making sure all fields are accounted for and no fields are duplicated.
const found_fields = try gpa.alloc(Zir.Inst.Index, resolved_ty.structFieldCount(zcu));
defer gpa.free(found_fields);
// The init values to use for the struct instance.
const field_inits = try gpa.alloc(Air.Inst.Ref, resolved_ty.structFieldCount(zcu));
defer gpa.free(field_inits);
@memset(field_inits, .none);
// Maps field index in the struct declaration to the field index in the initialization expression.
const field_assign_idxs = try gpa.alloc(?usize, resolved_ty.structFieldCount(zcu));
defer gpa.free(field_assign_idxs);
@memset(field_assign_idxs, null);
var field_i: u32 = 0;
var extra_index = extra.end;
const is_packed = resolved_ty.containerLayout(zcu) == .@"packed";
while (field_i < extra.data.fields_len) : (field_i += 1) {
const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra_index);
extra_index = item.end;
const field_type_data = zir_datas[@intFromEnum(item.data.field_type)].pl_node;
const field_src = block.src(.{ .node_offset_initializer = field_type_data.src_node });
const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
const field_name = try ip.getOrPutString(
gpa,
pt.tid,
sema.code.nullTerminatedString(field_type_extra.name_start),
.no_embedded_nulls,
);
const field_index = if (resolved_ty.isTuple(zcu))
try sema.tupleFieldIndex(block, resolved_ty, field_name, field_src)
else
try sema.structFieldIndex(block, resolved_ty, field_name, field_src);
assert(field_inits[field_index] == .none);
field_assign_idxs[field_index] = field_i;
found_fields[field_index] = item.data.field_type;
const uncoerced_init = try sema.resolveInst(item.data.init);
const field_ty = resolved_ty.fieldType(field_index, zcu);
field_inits[field_index] = try sema.coerce(block, field_ty, uncoerced_init, field_src);
if (!is_packed) {
try resolved_ty.resolveStructFieldInits(pt);
if (try resolved_ty.structFieldValueComptime(pt, field_index)) |default_value| {
const init_val = (try sema.resolveValue(field_inits[field_index])) orelse {
return sema.failWithNeededComptime(block, field_src, .{ .simple = .stored_to_comptime_field });
};
if (!init_val.eql(default_value, resolved_ty.fieldType(field_index, zcu), zcu)) {
return sema.failWithInvalidComptimeFieldStore(block, field_src, resolved_ty, field_index);
}
}
}
}
return sema.finishStructInit(block, src, src, field_inits, field_assign_idxs, resolved_ty, result_ty, is_ref);
} else if (resolved_ty.zigTypeTag(zcu) == .@"union") {
if (extra.data.fields_len != 1) {
return sema.fail(block, src, "union initialization expects exactly one field", .{});
}
const item = sema.code.extraData(Zir.Inst.StructInit.Item, extra.end);
const field_type_data = zir_datas[@intFromEnum(item.data.field_type)].pl_node;
const field_src = block.src(.{ .node_offset_initializer = field_type_data.src_node });
const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data;
const field_name = try ip.getOrPutString(
gpa,
pt.tid,
sema.code.nullTerminatedString(field_type_extra.name_start),
.no_embedded_nulls,
);
const field_index = try sema.unionFieldIndex(block, resolved_ty, field_name, field_src);
const tag_ty = resolved_ty.unionTagTypeHypothetical(zcu);
const tag_val = try pt.enumValueFieldIndex(tag_ty, field_index);
const field_ty: Type = .fromInterned(zcu.typeToUnion(resolved_ty).?.field_types.get(ip)[field_index]);
if (field_ty.zigTypeTag(zcu) == .noreturn) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "cannot initialize 'noreturn' field of union", .{});
errdefer msg.destroy(sema.gpa);
try sema.addFieldErrNote(resolved_ty, field_index, msg, "field '{f}' declared here", .{
field_name.fmt(ip),
});
try sema.addDeclaredHereNote(msg, resolved_ty);
break :msg msg;
});
}
const uncoerced_init_inst = try sema.resolveInst(item.data.init);
const init_inst = try sema.coerce(block, field_ty, uncoerced_init_inst, field_src);
if (try sema.resolveValue(init_inst)) |val| {
const struct_val = Value.fromInterned(try pt.internUnion(.{
.ty = resolved_ty.toIntern(),
.tag = tag_val.toIntern(),
.val = val.toIntern(),
}));
const final_val_inst = try sema.coerce(block, result_ty, Air.internedToRef(struct_val.toIntern()), src);
const final_val = (try sema.resolveValue(final_val_inst)).?;
return sema.addConstantMaybeRef(final_val.toIntern(), is_ref);
}
if (try resolved_ty.comptimeOnlySema(pt)) {
return sema.failWithNeededComptime(block, field_src, .{ .comptime_only = .{
.ty = resolved_ty,
.msg = .union_init,
} });
}
try sema.validateRuntimeValue(block, field_src, init_inst);
if (is_ref) {
const target = zcu.getTarget();
const alloc_ty = try pt.ptrTypeSema(.{
.child = result_ty.toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const alloc = try block.addTy(.alloc, alloc_ty);
const base_ptr = try sema.optEuBasePtrInit(block, alloc, src);
const field_ptr = try sema.unionFieldPtr(block, field_src, base_ptr, field_name, field_src, resolved_ty, true);
try sema.storePtr(block, src, field_ptr, init_inst);
if ((try sema.typeHasOnePossibleValue(tag_ty)) == null) {
const new_tag = Air.internedToRef(tag_val.toIntern());
_ = try block.addBinOp(.set_union_tag, base_ptr, new_tag);
}
return sema.makePtrConst(block, alloc);
}
try sema.requireRuntimeBlock(block, src, null);
const union_val = try block.addUnionInit(resolved_ty, field_index, init_inst);
return sema.coerce(block, result_ty, union_val, src);
}
unreachable;
}
fn finishStructInit(
sema: *Sema,
block: *Block,
init_src: LazySrcLoc,
dest_src: LazySrcLoc,
field_inits: []Air.Inst.Ref,
field_assign_idxs: []?usize,
struct_ty: Type,
result_ty: Type,
is_ref: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
var root_msg: ?*Zcu.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
switch (ip.indexToKey(struct_ty.toIntern())) {
.tuple_type => |tuple| {
// We can't get the slices, as the coercion may invalidate them.
for (0..tuple.types.len) |i| {
if (field_inits[i] != .none) {
// Coerce the init value to the field type.
const field_src = block.src(.{ .init_elem = .{
.init_node_offset = init_src.offset.node_offset.x,
.elem_index = @intCast(i),
} });
const field_ty: Type = .fromInterned(tuple.types.get(ip)[i]);
field_inits[i] = try sema.coerce(block, field_ty, field_inits[i], field_src);
continue;
}
const default_val = tuple.values.get(ip)[i];
if (default_val == .none) {
const template = "missing tuple field with index {d}";
if (root_msg) |msg| {
try sema.errNote(init_src, msg, template, .{i});
} else {
root_msg = try sema.errMsg(init_src, template, .{i});
}
} else {
field_inits[i] = Air.internedToRef(default_val);
}
}
},
.struct_type => {
const struct_type = ip.loadStructType(struct_ty.toIntern());
for (0..struct_type.field_types.len) |i| {
if (field_inits[i] != .none) {
// Coerce the init value to the field type.
const field_src = block.src(.{ .init_elem = .{
.init_node_offset = init_src.offset.node_offset.x,
.elem_index = @intCast(i),
} });
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
field_inits[i] = try sema.coerce(block, field_ty, field_inits[i], field_src);
continue;
}
try struct_ty.resolveStructFieldInits(pt);
const field_init = struct_type.fieldInit(ip, i);
if (field_init == .none) {
const field_name = struct_type.field_names.get(ip)[i];
const template = "missing struct field: {f}";
const args = .{field_name.fmt(ip)};
if (root_msg) |msg| {
try sema.errNote(init_src, msg, template, args);
} else {
root_msg = try sema.errMsg(init_src, template, args);
}
} else {
field_inits[i] = Air.internedToRef(field_init);
}
}
},
else => unreachable,
}
if (root_msg) |msg| {
try sema.addDeclaredHereNote(msg, struct_ty);
root_msg = null;
return sema.failWithOwnedErrorMsg(block, msg);
}
// Find which field forces the expression to be runtime, if any.
const opt_runtime_index = for (field_inits, field_assign_idxs) |field_init, field_assign| {
if (!(try sema.isComptimeKnown(field_init))) {
break field_assign;
}
} else null;
const runtime_index = opt_runtime_index orelse {
const elems = try sema.arena.alloc(InternPool.Index, field_inits.len);
for (elems, field_inits) |*elem, field_init| {
elem.* = (sema.resolveValue(field_init) catch unreachable).?.toIntern();
}
const struct_val = try pt.aggregateValue(struct_ty, elems);
const final_val_inst = try sema.coerce(block, result_ty, Air.internedToRef(struct_val.toIntern()), init_src);
const final_val = (try sema.resolveValue(final_val_inst)).?;
return sema.addConstantMaybeRef(final_val.toIntern(), is_ref);
};
if (try struct_ty.comptimeOnlySema(pt)) {
return sema.failWithNeededComptime(block, block.src(.{ .init_elem = .{
.init_node_offset = init_src.offset.node_offset.x,
.elem_index = @intCast(runtime_index),
} }), .{ .comptime_only = .{
.ty = struct_ty,
.msg = .struct_init,
} });
}
for (field_inits) |field_init| {
try sema.validateRuntimeValue(block, dest_src, field_init);
}
if (is_ref) {
try struct_ty.resolveLayout(pt);
const target = zcu.getTarget();
const alloc_ty = try pt.ptrTypeSema(.{
.child = result_ty.toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const alloc = try block.addTy(.alloc, alloc_ty);
const base_ptr = try sema.optEuBasePtrInit(block, alloc, init_src);
for (field_inits, 0..) |field_init, i_usize| {
const i: u32 = @intCast(i_usize);
const field_ptr = try sema.structFieldPtrByIndex(block, dest_src, base_ptr, i, struct_ty);
try sema.storePtr(block, dest_src, field_ptr, field_init);
}
return sema.makePtrConst(block, alloc);
}
try sema.requireRuntimeBlock(block, dest_src, block.src(.{ .init_elem = .{
.init_node_offset = init_src.offset.node_offset.x,
.elem_index = @intCast(runtime_index),
} }));
try struct_ty.resolveStructFieldInits(pt);
const struct_val = try block.addAggregateInit(struct_ty, field_inits);
return sema.coerce(block, result_ty, struct_val, init_src);
}
fn zirStructInitAnon(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.StructInitAnon, inst_data.payload_index);
return sema.structInitAnon(block, src, inst, .anon_init, extra.data, extra.end, false);
}
fn structInitAnon(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
inst: Zir.Inst.Index,
/// It is possible for a typed struct_init to be downgraded to an anonymous init due to a
/// generic poison type. In this case, we need to know to interpret the extra data differently.
comptime kind: enum { anon_init, typed_init },
extra_data: switch (kind) {
.anon_init => Zir.Inst.StructInitAnon,
.typed_init => Zir.Inst.StructInit,
},
extra_end: usize,
is_ref: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const zir_datas = sema.code.instructions.items(.data);
const types = try sema.arena.alloc(InternPool.Index, extra_data.fields_len);
const values = try sema.arena.alloc(InternPool.Index, types.len);
const names = try sema.arena.alloc(InternPool.NullTerminatedString, types.len);
var any_values = false;
// Find which field forces the expression to be runtime, if any.
const opt_runtime_index = rs: {
var runtime_index: ?usize = null;
var extra_index = extra_end;
for (types, values, names, 0..) |*field_ty, *field_val, *field_name, i_usize| {
const item = switch (kind) {
.anon_init => sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index),
.typed_init => sema.code.extraData(Zir.Inst.StructInit.Item, extra_index),
};
extra_index = item.end;
const name = switch (kind) {
.anon_init => sema.code.nullTerminatedString(item.data.field_name),
.typed_init => name: {
// `item.data.field_type` references a `field_type` instruction
const field_type_data = zir_datas[@intFromEnum(item.data.field_type)].pl_node;
const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index);
break :name sema.code.nullTerminatedString(field_type_extra.data.name_start);
},
};
field_name.* = try zcu.intern_pool.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
const init = try sema.resolveInst(item.data.init);
field_ty.* = sema.typeOf(init).toIntern();
if (Type.fromInterned(field_ty.*).zigTypeTag(zcu) == .@"opaque") {
const msg = msg: {
const field_src = block.src(.{ .init_elem = .{
.init_node_offset = src.offset.node_offset.x,
.elem_index = @intCast(i_usize),
} });
const msg = try sema.errMsg(field_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, .fromInterned(field_ty.*));
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
if (try sema.resolveValue(init)) |init_val| {
field_val.* = init_val.toIntern();
any_values = true;
} else {
field_val.* = .none;
runtime_index = @intCast(i_usize);
}
}
break :rs runtime_index;
};
// We treat anonymous struct types as reified types, because there are similarities:
// * They use a form of structural equivalence, which we can easily model using a custom hash
// * They do not have captures
// * They immediately have their fields resolved
// In general, other code should treat anon struct types and reified struct types identically,
// so there's no point having a separate `InternPool.NamespaceType` field for them.
const type_hash: u64 = hash: {
var hasher = std.hash.Wyhash.init(0);
hasher.update(std.mem.sliceAsBytes(types));
hasher.update(std.mem.sliceAsBytes(values));
hasher.update(std.mem.sliceAsBytes(names));
break :hash hasher.final();
};
const tracked_inst = try block.trackZir(inst);
const struct_ty = switch (try ip.getStructType(gpa, pt.tid, .{
.layout = .auto,
.fields_len = extra_data.fields_len,
.known_non_opv = false,
.requires_comptime = .unknown,
.any_comptime_fields = any_values,
.any_default_inits = any_values,
.inits_resolved = true,
.any_aligned_fields = false,
.key = .{ .reified = .{
.zir_index = tracked_inst,
.type_hash = type_hash,
} },
}, false)) {
.wip => |wip| ty: {
errdefer wip.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(block, .anon, "struct", inst, wip.index);
wip.setName(ip, type_name.name, type_name.nav);
const struct_type = ip.loadStructType(wip.index);
for (names, values, 0..) |name, init_val, field_idx| {
assert(struct_type.addFieldName(ip, name) == null);
if (init_val != .none) struct_type.setFieldComptime(ip, field_idx);
}
@memcpy(struct_type.field_types.get(ip), types);
if (any_values) {
@memcpy(struct_type.field_inits.get(ip), values);
}
const new_namespace_index = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
try zcu.comp.queueJob(.{ .resolve_type_fully = wip.index });
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip.index });
}
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip.index);
break :ty wip.finish(ip, new_namespace_index);
},
.existing => |ty| ty,
};
try sema.declareDependency(.{ .interned = struct_ty });
try sema.addTypeReferenceEntry(src, struct_ty);
_ = opt_runtime_index orelse {
const struct_val = try pt.aggregateValue(.fromInterned(struct_ty), values);
return sema.addConstantMaybeRef(struct_val.toIntern(), is_ref);
};
if (is_ref) {
const target = zcu.getTarget();
const alloc_ty = try pt.ptrTypeSema(.{
.child = struct_ty,
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const alloc = try block.addTy(.alloc, alloc_ty);
var extra_index = extra_end;
for (types, 0..) |field_ty, i_usize| {
const i: u32 = @intCast(i_usize);
const item = switch (kind) {
.anon_init => sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index),
.typed_init => sema.code.extraData(Zir.Inst.StructInit.Item, extra_index),
};
extra_index = item.end;
const field_ptr_ty = try pt.ptrTypeSema(.{
.child = field_ty,
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
if (values[i] == .none) {
const init = try sema.resolveInst(item.data.init);
const field_ptr = try block.addStructFieldPtr(alloc, i, field_ptr_ty);
_ = try block.addBinOp(.store, field_ptr, init);
}
}
return sema.makePtrConst(block, alloc);
}
const element_refs = try sema.arena.alloc(Air.Inst.Ref, types.len);
var extra_index = extra_end;
for (types, 0..) |_, i| {
const item = switch (kind) {
.anon_init => sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index),
.typed_init => sema.code.extraData(Zir.Inst.StructInit.Item, extra_index),
};
extra_index = item.end;
element_refs[i] = try sema.resolveInst(item.data.init);
}
return block.addAggregateInit(.fromInterned(struct_ty), element_refs);
}
fn zirArrayInit(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
is_ref: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
const args = sema.code.refSlice(extra.end, extra.data.operands_len);
assert(args.len >= 2); // array_ty + at least one element
const result_ty = try sema.resolveTypeOrPoison(block, src, args[0]) orelse {
// The type wasn't actually known, so treat this as an anon array init.
return sema.arrayInitAnon(block, src, args[1..], is_ref);
};
const array_ty = result_ty.optEuBaseType(zcu);
const is_tuple = array_ty.zigTypeTag(zcu) == .@"struct";
const sentinel_val = array_ty.sentinel(zcu);
var root_msg: ?*Zcu.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
const final_len = try sema.usizeCast(block, src, array_ty.arrayLenIncludingSentinel(zcu));
const resolved_args = try gpa.alloc(Air.Inst.Ref, final_len);
defer gpa.free(resolved_args);
for (resolved_args, 0..) |*dest, i| {
const elem_src = block.src(.{ .init_elem = .{
.init_node_offset = src.offset.node_offset.x,
.elem_index = @intCast(i),
} });
// Less inits than needed.
if (i + 2 > args.len) if (is_tuple) {
const default_val = array_ty.structFieldDefaultValue(i, zcu).toIntern();
if (default_val == .unreachable_value) {
const template = "missing tuple field with index {d}";
if (root_msg) |msg| {
try sema.errNote(src, msg, template, .{i});
} else {
root_msg = try sema.errMsg(src, template, .{i});
}
} else {
dest.* = Air.internedToRef(default_val);
}
continue;
} else {
dest.* = Air.internedToRef(sentinel_val.?.toIntern());
break;
};
const arg = args[i + 1];
const resolved_arg = try sema.resolveInst(arg);
const elem_ty = if (is_tuple)
array_ty.fieldType(i, zcu)
else
array_ty.elemType2(zcu);
dest.* = try sema.coerce(block, elem_ty, resolved_arg, elem_src);
if (is_tuple) {
if (array_ty.structFieldIsComptime(i, zcu))
try array_ty.resolveStructFieldInits(pt);
if (try array_ty.structFieldValueComptime(pt, i)) |field_val| {
const init_val = try sema.resolveConstValue(block, elem_src, dest.*, .{ .simple = .stored_to_comptime_field });
if (!field_val.eql(init_val, elem_ty, zcu)) {
return sema.failWithInvalidComptimeFieldStore(block, elem_src, array_ty, i);
}
}
}
}
if (root_msg) |msg| {
try sema.addDeclaredHereNote(msg, array_ty);
root_msg = null;
return sema.failWithOwnedErrorMsg(block, msg);
}
const opt_runtime_index: ?u32 = for (resolved_args, 0..) |arg, i| {
const comptime_known = try sema.isComptimeKnown(arg);
if (!comptime_known) break @intCast(i);
} else null;
_ = opt_runtime_index orelse {
const elem_vals = try sema.arena.alloc(InternPool.Index, resolved_args.len);
for (elem_vals, resolved_args) |*val, arg| {
// We checked that all args are comptime above.
val.* = (sema.resolveValue(arg) catch unreachable).?.toIntern();
}
const arr_val = try pt.aggregateValue(array_ty, elem_vals);
const result_ref = try sema.coerce(block, result_ty, Air.internedToRef(arr_val.toIntern()), src);
const result_val = (try sema.resolveValue(result_ref)).?;
return sema.addConstantMaybeRef(result_val.toIntern(), is_ref);
};
if (is_ref) {
const target = zcu.getTarget();
const alloc_ty = try pt.ptrTypeSema(.{
.child = result_ty.toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const alloc = try block.addTy(.alloc, alloc_ty);
const base_ptr = try sema.optEuBasePtrInit(block, alloc, src);
if (is_tuple) {
for (resolved_args, 0..) |arg, i| {
const elem_ptr_ty = try pt.ptrTypeSema(.{
.child = array_ty.fieldType(i, zcu).toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const elem_ptr_ty_ref = Air.internedToRef(elem_ptr_ty.toIntern());
const index = try pt.intRef(.usize, i);
const elem_ptr = try block.addPtrElemPtrTypeRef(base_ptr, index, elem_ptr_ty_ref);
_ = try block.addBinOp(.store, elem_ptr, arg);
}
return sema.makePtrConst(block, alloc);
}
const elem_ptr_ty = try pt.ptrTypeSema(.{
.child = array_ty.elemType2(zcu).toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const elem_ptr_ty_ref = Air.internedToRef(elem_ptr_ty.toIntern());
for (resolved_args, 0..) |arg, i| {
const index = try pt.intRef(.usize, i);
const elem_ptr = try block.addPtrElemPtrTypeRef(base_ptr, index, elem_ptr_ty_ref);
_ = try block.addBinOp(.store, elem_ptr, arg);
}
return sema.makePtrConst(block, alloc);
}
const arr_ref = try block.addAggregateInit(array_ty, resolved_args);
return sema.coerce(block, result_ty, arr_ref, src);
}
fn zirArrayInitAnon(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.MultiOp, inst_data.payload_index);
const operands = sema.code.refSlice(extra.end, extra.data.operands_len);
return sema.arrayInitAnon(block, src, operands, false);
}
fn arrayInitAnon(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operands: []const Zir.Inst.Ref,
is_ref: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const types = try sema.arena.alloc(InternPool.Index, operands.len);
const values = try sema.arena.alloc(InternPool.Index, operands.len);
var any_comptime = false;
const opt_runtime_src = rs: {
var runtime_src: ?LazySrcLoc = null;
for (operands, 0..) |operand, i| {
const operand_src = block.src(.{ .init_elem = .{
.init_node_offset = src.offset.node_offset.x,
.elem_index = @intCast(i),
} });
const elem = try sema.resolveInst(operand);
types[i] = sema.typeOf(elem).toIntern();
if (Type.fromInterned(types[i]).zigTypeTag(zcu) == .@"opaque") {
const msg = msg: {
const msg = try sema.errMsg(operand_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
errdefer msg.destroy(gpa);
try sema.addDeclaredHereNote(msg, .fromInterned(types[i]));
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
if (try sema.resolveValue(elem)) |val| {
values[i] = val.toIntern();
any_comptime = true;
} else {
values[i] = .none;
runtime_src = operand_src;
}
}
break :rs runtime_src;
};
// A field can't be `comptime` if it references a `comptime var` but the aggregate can still be comptime-known.
// Replace these fields with `.none` only for generating the type.
const values_no_comptime = if (!any_comptime) values else blk: {
const new_values = try sema.arena.alloc(InternPool.Index, operands.len);
for (values, new_values) |val, *new_val| {
if (val != .none and Value.fromInterned(val).canMutateComptimeVarState(zcu)) {
new_val.* = .none;
} else new_val.* = val;
}
break :blk new_values;
};
const tuple_ty: Type = .fromInterned(try ip.getTupleType(gpa, pt.tid, .{
.types = types,
.values = values_no_comptime,
}));
const runtime_src = opt_runtime_src orelse {
const tuple_val = try pt.aggregateValue(tuple_ty, values);
return sema.addConstantMaybeRef(tuple_val.toIntern(), is_ref);
};
try sema.requireRuntimeBlock(block, src, runtime_src);
for (operands, 0..) |operand, i| {
const operand_src = block.src(.{ .init_elem = .{
.init_node_offset = src.offset.node_offset.x,
.elem_index = @intCast(i),
} });
try sema.validateRuntimeValue(block, operand_src, try sema.resolveInst(operand));
}
if (is_ref) {
const target = sema.pt.zcu.getTarget();
const alloc_ty = try pt.ptrTypeSema(.{
.child = tuple_ty.toIntern(),
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
const alloc = try block.addTy(.alloc, alloc_ty);
for (operands, 0..) |operand, i_usize| {
const i: u32 = @intCast(i_usize);
const field_ptr_ty = try pt.ptrTypeSema(.{
.child = types[i],
.flags = .{ .address_space = target_util.defaultAddressSpace(target, .local) },
});
if (values[i] == .none) {
const field_ptr = try block.addStructFieldPtr(alloc, i, field_ptr_ty);
_ = try block.addBinOp(.store, field_ptr, try sema.resolveInst(operand));
}
}
return sema.makePtrConst(block, alloc);
}
const element_refs = try sema.arena.alloc(Air.Inst.Ref, operands.len);
for (operands, 0..) |operand, i| {
element_refs[i] = try sema.resolveInst(operand);
}
return block.addAggregateInit(tuple_ty, element_refs);
}
fn addConstantMaybeRef(sema: *Sema, val: InternPool.Index, is_ref: bool) !Air.Inst.Ref {
return if (is_ref) sema.uavRef(val) else Air.internedToRef(val);
}
fn zirFieldTypeRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.FieldTypeRef, inst_data.payload_index).data;
const ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const field_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type);
const field_name = try sema.resolveConstStringIntern(block, field_src, extra.field_name, .{ .simple = .field_name });
return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src);
}
fn zirStructInitFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data;
const ty_src = block.nodeOffset(inst_data.src_node);
const field_name_src = block.src(.{ .node_offset_field_name_init = inst_data.src_node });
const wrapped_aggregate_ty = try sema.resolveTypeOrPoison(block, ty_src, extra.container_type) orelse return .generic_poison_type;
const aggregate_ty = wrapped_aggregate_ty.optEuBaseType(zcu);
const zir_field_name = sema.code.nullTerminatedString(extra.name_start);
const field_name = try ip.getOrPutString(sema.gpa, pt.tid, zir_field_name, .no_embedded_nulls);
return sema.fieldType(block, aggregate_ty, field_name, field_name_src, ty_src);
}
fn fieldType(
sema: *Sema,
block: *Block,
aggregate_ty: Type,
field_name: InternPool.NullTerminatedString,
field_src: LazySrcLoc,
ty_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
var cur_ty = aggregate_ty;
while (true) {
try cur_ty.resolveFields(pt);
switch (cur_ty.zigTypeTag(zcu)) {
.@"struct" => switch (ip.indexToKey(cur_ty.toIntern())) {
.tuple_type => |tuple| {
const field_index = try sema.tupleFieldIndex(block, cur_ty, field_name, field_src);
return Air.internedToRef(tuple.types.get(ip)[field_index]);
},
.struct_type => {
const struct_type = ip.loadStructType(cur_ty.toIntern());
const field_index = struct_type.nameIndex(ip, field_name) orelse
return sema.failWithBadStructFieldAccess(block, cur_ty, struct_type, field_src, field_name);
const field_ty = struct_type.field_types.get(ip)[field_index];
return Air.internedToRef(field_ty);
},
else => unreachable,
},
.@"union" => {
const union_obj = zcu.typeToUnion(cur_ty).?;
const field_index = union_obj.loadTagType(ip).nameIndex(ip, field_name) orelse
return sema.failWithBadUnionFieldAccess(block, cur_ty, union_obj, field_src, field_name);
const field_ty = union_obj.field_types.get(ip)[field_index];
return Air.internedToRef(field_ty);
},
.optional => {
// Struct/array init through optional requires the child type to not be a pointer.
// If the child of .optional is a pointer it'll error on the next loop.
cur_ty = .fromInterned(ip.indexToKey(cur_ty.toIntern()).opt_type);
continue;
},
.error_union => {
cur_ty = cur_ty.errorUnionPayload(zcu);
continue;
},
else => {},
}
return sema.fail(block, ty_src, "expected struct or union; found '{f}'", .{
cur_ty.fmt(pt),
});
}
}
fn zirErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref {
return sema.getErrorReturnTrace(block);
}
fn getErrorReturnTrace(sema: *Sema, block: *Block) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const stack_trace_ty = try sema.getBuiltinType(block.nodeOffset(.zero), .StackTrace);
try stack_trace_ty.resolveFields(pt);
const ptr_stack_trace_ty = try pt.singleMutPtrType(stack_trace_ty);
const opt_ptr_stack_trace_ty = try pt.optionalType(ptr_stack_trace_ty.toIntern());
switch (sema.owner.unwrap()) {
.func => |func| if (ip.funcAnalysisUnordered(func).has_error_trace and block.ownerModule().error_tracing) {
return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty);
},
.@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
}
return Air.internedToRef(try pt.intern(.{ .opt = .{
.ty = opt_ptr_stack_trace_ty.toIntern(),
.val = .none,
} }));
}
fn zirFrame(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const src_node: std.zig.Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(extended.operand)));
const src = block.nodeOffset(src_node);
return sema.failWithUseOfAsync(block, src);
}
fn zirAlignOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const zcu = sema.pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const ty = try sema.resolveType(block, operand_src, inst_data.operand);
if (ty.isNoReturn(zcu)) {
return sema.fail(block, operand_src, "no align available for type '{f}'", .{ty.fmt(sema.pt)});
}
const val = try ty.lazyAbiAlignment(sema.pt);
return Air.internedToRef(val.toIntern());
}
fn zirIntFromBool(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const is_vector = operand_ty.zigTypeTag(zcu) == .vector;
const operand_scalar_ty = operand_ty.scalarType(zcu);
if (operand_scalar_ty.toIntern() != .bool_type) {
return sema.fail(block, src, "expected 'bool', found '{t}'", .{operand_scalar_ty.zigTypeTag(zcu)});
}
const len = if (is_vector) operand_ty.vectorLen(zcu) else undefined;
const dest_ty: Type = if (is_vector) try pt.vectorType(.{ .child = .u1_type, .len = len }) else .u1;
if (try sema.resolveValue(operand)) |val| {
if (!is_vector) {
return if (val.isUndef(zcu)) .undef_u1 else if (val.toBool()) .one_u1 else .zero_u1;
}
if (val.isUndef(zcu)) return pt.undefRef(dest_ty);
const new_elems = try sema.arena.alloc(InternPool.Index, len);
for (new_elems, 0..) |*new_elem, i| {
const old_elem = try val.elemValue(pt, i);
new_elem.* = if (old_elem.isUndef(zcu))
.undef_u1
else if (old_elem.toBool())
.one_u1
else
.zero_u1;
}
return Air.internedToRef((try pt.aggregateValue(dest_ty, new_elems)).toIntern());
}
return block.addBitCast(dest_ty, operand);
}
fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const uncoerced_operand = try sema.resolveInst(inst_data.operand);
const operand = try sema.coerce(block, .anyerror, uncoerced_operand, operand_src);
if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| {
const err_name = sema.pt.zcu.intern_pool.indexToKey(val.toIntern()).err.name;
return sema.addNullTerminatedStrLit(err_name);
}
// Similar to zirTagName, we have special AIR instruction for the error name in case an optimimzation pass
// might be able to resolve the result at compile time.
return block.addUnOp(.error_name, operand);
}
fn zirAbs(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand = try sema.resolveInst(inst_data.operand);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand_ty = sema.typeOf(operand);
const scalar_ty = operand_ty.scalarType(zcu);
const result_ty = switch (scalar_ty.zigTypeTag(zcu)) {
.comptime_float, .float, .comptime_int => operand_ty,
.int => if (scalar_ty.isSignedInt(zcu)) try operand_ty.toUnsigned(pt) else return operand,
else => return sema.fail(
block,
operand_src,
"expected integer, float, or vector of either integers or floats, found '{f}'",
.{operand_ty.fmt(pt)},
),
};
return (try sema.maybeConstantUnaryMath(operand, result_ty, Value.abs)) orelse {
try sema.requireRuntimeBlock(block, operand_src, null);
return block.addTyOp(.abs, result_ty, operand);
};
}
fn maybeConstantUnaryMath(
sema: *Sema,
operand: Air.Inst.Ref,
result_ty: Type,
comptime eval: fn (Value, Type, Allocator, Zcu.PerThread) Allocator.Error!Value,
) CompileError!?Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
switch (result_ty.zigTypeTag(zcu)) {
.vector => if (try sema.resolveValue(operand)) |val| {
const scalar_ty = result_ty.scalarType(zcu);
const vec_len = result_ty.vectorLen(zcu);
if (val.isUndef(zcu))
return try pt.undefRef(result_ty);
const elems = try sema.arena.alloc(InternPool.Index, vec_len);
for (elems, 0..) |*elem, i| {
const elem_val = try val.elemValue(pt, i);
elem.* = (try eval(elem_val, scalar_ty, sema.arena, pt)).toIntern();
}
return Air.internedToRef((try pt.aggregateValue(result_ty, elems)).toIntern());
},
else => if (try sema.resolveValue(operand)) |operand_val| {
if (operand_val.isUndef(zcu))
return try pt.undefRef(result_ty);
const result_val = try eval(operand_val, result_ty, sema.arena, pt);
return Air.internedToRef(result_val.toIntern());
},
}
return null;
}
fn zirUnaryMath(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
air_tag: Air.Inst.Tag,
comptime eval: fn (Value, Type, Allocator, Zcu.PerThread) Allocator.Error!Value,
) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand = try sema.resolveInst(inst_data.operand);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand_ty = sema.typeOf(operand);
const scalar_ty = operand_ty.scalarType(zcu);
switch (scalar_ty.zigTypeTag(zcu)) {
.comptime_float, .float => {},
else => return sema.fail(
block,
operand_src,
"expected vector of floats or float type, found '{f}'",
.{operand_ty.fmt(pt)},
),
}
return (try sema.maybeConstantUnaryMath(operand, operand_ty, eval)) orelse {
try sema.requireRuntimeBlock(block, operand_src, null);
return block.addUnOp(air_tag, operand);
};
}
fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const src = block.nodeOffset(inst_data.src_node);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
try operand_ty.resolveLayout(pt);
const enum_ty = switch (operand_ty.zigTypeTag(zcu)) {
.enum_literal => {
const val = (try sema.resolveDefinedValue(block, operand_src, operand)).?;
const tag_name = ip.indexToKey(val.toIntern()).enum_literal;
return sema.addNullTerminatedStrLit(tag_name);
},
.@"enum" => operand_ty,
.@"union" => operand_ty.unionTagType(zcu) orelse
return sema.fail(block, src, "union '{f}' is untagged", .{operand_ty.fmt(pt)}),
else => return sema.fail(block, operand_src, "expected enum or union; found '{f}'", .{
operand_ty.fmt(pt),
}),
};
if (enum_ty.enumFieldCount(zcu) == 0) {
// TODO I don't think this is the correct way to handle this but
// it prevents a crash.
// https://github.com/ziglang/zig/issues/15909
return sema.fail(block, operand_src, "cannot get @tagName of empty enum '{f}'", .{
enum_ty.fmt(pt),
});
}
const casted_operand = try sema.coerce(block, enum_ty, operand, operand_src);
if (try sema.resolveDefinedValue(block, operand_src, casted_operand)) |val| {
const field_index = enum_ty.enumTagFieldIndex(val, zcu) orelse {
const msg = msg: {
const msg = try sema.errMsg(src, "no field with value '{f}' in enum '{f}'", .{
val.fmtValueSema(pt, sema), enum_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(enum_ty.srcLoc(zcu), msg, "declared here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
};
// TODO: write something like getCoercedInts to avoid needing to dupe
const field_name = enum_ty.enumFieldName(field_index, zcu);
return sema.addNullTerminatedStrLit(field_name);
}
try sema.requireRuntimeBlock(block, src, operand_src);
if (block.wantSafety() and zcu.backendSupportsFeature(.is_named_enum_value)) {
const ok = try block.addUnOp(.is_named_enum_value, casted_operand);
try sema.addSafetyCheck(block, src, ok, .invalid_enum_value);
}
// In case the value is runtime-known, we have an AIR instruction for this instead
// of trying to lower it in Sema because an optimization pass may result in the operand
// being comptime-known, which would let us elide the `tag_name` AIR instruction.
return block.addUnOp(.tag_name, casted_operand);
}
fn zirReifyInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const signedness_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const bits_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const signedness = try sema.resolveBuiltinEnum(block, signedness_src, extra.lhs, .Signedness, .{ .simple = .int_signedness });
const bits: u16 = @intCast(try sema.resolveInt(block, bits_src, extra.rhs, .u16, .{ .simple = .int_bit_width }));
return .fromType(try sema.pt.intType(signedness, bits));
}
fn zirReifySliceArgTy(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const info: Zir.Inst.ReifySliceArgInfo = @enumFromInt(extended.small);
const src = block.nodeOffset(extra.node);
const comptime_reason: std.zig.SimpleComptimeReason, const in_scalar_ty: Type, const out_scalar_ty: Type = switch (info) {
// zig fmt: off
.type_to_fn_param_attrs => .{ .fn_param_attrs, .type, try sema.getBuiltinType(src, .@"Type.Fn.Param.Attributes") },
.string_to_struct_field_type => .{ .struct_field_types, .slice_const_u8, .type },
.string_to_union_field_type => .{ .union_field_types, .slice_const_u8, .type },
.string_to_struct_field_attrs => .{ .struct_field_attrs, .slice_const_u8, try sema.getBuiltinType(src, .@"Type.StructField.Attributes") },
.string_to_union_field_attrs => .{ .union_field_attrs, .slice_const_u8, try sema.getBuiltinType(src, .@"Type.UnionField.Attributes") },
// zig fmt: on
};
const operand_ty = try pt.ptrTypeSema(.{
.child = in_scalar_ty.toIntern(),
.flags = .{ .size = .slice, .is_const = true },
});
const operand_uncoerced = try sema.resolveInst(extra.operand);
const operand_coerced = try sema.coerce(block, operand_ty, operand_uncoerced, src);
const operand_val = try sema.resolveConstDefinedValue(block, src, operand_coerced, .{ .simple = comptime_reason });
const len_val: Value = .fromInterned(zcu.intern_pool.indexToKey(operand_val.toIntern()).slice.len);
if (len_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src, null);
const len = try len_val.toUnsignedIntSema(pt);
return .fromType(try pt.singleConstPtrType(try pt.arrayType(.{
.len = len,
.child = out_scalar_ty.toIntern(),
})));
}
fn zirReifyEnumValueSliceTy(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const int_tag_ty_src = block.builtinCallArgSrc(extra.node, 0);
const field_names_src = block.builtinCallArgSrc(extra.node, 2);
const int_tag_ty = try sema.resolveType(block, int_tag_ty_src, extra.lhs);
const operand_uncoerced = try sema.resolveInst(extra.rhs);
const operand_coerced = try sema.coerce(block, .slice_const_slice_const_u8, operand_uncoerced, field_names_src);
const operand_val = try sema.resolveConstDefinedValue(block, field_names_src, operand_coerced, .{ .simple = .enum_field_names });
const len_val: Value = .fromInterned(zcu.intern_pool.indexToKey(operand_val.toIntern()).slice.len);
if (len_val.isUndef(zcu)) return sema.failWithUseOfUndef(block, field_names_src, null);
const len = try len_val.toUnsignedIntSema(pt);
return .fromType(try pt.singleConstPtrType(try pt.arrayType(.{
.len = len,
.child = int_tag_ty.toIntern(),
})));
}
fn zirReifyPointerSentinelTy(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const elem_ty = try sema.resolveType(block, src, extra.operand);
return .fromType(switch (elem_ty.zigTypeTag(zcu)) {
else => try pt.optionalType(elem_ty.toIntern()),
// These types cannot be the child of an optional. To allow reifying pointers to them still,
// we treat the "sentinel" argument to `@Pointer` as `?noreturn` instead of `?T`.
.@"opaque", .null => .optional_noreturn,
});
}
fn zirReifyTuple(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const operand_src = block.builtinCallArgSrc(extra.node, 0);
const types_uncoerced = try sema.resolveInst(extra.operand);
const types_coerced = try sema.coerce(block, .slice_const_type, types_uncoerced, operand_src);
const types_slice_val = try sema.resolveConstDefinedValue(block, operand_src, types_coerced, .{ .simple = .tuple_field_types });
const types_array_val = try sema.derefSliceAsArray(block, operand_src, types_slice_val, .{ .simple = .tuple_field_types });
const fields_len: u32 = @intCast(types_array_val.typeOf(zcu).arrayLen(zcu));
const field_types = try sema.arena.alloc(InternPool.Index, fields_len);
for (field_types, 0..) |*field_ty, field_idx| {
const field_ty_val = try types_array_val.elemValue(pt, field_idx);
if (field_ty_val.isUndef(zcu)) {
return sema.failWithUseOfUndef(block, operand_src, null);
}
field_ty.* = field_ty_val.toIntern();
}
const field_values = try sema.arena.alloc(InternPool.Index, fields_len);
@memset(field_values, .none);
return .fromIntern(try zcu.intern_pool.getTupleType(zcu.gpa, pt.tid, .{
.types = field_types,
.values = field_values,
}));
}
fn zirReifyPointer(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.ReifyPointer, extended.operand).data;
const src = block.nodeOffset(extra.node);
const size_src = block.builtinCallArgSrc(extra.node, 0);
const attrs_src = block.builtinCallArgSrc(extra.node, 1);
const elem_ty_src = block.builtinCallArgSrc(extra.node, 2);
const sentinel_src = block.builtinCallArgSrc(extra.node, 3);
const size_ty = try sema.getBuiltinType(size_src, .@"Type.Pointer.Size");
const attrs_ty = try sema.getBuiltinType(attrs_src, .@"Type.Pointer.Attributes");
const size_uncoerced = try sema.resolveInst(extra.size);
const size_coerced = try sema.coerce(block, size_ty, size_uncoerced, size_src);
const size_val = try sema.resolveConstDefinedValue(block, size_src, size_coerced, .{ .simple = .pointer_size });
const size = try sema.interpretBuiltinType(block, size_src, size_val, std.builtin.Type.Pointer.Size);
const attrs_uncoerced = try sema.resolveInst(extra.attrs);
const attrs_coerced = try sema.coerce(block, attrs_ty, attrs_uncoerced, attrs_src);
const attrs_val = try sema.resolveConstDefinedValue(block, attrs_src, attrs_coerced, .{ .simple = .pointer_attrs });
const attrs = try sema.interpretBuiltinType(block, attrs_src, attrs_val, std.builtin.Type.Pointer.Attributes);
const @"align": Alignment = if (attrs.@"align") |bytes| a: {
break :a try sema.validateAlign(block, attrs_src, bytes);
} else .none;
const elem_ty = try sema.resolveType(block, elem_ty_src, extra.elem_ty);
switch (elem_ty.zigTypeTag(zcu)) {
.noreturn => return sema.fail(block, elem_ty_src, "pointer to noreturn not allowed", .{}),
// This needs to be disallowed, because the sentinel parameter would otherwise have type
// `?@TypeOf(null)`, which is not a valid type because you cannot differentiate between
// constructing the "inner" null value and the "outer" null value.
.null => return sema.fail(block, elem_ty_src, "cannot reify pointer to '@TypeOf(null)'", .{}),
.@"fn" => switch (size) {
.one => {},
.many, .c, .slice => return sema.fail(block, src, "function pointers must be single pointers", .{}),
},
.@"opaque" => switch (size) {
.one => {},
.many, .c, .slice => return sema.fail(block, src, "indexable pointer to opaque type '{f}' not allowed", .{elem_ty.fmt(pt)}),
},
else => {},
}
if (size == .c and !try sema.validateExternType(elem_ty, .other)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "C pointers cannot point to non-C-ABI-compatible type '{f}'", .{elem_ty.fmt(pt)});
errdefer msg.destroy(gpa);
try sema.explainWhyTypeIsNotExtern(msg, elem_ty_src, elem_ty, .other);
try sema.addDeclaredHereNote(msg, elem_ty);
break :msg msg;
});
}
const sentinel_ty = try pt.optionalType(elem_ty.toIntern());
const sentinel_uncoerced = try sema.resolveInst(extra.sentinel);
const sentinel_coerced = try sema.coerce(block, sentinel_ty, sentinel_uncoerced, sentinel_src);
const sentinel_val = try sema.resolveConstDefinedValue(block, sentinel_src, sentinel_coerced, .{ .simple = .pointer_sentinel });
const opt_sentinel = sentinel_val.optionalValue(zcu);
if (opt_sentinel) |sentinel| {
switch (size) {
.many, .slice => {},
.one, .c => return sema.fail(block, sentinel_src, "sentinels are only allowed on slices and unknown-length pointers", .{}),
}
try checkSentinelType(sema, block, sentinel_src, elem_ty);
if (sentinel.canMutateComptimeVarState(zcu)) {
const sentinel_name = try ip.getOrPutString(gpa, pt.tid, "sentinel", .no_embedded_nulls);
return sema.failWithContainsReferenceToComptimeVar(block, sentinel_src, sentinel_name, "sentinel", sentinel);
}
}
return .fromType(try pt.ptrTypeSema(.{
.child = elem_ty.toIntern(),
.sentinel = if (opt_sentinel) |s| s.toIntern() else .none,
.flags = .{
.size = size,
.is_const = attrs.@"const",
.is_volatile = attrs.@"volatile",
.is_allowzero = attrs.@"allowzero",
.address_space = attrs.@"addrspace" orelse as: {
if (elem_ty.zigTypeTag(zcu) == .@"fn" and zcu.getTarget().cpu.arch == .avr) break :as .flash;
break :as .generic;
},
.alignment = @"align",
},
}));
}
fn zirReifyFn(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.ReifyFn, extended.operand).data;
const param_types_src = block.builtinCallArgSrc(extra.node, 0);
const param_attrs_src = block.builtinCallArgSrc(extra.node, 1);
const ret_ty_src = block.builtinCallArgSrc(extra.node, 2);
const fn_attrs_src = block.builtinCallArgSrc(extra.node, 3);
const single_param_attrs_ty = try sema.getBuiltinType(param_attrs_src, .@"Type.Fn.Param.Attributes");
const fn_attrs_ty = try sema.getBuiltinType(fn_attrs_src, .@"Type.Fn.Attributes");
const param_types_uncoerced = try sema.resolveInst(extra.param_types);
const param_types_coerced = try sema.coerce(block, .slice_const_type, param_types_uncoerced, param_types_src);
const param_types_slice = try sema.resolveConstDefinedValue(block, param_types_src, param_types_coerced, .{ .simple = .fn_param_types });
const param_types_arr = try sema.derefSliceAsArray(block, param_types_src, param_types_slice, .{ .simple = .fn_param_types });
const params_len = param_types_arr.typeOf(zcu).arrayLen(zcu);
const param_attrs_ty = try pt.singleConstPtrType(try pt.arrayType(.{
.len = params_len,
.child = single_param_attrs_ty.toIntern(),
}));
const param_attrs_uncoerced = try sema.resolveInst(extra.param_attrs);
const param_attrs_coerced = try sema.coerce(block, param_attrs_ty, param_attrs_uncoerced, param_attrs_src);
const param_attrs_slice = try sema.resolveConstDefinedValue(block, param_attrs_src, param_attrs_coerced, .{ .simple = .fn_param_attrs });
const param_attrs_arr = try sema.derefSliceAsArray(block, param_attrs_src, param_attrs_slice, .{ .simple = .fn_param_attrs });
const ret_ty = try sema.resolveType(block, ret_ty_src, extra.ret_ty);
const fn_attrs_uncoerced = try sema.resolveInst(extra.fn_attrs);
const fn_attrs_coerced = try sema.coerce(block, fn_attrs_ty, fn_attrs_uncoerced, fn_attrs_src);
const fn_attrs_val = try sema.resolveConstDefinedValue(block, fn_attrs_src, fn_attrs_coerced, .{ .simple = .fn_attrs });
const fn_attrs = try sema.interpretBuiltinType(block, fn_attrs_src, fn_attrs_val, std.builtin.Type.Fn.Attributes);
var noalias_bits: u32 = 0;
const param_types_ip = try sema.arena.alloc(InternPool.Index, @intCast(params_len));
for (param_types_ip, 0..@intCast(params_len)) |*param_ty_ip, param_idx| {
const param_ty: Type = (try param_types_arr.elemValue(pt, param_idx)).toType();
const param_attrs = try sema.interpretBuiltinType(
block,
param_attrs_src,
try param_attrs_arr.elemValue(pt, param_idx),
std.builtin.Type.Fn.Param.Attributes,
);
try sema.checkParamTypeCommon(
block,
@intCast(param_idx),
param_ty,
param_attrs.@"noalias",
param_types_src,
fn_attrs.@"callconv",
);
if (try param_ty.comptimeOnlySema(pt)) {
return sema.fail(block, param_attrs_src, "cannot reify function type with comptime-only parameter type '{f}'", .{param_ty.fmt(pt)});
}
if (param_attrs.@"noalias") {
if (param_idx > 31) {
return sema.fail(block, param_attrs_src, "this compiler implementation only supports 'noalias' on the first 32 parameters", .{});
}
noalias_bits |= @as(u32, 1) << @intCast(param_idx);
}
param_ty_ip.* = param_ty.toIntern();
}
if (fn_attrs.varargs) {
try sema.checkCallConvSupportsVarArgs(block, fn_attrs_src, fn_attrs.@"callconv");
}
try sema.checkReturnTypeAndCallConvCommon(
block,
ret_ty,
ret_ty_src,
fn_attrs.@"callconv",
fn_attrs_src,
if (fn_attrs.varargs) fn_attrs_src else null,
false,
false,
);
if (try ret_ty.comptimeOnlySema(pt)) {
return sema.fail(block, param_attrs_src, "cannot reify function type with comptime-only return type '{f}'", .{ret_ty.fmt(pt)});
}
return .fromIntern(try ip.getFuncType(gpa, pt.tid, .{
.param_types = param_types_ip,
.noalias_bits = noalias_bits,
.comptime_bits = 0,
.return_type = ret_ty.toIntern(),
.cc = fn_attrs.@"callconv",
.is_var_args = fn_attrs.varargs,
.is_generic = false,
.is_noinline = false,
}));
}
fn zirReifyStruct(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
const extra = sema.code.extraData(Zir.Inst.ReifyStruct, extended.operand).data;
const tracked_inst = try block.trackZir(inst);
const src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .nodeOffset(.zero),
};
const layout_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 0,
} },
};
const backing_ty_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 1,
} },
};
const field_names_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 2,
} },
};
const field_types_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 3,
} },
};
const field_attrs_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 4,
} },
};
const container_layout_ty = try sema.getBuiltinType(layout_src, .@"Type.ContainerLayout");
const single_field_attrs_ty = try sema.getBuiltinType(field_attrs_src, .@"Type.StructField.Attributes");
const layout_uncoerced = try sema.resolveInst(extra.layout);
const layout_coerced = try sema.coerce(block, container_layout_ty, layout_uncoerced, layout_src);
const layout_val = try sema.resolveConstDefinedValue(block, layout_src, layout_coerced, .{ .simple = .struct_layout });
const layout = try sema.interpretBuiltinType(block, layout_src, layout_val, std.builtin.Type.ContainerLayout);
const backing_int_ty_uncoerced = try sema.resolveInst(extra.backing_ty);
const backing_int_ty_coerced = try sema.coerce(block, .optional_type, backing_int_ty_uncoerced, backing_ty_src);
const backing_int_ty_val = try sema.resolveConstDefinedValue(block, backing_ty_src, backing_int_ty_coerced, .{ .simple = .type });
const field_names_uncoerced = try sema.resolveInst(extra.field_names);
const field_names_coerced = try sema.coerce(block, .slice_const_slice_const_u8, field_names_uncoerced, field_names_src);
const field_names_slice = try sema.resolveConstDefinedValue(block, field_names_src, field_names_coerced, .{ .simple = .struct_field_names });
const field_names_arr = try sema.derefSliceAsArray(block, field_names_src, field_names_slice, .{ .simple = .struct_field_names });
const fields_len = try sema.usizeCast(block, src, field_names_arr.typeOf(zcu).arrayLen(zcu));
const field_types_ty = try pt.singleConstPtrType(try pt.arrayType(.{
.len = fields_len,
.child = .type_type,
}));
const field_attrs_ty = try pt.singleConstPtrType(try pt.arrayType(.{
.len = fields_len,
.child = single_field_attrs_ty.toIntern(),
}));
const field_types_uncoerced = try sema.resolveInst(extra.field_types);
const field_types_coerced = try sema.coerce(block, field_types_ty, field_types_uncoerced, field_types_src);
const field_types_slice = try sema.resolveConstDefinedValue(block, field_types_src, field_types_coerced, .{ .simple = .struct_field_types });
const field_types_arr = try sema.derefSliceAsArray(block, field_types_src, field_types_slice, .{ .simple = .struct_field_types });
const field_attrs_uncoerced = try sema.resolveInst(extra.field_attrs);
const field_attrs_coerced = try sema.coerce(block, field_attrs_ty, field_attrs_uncoerced, field_attrs_src);
const field_attrs_slice = try sema.resolveConstDefinedValue(block, field_attrs_src, field_attrs_coerced, .{ .simple = .struct_field_attrs });
const field_attrs_arr = try sema.derefSliceAsArray(block, field_attrs_src, field_attrs_slice, .{ .simple = .struct_field_attrs });
// Before we begin, check for undefs...
if (try sema.anyUndef(block, field_attrs_src, field_attrs_arr)) {
return sema.failWithUseOfUndef(block, field_attrs_src, null);
}
if (try sema.anyUndef(block, field_types_src, field_types_arr)) {
return sema.failWithUseOfUndef(block, field_types_src, null);
}
// We don't need to check `field_names_arr`, because `sliceToIpString` will check that for us.
if (try sema.anyUndef(block, backing_ty_src, backing_int_ty_val)) {
return sema.failWithUseOfUndef(block, backing_ty_src, null);
}
// The validation work here is non-trivial, and it's possible the type already exists.
// So in this first pass, let's just construct a hash to optimize for this case. If the
// inputs turn out to be invalid, we can cancel the WIP type later.
var any_comptime_fields = false;
var any_default_inits = false;
var any_aligned_fields = false;
// For deduplication purposes, we must create a hash including all details of this type.
// TODO: use a longer hash!
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, layout);
std.hash.autoHash(&hasher, backing_int_ty_val);
// The field *type* array has already been deduplicated for us thanks to the InternPool!
std.hash.autoHash(&hasher, field_types_arr);
// However, for field names and attributes, we need to actually iterate the individual fields,
// because the presence of pointers (the `[]const u8` for the name and the `*const anyopaque`
// for the default value) means that distinct interned values could ultimately result in the
// same struct type.
for (0..fields_len) |field_idx| {
const field_name_val = try field_names_arr.elemValue(pt, field_idx);
const field_attrs_val = try field_attrs_arr.elemValue(pt, field_idx);
const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, .{ .simple = .struct_field_names });
const field_attr_comptime = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
std.builtin.Type.StructField.Attributes,
"comptime",
).?);
const field_attr_align = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
std.builtin.Type.StructField.Attributes,
"align",
).?);
const field_attr_default_value_ptr = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
std.builtin.Type.StructField.Attributes,
"default_value_ptr",
).?);
const field_default: InternPool.Index = d: {
const ptr_val = field_attr_default_value_ptr.optionalValue(zcu) orelse break :d .none;
const field_ty = (try field_types_arr.elemValue(pt, field_idx)).toType();
const ptr_ty = try pt.singleConstPtrType(field_ty);
const deref_val = try sema.pointerDeref(block, field_attrs_src, ptr_val, ptr_ty) orelse return sema.failWithNeededComptime(
block,
field_attrs_src,
.{ .simple = .struct_field_default_value },
);
// Resolve the value so that lazy values do not create distinct types.
break :d (try sema.resolveLazyValue(deref_val)).toIntern();
};
std.hash.autoHash(&hasher, .{
field_name,
field_attr_comptime,
field_attr_align,
field_default,
});
if (field_attr_comptime.toBool()) any_comptime_fields = true;
if (field_attr_align.optionalValue(zcu)) |_| any_aligned_fields = true;
if (field_default != .none) any_default_inits = true;
}
// Some basic validation to avoid a bogus `getStructType` call...
const backing_int_ty: ?Type = if (backing_int_ty_val.optionalValue(zcu)) |backing| ty: {
switch (layout) {
.auto, .@"extern" => return sema.fail(block, backing_ty_src, "non-packed struct does not support backing integer type", .{}),
.@"packed" => {},
}
break :ty backing.toType();
} else null;
if (any_aligned_fields and layout == .@"packed") {
return sema.fail(block, field_attrs_src, "packed struct fields cannot be aligned", .{});
}
if (any_comptime_fields and layout != .auto) {
return sema.fail(block, field_attrs_src, "{t} struct fields cannot be marked comptime", .{layout});
}
const wip_ty = switch (try ip.getStructType(gpa, pt.tid, .{
.layout = layout,
.fields_len = @intCast(fields_len),
.known_non_opv = false,
.requires_comptime = .unknown,
.any_comptime_fields = any_comptime_fields,
.any_default_inits = any_default_inits,
.any_aligned_fields = any_aligned_fields,
.inits_resolved = true,
.key = .{ .reified = .{
.zir_index = tracked_inst,
.type_hash = hasher.final(),
} },
}, false)) {
.wip => |wip| wip,
.existing => |ty| {
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(src, ty);
return Air.internedToRef(ty);
},
};
errdefer wip_ty.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(
block,
name_strategy,
"struct",
inst,
wip_ty.index,
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const wip_struct_type = ip.loadStructType(wip_ty.index);
for (0..fields_len) |field_idx| {
const field_name_val = try field_names_arr.elemValue(pt, field_idx);
const field_attrs_val = try field_attrs_arr.elemValue(pt, field_idx);
const field_ty = (try field_types_arr.elemValue(pt, field_idx)).toType();
// Don't pass a reason; first loop acts as a check that this is valid.
const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
if (wip_struct_type.addFieldName(ip, field_name)) |prev_index| {
_ = prev_index; // TODO: better source location
return sema.fail(block, field_names_src, "duplicate struct field name {f}", .{field_name.fmt(ip)});
}
const field_attr_comptime = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
std.builtin.Type.StructField.Attributes,
"comptime",
).?);
const field_attr_align = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
std.builtin.Type.StructField.Attributes,
"align",
).?);
const field_attr_default_value_ptr = try field_attrs_val.fieldValue(pt, std.meta.fieldIndex(
std.builtin.Type.StructField.Attributes,
"default_value_ptr",
).?);
if (field_attr_align.optionalValue(zcu)) |field_align_val| {
assert(layout != .@"packed");
const bytes = try field_align_val.toUnsignedIntSema(pt);
const a = try sema.validateAlign(block, field_attrs_src, bytes);
wip_struct_type.field_aligns.get(ip)[field_idx] = a;
} else if (any_aligned_fields) {
assert(layout != .@"packed");
wip_struct_type.field_aligns.get(ip)[field_idx] = .none;
}
const field_default: InternPool.Index = d: {
const ptr_val = field_attr_default_value_ptr.optionalValue(zcu) orelse break :d .none;
assert(any_default_inits);
const ptr_ty = try pt.singleConstPtrType(field_ty);
// The first loop checked that this is comptime-dereferencable.
const deref_val = (try sema.pointerDeref(block, field_attrs_src, ptr_val, ptr_ty)).?;
// ...but we've not checked this yet!
if (deref_val.canMutateComptimeVarState(zcu)) {
return sema.failWithContainsReferenceToComptimeVar(block, field_attrs_src, field_name, "field default value", deref_val);
}
break :d (try sema.resolveLazyValue(deref_val)).toIntern();
};
if (field_attr_comptime.toBool()) {
assert(layout == .auto);
if (field_default == .none) {
return sema.fail(block, field_attrs_src, "comptime field without default initialization value", .{});
}
wip_struct_type.setFieldComptime(ip, field_idx);
}
wip_struct_type.field_types.get(ip)[field_idx] = field_ty.toIntern();
if (field_default != .none) {
wip_struct_type.field_inits.get(ip)[field_idx] = field_default;
}
switch (field_ty.zigTypeTag(zcu)) {
.@"opaque" => return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_types_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
errdefer msg.destroy(gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
}),
.noreturn => return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_types_src, "struct fields cannot be 'noreturn'", .{});
errdefer msg.destroy(gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
}),
else => {},
}
switch (layout) {
.auto => {},
.@"extern" => if (!try sema.validateExternType(field_ty, .struct_field)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_types_src, "extern structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(gpa);
try sema.explainWhyTypeIsNotExtern(msg, field_types_src, field_ty, .struct_field);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
});
},
.@"packed" => if (!try sema.validatePackedType(field_ty)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_types_src, "packed structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(gpa);
try sema.explainWhyTypeIsNotPacked(msg, field_types_src, field_ty);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
});
},
}
}
if (layout == .@"packed") {
var fields_bit_sum: u64 = 0;
for (0..wip_struct_type.field_types.len) |field_idx| {
const field_ty: Type = .fromInterned(wip_struct_type.field_types.get(ip)[field_idx]);
try field_ty.resolveLayout(pt);
fields_bit_sum += field_ty.bitSize(zcu);
}
if (backing_int_ty) |ty| {
try sema.checkBackingIntType(block, src, ty, fields_bit_sum);
wip_struct_type.setBackingIntType(ip, ty.toIntern());
} else {
const ty = try pt.intType(.unsigned, @intCast(fields_bit_sum));
wip_struct_type.setBackingIntType(ip, ty.toIntern());
}
}
const new_namespace_index = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip_ty.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
// This job depends on any resolve_type_fully jobs queued up before it.
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
}
try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return .fromIntern(wip_ty.finish(ip, new_namespace_index));
}
fn zirReifyUnion(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
const extra = sema.code.extraData(Zir.Inst.ReifyUnion, extended.operand).data;
const tracked_inst = try block.trackZir(inst);
const src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .nodeOffset(.zero),
};
const layout_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 0,
} },
};
const arg_ty_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 1,
} },
};
const field_names_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 2,
} },
};
const field_types_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 3,
} },
};
const field_attrs_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 4,
} },
};
const container_layout_ty = try sema.getBuiltinType(layout_src, .@"Type.ContainerLayout");
const single_field_attrs_ty = try sema.getBuiltinType(field_attrs_src, .@"Type.UnionField.Attributes");
const layout_uncoerced = try sema.resolveInst(extra.layout);
const layout_coerced = try sema.coerce(block, container_layout_ty, layout_uncoerced, layout_src);
const layout_val = try sema.resolveConstDefinedValue(block, layout_src, layout_coerced, .{ .simple = .union_layout });
const layout = try sema.interpretBuiltinType(block, layout_src, layout_val, std.builtin.Type.ContainerLayout);
const arg_ty_uncoerced = try sema.resolveInst(extra.arg_ty);
const arg_ty_coerced = try sema.coerce(block, .optional_type, arg_ty_uncoerced, arg_ty_src);
const arg_ty_val = try sema.resolveConstDefinedValue(block, arg_ty_src, arg_ty_coerced, .{ .simple = .type });
const field_names_uncoerced = try sema.resolveInst(extra.field_names);
const field_names_coerced = try sema.coerce(block, .slice_const_slice_const_u8, field_names_uncoerced, field_names_src);
const field_names_slice = try sema.resolveConstDefinedValue(block, field_names_src, field_names_coerced, .{ .simple = .union_field_names });
const field_names_arr = try sema.derefSliceAsArray(block, field_names_src, field_names_slice, .{ .simple = .union_field_names });
const fields_len = try sema.usizeCast(block, src, field_names_arr.typeOf(zcu).arrayLen(zcu));
const field_types_ty = try pt.singleConstPtrType(try pt.arrayType(.{
.len = fields_len,
.child = .type_type,
}));
const field_attrs_ty = try pt.singleConstPtrType(try pt.arrayType(.{
.len = fields_len,
.child = single_field_attrs_ty.toIntern(),
}));
const field_types_uncoerced = try sema.resolveInst(extra.field_types);
const field_types_coerced = try sema.coerce(block, field_types_ty, field_types_uncoerced, field_types_src);
const field_types_slice = try sema.resolveConstDefinedValue(block, field_types_src, field_types_coerced, .{ .simple = .union_field_types });
const field_types_arr = try sema.derefSliceAsArray(block, field_types_src, field_types_slice, .{ .simple = .union_field_types });
const field_attrs_uncoerced = try sema.resolveInst(extra.field_attrs);
const field_attrs_coerced = try sema.coerce(block, field_attrs_ty, field_attrs_uncoerced, field_attrs_src);
const field_attrs_slice = try sema.resolveConstDefinedValue(block, field_attrs_src, field_attrs_coerced, .{ .simple = .union_field_attrs });
const field_attrs_arr = try sema.derefSliceAsArray(block, field_attrs_src, field_attrs_slice, .{ .simple = .union_field_attrs });
// Before we begin, check for undefs...
if (try sema.anyUndef(block, field_attrs_src, field_attrs_arr)) {
return sema.failWithUseOfUndef(block, field_attrs_src, null);
}
if (try sema.anyUndef(block, field_types_src, field_types_arr)) {
return sema.failWithUseOfUndef(block, field_types_src, null);
}
// We don't need to check `field_names_arr`, because `sliceToIpString` will check that for us.
if (try sema.anyUndef(block, arg_ty_src, arg_ty_val)) {
return sema.failWithUseOfUndef(block, arg_ty_src, null);
}
// The validation work here is non-trivial, and it's possible the type already exists.
// So in this first pass, let's just construct a hash to optimize for this case. If the
// inputs turn out to be invalid, we can cancel the WIP type later.
var any_aligned_fields = false;
// For deduplication purposes, we must create a hash including all details of this type.
// TODO: use a longer hash!
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, layout);
std.hash.autoHash(&hasher, arg_ty_val);
// `field_types_arr` and `field_attrs_arr` are already deduplicated by the InternPool!
std.hash.autoHash(&hasher, field_types_arr);
std.hash.autoHash(&hasher, field_attrs_arr);
// However, for field names, we need to iterate the individual fields, because the pointers (the
// names are slices) mean that distinct values could ultimately result in the same union type.
for (0..fields_len) |field_idx| {
const field_name_val = try field_names_arr.elemValue(pt, field_idx);
const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, .{ .simple = .union_field_names });
std.hash.autoHash(&hasher, field_name);
const field_attrs = try sema.interpretBuiltinType(
block,
field_attrs_src,
try field_attrs_arr.elemValue(pt, field_idx),
std.builtin.Type.UnionField.Attributes,
);
if (field_attrs.@"align" != null) {
any_aligned_fields = true;
}
}
// Some basic validation to avoid a bogus `getUnionType` call...
const explicit_tag_ty: ?Type = if (arg_ty_val.optionalValue(zcu)) |arg_ty| ty: {
switch (layout) {
.@"extern", .@"packed" => return sema.fail(block, arg_ty_src, "{t} union does not support enum tag type", .{layout}),
.auto => {},
}
break :ty arg_ty.toType();
} else null;
if (any_aligned_fields and layout == .@"packed") {
return sema.fail(block, field_attrs_src, "packed union fields cannot be aligned", .{});
}
const wip_ty = switch (try ip.getUnionType(gpa, pt.tid, .{
.flags = .{
.layout = layout,
.status = .none,
.runtime_tag = rt: {
if (explicit_tag_ty != null) break :rt .tagged;
if (layout == .auto and block.wantSafeTypes()) break :rt .safety;
break :rt .none;
},
.any_aligned_fields = any_aligned_fields,
.requires_comptime = .unknown,
.assumed_runtime_bits = false,
.assumed_pointer_aligned = false,
.alignment = .none,
},
.fields_len = @intCast(fields_len),
.enum_tag_ty = .none, // set later because not yet validated
.field_types = &.{}, // set later
.field_aligns = &.{}, // set later
.key = .{ .reified = .{
.zir_index = tracked_inst,
.type_hash = hasher.final(),
} },
}, false)) {
.wip => |wip| wip,
.existing => |ty| {
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(src, ty);
return Air.internedToRef(ty);
},
};
errdefer wip_ty.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(
block,
name_strategy,
"union",
inst,
wip_ty.index,
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const loaded_union = ip.loadUnionType(wip_ty.index);
const enum_tag_ty, const has_explicit_tag = if (explicit_tag_ty) |enum_tag_ty| tag: {
if (enum_tag_ty.zigTypeTag(zcu) != .@"enum") {
return sema.fail(block, arg_ty_src, "tag type must be an enum type", .{});
}
const tag_ty_fields_len = enum_tag_ty.enumFieldCount(zcu);
for (0..fields_len) |field_idx| {
const field_name_val = try field_names_arr.elemValue(pt, field_idx);
// Don't pass a reason; first loop acts as a check that this is valid.
const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
if (field_idx >= tag_ty_fields_len) {
return sema.fail(block, field_names_src, "no field named '{f}' in enum '{f}'", .{
field_name.fmt(ip), enum_tag_ty.fmt(pt),
});
}
const enum_field_name = enum_tag_ty.enumFieldName(field_idx, zcu);
if (enum_field_name != field_name) {
return sema.fail(block, field_names_src, "union field name '{f}' does not match enum field name '{f}'", .{
field_name.fmt(ip), enum_field_name.fmt(ip),
});
}
}
if (tag_ty_fields_len > fields_len) return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_names_src, "{d} enum fields missing in union", .{
tag_ty_fields_len - fields_len,
});
errdefer msg.destroy(gpa);
for (fields_len..tag_ty_fields_len) |enum_field_idx| {
try sema.addFieldErrNote(enum_tag_ty, enum_field_idx, msg, "field '{f}' missing, declared here", .{
enum_tag_ty.enumFieldName(enum_field_idx, zcu).fmt(ip),
});
}
try sema.addDeclaredHereNote(msg, enum_tag_ty);
break :msg msg;
});
break :tag .{ enum_tag_ty.toIntern(), true };
} else tag: {
// We must track field names and set up the tag type ourselves.
var field_names: std.AutoArrayHashMapUnmanaged(InternPool.NullTerminatedString, void) = .empty;
try field_names.ensureTotalCapacity(sema.arena, fields_len);
for (0..fields_len) |field_idx| {
const field_name_val = try field_names_arr.elemValue(pt, field_idx);
// Don't pass a reason; first loop acts as a check that this is valid.
const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
const gop = field_names.getOrPutAssumeCapacity(field_name);
if (gop.found_existing) {
// TODO: better source location
return sema.fail(block, field_names_src, "duplicate union field {f}", .{field_name.fmt(ip)});
}
}
const enum_tag_ty = try sema.generateUnionTagTypeSimple(block, field_names.keys(), wip_ty.index, type_name.name);
break :tag .{ enum_tag_ty, false };
};
errdefer if (!has_explicit_tag) ip.remove(pt.tid, enum_tag_ty); // remove generated tag type on error
for (0..fields_len) |field_idx| {
const field_ty = (try field_types_arr.elemValue(pt, field_idx)).toType();
const field_attrs = try sema.interpretBuiltinType(
block,
field_attrs_src,
try field_attrs_arr.elemValue(pt, field_idx),
std.builtin.Type.UnionField.Attributes,
);
if (field_ty.zigTypeTag(zcu) == .@"opaque") {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_types_src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{});
errdefer msg.destroy(gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
});
}
switch (layout) {
.auto => {},
.@"extern" => if (!try sema.validateExternType(field_ty, .union_field)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_types_src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(gpa);
try sema.explainWhyTypeIsNotExtern(msg, field_types_src, field_ty, .union_field);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
});
},
.@"packed" => if (!try sema.validatePackedType(field_ty)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(field_types_src, "packed unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(gpa);
try sema.explainWhyTypeIsNotPacked(msg, field_types_src, field_ty);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
});
},
}
loaded_union.field_types.get(ip)[field_idx] = field_ty.toIntern();
if (field_attrs.@"align") |bytes| {
assert(layout != .@"packed");
const a = try sema.validateAlign(block, field_attrs_src, bytes);
loaded_union.field_aligns.get(ip)[field_idx] = a;
} else if (any_aligned_fields) {
assert(layout != .@"packed");
loaded_union.field_aligns.get(ip)[field_idx] = .none;
}
}
loaded_union.setTagType(ip, enum_tag_ty);
loaded_union.setStatus(ip, .have_field_types);
const new_namespace_index = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip_ty.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
try zcu.comp.queueJob(.{ .resolve_type_fully = wip_ty.index });
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
// This job depends on any resolve_type_fully jobs queued up before it.
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
}
try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
return .fromIntern(wip_ty.finish(ip, new_namespace_index));
}
fn zirReifyEnum(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
inst: Zir.Inst.Index,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const name_strategy: Zir.Inst.NameStrategy = @enumFromInt(extended.small);
const extra = sema.code.extraData(Zir.Inst.ReifyEnum, extended.operand).data;
const tracked_inst = try block.trackZir(inst);
const src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .nodeOffset(.zero),
};
const tag_ty_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 0,
} },
};
const mode_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 1,
} },
};
const field_names_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 2,
} },
};
const field_values_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .node_offset_builtin_call_arg = .{
.builtin_call_node = .zero,
.arg_index = 3,
} },
};
const enum_mode_ty = try sema.getBuiltinType(mode_src, .@"Type.Enum.Mode");
const tag_ty = try sema.resolveType(block, tag_ty_src, extra.tag_ty);
if (tag_ty.zigTypeTag(zcu) != .int) {
return sema.fail(block, tag_ty_src, "tag type must be an integer type", .{});
}
const mode_uncoerced = try sema.resolveInst(extra.mode);
const mode_coerced = try sema.coerce(block, enum_mode_ty, mode_uncoerced, mode_src);
const mode_val = try sema.resolveConstDefinedValue(block, mode_src, mode_coerced, .{ .simple = .type });
const nonexhaustive = switch (try sema.interpretBuiltinType(block, mode_src, mode_val, std.builtin.Type.Enum.Mode)) {
.exhaustive => false,
.nonexhaustive => true,
};
const field_names_uncoerced = try sema.resolveInst(extra.field_names);
const field_names_coerced = try sema.coerce(block, .slice_const_slice_const_u8, field_names_uncoerced, field_names_src);
const field_names_slice = try sema.resolveConstDefinedValue(block, field_names_src, field_names_coerced, .{ .simple = .enum_field_names });
const field_names_arr = try sema.derefSliceAsArray(block, field_names_src, field_names_slice, .{ .simple = .enum_field_names });
const fields_len = try sema.usizeCast(block, src, field_names_arr.typeOf(zcu).arrayLen(zcu));
const field_values_ty = try pt.singleConstPtrType(try pt.arrayType(.{
.len = fields_len,
.child = tag_ty.toIntern(),
}));
const field_values_uncoerced = try sema.resolveInst(extra.field_values);
const field_values_coerced = try sema.coerce(block, field_values_ty, field_values_uncoerced, field_values_src);
const field_values_slice = try sema.resolveConstDefinedValue(block, field_values_src, field_values_coerced, .{ .simple = .enum_field_values });
const field_values_arr = try sema.derefSliceAsArray(block, field_values_src, field_values_slice, .{ .simple = .enum_field_values });
// Before we begin, check for undefs...
if (try sema.anyUndef(block, field_values_src, field_values_arr)) {
return sema.failWithUseOfUndef(block, field_values_src, null);
}
// We don't need to check `field_names_arr`, because `sliceToIpString` will check that for us.
// The validation work here is non-trivial, and it's possible the type already exists.
// So in this first pass, let's just construct a hash to optimize for this case. If the
// inputs turn out to be invalid, we can cancel the WIP type later.
// For deduplication purposes, we must create a hash including all details of this type.
// TODO: use a longer hash!
var hasher = std.hash.Wyhash.init(0);
std.hash.autoHash(&hasher, tag_ty.toIntern());
std.hash.autoHash(&hasher, nonexhaustive);
std.hash.autoHash(&hasher, fields_len);
// `field_values_arr` is already deduplicated by the InternPool!
std.hash.autoHash(&hasher, field_values_arr);
// However, for field names, we need to iterate the individual fields, because the pointers (the
// names are slices) mean that distinct values could ultimately result in the same enum type.
for (0..fields_len) |field_idx| {
const field_name_val = try field_names_arr.elemValue(pt, field_idx);
const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, .{ .simple = .enum_field_names });
std.hash.autoHash(&hasher, field_name);
}
const wip_ty = switch (try ip.getEnumType(gpa, pt.tid, .{
.has_values = true,
.tag_mode = if (nonexhaustive) .nonexhaustive else .explicit,
.fields_len = @intCast(fields_len),
.key = .{ .reified = .{
.zir_index = tracked_inst,
.type_hash = hasher.final(),
} },
}, false)) {
.wip => |wip| wip,
.existing => |ty| {
try sema.declareDependency(.{ .interned = ty });
try sema.addTypeReferenceEntry(src, ty);
return .fromIntern(ty);
},
};
var done = false;
errdefer if (!done) wip_ty.cancel(ip, pt.tid);
const type_name = try sema.createTypeName(
block,
name_strategy,
"enum",
inst,
wip_ty.index,
);
wip_ty.setName(ip, type_name.name, type_name.nav);
const new_namespace_index = try pt.createNamespace(.{
.parent = block.namespace.toOptional(),
.owner_type = wip_ty.index,
.file_scope = block.getFileScopeIndex(zcu),
.generation = zcu.generation,
});
try sema.declareDependency(.{ .interned = wip_ty.index });
try sema.addTypeReferenceEntry(src, wip_ty.index);
if (zcu.comp.debugIncremental()) try zcu.incremental_debug_state.newType(zcu, wip_ty.index);
wip_ty.prepare(ip, new_namespace_index);
wip_ty.setTagTy(ip, tag_ty.toIntern());
done = true;
for (0..fields_len) |field_idx| {
const field_name_val = try field_names_arr.elemValue(pt, field_idx);
// Don't pass a reason; first loop acts as a check that this is valid.
const field_name = try sema.sliceToIpString(block, field_names_src, field_name_val, undefined);
const field_val = try field_values_arr.elemValue(pt, field_idx);
if (wip_ty.nextField(ip, field_name, field_val.toIntern())) |conflict| {
return sema.failWithOwnedErrorMsg(block, switch (conflict.kind) {
.name => msg: {
const msg = try sema.errMsg(field_names_src, "duplicate enum field '{f}'", .{field_name.fmt(ip)});
errdefer msg.destroy(gpa);
_ = conflict.prev_field_idx; // TODO: this note is incorrect
try sema.errNote(field_names_src, msg, "other field here", .{});
break :msg msg;
},
.value => msg: {
const msg = try sema.errMsg(field_values_src, "enum tag value {f} already taken", .{field_val.fmtValueSema(pt, sema)});
errdefer msg.destroy(gpa);
_ = conflict.prev_field_idx; // TODO: this note is incorrect
try sema.errNote(field_values_src, msg, "other enum tag value here", .{});
break :msg msg;
},
});
}
}
if (nonexhaustive and fields_len > 1 and std.math.log2_int(u64, fields_len) == tag_ty.bitSize(zcu)) {
return sema.fail(block, src, "non-exhaustive enum specified every value", .{});
}
codegen_type: {
if (zcu.comp.config.use_llvm) break :codegen_type;
if (block.ownerModule().strip) break :codegen_type;
// This job depends on any resolve_type_fully jobs queued up before it.
zcu.comp.link_prog_node.increaseEstimatedTotalItems(1);
try zcu.comp.queueJob(.{ .link_type = wip_ty.index });
}
return Air.internedToRef(wip_ty.index);
}
fn resolveVaListRef(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const va_list_ty = try sema.getBuiltinType(src, .VaList);
const va_list_ptr = try pt.singleMutPtrType(va_list_ty);
const inst = try sema.resolveInst(zir_ref);
return sema.coerce(block, va_list_ptr, inst, src);
}
fn zirCVaArg(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const va_list_src = block.builtinCallArgSrc(extra.node, 0);
const ty_src = block.builtinCallArgSrc(extra.node, 1);
const va_list_ref = try sema.resolveVaListRef(block, va_list_src, extra.lhs);
const arg_ty = try sema.resolveType(block, ty_src, extra.rhs);
if (!try sema.validateExternType(arg_ty, .param_ty)) {
const msg = msg: {
const msg = try sema.errMsg(ty_src, "cannot get '{f}' from variadic argument", .{arg_ty.fmt(sema.pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, ty_src, arg_ty, .param_ty);
try sema.addDeclaredHereNote(msg, arg_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
try sema.requireRuntimeBlock(block, src, null);
return block.addTyOp(.c_va_arg, arg_ty, va_list_ref);
}
fn zirCVaCopy(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const va_list_src = block.builtinCallArgSrc(extra.node, 0);
const va_list_ref = try sema.resolveVaListRef(block, va_list_src, extra.operand);
const va_list_ty = try sema.getBuiltinType(src, .VaList);
try sema.requireRuntimeBlock(block, src, null);
return block.addTyOp(.c_va_copy, va_list_ty, va_list_ref);
}
fn zirCVaEnd(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const va_list_src = block.builtinCallArgSrc(extra.node, 0);
const va_list_ref = try sema.resolveVaListRef(block, va_list_src, extra.operand);
try sema.requireRuntimeBlock(block, src, null);
return block.addUnOp(.c_va_end, va_list_ref);
}
fn zirCVaStart(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const src_node: std.zig.Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(extended.operand)));
const src = block.nodeOffset(src_node);
const va_list_ty = try sema.getBuiltinType(src, .VaList);
try sema.requireRuntimeBlock(block, src, null);
return block.addInst(.{
.tag = .c_va_start,
.data = .{ .ty = va_list_ty },
});
}
fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const ty = try sema.resolveType(block, ty_src, inst_data.operand);
const type_name = try ip.getOrPutStringFmt(sema.gpa, pt.tid, "{f}", .{ty.fmt(pt)}, .no_embedded_nulls);
return sema.addNullTerminatedStrLit(type_name);
}
fn zirFrameType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
return sema.failWithUseOfAsync(block, src);
}
fn zirIntFromFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@intFromFloat");
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
try sema.checkVectorizableBinaryOperands(block, operand_src, dest_ty, operand_ty, src, operand_src);
const is_vector = dest_ty.zigTypeTag(zcu) == .vector;
const dest_scalar_ty = dest_ty.scalarType(zcu);
const operand_scalar_ty = operand_ty.scalarType(zcu);
_ = try sema.checkIntType(block, src, dest_scalar_ty);
try sema.checkFloatType(block, operand_src, operand_scalar_ty);
if (try sema.resolveValue(operand)) |operand_val| {
const result_val = try sema.intFromFloat(block, operand_src, operand_val, operand_ty, dest_ty, .truncate);
return Air.internedToRef(result_val.toIntern());
} else if (dest_scalar_ty.zigTypeTag(zcu) == .comptime_int) {
return sema.failWithNeededComptime(block, operand_src, .{ .simple = .casted_to_comptime_int });
}
try sema.requireRuntimeBlock(block, src, operand_src);
if (dest_scalar_ty.intInfo(zcu).bits == 0) {
if (block.wantSafety()) {
// Emit an explicit safety check. We can do this one like `abs(x) < 1`.
const abs_ref = try block.addTyOp(.abs, operand_ty, operand);
const max_abs_ref = if (is_vector) try block.addReduce(abs_ref, .Max) else abs_ref;
const one_ref = Air.internedToRef((try pt.floatValue(operand_scalar_ty, 1.0)).toIntern());
const ok_ref = try block.addBinOp(.cmp_lt, max_abs_ref, one_ref);
try sema.addSafetyCheck(block, src, ok_ref, .integer_part_out_of_bounds);
}
const scalar_val = try pt.intValue(dest_scalar_ty, 0);
return Air.internedToRef((try sema.splat(dest_ty, scalar_val)).toIntern());
}
if (block.wantSafety()) {
try sema.preparePanicId(src, .integer_part_out_of_bounds);
return block.addTyOp(switch (block.float_mode) {
.optimized => .int_from_float_optimized_safe,
.strict => .int_from_float_safe,
}, dest_ty, operand);
}
return block.addTyOp(switch (block.float_mode) {
.optimized => .int_from_float_optimized,
.strict => .int_from_float,
}, dest_ty, operand);
}
fn zirFloatFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@floatFromInt");
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
try sema.checkVectorizableBinaryOperands(block, operand_src, dest_ty, operand_ty, src, operand_src);
const dest_scalar_ty = dest_ty.scalarType(zcu);
const operand_scalar_ty = operand_ty.scalarType(zcu);
try sema.checkFloatType(block, src, dest_scalar_ty);
_ = try sema.checkIntType(block, operand_src, operand_scalar_ty);
if (try sema.resolveValue(operand)) |operand_val| {
const result_val = try operand_val.floatFromIntAdvanced(sema.arena, operand_ty, dest_ty, pt, .sema);
return Air.internedToRef(result_val.toIntern());
} else if (dest_scalar_ty.zigTypeTag(zcu) == .comptime_float) {
return sema.failWithNeededComptime(block, operand_src, .{ .simple = .casted_to_comptime_float });
}
try sema.requireRuntimeBlock(block, src, operand_src);
return block.addTyOp(.float_from_int, dest_ty, operand);
}
fn zirPtrFromInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand_res = try sema.resolveInst(extra.rhs);
const uncoerced_operand_ty = sema.typeOf(operand_res);
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu, "@ptrFromInt");
try sema.checkVectorizableBinaryOperands(block, operand_src, dest_ty, uncoerced_operand_ty, src, operand_src);
const is_vector = dest_ty.zigTypeTag(zcu) == .vector;
const operand_ty: Type = if (is_vector) operand_ty: {
const len = dest_ty.vectorLen(zcu);
break :operand_ty try pt.vectorType(.{ .child = .usize_type, .len = len });
} else .usize;
const operand_coerced = try sema.coerce(block, operand_ty, operand_res, operand_src);
const ptr_ty = dest_ty.scalarType(zcu);
try sema.checkPtrType(block, src, ptr_ty, true);
const elem_ty = ptr_ty.elemType2(zcu);
const ptr_align = try ptr_ty.ptrAlignmentSema(pt);
if (ptr_ty.isSlice(zcu)) {
const msg = msg: {
const msg = try sema.errMsg(src, "integer cannot be converted to slice type '{f}'", .{ptr_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "slice length cannot be inferred from address", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
if (try sema.resolveDefinedValue(block, operand_src, operand_coerced)) |val| {
if (!is_vector) {
const ptr_val = try sema.ptrFromIntVal(block, operand_src, val, ptr_ty, ptr_align, null);
return Air.internedToRef(ptr_val.toIntern());
}
const len = dest_ty.vectorLen(zcu);
const new_elems = try sema.arena.alloc(InternPool.Index, len);
for (new_elems, 0..) |*new_elem, elem_idx| {
const elem = try val.elemValue(pt, elem_idx);
const ptr_val = try sema.ptrFromIntVal(block, operand_src, elem, ptr_ty, ptr_align, elem_idx);
new_elem.* = ptr_val.toIntern();
}
return Air.internedToRef((try pt.aggregateValue(dest_ty, new_elems)).toIntern());
}
if (try ptr_ty.comptimeOnlySema(pt)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "pointer to comptime-only type '{f}' must be comptime-known, but operand is runtime-known", .{ptr_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(msg, src, ptr_ty);
break :msg msg;
});
}
try sema.requireRuntimeBlock(block, src, operand_src);
try sema.checkLogicalPtrOperation(block, src, ptr_ty);
if (block.wantSafety() and (try elem_ty.hasRuntimeBitsSema(pt) or elem_ty.zigTypeTag(zcu) == .@"fn")) {
if (!ptr_ty.isAllowzeroPtr(zcu)) {
const is_non_zero = if (is_vector) all_non_zero: {
const zero_usize = Air.internedToRef((try sema.splat(operand_ty, .zero_usize)).toIntern());
const is_non_zero = try block.addCmpVector(operand_coerced, zero_usize, .neq);
break :all_non_zero try block.addReduce(is_non_zero, .And);
} else try block.addBinOp(.cmp_neq, operand_coerced, .zero_usize);
try sema.addSafetyCheck(block, src, is_non_zero, .cast_to_null);
}
if (ptr_align.compare(.gt, .@"1")) {
const align_bytes_minus_1 = ptr_align.toByteUnits().? - 1;
const align_mask = Air.internedToRef((try sema.splat(operand_ty, try pt.intValue(
.usize,
if (elem_ty.fnPtrMaskOrNull(zcu)) |mask|
align_bytes_minus_1 & mask
else
align_bytes_minus_1,
))).toIntern());
const remainder = try block.addBinOp(.bit_and, operand_coerced, align_mask);
const is_aligned = if (is_vector) all_aligned: {
const splat_zero_usize = Air.internedToRef((try sema.splat(operand_ty, .zero_usize)).toIntern());
const is_aligned = try block.addCmpVector(remainder, splat_zero_usize, .eq);
break :all_aligned try block.addReduce(is_aligned, .And);
} else try block.addBinOp(.cmp_eq, remainder, .zero_usize);
try sema.addSafetyCheck(block, src, is_aligned, .incorrect_alignment);
}
}
return block.addBitCast(dest_ty, operand_coerced);
}
fn ptrFromIntVal(
sema: *Sema,
block: *Block,
operand_src: LazySrcLoc,
operand_val: Value,
ptr_ty: Type,
ptr_align: Alignment,
vec_idx: ?usize,
) !Value {
const pt = sema.pt;
const zcu = pt.zcu;
if (operand_val.isUndef(zcu)) {
if (ptr_ty.isAllowzeroPtr(zcu) and ptr_align == .@"1") {
return pt.undefValue(ptr_ty);
}
return sema.failWithUseOfUndef(block, operand_src, vec_idx);
}
const addr = try operand_val.toUnsignedIntSema(pt);
if (!ptr_ty.isAllowzeroPtr(zcu) and addr == 0)
return sema.fail(block, operand_src, "pointer type '{f}' does not allow address zero", .{ptr_ty.fmt(pt)});
if (addr != 0 and ptr_align != .none) {
const masked_addr = if (ptr_ty.childType(zcu).fnPtrMaskOrNull(zcu)) |mask|
addr & mask
else
addr;
if (!ptr_align.check(masked_addr)) {
return sema.fail(block, operand_src, "pointer type '{f}' requires aligned address", .{ptr_ty.fmt(pt)});
}
}
return switch (ptr_ty.zigTypeTag(zcu)) {
.optional => Value.fromInterned(try pt.intern(.{ .opt = .{
.ty = ptr_ty.toIntern(),
.val = if (addr == 0) .none else (try pt.ptrIntValue(ptr_ty.childType(zcu), addr)).toIntern(),
} })),
.pointer => try pt.ptrIntValue(ptr_ty, addr),
else => unreachable,
};
}
fn zirErrorCast(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const operand_src = block.builtinCallArgSrc(extra.node, 0);
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_opt, "@errorCast");
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
const dest_tag = dest_ty.zigTypeTag(zcu);
const operand_tag = operand_ty.zigTypeTag(zcu);
if (dest_tag != .error_set and dest_tag != .error_union) {
return sema.fail(block, src, "expected error set or error union type, found '{s}'", .{@tagName(dest_tag)});
}
if (operand_tag != .error_set and operand_tag != .error_union) {
return sema.fail(block, src, "expected error set or error union type, found '{s}'", .{@tagName(operand_tag)});
}
if (dest_tag == .error_set and operand_tag == .error_union) {
return sema.fail(block, src, "cannot cast an error union type to error set", .{});
}
if (dest_tag == .error_union and operand_tag == .error_union and
dest_ty.errorUnionPayload(zcu).toIntern() != operand_ty.errorUnionPayload(zcu).toIntern())
{
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "payload types of error unions must match", .{});
errdefer msg.destroy(sema.gpa);
const dest_payload_ty = dest_ty.errorUnionPayload(zcu);
const operand_payload_ty = operand_ty.errorUnionPayload(zcu);
try sema.errNote(src, msg, "destination payload is '{f}'", .{dest_payload_ty.fmt(pt)});
try sema.errNote(src, msg, "operand payload is '{f}'", .{operand_payload_ty.fmt(pt)});
try addDeclaredHereNote(sema, msg, dest_ty);
try addDeclaredHereNote(sema, msg, operand_ty);
break :msg msg;
});
}
const dest_err_ty = switch (dest_tag) {
.error_union => dest_ty.errorUnionSet(zcu),
.error_set => dest_ty,
else => unreachable,
};
const operand_err_ty = switch (operand_tag) {
.error_union => operand_ty.errorUnionSet(zcu),
.error_set => operand_ty,
else => unreachable,
};
const disjoint = disjoint: {
// Try avoiding resolving inferred error sets if we can
if (!dest_err_ty.isAnyError(zcu) and dest_err_ty.errorSetIsEmpty(zcu)) break :disjoint true;
if (!operand_err_ty.isAnyError(zcu) and operand_err_ty.errorSetIsEmpty(zcu)) break :disjoint true;
if (dest_err_ty.isAnyError(zcu)) break :disjoint false;
if (operand_err_ty.isAnyError(zcu)) break :disjoint false;
const dest_err_names = dest_err_ty.errorSetNames(zcu);
for (0..dest_err_names.len) |dest_err_index| {
if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
break :disjoint false;
}
if (!ip.isInferredErrorSetType(dest_err_ty.toIntern()) and
!ip.isInferredErrorSetType(operand_err_ty.toIntern()))
{
break :disjoint true;
}
_ = try sema.resolveInferredErrorSetTy(block, src, dest_err_ty.toIntern());
_ = try sema.resolveInferredErrorSetTy(block, operand_src, operand_err_ty.toIntern());
for (0..dest_err_names.len) |dest_err_index| {
if (Type.errorSetHasFieldIp(ip, operand_err_ty.toIntern(), dest_err_names.get(ip)[dest_err_index]))
break :disjoint false;
}
break :disjoint true;
};
if (disjoint and !(operand_tag == .error_union and dest_tag == .error_union)) {
return sema.fail(block, src, "error sets '{f}' and '{f}' have no common errors", .{
operand_err_ty.fmt(pt), dest_err_ty.fmt(pt),
});
}
// operand must be defined since it can be an invalid error value
if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| {
const err_name: InternPool.NullTerminatedString = switch (operand_tag) {
.error_set => ip.indexToKey(operand_val.toIntern()).err.name,
.error_union => switch (ip.indexToKey(operand_val.toIntern()).error_union.val) {
.err_name => |name| name,
.payload => |payload_val| {
assert(dest_tag == .error_union); // should be guaranteed from the type checks above
return sema.coerce(block, dest_ty, Air.internedToRef(payload_val), operand_src);
},
},
else => unreachable,
};
if (!dest_err_ty.isAnyError(zcu) and !Type.errorSetHasFieldIp(ip, dest_err_ty.toIntern(), err_name)) {
return sema.fail(block, src, "'error.{f}' not a member of error set '{f}'", .{
err_name.fmt(ip), dest_err_ty.fmt(pt),
});
}
return Air.internedToRef(try pt.intern(switch (dest_tag) {
.error_set => .{ .err = .{
.ty = dest_ty.toIntern(),
.name = err_name,
} },
.error_union => .{ .error_union = .{
.ty = dest_ty.toIntern(),
.val = .{ .err_name = err_name },
} },
else => unreachable,
}));
}
const err_int_ty = try pt.errorIntType();
if (block.wantSafety() and !dest_err_ty.isAnyError(zcu) and
dest_err_ty.toIntern() != .adhoc_inferred_error_set_type and
zcu.backendSupportsFeature(.error_set_has_value))
{
const err_code_inst = switch (operand_tag) {
.error_set => operand,
.error_union => try block.addTyOp(.unwrap_errunion_err, operand_err_ty, operand),
else => unreachable,
};
const err_int_inst = try block.addBitCast(err_int_ty, err_code_inst);
if (dest_tag == .error_union) {
const zero_err = try pt.intRef(err_int_ty, 0);
const is_zero = try block.addBinOp(.cmp_eq, err_int_inst, zero_err);
if (disjoint) {
// Error must be zero.
try sema.addSafetyCheck(block, src, is_zero, .invalid_error_code);
} else {
// Error must be in destination set or zero.
const has_value = try block.addTyOp(.error_set_has_value, dest_err_ty, err_int_inst);
const ok = try block.addBinOp(.bool_or, has_value, is_zero);
try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
}
} else {
const ok = try block.addTyOp(.error_set_has_value, dest_err_ty, err_int_inst);
try sema.addSafetyCheck(block, src, ok, .invalid_error_code);
}
}
if (operand_tag == .error_set and dest_tag == .error_union) {
const err_val = try block.addBitCast(dest_err_ty, operand);
return block.addTyOp(.wrap_errunion_err, dest_ty, err_val);
} else {
return block.addBitCast(dest_ty, operand);
}
}
fn zirPtrCastFull(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const FlagsInt = @typeInfo(Zir.Inst.FullPtrCastFlags).@"struct".backing_integer.?;
const flags: Zir.Inst.FullPtrCastFlags = @bitCast(@as(FlagsInt, @truncate(extended.small)));
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const operand_src = block.src(.{ .node_offset_ptrcast_operand = extra.node });
const operand = try sema.resolveInst(extra.rhs);
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu, flags.needResultTypeBuiltinName());
return sema.ptrCastFull(
block,
flags,
src,
operand,
operand_src,
dest_ty,
flags.needResultTypeBuiltinName(),
);
}
fn zirPtrCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu, "@ptrCast");
const operand = try sema.resolveInst(extra.rhs);
return sema.ptrCastFull(
block,
.{ .ptr_cast = true },
src,
operand,
operand_src,
dest_ty,
"@ptrCast",
);
}
fn ptrCastFull(
sema: *Sema,
block: *Block,
flags: Zir.Inst.FullPtrCastFlags,
src: LazySrcLoc,
operand: Air.Inst.Ref,
operand_src: LazySrcLoc,
dest_ty: Type,
operation: []const u8,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
try sema.checkPtrType(block, src, dest_ty, true);
try sema.checkPtrOperand(block, operand_src, operand_ty);
const src_info = operand_ty.ptrInfo(zcu);
const dest_info = dest_ty.ptrInfo(zcu);
try Type.fromInterned(src_info.child).resolveLayout(pt);
try Type.fromInterned(dest_info.child).resolveLayout(pt);
const DestSliceLen = union(enum) {
undef,
constant: u64,
equal_runtime_src_slice,
change_runtime_src_slice: struct {
bytes_per_src: u64,
bytes_per_dest: u64,
},
};
// Populated iff the destination type is a slice.
const dest_slice_len: ?DestSliceLen = len: {
switch (dest_info.flags.size) {
.slice => {},
.many, .c, .one => break :len null,
}
// A `null` length means the operand is a runtime-known slice (so the length is runtime-known).
// `src_elem_type` is different from `src_info.child` if the latter is an array, to ensure we ignore sentinels.
const src_elem_ty: Type, const opt_src_len: ?u64 = switch (src_info.flags.size) {
.one => src: {
const true_child: Type = .fromInterned(src_info.child);
break :src switch (true_child.zigTypeTag(zcu)) {
.array => .{ true_child.childType(zcu), true_child.arrayLen(zcu) },
else => .{ true_child, 1 },
};
},
.slice => src: {
const operand_val = try sema.resolveValue(operand) orelse break :src .{ .fromInterned(src_info.child), null };
if (operand_val.isUndef(zcu)) break :len .undef;
const slice_val = switch (operand_ty.zigTypeTag(zcu)) {
.optional => operand_val.optionalValue(zcu) orelse break :len .undef,
.pointer => operand_val,
else => unreachable,
};
const slice_len_resolved = try sema.resolveLazyValue(.fromInterned(zcu.intern_pool.sliceLen(slice_val.toIntern())));
if (slice_len_resolved.isUndef(zcu)) break :len .undef;
break :src .{ .fromInterned(src_info.child), slice_len_resolved.toUnsignedInt(zcu) };
},
.many, .c => {
return sema.fail(block, src, "cannot infer length of slice from {s}", .{pointerSizeString(src_info.flags.size)});
},
};
const dest_elem_ty: Type = .fromInterned(dest_info.child);
if (dest_elem_ty.toIntern() == src_elem_ty.toIntern()) {
break :len if (opt_src_len) |l| .{ .constant = l } else .equal_runtime_src_slice;
}
if (!src_elem_ty.comptimeOnly(zcu) and !dest_elem_ty.comptimeOnly(zcu)) {
const src_elem_size = src_elem_ty.abiSize(zcu);
const dest_elem_size = dest_elem_ty.abiSize(zcu);
if (dest_elem_size == 0) {
return sema.fail(block, src, "cannot infer length of slice of zero-bit '{f}' from '{f}'", .{
dest_elem_ty.fmt(pt), operand_ty.fmt(pt),
});
}
if (opt_src_len) |src_len| {
const bytes = src_len * src_elem_size;
const dest_len = std.math.divExact(u64, bytes, dest_elem_size) catch switch (src_info.flags.size) {
.slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
.one => return sema.fail(block, src, "type '{f}' does not divide exactly into destination elements", .{Type.fromInterned(src_info.child).fmt(pt)}),
else => unreachable,
};
break :len .{ .constant = dest_len };
}
assert(src_info.flags.size == .slice);
break :len .{ .change_runtime_src_slice = .{
.bytes_per_src = src_elem_size,
.bytes_per_dest = dest_elem_size,
} };
}
// We apply rules for comptime memory consistent with comptime loads/stores, where arrays of
// comptime-only types can be "restructured".
const dest_base_ty: Type, const dest_base_per_elem: u64 = dest_elem_ty.arrayBase(zcu);
const src_base_ty: Type, const src_base_per_elem: u64 = src_elem_ty.arrayBase(zcu);
// The source value has `src_len * src_base_per_elem` values of type `src_base_ty`.
// The result value will have `dest_len * dest_base_per_elem` values of type `dest_base_ty`.
if (dest_base_ty.toIntern() != src_base_ty.toIntern()) {
return sema.fail(block, src, "cannot infer length of comptime-only '{f}' from incompatible '{f}'", .{
dest_ty.fmt(pt), operand_ty.fmt(pt),
});
}
// `src_base_ty` is comptime-only, so `src_elem_ty` is comptime-only, so `operand_ty` is
// comptime-only, so `operand` is comptime-known, so `opt_src_len` is non-`null`.
const src_len = opt_src_len.?;
const base_len = src_len * src_base_per_elem;
const dest_len = std.math.divExact(u64, base_len, dest_base_per_elem) catch switch (src_info.flags.size) {
.slice => return sema.fail(block, src, "slice length '{d}' does not divide exactly into destination elements", .{src_len}),
.one => return sema.fail(block, src, "type '{f}' does not divide exactly into destination elements", .{src_elem_ty.fmt(pt)}),
else => unreachable,
};
break :len .{ .constant = dest_len };
};
// The checking logic in this function must stay in sync with Sema.coerceInMemoryAllowedPtrs
if (!flags.ptr_cast) {
const is_array_ptr_to_slice = b: {
if (dest_info.flags.size != .slice) break :b false;
if (src_info.flags.size != .one) break :b false;
const src_pointer_child: Type = .fromInterned(src_info.child);
if (src_pointer_child.zigTypeTag(zcu) != .array) break :b false;
const src_elem = src_pointer_child.childType(zcu);
break :b src_elem.toIntern() == dest_info.child;
};
check_size: {
if (src_info.flags.size == dest_info.flags.size) break :check_size;
if (is_array_ptr_to_slice) break :check_size;
if (src_info.flags.size == .c) break :check_size;
if (dest_info.flags.size == .c) break :check_size;
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "cannot implicitly convert {s} to {s}", .{
pointerSizeString(src_info.flags.size),
pointerSizeString(dest_info.flags.size),
});
errdefer msg.destroy(sema.gpa);
if (dest_info.flags.size == .many and
(src_info.flags.size == .slice or
(src_info.flags.size == .one and Type.fromInterned(src_info.child).zigTypeTag(zcu) == .array)))
{
try sema.errNote(src, msg, "use 'ptr' field to convert slice to many pointer", .{});
} else {
try sema.errNote(src, msg, "use @ptrCast to change pointer size", .{});
}
break :msg msg;
});
}
check_child: {
const src_child: Type = if (dest_info.flags.size == .slice and src_info.flags.size == .one) blk: {
// *[n]T -> []T
break :blk Type.fromInterned(src_info.child).childType(zcu);
} else .fromInterned(src_info.child);
const dest_child: Type = .fromInterned(dest_info.child);
const imc_res = try sema.coerceInMemoryAllowed(
block,
dest_child,
src_child,
!dest_info.flags.is_const,
zcu.getTarget(),
src,
operand_src,
null,
);
if (imc_res == .ok) break :check_child;
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "pointer element type '{f}' cannot coerce into element type '{f}'", .{
src_child.fmt(pt), dest_child.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try imc_res.report(sema, src, msg);
try sema.errNote(src, msg, "use @ptrCast to cast pointer element type", .{});
break :msg msg;
});
}
check_sent: {
if (dest_info.sentinel == .none) break :check_sent;
if (src_info.flags.size == .c) break :check_sent;
if (src_info.sentinel != .none) {
const coerced_sent = try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, src_info.sentinel, dest_info.child);
if (dest_info.sentinel == coerced_sent) break :check_sent;
}
if (is_array_ptr_to_slice) {
// [*]nT -> []T
const arr_ty: Type = .fromInterned(src_info.child);
if (arr_ty.sentinel(zcu)) |src_sentinel| {
const coerced_sent = try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, src_sentinel.toIntern(), dest_info.child);
if (dest_info.sentinel == coerced_sent) break :check_sent;
}
}
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = if (src_info.sentinel == .none) blk: {
break :blk try sema.errMsg(src, "destination pointer requires '{f}' sentinel", .{
Value.fromInterned(dest_info.sentinel).fmtValueSema(pt, sema),
});
} else blk: {
break :blk try sema.errMsg(src, "pointer sentinel '{f}' cannot coerce into pointer sentinel '{f}'", .{
Value.fromInterned(src_info.sentinel).fmtValueSema(pt, sema),
Value.fromInterned(dest_info.sentinel).fmtValueSema(pt, sema),
});
};
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "use @ptrCast to cast pointer sentinel", .{});
break :msg msg;
});
}
if (src_info.packed_offset.host_size != dest_info.packed_offset.host_size) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "pointer host size '{d}' cannot coerce into pointer host size '{d}'", .{
src_info.packed_offset.host_size,
dest_info.packed_offset.host_size,
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "use @ptrCast to cast pointer host size", .{});
break :msg msg;
});
}
if (src_info.packed_offset.bit_offset != dest_info.packed_offset.bit_offset) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "pointer bit offset '{d}' cannot coerce into pointer bit offset '{d}'", .{
src_info.packed_offset.bit_offset,
dest_info.packed_offset.bit_offset,
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "use @ptrCast to cast pointer bit offset", .{});
break :msg msg;
});
}
check_allowzero: {
const src_allows_zero = operand_ty.ptrAllowsZero(zcu);
const dest_allows_zero = dest_ty.ptrAllowsZero(zcu);
if (!src_allows_zero) break :check_allowzero;
if (dest_allows_zero) break :check_allowzero;
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "'{f}' could have null values which are illegal in type '{f}'", .{
operand_ty.fmt(pt),
dest_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "use @ptrCast to assert the pointer is not null", .{});
break :msg msg;
});
}
// TODO: vector index?
}
const src_align = if (src_info.flags.alignment != .none)
src_info.flags.alignment
else
Type.fromInterned(src_info.child).abiAlignment(zcu);
const dest_align = if (dest_info.flags.alignment != .none)
dest_info.flags.alignment
else
Type.fromInterned(dest_info.child).abiAlignment(zcu);
if (!flags.align_cast) {
if (dest_align.compare(.gt, src_align)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "{s} increases pointer alignment", .{operation});
errdefer msg.destroy(sema.gpa);
try sema.errNote(operand_src, msg, "'{f}' has alignment '{d}'", .{
operand_ty.fmt(pt), src_align.toByteUnits() orelse 0,
});
try sema.errNote(src, msg, "'{f}' has alignment '{d}'", .{
dest_ty.fmt(pt), dest_align.toByteUnits() orelse 0,
});
try sema.errNote(src, msg, "use @alignCast to assert pointer alignment", .{});
break :msg msg;
});
}
}
if (!flags.addrspace_cast) {
if (src_info.flags.address_space != dest_info.flags.address_space) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "{s} changes pointer address space", .{operation});
errdefer msg.destroy(sema.gpa);
try sema.errNote(operand_src, msg, "'{f}' has address space '{s}'", .{
operand_ty.fmt(pt), @tagName(src_info.flags.address_space),
});
try sema.errNote(src, msg, "'{f}' has address space '{s}'", .{
dest_ty.fmt(pt), @tagName(dest_info.flags.address_space),
});
try sema.errNote(src, msg, "use @addrSpaceCast to cast pointer address space", .{});
break :msg msg;
});
}
} else {
// Some address space casts are always disallowed
if (!target_util.addrSpaceCastIsValid(zcu.getTarget(), src_info.flags.address_space, dest_info.flags.address_space)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "invalid address space cast", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(operand_src, msg, "address space '{s}' is not compatible with address space '{s}'", .{
@tagName(src_info.flags.address_space),
@tagName(dest_info.flags.address_space),
});
break :msg msg;
});
}
}
if (!flags.const_cast) {
if (src_info.flags.is_const and !dest_info.flags.is_const) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "{s} discards const qualifier", .{operation});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "use @constCast to discard const qualifier", .{});
break :msg msg;
});
}
}
if (!flags.volatile_cast) {
if (src_info.flags.is_volatile and !dest_info.flags.is_volatile) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "{s} discards volatile qualifier", .{operation});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "use @volatileCast to discard volatile qualifier", .{});
break :msg msg;
});
}
}
// Type validation done -- this cast is okay. Let's do it!
//
// `operand` is a maybe-optional pointer or slice.
// `dest_ty` is a maybe-optional pointer or slice.
//
// We have a few safety checks:
// * if the destination does not allow zero, check the operand is not null / 0
// * if the destination is more aligned than the operand, check the pointer alignment
// * if `slice_needs_len_change`, check the element count divides neatly
ct: {
if (flags.addrspace_cast) break :ct; // cannot `@addrSpaceCast` at comptime
const operand_val = try sema.resolveValue(operand) orelse break :ct;
if (operand_val.isUndef(zcu)) {
if (!dest_ty.ptrAllowsZero(zcu)) {
return sema.failWithUseOfUndef(block, operand_src, null);
}
return pt.undefRef(dest_ty);
}
if (operand_val.isNull(zcu)) {
if (!dest_ty.ptrAllowsZero(zcu)) {
return sema.fail(block, operand_src, "null pointer casted to type '{f}'", .{dest_ty.fmt(pt)});
}
if (dest_ty.zigTypeTag(zcu) == .optional) {
return Air.internedToRef((try pt.nullValue(dest_ty)).toIntern());
} else {
return Air.internedToRef((try pt.ptrIntValue(dest_ty, 0)).toIntern());
}
}
const ptr_val: Value = switch (src_info.flags.size) {
.slice => .fromInterned(zcu.intern_pool.indexToKey(operand_val.toIntern()).slice.ptr),
.one, .many, .c => operand_val,
};
if (dest_align.compare(.gt, src_align)) {
if (try ptr_val.getUnsignedIntSema(pt)) |addr| {
const masked_addr = if (Type.fromInterned(dest_info.child).fnPtrMaskOrNull(zcu)) |mask|
addr & mask
else
addr;
if (!dest_align.check(masked_addr)) {
return sema.fail(block, operand_src, "pointer address 0x{X} is not aligned to {d} bytes", .{
addr,
dest_align.toByteUnits().?,
});
}
}
}
if (dest_info.flags.size == .slice) {
// Because the operand is comptime-known and not `null`, the slice length has already been computed:
const len: Value = switch (dest_slice_len.?) {
.undef => .undef_usize,
.constant => |n| try pt.intValue(.usize, n),
.equal_runtime_src_slice => unreachable,
.change_runtime_src_slice => unreachable,
};
return Air.internedToRef(try pt.intern(.{ .slice = .{
.ty = dest_ty.toIntern(),
.ptr = (try pt.getCoerced(ptr_val, dest_ty.slicePtrFieldType(zcu))).toIntern(),
.len = len.toIntern(),
} }));
} else {
// Any to non-slice
const new_ptr_val = try pt.getCoerced(ptr_val, dest_ty);
return Air.internedToRef(new_ptr_val.toIntern());
}
}
try sema.validateRuntimeValue(block, operand_src, operand);
const can_cast_to_int = !target_util.shouldBlockPointerOps(zcu.getTarget(), operand_ty.ptrAddressSpace(zcu));
const need_null_check = can_cast_to_int and block.wantSafety() and operand_ty.ptrAllowsZero(zcu) and !dest_ty.ptrAllowsZero(zcu);
const need_align_check = can_cast_to_int and block.wantSafety() and dest_align.compare(.gt, src_align);
const slice_needs_len_change = if (dest_slice_len) |l| switch (l) {
.undef, .equal_runtime_src_slice => false,
.constant, .change_runtime_src_slice => true,
} else false;
// `operand` might be a slice. If `need_operand_ptr`, we'll populate `operand_ptr` with the raw pointer.
const need_operand_ptr = src_info.flags.size != .slice or // we already have it
dest_info.flags.size != .slice or // the result is a raw pointer
need_null_check or // safety check happens on pointer
need_align_check or // safety check happens on pointer
flags.addrspace_cast or // AIR addrspace_cast acts on a pointer
slice_needs_len_change; // to change the length, we reconstruct the slice
// This is not quite just the pointer part of `operand` -- it's also had the address space cast done already.
const operand_ptr: Air.Inst.Ref = ptr: {
if (!need_operand_ptr) break :ptr .none;
// First, just get the pointer.
const pre_addrspace_cast = inner: {
if (src_info.flags.size != .slice) break :inner operand;
if (operand_ty.zigTypeTag(zcu) == .optional) {
break :inner try sema.analyzeOptionalSlicePtr(block, operand_src, operand, operand_ty);
} else {
break :inner try sema.analyzeSlicePtr(block, operand_src, operand, operand_ty);
}
};
// Now, do an addrspace cast if necessary!
if (!flags.addrspace_cast) break :ptr pre_addrspace_cast;
const intermediate_ptr_ty = try pt.ptrTypeSema(info: {
var info = src_info;
info.flags.address_space = dest_info.flags.address_space;
break :info info;
});
const intermediate_ty = if (operand_ty.zigTypeTag(zcu) == .optional) blk: {
break :blk try pt.optionalType(intermediate_ptr_ty.toIntern());
} else intermediate_ptr_ty;
break :ptr try block.addInst(.{
.tag = .addrspace_cast,
.data = .{ .ty_op = .{
.ty = Air.internedToRef(intermediate_ty.toIntern()),
.operand = pre_addrspace_cast,
} },
});
};
// Whether we need to know if the (slice) operand has `len == 0`.
const need_operand_len_is_zero = src_info.flags.size == .slice and
dest_info.flags.size == .slice and
(need_null_check or need_align_check);
// Whether we need to get the (slice) operand's `len`.
const need_operand_len = need_len: {
if (src_info.flags.size != .slice) break :need_len false;
if (dest_info.flags.size != .slice) break :need_len false;
if (need_operand_len_is_zero) break :need_len true;
if (flags.addrspace_cast or slice_needs_len_change) break :need_len true;
break :need_len false;
};
// `.none` if `!need_operand_len`.
const operand_len: Air.Inst.Ref = len: {
if (!need_operand_len) break :len .none;
break :len try block.addTyOp(.slice_len, .usize, operand);
};
// `.none` if `!need_operand_len_is_zero`.
const operand_len_is_zero: Air.Inst.Ref = zero: {
if (!need_operand_len_is_zero) break :zero .none;
assert(need_operand_len);
break :zero try block.addBinOp(.cmp_eq, operand_len, .zero_usize);
};
// `operand_ptr` converted to an integer, for safety checks.
const operand_ptr_int: Air.Inst.Ref = if (need_null_check or need_align_check) i: {
assert(need_operand_ptr);
break :i try block.addBitCast(.usize, operand_ptr);
} else .none;
if (need_null_check) {
assert(operand_ptr_int != .none);
const ptr_is_non_zero = try block.addBinOp(.cmp_neq, operand_ptr_int, .zero_usize);
const ok = if (src_info.flags.size == .slice and dest_info.flags.size == .slice) ok: {
break :ok try block.addBinOp(.bool_or, operand_len_is_zero, ptr_is_non_zero);
} else ptr_is_non_zero;
try sema.addSafetyCheck(block, src, ok, .cast_to_null);
}
if (need_align_check) {
assert(operand_ptr_int != .none);
const align_mask = try pt.intRef(.usize, mask: {
const target_ptr_mask = Type.fromInterned(dest_info.child).fnPtrMaskOrNull(zcu) orelse ~@as(u64, 0);
break :mask (dest_align.toByteUnits().? - 1) & target_ptr_mask;
});
const ptr_masked = try block.addBinOp(.bit_and, operand_ptr_int, align_mask);
const is_aligned = try block.addBinOp(.cmp_eq, ptr_masked, .zero_usize);
const ok = if (src_info.flags.size == .slice and dest_info.flags.size == .slice) ok: {
break :ok try block.addBinOp(.bool_or, operand_len_is_zero, is_aligned);
} else is_aligned;
try sema.addSafetyCheck(block, src, ok, .incorrect_alignment);
}
if (dest_info.flags.size == .slice) {
if (src_info.flags.size == .slice and !flags.addrspace_cast and !slice_needs_len_change) {
// Fast path: just bitcast!
return block.addBitCast(dest_ty, operand);
}
// We need to deconstruct the slice (if applicable) and reconstruct it.
assert(need_operand_ptr);
const result_len: Air.Inst.Ref = switch (dest_slice_len.?) {
.undef => .undef_usize,
.constant => |n| try pt.intRef(.usize, n),
.equal_runtime_src_slice => len: {
assert(need_operand_len);
break :len operand_len;
},
.change_runtime_src_slice => |change| len: {
assert(need_operand_len);
// If `mul / div` is a whole number, then just multiply the length by it.
if (std.math.divExact(u64, change.bytes_per_src, change.bytes_per_dest)) |dest_per_src| {
const multiplier = try pt.intRef(.usize, dest_per_src);
break :len try block.addBinOp(.mul, operand_len, multiplier);
} else |err| switch (err) {
error.DivisionByZero => unreachable,
error.UnexpectedRemainder => {}, // fall through to code below
}
// If `div / mul` is a whole number, then just divide the length by it.
// This incurs a safety check.
if (std.math.divExact(u64, change.bytes_per_dest, change.bytes_per_src)) |src_per_dest| {
const divisor = try pt.intRef(.usize, src_per_dest);
if (block.wantSafety()) {
// Check that the element count divides neatly.
const remainder = try block.addBinOp(.rem, operand_len, divisor);
const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
}
break :len try block.addBinOp(.div_exact, operand_len, divisor);
} else |err| switch (err) {
error.DivisionByZero => unreachable,
error.UnexpectedRemainder => {}, // fall through to code below
}
// Fallback: the elements don't divide easily. We'll multiply *and* divide. This incurs a safety check.
const total_bytes_ref = try block.addBinOp(.mul, operand_len, try pt.intRef(.usize, change.bytes_per_src));
const bytes_per_dest_ref = try pt.intRef(.usize, change.bytes_per_dest);
if (block.wantSafety()) {
// Check that `total_bytes_ref` divides neatly into `bytes_per_dest_ref`.
const remainder = try block.addBinOp(.rem, total_bytes_ref, bytes_per_dest_ref);
const ok = try block.addBinOp(.cmp_eq, remainder, .zero_usize);
try sema.addSafetyCheckCall(block, src, ok, .@"panic.sliceCastLenRemainder", &.{operand_len});
}
break :len try block.addBinOp(.div_exact, total_bytes_ref, bytes_per_dest_ref);
},
};
const operand_ptr_ty = sema.typeOf(operand_ptr);
const want_ptr_ty = switch (dest_ty.zigTypeTag(zcu)) {
.optional => try pt.optionalType(dest_ty.childType(zcu).slicePtrFieldType(zcu).toIntern()),
.pointer => dest_ty.slicePtrFieldType(zcu),
else => unreachable,
};
const coerced_ptr = if (operand_ptr_ty.toIntern() != want_ptr_ty.toIntern()) ptr: {
break :ptr try block.addBitCast(want_ptr_ty, operand_ptr);
} else operand_ptr;
return block.addInst(.{
.tag = .slice,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(dest_ty.toIntern()),
.payload = try sema.addExtra(Air.Bin{
.lhs = coerced_ptr,
.rhs = result_len,
}),
} },
});
} else {
assert(need_operand_ptr);
// We just need to bitcast the pointer, if necessary.
// It might not be necessary, since we might have just needed the `addrspace_cast`.
const result = if (sema.typeOf(operand_ptr).toIntern() == dest_ty.toIntern())
operand_ptr
else
try block.addBitCast(dest_ty, operand_ptr);
try sema.checkKnownAllocPtr(block, operand, result);
return result;
}
}
fn zirPtrCastNoDest(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const FlagsInt = @typeInfo(Zir.Inst.FullPtrCastFlags).@"struct".backing_integer.?;
const flags: Zir.Inst.FullPtrCastFlags = @bitCast(@as(FlagsInt, @truncate(extended.small)));
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const operand_src = block.src(.{ .node_offset_ptrcast_operand = extra.node });
const operand = try sema.resolveInst(extra.operand);
const operand_ty = sema.typeOf(operand);
try sema.checkPtrOperand(block, operand_src, operand_ty);
var ptr_info = operand_ty.ptrInfo(zcu);
if (flags.const_cast) ptr_info.flags.is_const = false;
if (flags.volatile_cast) ptr_info.flags.is_volatile = false;
const dest_ty = blk: {
const dest_ty = try pt.ptrTypeSema(ptr_info);
if (operand_ty.zigTypeTag(zcu) == .optional) {
break :blk try pt.optionalType(dest_ty.toIntern());
}
break :blk dest_ty;
};
if (try sema.resolveValue(operand)) |operand_val| {
return Air.internedToRef((try pt.getCoerced(operand_val, dest_ty)).toIntern());
}
try sema.requireRuntimeBlock(block, src, null);
const new_ptr = try block.addBitCast(dest_ty, operand);
try sema.checkKnownAllocPtr(block, operand, new_ptr);
return new_ptr;
}
fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@truncate");
const dest_scalar_ty = try sema.checkIntOrVectorAllowComptime(block, dest_ty, src);
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
const operand_scalar_ty = try sema.checkIntOrVectorAllowComptime(block, operand_ty, operand_src);
const operand_is_vector = operand_ty.zigTypeTag(zcu) == .vector;
const dest_is_vector = dest_ty.zigTypeTag(zcu) == .vector;
if (operand_is_vector != dest_is_vector) {
return sema.failWithTypeMismatch(block, operand_src, dest_ty, operand_ty);
}
if (dest_scalar_ty.zigTypeTag(zcu) == .comptime_int) {
return sema.coerce(block, dest_ty, operand, operand_src);
}
const dest_info = dest_scalar_ty.intInfo(zcu);
if (try sema.typeHasOnePossibleValue(dest_ty)) |val| {
return Air.internedToRef(val.toIntern());
}
if (operand_scalar_ty.zigTypeTag(zcu) != .comptime_int) {
const operand_info = operand_ty.intInfo(zcu);
if (try sema.typeHasOnePossibleValue(operand_ty)) |val| {
return Air.internedToRef(val.toIntern());
}
if (operand_info.signedness != dest_info.signedness) {
return sema.fail(block, operand_src, "expected {s} integer type, found '{f}'", .{
@tagName(dest_info.signedness), operand_ty.fmt(pt),
});
}
switch (std.math.order(dest_info.bits, operand_info.bits)) {
.gt => {
const msg = msg: {
const msg = try sema.errMsg(
src,
"destination type '{f}' has more bits than source type '{f}'",
.{ dest_ty.fmt(pt), operand_ty.fmt(pt) },
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "destination type has {d} bits", .{
dest_info.bits,
});
try sema.errNote(operand_src, msg, "operand type has {d} bits", .{
operand_info.bits,
});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
.eq => return operand,
.lt => {},
}
}
if (try sema.resolveValueResolveLazy(operand)) |val| {
const result_val = try arith.truncate(sema, val, operand_ty, dest_ty, dest_info.signedness, dest_info.bits);
return Air.internedToRef(result_val.toIntern());
}
try sema.requireRuntimeBlock(block, src, operand_src);
return block.addTyOp(.trunc, dest_ty, operand);
}
fn zirBitCount(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
air_tag: Air.Inst.Tag,
comptime comptimeOp: fn (val: Value, ty: Type, zcu: *Zcu) u64,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
_ = try sema.checkIntOrVector(block, operand, operand_src);
const bits = operand_ty.intInfo(zcu).bits;
if (try sema.typeHasOnePossibleValue(operand_ty)) |val| {
return Air.internedToRef(val.toIntern());
}
const result_scalar_ty = try pt.smallestUnsignedInt(bits);
switch (operand_ty.zigTypeTag(zcu)) {
.vector => {
const vec_len = operand_ty.vectorLen(zcu);
const result_ty = try pt.vectorType(.{
.len = vec_len,
.child = result_scalar_ty.toIntern(),
});
if (try sema.resolveValue(operand)) |val| {
if (val.isUndef(zcu)) return pt.undefRef(result_ty);
const elems = try sema.arena.alloc(InternPool.Index, vec_len);
const scalar_ty = operand_ty.scalarType(zcu);
for (elems, 0..) |*elem, i| {
const elem_val = try val.elemValue(pt, i);
const count = comptimeOp(elem_val, scalar_ty, zcu);
elem.* = (try pt.intValue(result_scalar_ty, count)).toIntern();
}
return Air.internedToRef((try pt.aggregateValue(result_ty, elems)).toIntern());
} else {
try sema.requireRuntimeBlock(block, src, operand_src);
return block.addTyOp(air_tag, result_ty, operand);
}
},
.int => {
if (try sema.resolveValueResolveLazy(operand)) |val| {
if (val.isUndef(zcu)) return pt.undefRef(result_scalar_ty);
return pt.intRef(result_scalar_ty, comptimeOp(val, operand_ty, zcu));
} else {
try sema.requireRuntimeBlock(block, src, operand_src);
return block.addTyOp(air_tag, result_scalar_ty, operand);
}
},
else => unreachable,
}
}
fn zirByteSwap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
const scalar_ty = try sema.checkIntOrVector(block, operand, operand_src);
const bits = scalar_ty.intInfo(zcu).bits;
if (bits % 8 != 0) {
return sema.fail(
block,
operand_src,
"@byteSwap requires the number of bits to be evenly divisible by 8, but {f} has {d} bits",
.{ scalar_ty.fmt(pt), bits },
);
}
if (try sema.typeHasOnePossibleValue(operand_ty)) |val| {
return .fromValue(val);
}
if (try sema.resolveValue(operand)) |operand_val| {
return .fromValue(try arith.byteSwap(sema, operand_val, operand_ty));
}
return block.addTyOp(.byte_swap, operand_ty, operand);
}
fn zirBitReverse(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand = try sema.resolveInst(inst_data.operand);
const operand_ty = sema.typeOf(operand);
_ = try sema.checkIntOrVector(block, operand, operand_src);
if (try sema.typeHasOnePossibleValue(operand_ty)) |val| {
return .fromValue(val);
}
if (try sema.resolveValue(operand)) |operand_val| {
return .fromValue(try arith.bitReverse(sema, operand_val, operand_ty));
}
return block.addTyOp(.bit_reverse, operand_ty, operand);
}
fn zirBitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const offset = try sema.bitOffsetOf(block, inst);
return sema.pt.intRef(.comptime_int, offset);
}
fn zirOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const offset = try sema.bitOffsetOf(block, inst);
// TODO reminder to make this a compile error for packed structs
return sema.pt.intRef(.comptime_int, offset / 8);
}
fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u64 {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const src = block.src(.{ .node_offset_bin_op = inst_data.src_node });
const ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const field_name_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const ty = try sema.resolveType(block, ty_src, extra.lhs);
const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.rhs, .{ .simple = .field_name });
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
try ty.resolveLayout(pt);
switch (ty.zigTypeTag(zcu)) {
.@"struct" => {},
else => return sema.fail(block, ty_src, "expected struct type, found '{f}'", .{ty.fmt(pt)}),
}
const field_index = if (ty.isTuple(zcu)) blk: {
if (field_name.eqlSlice("len", ip)) {
return sema.fail(block, src, "no offset available for 'len' field of tuple", .{});
}
break :blk try sema.tupleFieldIndex(block, ty, field_name, field_name_src);
} else try sema.structFieldIndex(block, ty, field_name, field_name_src);
if (ty.structFieldIsComptime(field_index, zcu)) {
return sema.fail(block, src, "no offset available for comptime field", .{});
}
switch (ty.containerLayout(zcu)) {
.@"packed" => {
var bit_sum: u64 = 0;
const struct_type = ip.loadStructType(ty.toIntern());
for (0..struct_type.field_types.len) |i| {
if (i == field_index) {
return bit_sum;
}
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
bit_sum += field_ty.bitSize(zcu);
} else unreachable;
},
else => return ty.structFieldOffset(field_index, zcu) * 8,
}
}
fn checkNamespaceType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.@"struct", .@"enum", .@"union", .@"opaque" => return,
else => return sema.fail(block, src, "expected struct, enum, union, or opaque; found '{f}'", .{ty.fmt(pt)}),
}
}
/// Returns `true` if the type was a comptime_int.
fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.comptime_int => return true,
.int => return false,
else => return sema.fail(block, src, "expected integer type, found '{f}'", .{ty.fmt(pt)}),
}
}
fn checkInvalidPtrIntArithmetic(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ty: Type,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.pointer => switch (ty.ptrSize(zcu)) {
.one, .slice => return,
.many, .c => return sema.failWithInvalidPtrArithmetic(block, src, "pointer-integer", "addition and subtraction"),
},
else => return,
}
}
fn checkArithmeticOp(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
scalar_tag: std.builtin.TypeId,
lhs_zig_ty_tag: std.builtin.TypeId,
rhs_zig_ty_tag: std.builtin.TypeId,
zir_tag: Zir.Inst.Tag,
) CompileError!void {
const is_int = scalar_tag == .int or scalar_tag == .comptime_int;
const is_float = scalar_tag == .float or scalar_tag == .comptime_float;
if (!is_int and !(is_float and floatOpAllowed(zir_tag))) {
return sema.fail(block, src, "invalid operands to binary expression: '{s}' and '{s}'", .{
@tagName(lhs_zig_ty_tag), @tagName(rhs_zig_ty_tag),
});
}
}
fn checkPtrOperand(
sema: *Sema,
block: *Block,
ty_src: LazySrcLoc,
ty: Type,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.pointer => return,
.@"fn" => {
const msg = msg: {
const msg = try sema.errMsg(
ty_src,
"expected pointer, found '{f}'",
.{ty.fmt(pt)},
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(ty_src, msg, "use '&' to obtain a function pointer", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
.optional => if (ty.childType(zcu).zigTypeTag(zcu) == .pointer) return,
else => {},
}
return sema.fail(block, ty_src, "expected pointer type, found '{f}'", .{ty.fmt(pt)});
}
fn checkPtrType(
sema: *Sema,
block: *Block,
ty_src: LazySrcLoc,
ty: Type,
allow_slice: bool,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.pointer => if (allow_slice or !ty.isSlice(zcu)) return,
.@"fn" => {
const msg = msg: {
const msg = try sema.errMsg(
ty_src,
"expected pointer type, found '{f}'",
.{ty.fmt(pt)},
);
errdefer msg.destroy(sema.gpa);
try sema.errNote(ty_src, msg, "use '*const ' to make a function pointer type", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
},
.optional => if (ty.childType(zcu).zigTypeTag(zcu) == .pointer) return,
else => {},
}
return sema.fail(block, ty_src, "expected pointer type, found '{f}'", .{ty.fmt(pt)});
}
fn checkLogicalPtrOperation(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
const pt = sema.pt;
const zcu = pt.zcu;
if (zcu.intern_pool.indexToKey(ty.toIntern()) == .ptr_type) {
const target = zcu.getTarget();
const as = ty.ptrAddressSpace(zcu);
if (target_util.shouldBlockPointerOps(target, as)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "illegal operation on logical pointer of type '{f}'", .{ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(
src,
msg,
"cannot perform arithmetic on pointers with address space '{s}' on target {s}-{s}",
.{
@tagName(as),
@tagName(target.cpu.arch.family()),
@tagName(target.os.tag),
},
);
break :msg msg;
});
}
}
}
fn checkVectorElemType(
sema: *Sema,
block: *Block,
ty_src: LazySrcLoc,
ty: Type,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.int, .float, .bool => return,
.optional, .pointer => if (ty.isPtrAtRuntime(zcu)) return,
else => {},
}
return sema.fail(block, ty_src, "expected integer, float, bool, or pointer for the vector element type; found '{f}'", .{ty.fmt(pt)});
}
fn checkFloatType(
sema: *Sema,
block: *Block,
ty_src: LazySrcLoc,
ty: Type,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.comptime_int, .comptime_float, .float => {},
else => return sema.fail(block, ty_src, "expected float type, found '{f}'", .{ty.fmt(pt)}),
}
}
fn checkNumericType(
sema: *Sema,
block: *Block,
ty_src: LazySrcLoc,
ty: Type,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.comptime_float, .float, .comptime_int, .int => {},
.vector => switch (ty.childType(zcu).zigTypeTag(zcu)) {
.comptime_float, .float, .comptime_int, .int => {},
else => |t| return sema.fail(block, ty_src, "expected number, found '{t}'", .{t}),
},
else => return sema.fail(block, ty_src, "expected number, found '{f}'", .{ty.fmt(pt)}),
}
}
/// Returns the casted pointer.
fn checkAtomicPtrOperand(
sema: *Sema,
block: *Block,
elem_ty: Type,
elem_ty_src: LazySrcLoc,
ptr: Air.Inst.Ref,
ptr_src: LazySrcLoc,
ptr_const: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
var diag: Zcu.AtomicPtrAlignmentDiagnostics = .{};
const alignment = zcu.atomicPtrAlignment(elem_ty, &diag) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.FloatTooBig => return sema.fail(
block,
elem_ty_src,
"expected {d}-bit float type or smaller; found {d}-bit float type",
.{ diag.max_bits, diag.bits },
),
error.IntTooBig => return sema.fail(
block,
elem_ty_src,
"expected {d}-bit integer type or smaller; found {d}-bit integer type",
.{ diag.max_bits, diag.bits },
),
error.BadType => return sema.fail(
block,
elem_ty_src,
"expected bool, integer, float, enum, packed struct, or pointer type; found '{f}'",
.{elem_ty.fmt(pt)},
),
};
var wanted_ptr_data: InternPool.Key.PtrType = .{
.child = elem_ty.toIntern(),
.flags = .{
.alignment = alignment,
.is_const = ptr_const,
},
};
const ptr_ty = sema.typeOf(ptr);
const ptr_data = switch (ptr_ty.zigTypeTag(zcu)) {
.pointer => ptr_ty.ptrInfo(zcu),
else => {
const wanted_ptr_ty = try pt.ptrTypeSema(wanted_ptr_data);
_ = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src);
unreachable;
},
};
wanted_ptr_data.flags.address_space = ptr_data.flags.address_space;
wanted_ptr_data.flags.is_allowzero = ptr_data.flags.is_allowzero;
wanted_ptr_data.flags.is_volatile = ptr_data.flags.is_volatile;
const wanted_ptr_ty = try pt.ptrTypeSema(wanted_ptr_data);
const casted_ptr = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src);
return casted_ptr;
}
fn checkPtrIsNotComptimeMutable(
sema: *Sema,
block: *Block,
ptr_val: Value,
ptr_src: LazySrcLoc,
operand_src: LazySrcLoc,
) CompileError!void {
_ = operand_src;
if (sema.isComptimeMutablePtr(ptr_val)) {
return sema.fail(block, ptr_src, "cannot store runtime value in compile time variable", .{});
}
}
fn checkIntOrVector(
sema: *Sema,
block: *Block,
operand: Air.Inst.Ref,
operand_src: LazySrcLoc,
) CompileError!Type {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
switch (operand_ty.zigTypeTag(zcu)) {
.int => return operand_ty,
.vector => {
const elem_ty = operand_ty.childType(zcu);
switch (elem_ty.zigTypeTag(zcu)) {
.int => return elem_ty,
else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{f}'", .{
elem_ty.fmt(pt),
}),
}
},
else => return sema.fail(block, operand_src, "expected integer or vector, found '{f}'", .{
operand_ty.fmt(pt),
}),
}
}
fn checkIntOrVectorAllowComptime(
sema: *Sema,
block: *Block,
operand_ty: Type,
operand_src: LazySrcLoc,
) CompileError!Type {
const pt = sema.pt;
const zcu = pt.zcu;
switch (operand_ty.zigTypeTag(zcu)) {
.int, .comptime_int => return operand_ty,
.vector => {
const elem_ty = operand_ty.childType(zcu);
switch (elem_ty.zigTypeTag(zcu)) {
.int, .comptime_int => return elem_ty,
else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{f}'", .{
elem_ty.fmt(pt),
}),
}
},
else => return sema.fail(block, operand_src, "expected integer or vector, found '{f}'", .{
operand_ty.fmt(pt),
}),
}
}
const SimdBinOp = struct {
len: ?usize,
/// Coerced to `result_ty`.
lhs: Air.Inst.Ref,
/// Coerced to `result_ty`.
rhs: Air.Inst.Ref,
lhs_val: ?Value,
rhs_val: ?Value,
/// Only different than `scalar_ty` when it is a vector operation.
result_ty: Type,
scalar_ty: Type,
};
fn checkSimdBinOp(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
uncasted_lhs: Air.Inst.Ref,
uncasted_rhs: Air.Inst.Ref,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
) CompileError!SimdBinOp {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs_ty = sema.typeOf(uncasted_lhs);
const rhs_ty = sema.typeOf(uncasted_rhs);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
const vec_len: ?usize = if (lhs_ty.zigTypeTag(zcu) == .vector) lhs_ty.vectorLen(zcu) else null;
const result_ty = try sema.resolvePeerTypes(block, src, &.{ uncasted_lhs, uncasted_rhs }, .{
.override = &[_]?LazySrcLoc{ lhs_src, rhs_src },
});
const lhs = try sema.coerce(block, result_ty, uncasted_lhs, lhs_src);
const rhs = try sema.coerce(block, result_ty, uncasted_rhs, rhs_src);
return SimdBinOp{
.len = vec_len,
.lhs = lhs,
.rhs = rhs,
.lhs_val = try sema.resolveValue(lhs),
.rhs_val = try sema.resolveValue(rhs),
.result_ty = result_ty,
.scalar_ty = result_ty.scalarType(zcu),
};
}
fn checkVectorizableBinaryOperands(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
lhs_ty: Type,
rhs_ty: Type,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs_zig_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_zig_ty_tag = rhs_ty.zigTypeTag(zcu);
if (lhs_zig_ty_tag != .vector and rhs_zig_ty_tag != .vector) return;
const lhs_is_vector = switch (lhs_zig_ty_tag) {
.vector, .array => true,
else => false,
};
const rhs_is_vector = switch (rhs_zig_ty_tag) {
.vector, .array => true,
else => false,
};
if (lhs_is_vector and rhs_is_vector) {
const lhs_len = lhs_ty.arrayLen(zcu);
const rhs_len = rhs_ty.arrayLen(zcu);
if (lhs_len != rhs_len) {
const msg = msg: {
const msg = try sema.errMsg(src, "vector length mismatch", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(lhs_src, msg, "length {d} here", .{lhs_len});
try sema.errNote(rhs_src, msg, "length {d} here", .{rhs_len});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
} else {
const msg = msg: {
const msg = try sema.errMsg(src, "mixed scalar and vector operands: '{f}' and '{f}'", .{
lhs_ty.fmt(pt), rhs_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
if (lhs_is_vector) {
try sema.errNote(lhs_src, msg, "vector here", .{});
try sema.errNote(rhs_src, msg, "scalar here", .{});
} else {
try sema.errNote(lhs_src, msg, "scalar here", .{});
try sema.errNote(rhs_src, msg, "vector here", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
fn checkAllScalarsDefined(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) CompileError!void {
const zcu = sema.pt.zcu;
switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.int, .float => {},
.undef => return sema.failWithUseOfUndef(block, src, null),
.aggregate => |agg| {
assert(Type.fromInterned(agg.ty).zigTypeTag(zcu) == .vector);
for (agg.storage.values(), 0..) |elem_val, elem_idx| {
if (Value.fromInterned(elem_val).isUndef(zcu))
return sema.failWithUseOfUndef(block, src, elem_idx);
}
},
else => unreachable,
}
}
fn resolveExportOptions(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
) CompileError!Zcu.Export.Options {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const export_options_ty = try sema.getBuiltinType(src, .ExportOptions);
const air_ref = try sema.resolveInst(zir_ref);
const options = try sema.coerce(block, export_options_ty, air_ref, src);
const name_src = block.src(.{ .init_field_name = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const linkage_src = block.src(.{ .init_field_linkage = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const section_src = block.src(.{ .init_field_section = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const visibility_src = block.src(.{ .init_field_visibility = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const name_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "name", .no_embedded_nulls), name_src);
const name = try sema.toConstString(block, name_src, name_operand, .{ .simple = .export_options });
const linkage_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "linkage", .no_embedded_nulls), linkage_src);
const linkage_val = try sema.resolveConstDefinedValue(block, linkage_src, linkage_operand, .{ .simple = .export_options });
const linkage = try sema.interpretBuiltinType(block, linkage_src, linkage_val, std.builtin.GlobalLinkage);
const section_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "section", .no_embedded_nulls), section_src);
const section_opt_val = try sema.resolveConstDefinedValue(block, section_src, section_operand, .{ .simple = .export_options });
const section = if (section_opt_val.optionalValue(zcu)) |section_val|
try sema.toConstString(block, section_src, Air.internedToRef(section_val.toIntern()), .{ .simple = .export_options })
else
null;
const visibility_operand = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "visibility", .no_embedded_nulls), visibility_src);
const visibility_val = try sema.resolveConstDefinedValue(block, visibility_src, visibility_operand, .{ .simple = .export_options });
const visibility = try sema.interpretBuiltinType(block, visibility_src, visibility_val, std.builtin.SymbolVisibility);
if (name.len < 1) {
return sema.fail(block, name_src, "exported symbol name cannot be empty", .{});
}
if (visibility != .default and linkage == .internal) {
return sema.fail(block, visibility_src, "symbol '{s}' exported with internal linkage has non-default visibility {s}", .{
name, @tagName(visibility),
});
}
return .{
.name = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls),
.linkage = linkage,
.section = try ip.getOrPutStringOpt(gpa, pt.tid, section, .no_embedded_nulls),
.visibility = visibility,
};
}
fn resolveBuiltinEnum(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
comptime name: Zcu.BuiltinDecl,
reason: ComptimeReason,
) CompileError!@field(std.builtin, @tagName(name)) {
const ty = try sema.getBuiltinType(src, name);
const air_ref = try sema.resolveInst(zir_ref);
const coerced = try sema.coerce(block, ty, air_ref, src);
const val = try sema.resolveConstDefinedValue(block, src, coerced, reason);
return sema.interpretBuiltinType(block, src, val, @field(std.builtin, @tagName(name)));
}
fn resolveAtomicOrder(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
reason: ComptimeReason,
) CompileError!std.builtin.AtomicOrder {
return sema.resolveBuiltinEnum(block, src, zir_ref, .AtomicOrder, reason);
}
fn resolveAtomicRmwOp(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
) CompileError!std.builtin.AtomicRmwOp {
return sema.resolveBuiltinEnum(block, src, zir_ref, .AtomicRmwOp, .{ .simple = .operand_atomicRmw_operation });
}
fn zirCmpxchg(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.Cmpxchg, extended.operand).data;
const air_tag: Air.Inst.Tag = switch (extended.small) {
0 => .cmpxchg_weak,
1 => .cmpxchg_strong,
else => unreachable,
};
const src = block.nodeOffset(extra.node);
// zig fmt: off
const elem_ty_src = block.builtinCallArgSrc(extra.node, 0);
const ptr_src = block.builtinCallArgSrc(extra.node, 1);
const expected_src = block.builtinCallArgSrc(extra.node, 2);
const new_value_src = block.builtinCallArgSrc(extra.node, 3);
const success_order_src = block.builtinCallArgSrc(extra.node, 4);
const failure_order_src = block.builtinCallArgSrc(extra.node, 5);
// zig fmt: on
const expected_value = try sema.resolveInst(extra.expected_value);
const elem_ty = sema.typeOf(expected_value);
if (elem_ty.zigTypeTag(zcu) == .float) {
return sema.fail(
block,
elem_ty_src,
"expected bool, integer, enum, packed struct, or pointer type; found '{f}'",
.{elem_ty.fmt(pt)},
);
}
const uncasted_ptr = try sema.resolveInst(extra.ptr);
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, false);
const new_value = try sema.coerce(block, elem_ty, try sema.resolveInst(extra.new_value), new_value_src);
const success_order = try sema.resolveAtomicOrder(block, success_order_src, extra.success_order, .{ .simple = .atomic_order });
const failure_order = try sema.resolveAtomicOrder(block, failure_order_src, extra.failure_order, .{ .simple = .atomic_order });
if (@intFromEnum(success_order) < @intFromEnum(std.builtin.AtomicOrder.monotonic)) {
return sema.fail(block, success_order_src, "success atomic ordering must be monotonic or stricter", .{});
}
if (@intFromEnum(failure_order) < @intFromEnum(std.builtin.AtomicOrder.monotonic)) {
return sema.fail(block, failure_order_src, "failure atomic ordering must be monotonic or stricter", .{});
}
if (@intFromEnum(failure_order) > @intFromEnum(success_order)) {
return sema.fail(block, failure_order_src, "failure atomic ordering must be no stricter than success", .{});
}
if (failure_order == .release or failure_order == .acq_rel) {
return sema.fail(block, failure_order_src, "failure atomic ordering must not be release or acq_rel", .{});
}
const result_ty = try pt.optionalType(elem_ty.toIntern());
// special case zero bit types
if ((try sema.typeHasOnePossibleValue(elem_ty)) != null) {
return Air.internedToRef((try pt.intern(.{ .opt = .{
.ty = result_ty.toIntern(),
.val = .none,
} })));
}
const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: {
if (try sema.resolveValue(expected_value)) |expected_val| {
if (try sema.resolveValue(new_value)) |new_val| {
if (expected_val.isUndef(zcu) or new_val.isUndef(zcu)) {
// TODO: this should probably cause the memory stored at the pointer
// to become undef as well
return pt.undefRef(result_ty);
}
const ptr_ty = sema.typeOf(ptr);
const stored_val = (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) orelse break :rs ptr_src;
const result_val = try pt.intern(.{ .opt = .{
.ty = result_ty.toIntern(),
.val = if (stored_val.eql(expected_val, elem_ty, zcu)) blk: {
try sema.storePtr(block, src, ptr, new_value);
break :blk .none;
} else stored_val.toIntern(),
} });
return Air.internedToRef(result_val);
} else break :rs new_value_src;
} else break :rs expected_src;
} else ptr_src;
const flags: u32 = @as(u32, @intFromEnum(success_order)) |
(@as(u32, @intFromEnum(failure_order)) << 3);
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addInst(.{
.tag = air_tag,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(result_ty.toIntern()),
.payload = try sema.addExtra(Air.Cmpxchg{
.ptr = ptr,
.expected_value = expected_value,
.new_value = new_value,
.flags = flags,
}),
} },
});
}
fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const scalar_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const dest_ty = try sema.resolveDestType(block, src, extra.lhs, .remove_eu_opt, "@splat");
switch (dest_ty.zigTypeTag(zcu)) {
.array, .vector => {},
else => return sema.fail(block, src, "expected array or vector type, found '{f}'", .{dest_ty.fmt(pt)}),
}
const operand = try sema.resolveInst(extra.rhs);
const scalar_ty = dest_ty.childType(zcu);
const scalar = try sema.coerce(block, scalar_ty, operand, scalar_src);
const len = try sema.usizeCast(block, src, dest_ty.arrayLen(zcu));
if (try sema.typeHasOnePossibleValue(dest_ty)) |val| {
return Air.internedToRef(val.toIntern());
}
// We also need this case because `[0:s]T` is not OPV.
if (len == 0) return .fromValue(try pt.aggregateValue(dest_ty, &.{}));
const maybe_sentinel = dest_ty.sentinel(zcu);
if (try sema.resolveValue(scalar)) |scalar_val| {
full: {
if (dest_ty.zigTypeTag(zcu) == .vector) break :full;
const sentinel = maybe_sentinel orelse break :full;
if (sentinel.toIntern() == scalar_val.toIntern()) break :full;
// This is a array with non-zero length and a sentinel which does not match the element.
// We have to use the full `elems` representation.
const elems = try sema.arena.alloc(InternPool.Index, len + 1);
@memset(elems[0..len], scalar_val.toIntern());
elems[len] = sentinel.toIntern();
return .fromValue(try pt.aggregateValue(dest_ty, elems));
}
return .fromValue(try pt.aggregateSplatValue(dest_ty, scalar_val));
}
try sema.requireRuntimeBlock(block, src, scalar_src);
switch (dest_ty.zigTypeTag(zcu)) {
.array => {
const elems = try sema.arena.alloc(Air.Inst.Ref, len + @intFromBool(maybe_sentinel != null));
@memset(elems[0..len], scalar);
if (maybe_sentinel) |s| elems[len] = Air.internedToRef(s.toIntern());
return block.addAggregateInit(dest_ty, elems);
},
.vector => return block.addTyOp(.splat, dest_ty, scalar),
else => unreachable,
}
}
fn zirReduce(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const op_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const operation = try sema.resolveBuiltinEnum(block, op_src, extra.lhs, .ReduceOp, .{ .simple = .operand_reduce_operation });
const operand = try sema.resolveInst(extra.rhs);
const operand_ty = sema.typeOf(operand);
const pt = sema.pt;
const zcu = pt.zcu;
if (operand_ty.zigTypeTag(zcu) != .vector) {
return sema.fail(block, operand_src, "expected vector, found '{f}'", .{operand_ty.fmt(pt)});
}
const scalar_ty = operand_ty.childType(zcu);
// Type-check depending on operation.
switch (operation) {
.And, .Or, .Xor => switch (scalar_ty.zigTypeTag(zcu)) {
.int, .bool => {},
else => return sema.fail(block, operand_src, "@reduce operation '{s}' requires integer or boolean operand; found '{f}'", .{
@tagName(operation), operand_ty.fmt(pt),
}),
},
.Min, .Max, .Add, .Mul => switch (scalar_ty.zigTypeTag(zcu)) {
.int, .float => {},
else => return sema.fail(block, operand_src, "@reduce operation '{s}' requires integer or float operand; found '{f}'", .{
@tagName(operation), operand_ty.fmt(pt),
}),
},
}
const vec_len = operand_ty.vectorLen(zcu);
if (vec_len == 0) {
// TODO re-evaluate if we should introduce a "neutral value" for some operations,
// e.g. zero for add and one for mul.
return sema.fail(block, operand_src, "@reduce operation requires a vector with nonzero length", .{});
}
if (try sema.resolveValue(operand)) |operand_val| {
if (operand_val.isUndef(zcu)) return pt.undefRef(scalar_ty);
var accum: Value = try operand_val.elemValue(pt, 0);
var i: u32 = 1;
while (i < vec_len) : (i += 1) {
const elem_val = try operand_val.elemValue(pt, i);
accum = switch (operation) {
// zig fmt: off
.And => try arith.bitwiseBin (sema, scalar_ty, accum, elem_val, .@"and"),
.Or => try arith.bitwiseBin (sema, scalar_ty, accum, elem_val, .@"or"),
.Xor => try arith.bitwiseBin (sema, scalar_ty, accum, elem_val, .xor),
.Min => Value.numberMin ( accum, elem_val, zcu),
.Max => Value.numberMax ( accum, elem_val, zcu),
.Add => try arith.addMaybeWrap(sema, scalar_ty, accum, elem_val),
.Mul => try arith.mulMaybeWrap(sema, scalar_ty, accum, elem_val),
// zig fmt: on
};
}
return Air.internedToRef(accum.toIntern());
}
try sema.requireRuntimeBlock(block, block.nodeOffset(inst_data.src_node), operand_src);
return block.addReduce(operand, operation);
}
fn zirShuffle(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Shuffle, inst_data.payload_index).data;
const elem_ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const mask_src = block.builtinCallArgSrc(inst_data.src_node, 3);
const elem_ty = try sema.resolveType(block, elem_ty_src, extra.elem_type);
try sema.checkVectorElemType(block, elem_ty_src, elem_ty);
const a = try sema.resolveInst(extra.a);
const b = try sema.resolveInst(extra.b);
var mask = try sema.resolveInst(extra.mask);
var mask_ty = sema.typeOf(mask);
const mask_len = switch (sema.typeOf(mask).zigTypeTag(zcu)) {
.array, .vector => sema.typeOf(mask).arrayLen(zcu),
else => return sema.fail(block, mask_src, "expected vector or array, found '{f}'", .{sema.typeOf(mask).fmt(pt)}),
};
mask_ty = try pt.vectorType(.{
.len = @intCast(mask_len),
.child = .i32_type,
});
mask = try sema.coerce(block, mask_ty, mask, mask_src);
const mask_val = try sema.resolveConstValue(block, mask_src, mask, .{ .simple = .operand_shuffle_mask });
return sema.analyzeShuffle(block, inst_data.src_node, elem_ty, a, b, mask_val, @intCast(mask_len));
}
fn analyzeShuffle(
sema: *Sema,
block: *Block,
src_node: std.zig.Ast.Node.Offset,
elem_ty: Type,
a_uncoerced: Air.Inst.Ref,
b_uncoerced: Air.Inst.Ref,
mask: Value,
mask_len: u32,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const a_src = block.builtinCallArgSrc(src_node, 1);
const b_src = block.builtinCallArgSrc(src_node, 2);
const mask_src = block.builtinCallArgSrc(src_node, 3);
// If the type of `a` is `@TypeOf(undefined)`, i.e. the argument is untyped,
// this is 0, because it is an error to index into this vector.
const a_len: u32 = switch (sema.typeOf(a_uncoerced).zigTypeTag(zcu)) {
.array, .vector => @intCast(sema.typeOf(a_uncoerced).arrayLen(zcu)),
.undefined => 0,
else => return sema.fail(block, a_src, "expected vector of '{f}', found '{f}'", .{
elem_ty.fmt(pt), sema.typeOf(a_uncoerced).fmt(pt),
}),
};
const a_ty = try pt.vectorType(.{ .len = a_len, .child = elem_ty.toIntern() });
const a_coerced = try sema.coerce(block, a_ty, a_uncoerced, a_src);
// If the type of `b` is `@TypeOf(undefined)`, i.e. the argument is untyped, this is 0, because it is an error to index into this vector.
const b_len: u32 = switch (sema.typeOf(b_uncoerced).zigTypeTag(zcu)) {
.array, .vector => @intCast(sema.typeOf(b_uncoerced).arrayLen(zcu)),
.undefined => 0,
else => return sema.fail(block, b_src, "expected vector of '{f}', found '{f}'", .{
elem_ty.fmt(pt), sema.typeOf(b_uncoerced).fmt(pt),
}),
};
const b_ty = try pt.vectorType(.{ .len = b_len, .child = elem_ty.toIntern() });
const b_coerced = try sema.coerce(block, b_ty, b_uncoerced, b_src);
const result_ty = try pt.vectorType(.{ .len = mask_len, .child = elem_ty.toIntern() });
// We're going to pre-emptively reserve space in `sema.air_extra`. The reason for this is we need
// a `u32` buffer of length `mask_len` anyway, and putting it in `sema.air_extra` avoids a copy
// in the runtime case. If the result is comptime-known, we'll shrink `air_extra` back.
const air_extra_idx: u32 = @intCast(sema.air_extra.items.len);
const air_mask_buf = try sema.air_extra.addManyAsSlice(sema.gpa, mask_len);
// We want to interpret that buffer in `air_extra` in a few ways. Initially, we'll consider its
// elements as `Air.Inst.ShuffleTwoMask`, essentially representing the raw mask values; then, we'll
// convert it to `InternPool.Index` or `Air.Inst.ShuffleOneMask` if there are comptime-known operands.
const mask_ip_index: []InternPool.Index = @ptrCast(air_mask_buf);
const mask_shuffle_one: []Air.ShuffleOneMask = @ptrCast(air_mask_buf);
const mask_shuffle_two: []Air.ShuffleTwoMask = @ptrCast(air_mask_buf);
// Initial loop: check mask elements, populate `mask_shuffle_two`.
var a_used = false;
var b_used = false;
for (mask_shuffle_two, 0..mask_len) |*out, mask_idx| {
const mask_val = try mask.elemValue(pt, mask_idx);
if (mask_val.isUndef(zcu)) {
out.* = .undef;
continue;
}
// Safe because mask elements are `i32` and we already checked for undef:
const raw = (try sema.resolveLazyValue(mask_val)).toSignedInt(zcu);
if (raw >= 0) {
const idx: u32 = @intCast(raw);
a_used = true;
out.* = .aElem(idx);
if (idx >= a_len) return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(mask_src, "mask element at index '{d}' selects out-of-bounds index", .{mask_idx});
errdefer msg.destroy(sema.gpa);
try sema.errNote(a_src, msg, "index '{d}' exceeds bounds of '{f}' given here", .{ idx, a_ty.fmt(pt) });
if (idx < b_len) {
try sema.errNote(b_src, msg, "use '~@as(u32, {d})' to index into second vector given here", .{idx});
}
break :msg msg;
});
} else {
const idx: u32 = @intCast(~raw);
b_used = true;
out.* = .bElem(idx);
if (idx >= b_len) return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(mask_src, "mask element at index '{d}' selects out-of-bounds index", .{mask_idx});
errdefer msg.destroy(sema.gpa);
try sema.errNote(b_src, msg, "index '{d}' exceeds bounds of '{f}' given here", .{ idx, b_ty.fmt(pt) });
break :msg msg;
});
}
}
const maybe_a_val = try sema.resolveValue(a_coerced);
const maybe_b_val = try sema.resolveValue(b_coerced);
const a_rt = a_used and maybe_a_val == null;
const b_rt = b_used and maybe_b_val == null;
if (a_rt and b_rt) {
// Both operands are needed and runtime-known. We need a `[]ShuffleTwomask`... which is
// exactly what we already have in `mask_shuffle_two`! So, we're basically done already.
// We just need to append the two operands.
try sema.air_extra.ensureUnusedCapacity(sema.gpa, 2);
sema.appendRefsAssumeCapacity(&.{ a_coerced, b_coerced });
return block.addInst(.{
.tag = .shuffle_two,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(result_ty.toIntern()),
.payload = air_extra_idx,
} },
});
} else if (a_rt) {
// We need to convert the `ShuffleTwoMask` values to `ShuffleOneMask`.
for (mask_shuffle_two, mask_shuffle_one) |in, *out| {
out.* = switch (in.unwrap()) {
.undef => .value(try pt.undefValue(elem_ty)),
.a_elem => |idx| .elem(idx),
.b_elem => |idx| .value(try maybe_b_val.?.elemValue(pt, idx)),
};
}
// Now just append our single runtime operand, and we're done.
try sema.air_extra.ensureUnusedCapacity(sema.gpa, 1);
sema.appendRefsAssumeCapacity(&.{a_coerced});
return block.addInst(.{
.tag = .shuffle_one,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(result_ty.toIntern()),
.payload = air_extra_idx,
} },
});
} else if (b_rt) {
// We need to convert the `ShuffleTwoMask` values to `ShuffleOneMask`.
for (mask_shuffle_two, mask_shuffle_one) |in, *out| {
out.* = switch (in.unwrap()) {
.undef => .value(try pt.undefValue(elem_ty)),
.a_elem => |idx| .value(try maybe_a_val.?.elemValue(pt, idx)),
.b_elem => |idx| .elem(idx),
};
}
// Now just append our single runtime operand, and we're done.
try sema.air_extra.ensureUnusedCapacity(sema.gpa, 1);
sema.appendRefsAssumeCapacity(&.{b_coerced});
return block.addInst(.{
.tag = .shuffle_one,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(result_ty.toIntern()),
.payload = air_extra_idx,
} },
});
} else {
// The result will be comptime-known. We must convert the `ShuffleTwoMask` values to
// `InternPool.Index` values using the known operands.
for (mask_shuffle_two, mask_ip_index) |in, *out| {
const val: Value = switch (in.unwrap()) {
.undef => try pt.undefValue(elem_ty),
.a_elem => |idx| try maybe_a_val.?.elemValue(pt, idx),
.b_elem => |idx| try maybe_b_val.?.elemValue(pt, idx),
};
out.* = val.toIntern();
}
const res = try pt.aggregateValue(result_ty, mask_ip_index);
// We have a comptime-known result, so didn't need `air_mask_buf` -- remove it from `sema.air_extra`.
assert(sema.air_extra.items.len == air_extra_idx + air_mask_buf.len);
sema.air_extra.shrinkRetainingCapacity(air_extra_idx);
return Air.internedToRef(res.toIntern());
}
}
fn zirSelect(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.Select, extended.operand).data;
const src = block.nodeOffset(extra.node);
const elem_ty_src = block.builtinCallArgSrc(extra.node, 0);
const pred_src = block.builtinCallArgSrc(extra.node, 1);
const a_src = block.builtinCallArgSrc(extra.node, 2);
const b_src = block.builtinCallArgSrc(extra.node, 3);
const elem_ty = try sema.resolveType(block, elem_ty_src, extra.elem_type);
try sema.checkVectorElemType(block, elem_ty_src, elem_ty);
const pred_uncoerced = try sema.resolveInst(extra.pred);
const pred_ty = sema.typeOf(pred_uncoerced);
const vec_len_u64 = switch (pred_ty.zigTypeTag(zcu)) {
.vector, .array => pred_ty.arrayLen(zcu),
else => return sema.fail(block, pred_src, "expected vector or array, found '{f}'", .{pred_ty.fmt(pt)}),
};
const vec_len: u32 = @intCast(try sema.usizeCast(block, pred_src, vec_len_u64));
const bool_vec_ty = try pt.vectorType(.{
.len = vec_len,
.child = .bool_type,
});
const pred = try sema.coerce(block, bool_vec_ty, pred_uncoerced, pred_src);
const vec_ty = try pt.vectorType(.{
.len = vec_len,
.child = elem_ty.toIntern(),
});
const a = try sema.coerce(block, vec_ty, try sema.resolveInst(extra.a), a_src);
const b = try sema.coerce(block, vec_ty, try sema.resolveInst(extra.b), b_src);
const maybe_pred = try sema.resolveValue(pred);
const maybe_a = try sema.resolveValue(a);
const maybe_b = try sema.resolveValue(b);
const runtime_src = if (maybe_pred) |pred_val| rs: {
if (pred_val.isUndef(zcu)) return pt.undefRef(vec_ty);
if (maybe_a) |a_val| {
if (a_val.isUndef(zcu)) return pt.undefRef(vec_ty);
if (maybe_b) |b_val| {
if (b_val.isUndef(zcu)) return pt.undefRef(vec_ty);
const elems = try sema.gpa.alloc(InternPool.Index, vec_len);
defer sema.gpa.free(elems);
for (elems, 0..) |*elem, i| {
const pred_elem_val = try pred_val.elemValue(pt, i);
const should_choose_a = pred_elem_val.toBool();
elem.* = (try (if (should_choose_a) a_val else b_val).elemValue(pt, i)).toIntern();
}
return Air.internedToRef((try pt.aggregateValue(vec_ty, elems)).toIntern());
} else {
break :rs b_src;
}
} else {
if (maybe_b) |b_val| {
if (b_val.isUndef(zcu)) return pt.undefRef(vec_ty);
}
break :rs a_src;
}
} else rs: {
if (maybe_a) |a_val| {
if (a_val.isUndef(zcu)) return pt.undefRef(vec_ty);
}
if (maybe_b) |b_val| {
if (b_val.isUndef(zcu)) return pt.undefRef(vec_ty);
}
break :rs pred_src;
};
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addInst(.{
.tag = .select,
.data = .{ .pl_op = .{
.operand = pred,
.payload = try block.sema.addExtra(Air.Bin{
.lhs = a,
.rhs = b,
}),
} },
});
}
fn zirAtomicLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.AtomicLoad, inst_data.payload_index).data;
// zig fmt: off
const elem_ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const ptr_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const order_src = block.builtinCallArgSrc(inst_data.src_node, 2);
// zig fmt: on
const elem_ty = try sema.resolveType(block, elem_ty_src, extra.elem_type);
const uncasted_ptr = try sema.resolveInst(extra.ptr);
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, true);
const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering, .{ .simple = .atomic_order });
switch (order) {
.release, .acq_rel => {
return sema.fail(
block,
order_src,
"@atomicLoad atomic ordering must not be release or acq_rel",
.{},
);
},
else => {},
}
if (try sema.typeHasOnePossibleValue(elem_ty)) |val| {
return Air.internedToRef(val.toIntern());
}
if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {
if (try sema.pointerDeref(block, ptr_src, ptr_val, sema.typeOf(ptr))) |elem_val| {
return Air.internedToRef(elem_val.toIntern());
}
}
try sema.requireRuntimeBlock(block, block.nodeOffset(inst_data.src_node), ptr_src);
return block.addInst(.{
.tag = .atomic_load,
.data = .{ .atomic_load = .{
.ptr = ptr,
.order = order,
} },
});
}
fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.AtomicRmw, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
// zig fmt: off
const elem_ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const ptr_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const op_src = block.builtinCallArgSrc(inst_data.src_node, 2);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 3);
const order_src = block.builtinCallArgSrc(inst_data.src_node, 4);
// zig fmt: on
const operand = try sema.resolveInst(extra.operand);
const elem_ty = sema.typeOf(operand);
const uncasted_ptr = try sema.resolveInst(extra.ptr);
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, false);
const op = try sema.resolveAtomicRmwOp(block, op_src, extra.operation);
switch (elem_ty.zigTypeTag(zcu)) {
.@"enum" => if (op != .Xchg) {
return sema.fail(block, op_src, "@atomicRmw with enum only allowed with .Xchg", .{});
},
.bool => if (op != .Xchg) {
return sema.fail(block, op_src, "@atomicRmw with bool only allowed with .Xchg", .{});
},
.float => switch (op) {
.Xchg, .Add, .Sub, .Max, .Min => {},
else => return sema.fail(block, op_src, "@atomicRmw with float only allowed with .Xchg, .Add, .Sub, .Max, and .Min", .{}),
},
else => {},
}
const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering, .{ .simple = .atomic_order });
if (order == .unordered) {
return sema.fail(block, order_src, "@atomicRmw atomic ordering must not be unordered", .{});
}
// special case zero bit types
if (try sema.typeHasOnePossibleValue(elem_ty)) |val| {
return Air.internedToRef(val.toIntern());
}
const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: {
const maybe_operand_val = try sema.resolveValue(operand);
const operand_val = maybe_operand_val orelse {
try sema.checkPtrIsNotComptimeMutable(block, ptr_val, ptr_src, operand_src);
break :rs operand_src;
};
if (sema.isComptimeMutablePtr(ptr_val)) {
const ptr_ty = sema.typeOf(ptr);
const stored_val = (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) orelse break :rs ptr_src;
const new_val = switch (op) {
// zig fmt: off
.Xchg => operand_val,
.Add => try arith.addMaybeWrap(sema, elem_ty, stored_val, operand_val),
.Sub => try arith.subMaybeWrap(sema, elem_ty, stored_val, operand_val),
.And => try arith.bitwiseBin (sema, elem_ty, stored_val, operand_val, .@"and"),
.Nand => try arith.bitwiseBin (sema, elem_ty, stored_val, operand_val, .nand),
.Or => try arith.bitwiseBin (sema, elem_ty, stored_val, operand_val, .@"or"),
.Xor => try arith.bitwiseBin (sema, elem_ty, stored_val, operand_val, .xor),
.Max => Value.numberMax ( stored_val, operand_val, zcu),
.Min => Value.numberMin ( stored_val, operand_val, zcu),
// zig fmt: on
};
try sema.storePtrVal(block, src, ptr_val, new_val, elem_ty);
return Air.internedToRef(stored_val.toIntern());
} else break :rs ptr_src;
} else ptr_src;
const flags: u32 = @as(u32, @intFromEnum(order)) | (@as(u32, @intFromEnum(op)) << 3);
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addInst(.{
.tag = .atomic_rmw,
.data = .{ .pl_op = .{
.operand = ptr,
.payload = try sema.addExtra(Air.AtomicRmw{
.operand = operand,
.flags = flags,
}),
} },
});
}
fn zirAtomicStore(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.AtomicStore, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
// zig fmt: off
const elem_ty_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const ptr_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const operand_src = block.builtinCallArgSrc(inst_data.src_node, 2);
const order_src = block.builtinCallArgSrc(inst_data.src_node, 3);
// zig fmt: on
const operand = try sema.resolveInst(extra.operand);
const elem_ty = sema.typeOf(operand);
const uncasted_ptr = try sema.resolveInst(extra.ptr);
const ptr = try sema.checkAtomicPtrOperand(block, elem_ty, elem_ty_src, uncasted_ptr, ptr_src, false);
const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering, .{ .simple = .atomic_order });
const air_tag: Air.Inst.Tag = switch (order) {
.acquire, .acq_rel => {
return sema.fail(
block,
order_src,
"@atomicStore atomic ordering must not be acquire or acq_rel",
.{},
);
},
.unordered => .atomic_store_unordered,
.monotonic => .atomic_store_monotonic,
.release => .atomic_store_release,
.seq_cst => .atomic_store_seq_cst,
};
return sema.storePtr2(block, src, ptr, ptr_src, operand, operand_src, air_tag);
}
fn zirMulAdd(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.MulAdd, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const mulend1_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const mulend2_src = block.builtinCallArgSrc(inst_data.src_node, 2);
const addend_src = block.builtinCallArgSrc(inst_data.src_node, 3);
const addend = try sema.resolveInst(extra.addend);
const ty = sema.typeOf(addend);
const mulend1 = try sema.coerce(block, ty, try sema.resolveInst(extra.mulend1), mulend1_src);
const mulend2 = try sema.coerce(block, ty, try sema.resolveInst(extra.mulend2), mulend2_src);
const maybe_mulend1 = try sema.resolveValue(mulend1);
const maybe_mulend2 = try sema.resolveValue(mulend2);
const maybe_addend = try sema.resolveValue(addend);
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.scalarType(zcu).zigTypeTag(zcu)) {
.comptime_float, .float => {},
else => return sema.fail(block, src, "expected vector of floats or float type, found '{f}'", .{ty.fmt(pt)}),
}
const runtime_src = if (maybe_mulend1) |mulend1_val| rs: {
if (maybe_mulend2) |mulend2_val| {
if (mulend2_val.isUndef(zcu)) return pt.undefRef(ty);
if (maybe_addend) |addend_val| {
if (addend_val.isUndef(zcu)) return pt.undefRef(ty);
const result_val = try Value.mulAdd(ty, mulend1_val, mulend2_val, addend_val, sema.arena, pt);
return Air.internedToRef(result_val.toIntern());
} else {
break :rs addend_src;
}
} else {
if (maybe_addend) |addend_val| {
if (addend_val.isUndef(zcu)) return pt.undefRef(ty);
}
break :rs mulend2_src;
}
} else rs: {
if (maybe_mulend2) |mulend2_val| {
if (mulend2_val.isUndef(zcu)) return pt.undefRef(ty);
}
if (maybe_addend) |addend_val| {
if (addend_val.isUndef(zcu)) return pt.undefRef(ty);
}
break :rs mulend1_src;
};
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addInst(.{
.tag = .mul_add,
.data = .{ .pl_op = .{
.operand = addend,
.payload = try sema.addExtra(Air.Bin{
.lhs = mulend1,
.rhs = mulend2,
}),
} },
});
}
fn zirBuiltinCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const modifier_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const func_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const args_src = block.builtinCallArgSrc(inst_data.src_node, 2);
const call_src = block.nodeOffset(inst_data.src_node);
const extra = sema.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data;
const func = try sema.resolveInst(extra.callee);
const modifier_ty = try sema.getBuiltinType(call_src, .CallModifier);
const air_ref = try sema.resolveInst(extra.modifier);
const modifier_ref = try sema.coerce(block, modifier_ty, air_ref, modifier_src);
const modifier_val = try sema.resolveConstDefinedValue(block, modifier_src, modifier_ref, .{ .simple = .call_modifier });
var modifier = try sema.interpretBuiltinType(block, modifier_src, modifier_val, std.builtin.CallModifier);
switch (modifier) {
// These can be upgraded to comptime or nosuspend calls.
.auto, .never_tail, .no_suspend => {
if (block.isComptime()) {
if (modifier == .never_tail) {
return sema.fail(block, modifier_src, "unable to perform 'never_tail' call at compile-time", .{});
}
modifier = .compile_time;
} else if (extra.flags.is_nosuspend) {
modifier = .no_suspend;
}
},
// These can be upgraded to comptime. nosuspend bit can be safely ignored.
.always_inline, .compile_time => {
_ = (try sema.resolveDefinedValue(block, func_src, func)) orelse {
return sema.fail(block, func_src, "modifier '{s}' requires a comptime-known function", .{@tagName(modifier)});
};
if (block.isComptime()) {
modifier = .compile_time;
}
},
.always_tail => {
if (block.isComptime()) {
modifier = .compile_time;
}
},
.never_inline => {
if (block.isComptime()) {
return sema.fail(block, modifier_src, "unable to perform 'never_inline' call at compile-time", .{});
}
},
}
const args = try sema.resolveInst(extra.args);
const args_ty = sema.typeOf(args);
if (!args_ty.isTuple(zcu)) {
return sema.fail(block, args_src, "expected a tuple, found '{f}'", .{args_ty.fmt(pt)});
}
const resolved_args: []Air.Inst.Ref = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount(zcu));
for (resolved_args, 0..) |*resolved, i| {
resolved.* = try sema.tupleFieldValByIndex(block, args, @intCast(i), args_ty);
}
const callee_ty = sema.typeOf(func);
const func_ty = try sema.checkCallArgumentCount(block, func, func_src, callee_ty, resolved_args.len, false);
const ensure_result_used = extra.flags.ensure_result_used;
return sema.analyzeCall(
block,
func,
func_ty,
func_src,
call_src,
modifier,
ensure_result_used,
.{ .call_builtin = .{
.call_node_offset = inst_data.src_node,
.args = resolved_args,
} },
null,
.@"@call",
);
}
fn zirFieldParentPtr(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.FieldParentPtr, extended.operand).data;
const FlagsInt = @typeInfo(Zir.Inst.FullPtrCastFlags).@"struct".backing_integer.?;
const flags: Zir.Inst.FullPtrCastFlags = @bitCast(@as(FlagsInt, @truncate(extended.small)));
assert(!flags.ptr_cast);
const inst_src = block.nodeOffset(extra.src_node);
const field_name_src = block.builtinCallArgSrc(extra.src_node, 0);
const field_ptr_src = block.builtinCallArgSrc(extra.src_node, 1);
const parent_ptr_ty = try sema.resolveDestType(block, inst_src, extra.parent_ptr_type, .remove_eu, "@fieldParentPtr");
try sema.checkPtrType(block, inst_src, parent_ptr_ty, true);
const parent_ptr_info = parent_ptr_ty.ptrInfo(zcu);
if (parent_ptr_info.flags.size != .one) {
return sema.fail(block, inst_src, "expected single pointer type, found '{f}'", .{parent_ptr_ty.fmt(pt)});
}
const parent_ty: Type = .fromInterned(parent_ptr_info.child);
switch (parent_ty.zigTypeTag(zcu)) {
.@"struct", .@"union" => {},
else => return sema.fail(block, inst_src, "expected pointer to struct or union type, found '{f}'", .{parent_ptr_ty.fmt(pt)}),
}
try parent_ty.resolveLayout(pt);
const field_name = try sema.resolveConstStringIntern(block, field_name_src, extra.field_name, .{ .simple = .field_name });
const field_index = switch (parent_ty.zigTypeTag(zcu)) {
.@"struct" => blk: {
if (parent_ty.isTuple(zcu)) {
if (field_name.eqlSlice("len", ip)) {
return sema.fail(block, inst_src, "cannot get @fieldParentPtr of 'len' field of tuple", .{});
}
break :blk try sema.tupleFieldIndex(block, parent_ty, field_name, field_name_src);
} else {
break :blk try sema.structFieldIndex(block, parent_ty, field_name, field_name_src);
}
},
.@"union" => try sema.unionFieldIndex(block, parent_ty, field_name, field_name_src),
else => unreachable,
};
if (parent_ty.zigTypeTag(zcu) == .@"struct" and parent_ty.structFieldIsComptime(field_index, zcu)) {
return sema.fail(block, field_name_src, "cannot get @fieldParentPtr of a comptime field", .{});
}
const field_ptr = try sema.resolveInst(extra.field_ptr);
const field_ptr_ty = sema.typeOf(field_ptr);
try sema.checkPtrOperand(block, field_ptr_src, field_ptr_ty);
const field_ptr_info = field_ptr_ty.ptrInfo(zcu);
var actual_parent_ptr_info: InternPool.Key.PtrType = .{
.child = parent_ty.toIntern(),
.flags = .{
.alignment = try parent_ptr_ty.ptrAlignmentSema(pt),
.is_const = field_ptr_info.flags.is_const,
.is_volatile = field_ptr_info.flags.is_volatile,
.is_allowzero = field_ptr_info.flags.is_allowzero,
.address_space = field_ptr_info.flags.address_space,
},
.packed_offset = parent_ptr_info.packed_offset,
};
const field_ty = parent_ty.fieldType(field_index, zcu);
var actual_field_ptr_info: InternPool.Key.PtrType = .{
.child = field_ty.toIntern(),
.flags = .{
.alignment = try field_ptr_ty.ptrAlignmentSema(pt),
.is_const = field_ptr_info.flags.is_const,
.is_volatile = field_ptr_info.flags.is_volatile,
.is_allowzero = field_ptr_info.flags.is_allowzero,
.address_space = field_ptr_info.flags.address_space,
},
.packed_offset = field_ptr_info.packed_offset,
};
switch (parent_ty.containerLayout(zcu)) {
.auto => {
actual_parent_ptr_info.flags.alignment = actual_field_ptr_info.flags.alignment.minStrict(
if (zcu.typeToStruct(parent_ty)) |struct_obj|
try field_ty.structFieldAlignmentSema(
struct_obj.fieldAlign(ip, field_index),
struct_obj.layout,
pt,
)
else if (zcu.typeToUnion(parent_ty)) |union_obj|
try field_ty.unionFieldAlignmentSema(
union_obj.fieldAlign(ip, field_index),
union_obj.flagsUnordered(ip).layout,
pt,
)
else
actual_field_ptr_info.flags.alignment,
);
actual_parent_ptr_info.packed_offset = .{ .bit_offset = 0, .host_size = 0 };
actual_field_ptr_info.packed_offset = .{ .bit_offset = 0, .host_size = 0 };
},
.@"extern" => {
const field_offset = parent_ty.structFieldOffset(field_index, zcu);
actual_parent_ptr_info.flags.alignment = actual_field_ptr_info.flags.alignment.minStrict(if (field_offset > 0)
Alignment.fromLog2Units(@ctz(field_offset))
else
actual_field_ptr_info.flags.alignment);
actual_parent_ptr_info.packed_offset = .{ .bit_offset = 0, .host_size = 0 };
actual_field_ptr_info.packed_offset = .{ .bit_offset = 0, .host_size = 0 };
},
.@"packed" => {
const byte_offset = std.math.divExact(u32, @abs(@as(i32, actual_parent_ptr_info.packed_offset.bit_offset) +
(if (zcu.typeToStruct(parent_ty)) |struct_obj| zcu.structPackedFieldBitOffset(struct_obj, field_index) else 0) -
actual_field_ptr_info.packed_offset.bit_offset), 8) catch
return sema.fail(block, inst_src, "pointer bit-offset mismatch", .{});
actual_parent_ptr_info.flags.alignment = actual_field_ptr_info.flags.alignment.minStrict(if (byte_offset > 0)
Alignment.fromLog2Units(@ctz(byte_offset))
else
actual_field_ptr_info.flags.alignment);
},
}
const actual_field_ptr_ty = try pt.ptrTypeSema(actual_field_ptr_info);
const casted_field_ptr = try sema.coerce(block, actual_field_ptr_ty, field_ptr, field_ptr_src);
const actual_parent_ptr_ty = try pt.ptrTypeSema(actual_parent_ptr_info);
const result = if (try sema.resolveDefinedValue(block, field_ptr_src, casted_field_ptr)) |field_ptr_val| result: {
switch (parent_ty.zigTypeTag(zcu)) {
.@"struct" => switch (parent_ty.containerLayout(zcu)) {
.auto => {},
.@"extern" => {
const byte_offset = parent_ty.structFieldOffset(field_index, zcu);
const parent_ptr_val = try sema.ptrSubtract(block, field_ptr_src, field_ptr_val, byte_offset, actual_parent_ptr_ty);
break :result Air.internedToRef(parent_ptr_val.toIntern());
},
.@"packed" => {
// Logic lifted from type computation above - I'm just assuming it's correct.
// `catch unreachable` since error case handled above.
const byte_offset = std.math.divExact(u32, @abs(@as(i32, actual_parent_ptr_info.packed_offset.bit_offset) +
zcu.structPackedFieldBitOffset(zcu.typeToStruct(parent_ty).?, field_index) -
actual_field_ptr_info.packed_offset.bit_offset), 8) catch unreachable;
const parent_ptr_val = try sema.ptrSubtract(block, field_ptr_src, field_ptr_val, byte_offset, actual_parent_ptr_ty);
break :result Air.internedToRef(parent_ptr_val.toIntern());
},
},
.@"union" => switch (parent_ty.containerLayout(zcu)) {
.auto => {},
.@"extern", .@"packed" => {
// For an extern or packed union, just coerce the pointer.
const parent_ptr_val = try pt.getCoerced(field_ptr_val, actual_parent_ptr_ty);
break :result Air.internedToRef(parent_ptr_val.toIntern());
},
},
else => unreachable,
}
const opt_field: ?InternPool.Key.Ptr.BaseAddr.BaseIndex = opt_field: {
const ptr = switch (ip.indexToKey(field_ptr_val.toIntern())) {
.ptr => |ptr| ptr,
else => break :opt_field null,
};
if (ptr.byte_offset != 0) break :opt_field null;
break :opt_field switch (ptr.base_addr) {
.field => |field| field,
else => null,
};
};
const field = opt_field orelse {
return sema.fail(block, field_ptr_src, "pointer value not based on parent struct", .{});
};
if (Value.fromInterned(field.base).typeOf(zcu).childType(zcu).toIntern() != parent_ty.toIntern()) {
return sema.fail(block, field_ptr_src, "pointer value not based on parent struct", .{});
}
if (field.index != field_index) {
return sema.fail(block, inst_src, "field '{f}' has index '{d}' but pointer value is index '{d}' of struct '{f}'", .{
field_name.fmt(ip), field_index, field.index, parent_ty.fmt(pt),
});
}
break :result try sema.coerce(block, actual_parent_ptr_ty, Air.internedToRef(field.base), inst_src);
} else result: {
try sema.requireRuntimeBlock(block, inst_src, field_ptr_src);
break :result try block.addInst(.{
.tag = .field_parent_ptr,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(actual_parent_ptr_ty.toIntern()),
.payload = try block.sema.addExtra(Air.FieldParentPtr{
.field_ptr = casted_field_ptr,
.field_index = @intCast(field_index),
}),
} },
});
};
return sema.ptrCastFull(block, flags, inst_src, result, inst_src, parent_ptr_ty, "@fieldParentPtr");
}
fn ptrSubtract(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, byte_subtract: u64, new_ty: Type) !Value {
const pt = sema.pt;
const zcu = pt.zcu;
if (byte_subtract == 0) return pt.getCoerced(ptr_val, new_ty);
var ptr = switch (zcu.intern_pool.indexToKey(ptr_val.toIntern())) {
.undef => return sema.failWithUseOfUndef(block, src, null),
.ptr => |ptr| ptr,
else => unreachable,
};
if (ptr.byte_offset < byte_subtract) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "pointer computation here causes illegal behavior", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "resulting pointer exceeds bounds of containing value which may trigger overflow", .{});
break :msg msg;
});
}
ptr.byte_offset -= byte_subtract;
ptr.ty = new_ty.toIntern();
return Value.fromInterned(try pt.intern(.{ .ptr = ptr }));
}
fn zirMinMax(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
comptime air_tag: Air.Inst.Tag,
) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const lhs_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const rhs_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const lhs = try sema.resolveInst(extra.lhs);
const rhs = try sema.resolveInst(extra.rhs);
return sema.analyzeMinMax(block, src, air_tag, &.{ lhs, rhs }, &.{ lhs_src, rhs_src });
}
fn zirMinMaxMulti(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
comptime air_tag: Air.Inst.Tag,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand);
const src_node = extra.data.src_node;
const src = block.nodeOffset(src_node);
const operands = sema.code.refSlice(extra.end, extended.small);
const air_refs = try sema.arena.alloc(Air.Inst.Ref, operands.len);
const operand_srcs = try sema.arena.alloc(LazySrcLoc, operands.len);
for (operands, air_refs, operand_srcs, 0..) |zir_ref, *air_ref, *op_src, i| {
op_src.* = block.builtinCallArgSrc(src_node, @intCast(i));
air_ref.* = try sema.resolveInst(zir_ref);
}
return sema.analyzeMinMax(block, src, air_tag, air_refs, operand_srcs);
}
fn analyzeMinMax(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
comptime air_tag: Air.Inst.Tag,
operands: []const Air.Inst.Ref,
operand_srcs: []const LazySrcLoc,
) CompileError!Air.Inst.Ref {
assert(operands.len == operand_srcs.len);
assert(operands.len > 0);
const pt = sema.pt;
const zcu = pt.zcu;
// This function has the signature `fn (Value, Value, *Zcu) Value`.
// It is only used on scalar values, although the values may have different types.
// If either operand is undef, it returns undef.
const opFunc = switch (air_tag) {
.min => Value.numberMin,
.max => Value.numberMax,
else => comptime unreachable,
};
if (operands.len == 1) {
try sema.checkNumericType(block, operand_srcs[0], sema.typeOf(operands[0]));
return operands[0];
}
// First, basic type validation; we'll make sure all the operands are numeric and agree on vector length.
// This value will be `null` for a scalar type, otherwise the length of the vector type.
const vector_len: ?u64 = vec_len: {
const first_operand_ty = sema.typeOf(operands[0]);
try sema.checkNumericType(block, operand_srcs[0], first_operand_ty);
if (first_operand_ty.zigTypeTag(zcu) == .vector) {
const vec_len = first_operand_ty.vectorLen(zcu);
for (operands[1..], operand_srcs[1..]) |operand, operand_src| {
const operand_ty = sema.typeOf(operand);
try sema.checkNumericType(block, operand_src, operand_ty);
if (operand_ty.zigTypeTag(zcu) != .vector) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(operand_src, "expected vector, found '{f}'", .{operand_ty.fmt(pt)});
errdefer msg.destroy(zcu.gpa);
try sema.errNote(operand_srcs[0], msg, "vector operand here", .{});
break :msg msg;
});
}
if (operand_ty.vectorLen(zcu) != vec_len) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(operand_src, "expected vector of length '{d}', found '{f}'", .{ vec_len, operand_ty.fmt(pt) });
errdefer msg.destroy(zcu.gpa);
try sema.errNote(operand_srcs[0], msg, "vector of length '{d}' here", .{vec_len});
break :msg msg;
});
}
}
break :vec_len vec_len;
} else {
for (operands[1..], operand_srcs[1..]) |operand, operand_src| {
const operand_ty = sema.typeOf(operand);
try sema.checkNumericType(block, operand_src, operand_ty);
if (operand_ty.zigTypeTag(zcu) == .vector) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(operand_srcs[0], "expected vector, found '{f}'", .{first_operand_ty.fmt(pt)});
errdefer msg.destroy(zcu.gpa);
try sema.errNote(operand_src, msg, "vector operand here", .{});
break :msg msg;
});
}
}
break :vec_len null;
}
};
// Now we want to look at the scalar types. If any is a float, our result will be a float. This
// union is in "priority" order: `float` overrides `comptime_float` overrides `int`.
const TypeStrat = union(enum) {
float: Type,
comptime_float,
int: struct {
/// If this is still `true` at the end, we will just use a `comptime_int`.
all_comptime_int: bool,
// These two fields tells us about the *result* type, which is refined based on operand types.
// e.g. `@max(u32, i64)` results in a `u63`, because the result is >=0 and <=maxInt(i64).
result_min: Value,
result_max: Value,
// These two fields tell us the *intermediate* type to use for actually computing the min/max.
// e.g. `@max(u32, i64)` uses an intermediate `i64`, because it can fit all our operands.
operand_min: Value,
operand_max: Value,
},
none,
};
var cur_strat: TypeStrat = .none;
for (operands) |operand| {
const operand_scalar_ty = sema.typeOf(operand).scalarType(zcu);
const want_strat: TypeStrat = switch (operand_scalar_ty.zigTypeTag(zcu)) {
.comptime_int => s: {
const val = (try sema.resolveValueResolveLazy(operand)).?;
if (val.isUndef(zcu)) break :s .none;
break :s .{ .int = .{
.all_comptime_int = true,
.result_min = val,
.result_max = val,
.operand_min = val,
.operand_max = val,
} };
},
.comptime_float => .comptime_float,
.float => .{ .float = operand_scalar_ty },
.int => s: {
// If the *value* is comptime-known, we will use that to get tighter bounds. If #3806
// is accepted and implemented, so that integer literals have a tightly-bounded ranged
// integer type (and `comptime_int` ceases to exist), this block should probably go away
// (replaced with just the simple calls to `Type.minInt`/`Type.maxInt`) so that we only
// use the input *types* to determine the result type.
const min: Value, const max: Value = bounds: {
if (try sema.resolveValueResolveLazy(operand)) |operand_val| {
if (vector_len) |len| {
var min = try operand_val.elemValue(pt, 0);
var max = min;
for (1..@intCast(len)) |elem_idx| {
const elem_val = try operand_val.elemValue(pt, elem_idx);
min = Value.numberMin(min, elem_val, zcu);
max = Value.numberMax(max, elem_val, zcu);
}
if (!min.isUndef(zcu) and !max.isUndef(zcu)) {
break :bounds .{ min, max };
}
} else {
if (!operand_val.isUndef(zcu)) {
break :bounds .{ operand_val, operand_val };
}
}
}
break :bounds .{
try operand_scalar_ty.minInt(pt, operand_scalar_ty),
try operand_scalar_ty.maxInt(pt, operand_scalar_ty),
};
};
break :s .{ .int = .{
.all_comptime_int = false,
.result_min = min,
.result_max = max,
.operand_min = min,
.operand_max = max,
} };
},
else => unreachable,
};
if (@intFromEnum(want_strat) < @intFromEnum(cur_strat)) {
// `want_strat` overrides `cur_strat`.
cur_strat = want_strat;
} else if (@intFromEnum(want_strat) == @intFromEnum(cur_strat)) {
// The behavior depends on the tag.
switch (cur_strat) {
.none, .comptime_float => {}, // no payload, so nop
.float => |cur_float| {
const want_float = want_strat.float;
// Select the larger bit size. If the bit size is the same, select whichever is not c_longdouble.
const cur_bits = cur_float.floatBits(zcu.getTarget());
const want_bits = want_float.floatBits(zcu.getTarget());
if (want_bits > cur_bits or
(want_bits == cur_bits and
cur_float.toIntern() == .c_longdouble_type and
want_float.toIntern() != .c_longdouble_type))
{
cur_strat = want_strat;
}
},
.int => |*cur_int| {
const want_int = want_strat.int;
if (!want_int.all_comptime_int) cur_int.all_comptime_int = false;
cur_int.result_min = opFunc(cur_int.result_min, want_int.result_min, zcu);
cur_int.result_max = opFunc(cur_int.result_max, want_int.result_max, zcu);
cur_int.operand_min = Value.numberMin(cur_int.operand_min, want_int.operand_min, zcu);
cur_int.operand_max = Value.numberMax(cur_int.operand_max, want_int.operand_max, zcu);
},
}
}
}
// Use `cur_strat` to actually resolve the result type (and intermediate type).
const result_scalar_ty: Type, const intermediate_scalar_ty: Type = switch (cur_strat) {
.float => |ty| .{ ty, ty },
.comptime_float => .{ .comptime_float, .comptime_float },
.int => |int| if (int.all_comptime_int) .{
.comptime_int,
.comptime_int,
} else .{
try pt.intFittingRange(int.result_min, int.result_max),
try pt.intFittingRange(int.operand_min, int.operand_max),
},
.none => .{ .comptime_int, .comptime_int }, // all undef comptime ints
};
const result_ty: Type = if (vector_len) |l| try pt.vectorType(.{
.len = @intCast(l),
.child = result_scalar_ty.toIntern(),
}) else result_scalar_ty;
const intermediate_ty: Type = if (vector_len) |l| try pt.vectorType(.{
.len = @intCast(l),
.child = intermediate_scalar_ty.toIntern(),
}) else intermediate_scalar_ty;
// This value, if not `null`, will have type `intermediate_ty`.
const comptime_part: ?Value = ct: {
// Contains the comptime-known scalar result values.
// Values are scalars with no particular type.
// `elems.len` is `vector_len orelse 1`.
const elems: []InternPool.Index = try sema.arena.alloc(
InternPool.Index,
try sema.usizeCast(block, src, vector_len orelse 1),
);
// If `false`, we've not seen any comptime-known operand yet, so `elems` contains `undefined`.
// Otherwise, `elems` is populated with the comptime-known results so far.
var elems_populated = false;
// Populated when we see a runtime-known operand.
var opt_runtime_src: ?LazySrcLoc = null;
for (operands, operand_srcs) |operand, operand_src| {
const operand_val = try sema.resolveValueResolveLazy(operand) orelse {
if (opt_runtime_src == null) opt_runtime_src = operand_src;
continue;
};
if (vector_len) |len| {
// Vector case; apply `opFunc` to each element.
if (elems_populated) {
for (elems, 0..@intCast(len)) |*elem, elem_idx| {
const new_elem = try operand_val.elemValue(pt, elem_idx);
elem.* = opFunc(.fromInterned(elem.*), new_elem, zcu).toIntern();
}
} else {
elems_populated = true;
for (elems, 0..@intCast(len)) |*elem_out, elem_idx| {
elem_out.* = (try operand_val.elemValue(pt, elem_idx)).toIntern();
}
}
} else {
// Scalar case; just apply `opFunc`.
if (elems_populated) {
elems[0] = opFunc(.fromInterned(elems[0]), operand_val, zcu).toIntern();
} else {
elems_populated = true;
elems[0] = operand_val.toIntern();
}
}
}
const runtime_src = opt_runtime_src orelse {
// The result is comptime-known. Coerce each element to its scalar type.
assert(elems_populated);
for (elems) |*elem| {
if (Value.fromInterned(elem.*).isUndef(zcu)) {
elem.* = (try pt.undefValue(result_scalar_ty)).toIntern();
} else {
// This coercion will always succeed, because `result_scalar_ty` can definitely hold the result.
const coerced_ref = try sema.coerce(block, result_scalar_ty, Air.internedToRef(elem.*), .unneeded);
elem.* = coerced_ref.toInterned().?;
}
}
if (vector_len == null) return Air.internedToRef(elems[0]);
return Air.internedToRef((try pt.aggregateValue(result_ty, elems)).toIntern());
};
_ = runtime_src;
// The result is runtime-known.
// Coerce each element to the intermediate scalar type, unless there were no comptime-known operands.
if (!elems_populated) break :ct null;
for (elems) |*elem| {
if (Value.fromInterned(elem.*).isUndef(zcu)) {
elem.* = (try pt.undefValue(intermediate_scalar_ty)).toIntern();
} else {
// This coercion will always succeed, because `intermediate_scalar_ty` can definitely hold all operands.
const coerced_ref = try sema.coerce(block, intermediate_scalar_ty, Air.internedToRef(elem.*), .unneeded);
elem.* = coerced_ref.toInterned().?;
}
}
break :ct if (vector_len != null)
try pt.aggregateValue(intermediate_ty, elems)
else
.fromInterned(elems[0]);
};
// Time to emit the runtime operations. All runtime-known peers are coerced to `intermediate_ty`, and we cast down to `result_ty` at the end.
// `.none` indicates no result so far.
var cur_result: Air.Inst.Ref = if (comptime_part) |val| Air.internedToRef(val.toIntern()) else .none;
for (operands, operand_srcs) |operand, operand_src| {
if (try sema.isComptimeKnown(operand)) continue; // already in `comptime_part`
// This coercion could fail; e.g. coercing a runtime integer peer to a `comptime_float` in a case like `@min(runtime_int, 1.5)`.
const operand_coerced = try sema.coerce(block, intermediate_ty, operand, operand_src);
if (cur_result == .none) {
cur_result = operand_coerced;
} else {
cur_result = try block.addBinOp(air_tag, cur_result, operand_coerced);
}
}
assert(cur_result != .none);
assert(sema.typeOf(cur_result).toIntern() == intermediate_ty.toIntern());
// If there is a comptime-known undef operand, we actually return comptime-known undef -- but we had to do the runtime stuff to check for coercion errors.
if (comptime_part) |val| {
if (val.isUndef(zcu)) {
return pt.undefRef(result_ty);
}
}
if (result_ty.toIntern() == intermediate_ty.toIntern()) {
// No final cast needed; we're all done.
return cur_result;
}
// A final cast is needed. The only case where `intermediate_ty` is different is for integers,
// where we have refined the range, so we should be doing an intcast.
assert(intermediate_scalar_ty.zigTypeTag(zcu) == .int);
assert(result_scalar_ty.zigTypeTag(zcu) == .int);
return block.addTyOp(.intcast, result_ty, cur_result);
}
fn upgradeToArrayPtr(sema: *Sema, block: *Block, ptr: Air.Inst.Ref, len: u64) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ptr_ty = sema.typeOf(ptr);
const info = ptr_ty.ptrInfo(zcu);
if (info.flags.size == .one) {
// Already an array pointer.
return ptr;
}
const new_ty = try pt.ptrTypeSema(.{
.child = (try pt.arrayType(.{
.len = len,
.sentinel = info.sentinel,
.child = info.child,
})).toIntern(),
.flags = .{
.alignment = info.flags.alignment,
.is_const = info.flags.is_const,
.is_volatile = info.flags.is_volatile,
.is_allowzero = info.flags.is_allowzero,
.address_space = info.flags.address_space,
},
});
const non_slice_ptr = if (info.flags.size == .slice)
try block.addTyOp(.slice_ptr, ptr_ty.slicePtrFieldType(zcu), ptr)
else
ptr;
return block.addBitCast(new_ty, non_slice_ptr);
}
fn zirMemcpy(
sema: *Sema,
block: *Block,
inst: Zir.Inst.Index,
air_tag: Air.Inst.Tag,
check_aliasing: bool,
) CompileError!void {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const dest_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const src_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const dest_ptr = try sema.resolveInst(extra.lhs);
const src_ptr = try sema.resolveInst(extra.rhs);
const dest_ty = sema.typeOf(dest_ptr);
const src_ty = sema.typeOf(src_ptr);
const dest_len = try indexablePtrLenOrNone(sema, block, dest_src, dest_ptr);
const src_len = try indexablePtrLenOrNone(sema, block, src_src, src_ptr);
const pt = sema.pt;
const zcu = pt.zcu;
if (dest_ty.isConstPtr(zcu)) {
return sema.fail(block, dest_src, "cannot copy to constant pointer", .{});
}
if (dest_len == .none and src_len == .none) {
const msg = msg: {
const msg = try sema.errMsg(src, "unknown copy length", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(dest_src, msg, "destination type '{f}' provides no length", .{
dest_ty.fmt(pt),
});
try sema.errNote(src_src, msg, "source type '{f}' provides no length", .{
src_ty.fmt(pt),
});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const dest_elem_ty = dest_ty.indexablePtrElem(zcu);
const src_elem_ty = src_ty.indexablePtrElem(zcu);
const imc = try sema.coerceInMemoryAllowed(
block,
dest_elem_ty,
src_elem_ty,
false,
zcu.getTarget(),
dest_src,
src_src,
null,
);
if (imc != .ok) return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(
src,
"pointer element type '{f}' cannot coerce into element type '{f}'",
.{ src_elem_ty.fmt(pt), dest_elem_ty.fmt(pt) },
);
errdefer msg.destroy(sema.gpa);
try imc.report(sema, src, msg);
break :msg msg;
});
var len_val: ?Value = null;
if (dest_len != .none and src_len != .none) check: {
// If we can check at compile-time, no need for runtime safety.
if (try sema.resolveDefinedValue(block, dest_src, dest_len)) |dest_len_val| {
len_val = dest_len_val;
if (try sema.resolveDefinedValue(block, src_src, src_len)) |src_len_val| {
if (!(try sema.valuesEqual(dest_len_val, src_len_val, .usize))) {
const msg = msg: {
const msg = try sema.errMsg(src, "non-matching copy lengths", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(dest_src, msg, "length {f} here", .{
dest_len_val.fmtValueSema(pt, sema),
});
try sema.errNote(src_src, msg, "length {f} here", .{
src_len_val.fmtValueSema(pt, sema),
});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
break :check;
}
} else if (try sema.resolveDefinedValue(block, src_src, src_len)) |src_len_val| {
len_val = src_len_val;
}
if (block.wantSafety()) {
const ok = try block.addBinOp(.cmp_eq, dest_len, src_len);
try sema.addSafetyCheck(block, src, ok, .copy_len_mismatch);
}
} else if (dest_len != .none) {
if (try sema.resolveDefinedValue(block, dest_src, dest_len)) |dest_len_val| {
len_val = dest_len_val;
}
} else if (src_len != .none) {
if (try sema.resolveDefinedValue(block, src_src, src_len)) |src_len_val| {
len_val = src_len_val;
}
}
zero_bit: {
const src_comptime = try src_elem_ty.comptimeOnlySema(pt);
const dest_comptime = try dest_elem_ty.comptimeOnlySema(pt);
assert(src_comptime == dest_comptime); // IMC
if (src_comptime) break :zero_bit;
const src_has_bits = try src_elem_ty.hasRuntimeBitsIgnoreComptimeSema(pt);
const dest_has_bits = try dest_elem_ty.hasRuntimeBitsIgnoreComptimeSema(pt);
assert(src_has_bits == dest_has_bits); // IMC
if (src_has_bits) break :zero_bit;
// The element type is zero-bit. We've done all validation (aside from the aliasing check,
// which we must skip) so we're done.
return;
}
const runtime_src = rs: {
const dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr) orelse break :rs dest_src;
const src_ptr_val = try sema.resolveDefinedValue(block, src_src, src_ptr) orelse break :rs src_src;
const raw_dest_ptr = if (dest_ty.isSlice(zcu)) dest_ptr_val.slicePtr(zcu) else dest_ptr_val;
const raw_src_ptr = if (src_ty.isSlice(zcu)) src_ptr_val.slicePtr(zcu) else src_ptr_val;
const len_u64 = try len_val.?.toUnsignedIntSema(pt);
if (check_aliasing) {
if (Value.doPointersOverlap(
raw_src_ptr,
raw_dest_ptr,
len_u64,
zcu,
)) return sema.fail(block, src, "'@memcpy' arguments alias", .{});
}
if (!sema.isComptimeMutablePtr(dest_ptr_val)) break :rs dest_src;
// Because comptime pointer access is a somewhat expensive operation, we implement @memcpy
// as one load and store of an array, rather than N loads and stores of individual elements.
const array_ty = try pt.arrayType(.{
.child = dest_elem_ty.toIntern(),
.len = len_u64,
});
const dest_array_ptr_ty = try pt.ptrType(info: {
var info = dest_ty.ptrInfo(zcu);
info.flags.size = .one;
info.child = array_ty.toIntern();
info.sentinel = .none;
break :info info;
});
const src_array_ptr_ty = try pt.ptrType(info: {
var info = src_ty.ptrInfo(zcu);
info.flags.size = .one;
info.child = array_ty.toIntern();
info.sentinel = .none;
break :info info;
});
const coerced_dest_ptr = try pt.getCoerced(raw_dest_ptr, dest_array_ptr_ty);
const coerced_src_ptr = try pt.getCoerced(raw_src_ptr, src_array_ptr_ty);
const array_val = try sema.pointerDeref(block, src_src, coerced_src_ptr, src_array_ptr_ty) orelse break :rs src_src;
try sema.storePtrVal(block, dest_src, coerced_dest_ptr, array_val, array_ty);
return;
};
// If the length is comptime-known, then upgrade src and destination types
// into pointer-to-array. At this point we know they are both pointers
// already.
var new_dest_ptr = dest_ptr;
var new_src_ptr = src_ptr;
if (len_val) |val| {
const len = try val.toUnsignedIntSema(pt);
if (len == 0) {
// This AIR instruction guarantees length > 0 if it is comptime-known.
return;
}
new_dest_ptr = try upgradeToArrayPtr(sema, block, dest_ptr, len);
new_src_ptr = try upgradeToArrayPtr(sema, block, src_ptr, len);
}
if (dest_len != .none) {
// Change the src from slice to a many pointer, to avoid multiple ptr
// slice extractions in AIR instructions.
const new_src_ptr_ty = sema.typeOf(new_src_ptr);
if (new_src_ptr_ty.isSlice(zcu)) {
new_src_ptr = try sema.analyzeSlicePtr(block, src_src, new_src_ptr, new_src_ptr_ty);
}
} else if (dest_len == .none and len_val == null) {
// Change the dest to a slice, since its type must have the length.
const dest_ptr_ptr = try sema.analyzeRef(block, dest_src, new_dest_ptr);
new_dest_ptr = try sema.analyzeSlice(block, dest_src, dest_ptr_ptr, .zero, src_len, .none, LazySrcLoc.unneeded, dest_src, dest_src, dest_src, false);
const new_src_ptr_ty = sema.typeOf(new_src_ptr);
if (new_src_ptr_ty.isSlice(zcu)) {
new_src_ptr = try sema.analyzeSlicePtr(block, src_src, new_src_ptr, new_src_ptr_ty);
}
}
try sema.requireRuntimeBlock(block, src, runtime_src);
try sema.validateRuntimeValue(block, dest_src, dest_ptr);
try sema.validateRuntimeValue(block, src_src, src_ptr);
// Aliasing safety check.
if (check_aliasing and block.wantSafety()) {
const len = if (len_val) |v|
Air.internedToRef(v.toIntern())
else if (dest_len != .none)
dest_len
else
src_len;
// Extract raw pointer from dest slice. The AIR instructions could support them, but
// it would cause redundant machine code instructions.
const new_dest_ptr_ty = sema.typeOf(new_dest_ptr);
const raw_dest_ptr = if (new_dest_ptr_ty.isSlice(zcu))
try sema.analyzeSlicePtr(block, dest_src, new_dest_ptr, new_dest_ptr_ty)
else if (new_dest_ptr_ty.ptrSize(zcu) == .one) ptr: {
var dest_manyptr_ty_key = zcu.intern_pool.indexToKey(new_dest_ptr_ty.toIntern()).ptr_type;
assert(dest_manyptr_ty_key.flags.size == .one);
dest_manyptr_ty_key.child = dest_elem_ty.toIntern();
dest_manyptr_ty_key.flags.size = .many;
break :ptr try sema.coerceCompatiblePtrs(block, try pt.ptrTypeSema(dest_manyptr_ty_key), new_dest_ptr, dest_src);
} else new_dest_ptr;
const new_src_ptr_ty = sema.typeOf(new_src_ptr);
const raw_src_ptr = if (new_src_ptr_ty.isSlice(zcu))
try sema.analyzeSlicePtr(block, src_src, new_src_ptr, new_src_ptr_ty)
else if (new_src_ptr_ty.ptrSize(zcu) == .one) ptr: {
var src_manyptr_ty_key = zcu.intern_pool.indexToKey(new_src_ptr_ty.toIntern()).ptr_type;
assert(src_manyptr_ty_key.flags.size == .one);
src_manyptr_ty_key.child = src_elem_ty.toIntern();
src_manyptr_ty_key.flags.size = .many;
break :ptr try sema.coerceCompatiblePtrs(block, try pt.ptrTypeSema(src_manyptr_ty_key), new_src_ptr, src_src);
} else new_src_ptr;
// ok1: dest >= src + len
// ok2: src >= dest + len
const src_plus_len = try sema.analyzePtrArithmetic(block, src, raw_src_ptr, len, .ptr_add, src_src, src);
const dest_plus_len = try sema.analyzePtrArithmetic(block, src, raw_dest_ptr, len, .ptr_add, dest_src, src);
const ok1 = try block.addBinOp(.cmp_gte, raw_dest_ptr, src_plus_len);
const ok2 = try block.addBinOp(.cmp_gte, new_src_ptr, dest_plus_len);
const ok = try block.addBinOp(.bool_or, ok1, ok2);
try sema.addSafetyCheck(block, src, ok, .memcpy_alias);
}
_ = try block.addInst(.{
.tag = air_tag,
.data = .{ .bin_op = .{
.lhs = new_dest_ptr,
.rhs = new_src_ptr,
} },
});
}
fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data;
const src = block.nodeOffset(inst_data.src_node);
const dest_src = block.builtinCallArgSrc(inst_data.src_node, 0);
const value_src = block.builtinCallArgSrc(inst_data.src_node, 1);
const dest_ptr = try sema.resolveInst(extra.lhs);
const uncoerced_elem = try sema.resolveInst(extra.rhs);
const dest_ptr_ty = sema.typeOf(dest_ptr);
try checkMemOperand(sema, block, dest_src, dest_ptr_ty);
if (dest_ptr_ty.isConstPtr(zcu)) {
return sema.fail(block, dest_src, "cannot memset constant pointer", .{});
}
const dest_elem_ty: Type = dest_elem_ty: {
const ptr_info = dest_ptr_ty.ptrInfo(zcu);
switch (ptr_info.flags.size) {
.slice => break :dest_elem_ty .fromInterned(ptr_info.child),
.one => {
if (Type.fromInterned(ptr_info.child).zigTypeTag(zcu) == .array) {
break :dest_elem_ty Type.fromInterned(ptr_info.child).childType(zcu);
}
},
.many, .c => {},
}
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "unknown @memset length", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(dest_src, msg, "destination type '{f}' provides no length", .{
dest_ptr_ty.fmt(pt),
});
break :msg msg;
});
};
const elem = try sema.coerce(block, dest_elem_ty, uncoerced_elem, value_src);
const runtime_src = rs: {
const len_air_ref = try sema.fieldVal(block, src, dest_ptr, try ip.getOrPutString(gpa, pt.tid, "len", .no_embedded_nulls), dest_src);
const len_val = (try sema.resolveDefinedValue(block, dest_src, len_air_ref)) orelse break :rs dest_src;
const len_u64 = try len_val.toUnsignedIntSema(pt);
const len = try sema.usizeCast(block, dest_src, len_u64);
if (len == 0) {
// This AIR instruction guarantees length > 0 if it is comptime-known.
return;
}
const ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr) orelse break :rs dest_src;
if (!sema.isComptimeMutablePtr(ptr_val)) break :rs dest_src;
const elem_val = try sema.resolveValue(elem) orelse break :rs value_src;
const array_ty = try pt.arrayType(.{
.child = dest_elem_ty.toIntern(),
.len = len_u64,
});
const array_val = try pt.aggregateSplatValue(array_ty, elem_val);
const array_ptr_ty = ty: {
var info = dest_ptr_ty.ptrInfo(zcu);
info.flags.size = .one;
info.child = array_ty.toIntern();
break :ty try pt.ptrType(info);
};
const raw_ptr_val = if (dest_ptr_ty.isSlice(zcu)) ptr_val.slicePtr(zcu) else ptr_val;
const array_ptr_val = try pt.getCoerced(raw_ptr_val, array_ptr_ty);
return sema.storePtrVal(block, src, array_ptr_val, array_val, array_ty);
};
try sema.requireRuntimeBlock(block, src, runtime_src);
try sema.validateRuntimeValue(block, dest_src, dest_ptr);
try sema.validateRuntimeValue(block, value_src, elem);
_ = try block.addInst(.{
.tag = if (block.wantSafety()) .memset_safe else .memset,
.data = .{ .bin_op = .{
.lhs = dest_ptr,
.rhs = elem,
} },
});
}
fn zirResume(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].un_node;
const src = block.nodeOffset(inst_data.src_node);
return sema.failWithUseOfAsync(block, src);
}
fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_data = sema.code.instructions.items(.data)[@intFromEnum(inst)].pl_node;
const extra = sema.code.extraData(Zir.Inst.FuncFancy, inst_data.payload_index);
const target = zcu.getTarget();
const cc_src = block.src(.{ .node_offset_fn_type_cc = inst_data.src_node });
const ret_src = block.src(.{ .node_offset_fn_type_ret_ty = inst_data.src_node });
const has_body = extra.data.body_len != 0;
var extra_index: usize = extra.end;
const cc: std.builtin.CallingConvention = if (extra.data.bits.has_cc_body) blk: {
const body_len = sema.code.extra[extra_index];
extra_index += 1;
const body = sema.code.bodySlice(extra_index, body_len);
extra_index += body.len;
const cc_ty = try sema.getBuiltinType(cc_src, .CallingConvention);
const val = try sema.resolveGenericBody(block, cc_src, body, inst, cc_ty, .{ .simple = .@"callconv" });
break :blk try sema.analyzeValueAsCallconv(block, cc_src, val);
} else if (extra.data.bits.has_cc_ref) blk: {
const cc_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
const cc_ty = try sema.getBuiltinType(cc_src, .CallingConvention);
const uncoerced_cc = try sema.resolveInst(cc_ref);
const coerced_cc = try sema.coerce(block, cc_ty, uncoerced_cc, cc_src);
const cc_val = try sema.resolveConstDefinedValue(block, cc_src, coerced_cc, .{ .simple = .@"callconv" });
break :blk try sema.analyzeValueAsCallconv(block, cc_src, cc_val);
} else cc: {
if (has_body) {
const func_decl_nav = sema.owner.unwrap().nav_val;
const func_decl_inst = ip.getNav(func_decl_nav).analysis.?.zir_index.resolve(&zcu.intern_pool) orelse return error.AnalysisFail;
const zir_decl = sema.code.getDeclaration(func_decl_inst);
if (zir_decl.linkage == .@"export") {
break :cc target.cCallingConvention() orelse {
// This target has no default C calling convention. We sometimes trigger a similar
// error by trying to evaluate `std.builtin.CallingConvention.c`, so for consistency,
// let's eval that now and just get the transitive error. (It's guaranteed to error
// because it does the exact `cCallingConvention` call we just did.)
const cc_type = try sema.getBuiltinType(cc_src, .CallingConvention);
_ = try sema.namespaceLookupVal(
block,
LazySrcLoc.unneeded,
cc_type.getNamespaceIndex(zcu),
try ip.getOrPutString(sema.gpa, pt.tid, "c", .no_embedded_nulls),
);
// The above should have errored.
@panic("std.builtin is corrupt");
};
}
}
break :cc .auto;
};
const ret_ty: Type = if (extra.data.bits.has_ret_ty_body) blk: {
const body_len = sema.code.extra[extra_index];
extra_index += 1;
const body = sema.code.bodySlice(extra_index, body_len);
extra_index += body.len;
if (extra.data.bits.ret_ty_is_generic) break :blk .generic_poison;
const val = try sema.resolveGenericBody(block, ret_src, body, inst, .type, .{ .simple = .fn_ret_ty });
const ty = val.toType();
break :blk ty;
} else if (extra.data.bits.has_ret_ty_ref) blk: {
const ret_ty_ref: Zir.Inst.Ref = @enumFromInt(sema.code.extra[extra_index]);
extra_index += 1;
if (extra.data.bits.ret_ty_is_generic) break :blk .generic_poison;
break :blk try sema.resolveType(block, ret_src, ret_ty_ref);
} else .void;
const noalias_bits: u32 = if (extra.data.bits.has_any_noalias) blk: {
const x = sema.code.extra[extra_index];
extra_index += 1;
break :blk x;
} else 0;
var src_locs: Zir.Inst.Func.SrcLocs = undefined;
if (has_body) {
extra_index += extra.data.body_len;
src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data;
}
const is_var_args = extra.data.bits.is_var_args;
const is_inferred_error = extra.data.bits.is_inferred_error;
const is_noinline = extra.data.bits.is_noinline;
return sema.funcCommon(
block,
inst_data.src_node,
inst,
cc,
ret_ty,
is_var_args,
is_inferred_error,
has_body,
src_locs,
noalias_bits,
is_noinline,
);
}
fn zirCUndef(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.builtinCallArgSrc(extra.node, 0);
const name = try sema.resolveConstString(block, src, extra.operand, .{ .simple = .operand_cUndef_macro_name });
try block.c_import_buf.?.print("#undef {s}\n", .{name});
return .void_value;
}
fn zirCInclude(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const src = block.builtinCallArgSrc(extra.node, 0);
const name = try sema.resolveConstString(block, src, extra.operand, .{ .simple = .operand_cInclude_file_name });
try block.c_import_buf.?.print("#include <{s}>\n", .{name});
return .void_value;
}
fn zirCDefine(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const name_src = block.builtinCallArgSrc(extra.node, 0);
const val_src = block.builtinCallArgSrc(extra.node, 1);
const name = try sema.resolveConstString(block, name_src, extra.lhs, .{ .simple = .operand_cDefine_macro_name });
const rhs = try sema.resolveInst(extra.rhs);
if (sema.typeOf(rhs).zigTypeTag(zcu) != .void) {
const value = try sema.resolveConstString(block, val_src, extra.rhs, .{ .simple = .operand_cDefine_macro_value });
try block.c_import_buf.?.print("#define {s} {s}\n", .{ name, value });
} else {
try block.c_import_buf.?.print("#define {s}\n", .{name});
}
return .void_value;
}
fn zirWasmMemorySize(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const index_src = block.builtinCallArgSrc(extra.node, 0);
const builtin_src = block.nodeOffset(extra.node);
const target = sema.pt.zcu.getTarget();
if (!target.cpu.arch.isWasm()) {
return sema.fail(block, builtin_src, "builtin @wasmMemorySize is available when targeting WebAssembly; targeted CPU architecture is {s}", .{@tagName(target.cpu.arch)});
}
const index: u32 = @intCast(try sema.resolveInt(block, index_src, extra.operand, .u32, .{ .simple = .wasm_memory_index }));
try sema.requireRuntimeBlock(block, builtin_src, null);
return block.addInst(.{
.tag = .wasm_memory_size,
.data = .{ .pl_op = .{
.operand = .none,
.payload = index,
} },
});
}
fn zirWasmMemoryGrow(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const builtin_src = block.nodeOffset(extra.node);
const index_src = block.builtinCallArgSrc(extra.node, 0);
const delta_src = block.builtinCallArgSrc(extra.node, 1);
const target = sema.pt.zcu.getTarget();
if (!target.cpu.arch.isWasm()) {
return sema.fail(block, builtin_src, "builtin @wasmMemoryGrow is available when targeting WebAssembly; targeted CPU architecture is {s}", .{@tagName(target.cpu.arch)});
}
const index: u32 = @intCast(try sema.resolveInt(block, index_src, extra.lhs, .u32, .{ .simple = .wasm_memory_index }));
const delta = try sema.coerce(block, .usize, try sema.resolveInst(extra.rhs), delta_src);
try sema.requireRuntimeBlock(block, builtin_src, null);
return block.addInst(.{
.tag = .wasm_memory_grow,
.data = .{ .pl_op = .{
.operand = delta,
.payload = index,
} },
});
}
fn resolvePrefetchOptions(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
) CompileError!std.builtin.PrefetchOptions {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const options_ty = try sema.getBuiltinType(src, .PrefetchOptions);
const options = try sema.coerce(block, options_ty, try sema.resolveInst(zir_ref), src);
const rw_src = block.src(.{ .init_field_rw = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const locality_src = block.src(.{ .init_field_locality = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const cache_src = block.src(.{ .init_field_cache = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const rw = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "rw", .no_embedded_nulls), rw_src);
const rw_val = try sema.resolveConstDefinedValue(block, rw_src, rw, .{ .simple = .prefetch_options });
const locality = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "locality", .no_embedded_nulls), locality_src);
const locality_val = try sema.resolveConstDefinedValue(block, locality_src, locality, .{ .simple = .prefetch_options });
const cache = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "cache", .no_embedded_nulls), cache_src);
const cache_val = try sema.resolveConstDefinedValue(block, cache_src, cache, .{ .simple = .prefetch_options });
return std.builtin.PrefetchOptions{
.rw = try sema.interpretBuiltinType(block, rw_src, rw_val, std.builtin.PrefetchOptions.Rw),
.locality = @intCast(try locality_val.toUnsignedIntSema(pt)),
.cache = try sema.interpretBuiltinType(block, cache_src, cache_val, std.builtin.PrefetchOptions.Cache),
};
}
fn zirPrefetch(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const ptr_src = block.builtinCallArgSrc(extra.node, 0);
const opts_src = block.builtinCallArgSrc(extra.node, 1);
const ptr = try sema.resolveInst(extra.lhs);
try sema.checkPtrOperand(block, ptr_src, sema.typeOf(ptr));
const options = try sema.resolvePrefetchOptions(block, opts_src, extra.rhs);
if (!block.isComptime()) {
_ = try block.addInst(.{
.tag = .prefetch,
.data = .{ .prefetch = .{
.ptr = ptr,
.rw = options.rw,
.locality = options.locality,
.cache = options.cache,
} },
});
}
return .void_value;
}
fn resolveExternOptions(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
) CompileError!struct {
name: InternPool.NullTerminatedString,
library_name: InternPool.OptionalNullTerminatedString,
linkage: std.builtin.GlobalLinkage,
visibility: std.builtin.SymbolVisibility,
is_thread_local: bool,
is_dll_import: bool,
relocation: std.builtin.ExternOptions.Relocation,
} {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const options_inst = try sema.resolveInst(zir_ref);
const extern_options_ty = try sema.getBuiltinType(src, .ExternOptions);
const options = try sema.coerce(block, extern_options_ty, options_inst, src);
const name_src = block.src(.{ .init_field_name = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const library_src = block.src(.{ .init_field_library = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const linkage_src = block.src(.{ .init_field_linkage = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const visibility_src = block.src(.{ .init_field_visibility = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const thread_local_src = block.src(.{ .init_field_thread_local = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const dll_import_src = block.src(.{ .init_field_dll_import = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const relocation_src = block.src(.{ .init_field_relocation = src.offset.node_offset_builtin_call_arg.builtin_call_node });
const name_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "name", .no_embedded_nulls), name_src);
const name = try sema.toConstString(block, name_src, name_ref, .{ .simple = .extern_options });
const library_name_inst = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "library_name", .no_embedded_nulls), library_src);
const library_name_val = try sema.resolveConstDefinedValue(block, library_src, library_name_inst, .{ .simple = .extern_options });
const linkage_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "linkage", .no_embedded_nulls), linkage_src);
const linkage_val = try sema.resolveConstDefinedValue(block, linkage_src, linkage_ref, .{ .simple = .extern_options });
const linkage = try sema.interpretBuiltinType(block, linkage_src, linkage_val, std.builtin.GlobalLinkage);
const visibility_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "visibility", .no_embedded_nulls), visibility_src);
const visibility_val = try sema.resolveConstDefinedValue(block, visibility_src, visibility_ref, .{ .simple = .extern_options });
const visibility = try sema.interpretBuiltinType(block, visibility_src, visibility_val, std.builtin.SymbolVisibility);
const is_thread_local = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "is_thread_local", .no_embedded_nulls), thread_local_src);
const is_thread_local_val = try sema.resolveConstDefinedValue(block, thread_local_src, is_thread_local, .{ .simple = .extern_options });
const library_name = if (library_name_val.optionalValue(zcu)) |library_name_payload| library_name: {
const library_name = try sema.toConstString(block, library_src, Air.internedToRef(library_name_payload.toIntern()), .{ .simple = .extern_options });
if (library_name.len == 0) {
return sema.fail(block, library_src, "library name cannot be empty", .{});
}
try sema.handleExternLibName(block, library_src, library_name);
break :library_name library_name;
} else null;
const is_dll_import_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "is_dll_import", .no_embedded_nulls), dll_import_src);
const is_dll_import_val = try sema.resolveConstDefinedValue(block, dll_import_src, is_dll_import_ref, .{ .simple = .extern_options });
const relocation_ref = try sema.fieldVal(block, src, options, try ip.getOrPutString(gpa, pt.tid, "relocation", .no_embedded_nulls), relocation_src);
const relocation_val = try sema.resolveConstDefinedValue(block, relocation_src, relocation_ref, .{ .simple = .extern_options });
const relocation = try sema.interpretBuiltinType(block, relocation_src, relocation_val, std.builtin.ExternOptions.Relocation);
if (name.len == 0) {
return sema.fail(block, name_src, "extern symbol name cannot be empty", .{});
}
if (linkage != .weak and linkage != .strong) {
return sema.fail(block, linkage_src, "extern symbol must use strong or weak linkage", .{});
}
return .{
.name = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls),
.library_name = try ip.getOrPutStringOpt(gpa, pt.tid, library_name, .no_embedded_nulls),
.linkage = linkage,
.visibility = visibility,
.is_thread_local = is_thread_local_val.toBool(),
.is_dll_import = is_dll_import_val.toBool(),
.relocation = relocation,
};
}
fn zirBuiltinExtern(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data;
const src = block.nodeOffset(extra.node);
const ty_src = block.builtinCallArgSrc(extra.node, 0);
const options_src = block.builtinCallArgSrc(extra.node, 1);
var ty = try sema.resolveType(block, ty_src, extra.lhs);
if (!ty.isPtrAtRuntime(zcu)) {
return sema.fail(block, ty_src, "expected (optional) pointer", .{});
}
if (!try sema.validateExternType(ty, .other)) {
const msg = msg: {
const msg = try sema.errMsg(ty_src, "extern symbol cannot have type '{f}'", .{ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, ty_src, ty, .other);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const options = try sema.resolveExternOptions(block, options_src, extra.rhs);
switch (options.linkage) {
.internal => if (options.visibility != .default) {
return sema.fail(block, options_src, "internal symbol cannot have non-default visibility", .{});
},
.strong, .weak => {},
.link_once => return sema.fail(block, options_src, "external symbol cannot have link once linkage", .{}),
}
switch (options.relocation) {
.any => {},
.pcrel => if (options.visibility == .default) return sema.fail(block, options_src, "cannot require a pc-relative relocation to a symbol with default visibility", .{}),
}
// TODO: error for threadlocal functions, non-const functions, etc
if (options.linkage == .weak and !ty.ptrAllowsZero(zcu)) {
ty = try pt.optionalType(ty.toIntern());
}
const ptr_info = ty.ptrInfo(zcu);
const extern_val = try pt.getExtern(.{
.name = options.name,
.ty = ptr_info.child,
.lib_name = options.library_name,
.linkage = options.linkage,
.visibility = options.visibility,
.is_threadlocal = options.is_thread_local,
.is_dll_import = options.is_dll_import,
.relocation = options.relocation,
.is_const = ptr_info.flags.is_const,
.alignment = ptr_info.flags.alignment,
.@"addrspace" = ptr_info.flags.address_space,
// This instruction is just for source locations.
// `builtin_extern` doesn't provide enough information, and isn't currently tracked.
// So, for now, just use our containing `declaration`.
.zir_index = switch (sema.owner.unwrap()) {
.@"comptime" => |cu| ip.getComptimeUnit(cu).zir_index,
.type => |owner_ty| Type.fromInterned(owner_ty).typeDeclInst(zcu).?,
.memoized_state => unreachable,
.nav_ty, .nav_val => |nav| ip.getNav(nav).analysis.?.zir_index,
.func => |func| zir_index: {
const func_info = zcu.funcInfo(func);
const owner_func_info = if (func_info.generic_owner != .none) owner: {
break :owner zcu.funcInfo(func_info.generic_owner);
} else func_info;
break :zir_index ip.getNav(owner_func_info.owner_nav).analysis.?.zir_index;
},
},
.owner_nav = undefined, // ignored by `getExtern`
.source = .builtin,
});
const uncasted_ptr = try sema.analyzeNavRef(block, src, ip.indexToKey(extern_val).@"extern".owner_nav);
// We want to cast to `ty`, but that isn't necessarily an allowed coercion.
if (try sema.resolveValue(uncasted_ptr)) |uncasted_ptr_val| {
const casted_ptr_val = try pt.getCoerced(uncasted_ptr_val, ty);
return Air.internedToRef(casted_ptr_val.toIntern());
} else {
return block.addBitCast(ty, uncasted_ptr);
}
}
fn zirWorkItem(
sema: *Sema,
block: *Block,
extended: Zir.Inst.Extended.InstData,
zir_tag: Zir.Inst.Extended,
) CompileError!Air.Inst.Ref {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const dimension_src = block.builtinCallArgSrc(extra.node, 0);
const builtin_src = block.nodeOffset(extra.node);
const target = sema.pt.zcu.getTarget();
switch (target.cpu.arch) {
// TODO: Allow for other GPU targets.
.amdgcn, .spirv64, .spirv32, .nvptx, .nvptx64 => {},
else => {
return sema.fail(block, builtin_src, "builtin only available on GPU targets; targeted architecture is {s}", .{@tagName(target.cpu.arch)});
},
}
const dimension: u32 = @intCast(try sema.resolveInt(block, dimension_src, extra.operand, .u32, .{ .simple = .work_group_dim_index }));
try sema.requireRuntimeBlock(block, builtin_src, null);
return block.addInst(.{
.tag = switch (zir_tag) {
.work_item_id => .work_item_id,
.work_group_size => .work_group_size,
.work_group_id => .work_group_id,
else => unreachable,
},
.data = .{ .pl_op = .{
.operand = .none,
.payload = dimension,
} },
});
}
fn zirInComptime(
sema: *Sema,
block: *Block,
) CompileError!Air.Inst.Ref {
_ = sema;
return if (block.isComptime()) .bool_true else .bool_false;
}
fn zirBuiltinValue(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const src_node: std.zig.Ast.Node.Offset = @enumFromInt(@as(i32, @bitCast(extended.operand)));
const src = block.nodeOffset(src_node);
const value: Zir.Inst.BuiltinValue = @enumFromInt(extended.small);
const builtin_type: Zcu.BuiltinDecl = switch (value) {
// zig fmt: off
.atomic_order => .AtomicOrder,
.atomic_rmw_op => .AtomicRmwOp,
.calling_convention => .CallingConvention,
.address_space => .AddressSpace,
.float_mode => .FloatMode,
.signedness => .Signedness,
.reduce_op => .ReduceOp,
.call_modifier => .CallModifier,
.prefetch_options => .PrefetchOptions,
.export_options => .ExportOptions,
.extern_options => .ExternOptions,
.branch_hint => .BranchHint,
.clobbers => .@"assembly.Clobbers",
.pointer_size => .@"Type.Pointer.Size",
.pointer_attributes => .@"Type.Pointer.Attributes",
.fn_attributes, => .@"Type.Fn.Attributes",
.container_layout => .@"Type.ContainerLayout",
.enum_mode => .@"Type.Enum.Mode",
// zig fmt: on
// Values are handled here.
.calling_convention_c => {
const callconv_ty = try sema.getBuiltinType(src, .CallingConvention);
return try sema.namespaceLookupVal(
block,
src,
callconv_ty.getNamespaceIndex(zcu),
try ip.getOrPutString(gpa, pt.tid, "c", .no_embedded_nulls),
) orelse @panic("std.builtin is corrupt");
},
.calling_convention_inline => {
comptime assert(@typeInfo(std.builtin.CallingConvention.Tag).@"enum".tag_type == u8);
const callconv_ty = try sema.getBuiltinType(src, .CallingConvention);
const callconv_tag_ty = callconv_ty.unionTagType(zcu) orelse @panic("std.builtin is corrupt");
const inline_tag_val = try pt.enumValue(
callconv_tag_ty,
(try pt.intValue(
.u8,
@intFromEnum(std.builtin.CallingConvention.@"inline"),
)).toIntern(),
);
return sema.coerce(block, callconv_ty, Air.internedToRef(inline_tag_val.toIntern()), src);
},
};
return .fromType(try sema.getBuiltinType(src, builtin_type));
}
fn zirInplaceArithResultTy(sema: *Sema, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs = try sema.resolveInst(@enumFromInt(extended.operand));
const lhs_ty = sema.typeOf(lhs);
const op: Zir.Inst.InplaceOp = @enumFromInt(extended.small);
const ty: Type = switch (op) {
.add_eq => ty: {
const ptr_size = lhs_ty.ptrSizeOrNull(zcu) orelse break :ty lhs_ty;
switch (ptr_size) {
.one, .slice => break :ty lhs_ty, // invalid, let it error
.many, .c => break :ty .usize, // `[*]T + usize`
}
},
.sub_eq => ty: {
const ptr_size = lhs_ty.ptrSizeOrNull(zcu) orelse break :ty lhs_ty;
switch (ptr_size) {
.one, .slice => break :ty lhs_ty, // invalid, let it error
.many, .c => break :ty .generic_poison, // could be `[*]T - [*]T` or `[*]T - usize`
}
},
};
return Air.internedToRef(ty.toIntern());
}
fn zirBranchHint(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void {
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const uncoerced_hint = try sema.resolveInst(extra.operand);
const operand_src = block.builtinCallArgSrc(extra.node, 0);
const hint_ty = try sema.getBuiltinType(operand_src, .BranchHint);
const coerced_hint = try sema.coerce(block, hint_ty, uncoerced_hint, operand_src);
const hint_val = try sema.resolveConstDefinedValue(block, operand_src, coerced_hint, .{ .simple = .operand_branchHint });
// We only apply the first hint in a branch.
// This allows user-provided hints to override implicit cold hints.
if (sema.branch_hint == null) {
sema.branch_hint = try sema.interpretBuiltinType(block, operand_src, hint_val, std.builtin.BranchHint);
}
}
fn zirFloatOpResultType(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data;
const operand_src = block.builtinCallArgSrc(extra.node, 0);
const raw_ty = try sema.resolveTypeOrPoison(block, operand_src, extra.operand) orelse return .generic_poison_type;
const float_ty = raw_ty.optEuBaseType(zcu);
switch (float_ty.scalarType(zcu).zigTypeTag(zcu)) {
.float, .comptime_float => {},
else => return sema.fail(
block,
operand_src,
"expected vector of floats or float type, found '{f}'",
.{float_ty.fmt(sema.pt)},
),
}
return .fromType(float_ty);
}
fn requireRuntimeBlock(sema: *Sema, block: *Block, src: LazySrcLoc, runtime_src: ?LazySrcLoc) !void {
if (block.isComptime()) {
const msg, const fail_block = msg: {
const msg = try sema.errMsg(src, "unable to evaluate comptime expression", .{});
errdefer msg.destroy(sema.gpa);
if (runtime_src) |some| {
try sema.errNote(some, msg, "operation is runtime due to this operand", .{});
}
const fail_block = try block.explainWhyBlockIsComptime(msg);
break :msg .{ msg, fail_block };
};
return sema.failWithOwnedErrorMsg(fail_block, msg);
}
}
/// Emit a compile error if type cannot be used for a runtime variable.
pub fn validateVarType(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
var_ty: Type,
is_extern: bool,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
if (is_extern) {
if (!try sema.validateExternType(var_ty, .other)) {
const msg = msg: {
const msg = try sema.errMsg(src, "extern variable cannot have type '{f}'", .{var_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, src, var_ty, .other);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
} else {
if (var_ty.zigTypeTag(zcu) == .@"opaque") {
return sema.fail(
block,
src,
"non-extern variable with opaque type '{f}'",
.{var_ty.fmt(pt)},
);
}
}
if (!try var_ty.comptimeOnlySema(pt)) return;
const msg = msg: {
const msg = try sema.errMsg(src, "variable of type '{f}' must be const or comptime", .{var_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(msg, src, var_ty);
if (var_ty.zigTypeTag(zcu) == .comptime_int or var_ty.zigTypeTag(zcu) == .comptime_float) {
try sema.errNote(src, msg, "to modify this variable at runtime, it must be given an explicit fixed-size number type", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const TypeSet = std.AutoHashMapUnmanaged(InternPool.Index, void);
fn explainWhyTypeIsComptime(
sema: *Sema,
msg: *Zcu.ErrorMsg,
src_loc: LazySrcLoc,
ty: Type,
) CompileError!void {
var type_set = TypeSet{};
defer type_set.deinit(sema.gpa);
try ty.resolveFully(sema.pt);
return sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty, &type_set);
}
fn explainWhyTypeIsComptimeInner(
sema: *Sema,
msg: *Zcu.ErrorMsg,
src_loc: LazySrcLoc,
ty: Type,
type_set: *TypeSet,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
switch (ty.zigTypeTag(zcu)) {
.bool,
.int,
.float,
.error_set,
.@"enum",
.frame,
.@"anyframe",
.void,
=> return,
.@"fn" => {
try sema.errNote(src_loc, msg, "use '*const {f}' for a function pointer type", .{ty.fmt(pt)});
},
.type => {
try sema.errNote(src_loc, msg, "types are not available at runtime", .{});
},
.comptime_float,
.comptime_int,
.enum_literal,
.noreturn,
.undefined,
.null,
=> return,
.@"opaque" => {
try sema.errNote(src_loc, msg, "opaque type '{f}' has undefined size", .{ty.fmt(pt)});
},
.array, .vector => {
try sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty.childType(zcu), type_set);
},
.pointer => {
const elem_ty = ty.elemType2(zcu);
if (elem_ty.zigTypeTag(zcu) == .@"fn") {
const fn_info = zcu.typeToFunc(elem_ty).?;
if (fn_info.is_generic) {
try sema.errNote(src_loc, msg, "function is generic", .{});
}
switch (fn_info.cc) {
.@"inline" => try sema.errNote(src_loc, msg, "function has inline calling convention", .{}),
else => {},
}
if (Type.fromInterned(fn_info.return_type).comptimeOnly(zcu)) {
try sema.errNote(src_loc, msg, "function has a comptime-only return type", .{});
}
return;
}
try sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty.childType(zcu), type_set);
},
.optional => {
try sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty.optionalChild(zcu), type_set);
},
.error_union => {
try sema.explainWhyTypeIsComptimeInner(msg, src_loc, ty.errorUnionPayload(zcu), type_set);
},
.@"struct" => {
if ((try type_set.getOrPut(sema.gpa, ty.toIntern())).found_existing) return;
if (zcu.typeToStruct(ty)) |struct_type| {
for (0..struct_type.field_types.len) |i| {
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
const field_src: LazySrcLoc = .{
.base_node_inst = struct_type.zir_index,
.offset = .{ .container_field_type = @intCast(i) },
};
if (try field_ty.comptimeOnlySema(pt)) {
try sema.errNote(field_src, msg, "struct requires comptime because of this field", .{});
try sema.explainWhyTypeIsComptimeInner(msg, field_src, field_ty, type_set);
}
}
}
// TODO tuples
},
.@"union" => {
if ((try type_set.getOrPut(sema.gpa, ty.toIntern())).found_existing) return;
if (zcu.typeToUnion(ty)) |union_obj| {
for (0..union_obj.field_types.len) |i| {
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[i]);
const field_src: LazySrcLoc = .{
.base_node_inst = union_obj.zir_index,
.offset = .{ .container_field_type = @intCast(i) },
};
if (try field_ty.comptimeOnlySema(pt)) {
try sema.errNote(field_src, msg, "union requires comptime because of this field", .{});
try sema.explainWhyTypeIsComptimeInner(msg, field_src, field_ty, type_set);
}
}
}
},
}
}
const ExternPosition = enum {
ret_ty,
param_ty,
union_field,
struct_field,
element,
other,
};
/// Returns true if `ty` is allowed in extern types.
/// Does *NOT* require `ty` to be resolved in any way.
/// Calls `resolveLayout` for packed containers.
fn validateExternType(
sema: *Sema,
ty: Type,
position: ExternPosition,
) !bool {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.type,
.comptime_float,
.comptime_int,
.enum_literal,
.undefined,
.null,
.error_union,
.error_set,
.frame,
=> return false,
.void => return position == .union_field or position == .ret_ty or position == .struct_field or position == .element,
.noreturn => return position == .ret_ty,
.@"opaque",
.bool,
.float,
.@"anyframe",
=> return true,
.pointer => {
if (ty.childType(zcu).zigTypeTag(zcu) == .@"fn") {
return ty.isConstPtr(zcu) and try sema.validateExternType(ty.childType(zcu), .other);
}
return !(ty.isSlice(zcu) or try ty.comptimeOnlySema(pt));
},
.int => switch (ty.intInfo(zcu).bits) {
0, 8, 16, 32, 64, 128 => return true,
else => return false,
},
.@"fn" => {
if (position != .other) return false;
// For now we want to authorize PTX kernel to use zig objects, even if we end up exposing the ABI.
// The goal is to experiment with more integrated CPU/GPU code.
if (ty.fnCallingConvention(zcu) == .nvptx_kernel) {
return true;
}
return !target_util.fnCallConvAllowsZigTypes(ty.fnCallingConvention(zcu));
},
.@"enum" => {
return sema.validateExternType(ty.intTagType(zcu), position);
},
.@"struct", .@"union" => switch (ty.containerLayout(zcu)) {
.@"extern" => return true,
.@"packed" => {
const bit_size = try ty.bitSizeSema(pt);
switch (bit_size) {
0, 8, 16, 32, 64, 128 => return true,
else => return false,
}
},
.auto => return !(try ty.hasRuntimeBitsSema(pt)),
},
.array => {
if (position == .ret_ty or position == .param_ty) return false;
return sema.validateExternType(ty.elemType2(zcu), .element);
},
.vector => return sema.validateExternType(ty.elemType2(zcu), .element),
.optional => return ty.isPtrLikeOptional(zcu),
}
}
fn explainWhyTypeIsNotExtern(
sema: *Sema,
msg: *Zcu.ErrorMsg,
src_loc: LazySrcLoc,
ty: Type,
position: ExternPosition,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.@"opaque",
.bool,
.float,
.@"anyframe",
=> return,
.type,
.comptime_float,
.comptime_int,
.enum_literal,
.undefined,
.null,
.error_union,
.error_set,
.frame,
=> return,
.pointer => {
if (ty.isSlice(zcu)) {
try sema.errNote(src_loc, msg, "slices have no guaranteed in-memory representation", .{});
} else {
const pointee_ty = ty.childType(zcu);
if (!ty.isConstPtr(zcu) and pointee_ty.zigTypeTag(zcu) == .@"fn") {
try sema.errNote(src_loc, msg, "pointer to extern function must be 'const'", .{});
} else if (try ty.comptimeOnlySema(pt)) {
try sema.errNote(src_loc, msg, "pointer to comptime-only type '{f}'", .{pointee_ty.fmt(pt)});
try sema.explainWhyTypeIsComptime(msg, src_loc, ty);
}
try sema.explainWhyTypeIsNotExtern(msg, src_loc, pointee_ty, .other);
}
},
.void => try sema.errNote(src_loc, msg, "'void' is a zero bit type; for C 'void' use 'anyopaque'", .{}),
.noreturn => try sema.errNote(src_loc, msg, "'noreturn' is only allowed as a return type", .{}),
.int => if (!std.math.isPowerOfTwo(ty.intInfo(zcu).bits)) {
try sema.errNote(src_loc, msg, "only integers with 0 or power of two bits are extern compatible", .{});
} else {
try sema.errNote(src_loc, msg, "only integers with 0, 8, 16, 32, 64 and 128 bits are extern compatible", .{});
},
.@"fn" => {
if (position != .other) {
try sema.errNote(src_loc, msg, "type has no guaranteed in-memory representation", .{});
try sema.errNote(src_loc, msg, "use '*const ' to make a function pointer type", .{});
return;
}
switch (ty.fnCallingConvention(zcu)) {
.auto => try sema.errNote(src_loc, msg, "extern function must specify calling convention", .{}),
.async => try sema.errNote(src_loc, msg, "async function cannot be extern", .{}),
.@"inline" => try sema.errNote(src_loc, msg, "inline function cannot be extern", .{}),
else => return,
}
},
.@"enum" => {
const tag_ty = ty.intTagType(zcu);
try sema.errNote(src_loc, msg, "enum tag type '{f}' is not extern compatible", .{tag_ty.fmt(pt)});
try sema.explainWhyTypeIsNotExtern(msg, src_loc, tag_ty, position);
},
.@"struct" => try sema.errNote(src_loc, msg, "only extern structs and ABI sized packed structs are extern compatible", .{}),
.@"union" => try sema.errNote(src_loc, msg, "only extern unions and ABI sized packed unions are extern compatible", .{}),
.array => {
if (position == .ret_ty) {
return sema.errNote(src_loc, msg, "arrays are not allowed as a return type", .{});
} else if (position == .param_ty) {
return sema.errNote(src_loc, msg, "arrays are not allowed as a parameter type", .{});
}
try sema.explainWhyTypeIsNotExtern(msg, src_loc, ty.elemType2(zcu), .element);
},
.vector => try sema.explainWhyTypeIsNotExtern(msg, src_loc, ty.elemType2(zcu), .element),
.optional => try sema.errNote(src_loc, msg, "only pointer like optionals are extern compatible", .{}),
}
}
/// Returns true if `ty` is allowed in packed types.
/// Does not require `ty` to be resolved in any way, but may resolve whether it is comptime-only.
fn validatePackedType(sema: *Sema, ty: Type) !bool {
const pt = sema.pt;
const zcu = pt.zcu;
return switch (ty.zigTypeTag(zcu)) {
.type,
.comptime_float,
.comptime_int,
.enum_literal,
.undefined,
.null,
.error_union,
.error_set,
.frame,
.noreturn,
.@"opaque",
.@"anyframe",
.@"fn",
.array,
=> false,
.optional => return ty.isPtrLikeOptional(zcu),
.void,
.bool,
.float,
.int,
.vector,
=> true,
.@"enum" => switch (zcu.intern_pool.loadEnumType(ty.toIntern()).tag_mode) {
.auto => false,
.explicit, .nonexhaustive => true,
},
.pointer => !ty.isSlice(zcu) and !try ty.comptimeOnlySema(pt),
.@"struct", .@"union" => ty.containerLayout(zcu) == .@"packed",
};
}
fn explainWhyTypeIsNotPacked(
sema: *Sema,
msg: *Zcu.ErrorMsg,
src_loc: LazySrcLoc,
ty: Type,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
switch (ty.zigTypeTag(zcu)) {
.void,
.bool,
.float,
.int,
.vector,
.@"enum",
=> return,
.type,
.comptime_float,
.comptime_int,
.enum_literal,
.undefined,
.null,
.frame,
.noreturn,
.@"opaque",
.error_union,
.error_set,
.@"anyframe",
.optional,
.array,
=> try sema.errNote(src_loc, msg, "type has no guaranteed in-memory representation", .{}),
.pointer => if (ty.isSlice(zcu)) {
try sema.errNote(src_loc, msg, "slices have no guaranteed in-memory representation", .{});
} else {
try sema.errNote(src_loc, msg, "comptime-only pointer has no guaranteed in-memory representation", .{});
try sema.explainWhyTypeIsComptime(msg, src_loc, ty);
},
.@"fn" => {
try sema.errNote(src_loc, msg, "type has no guaranteed in-memory representation", .{});
try sema.errNote(src_loc, msg, "use '*const ' to make a function pointer type", .{});
},
.@"struct" => try sema.errNote(src_loc, msg, "only packed structs layout are allowed in packed types", .{}),
.@"union" => try sema.errNote(src_loc, msg, "only packed unions layout are allowed in packed types", .{}),
}
}
/// Backends depend on panic decls being available when lowering safety-checked
/// instructions. This function ensures the panic function will be available to
/// be called during that time.
fn preparePanicId(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !void {
const zcu = sema.pt.zcu;
// If the backend doesn't support `.panic_fn`, it doesn't want us to lower the panic handlers.
// The backend will transform panics into traps instead.
if (!zcu.backendSupportsFeature(.panic_fn)) return;
const fn_index = try sema.getPanicIdFunc(src, panic_id);
const orig_fn_index = zcu.intern_pool.unwrapCoercedFunc(fn_index);
try sema.addReferenceEntry(null, src, .wrap(.{ .func = orig_fn_index }));
try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
}
fn getPanicIdFunc(sema: *Sema, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) !InternPool.Index {
const zcu = sema.pt.zcu;
try sema.ensureMemoizedStateResolved(src, .panic);
const panic_fn_index = zcu.builtin_decl_values.get(panic_id.toBuiltin());
switch (sema.owner.unwrap()) {
.@"comptime", .nav_ty, .nav_val, .type, .memoized_state => {},
.func => |owner_func| zcu.intern_pool.funcSetHasErrorTrace(owner_func, true),
}
return panic_fn_index;
}
fn addSafetyCheck(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
ok: Air.Inst.Ref,
panic_id: Zcu.SimplePanicId,
) !void {
const gpa = sema.gpa;
assert(!parent_block.isComptime());
var fail_block: Block = .{
.parent = parent_block,
.sema = sema,
.namespace = parent_block.namespace,
.instructions = .{},
.inlining = parent_block.inlining,
.comptime_reason = null,
.src_base_inst = parent_block.src_base_inst,
.type_name_ctx = parent_block.type_name_ctx,
};
defer fail_block.instructions.deinit(gpa);
try sema.safetyPanic(&fail_block, src, panic_id);
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
}
fn addSafetyCheckExtra(
sema: *Sema,
parent_block: *Block,
ok: Air.Inst.Ref,
fail_block: *Block,
) !void {
const gpa = sema.gpa;
try parent_block.instructions.ensureUnusedCapacity(gpa, 1);
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).@"struct".fields.len +
1 + // The main block only needs space for the cond_br.
@typeInfo(Air.CondBr).@"struct".fields.len +
1 + // The ok branch of the cond_br only needs space for the br.
fail_block.instructions.items.len);
try sema.air_instructions.ensureUnusedCapacity(gpa, 3);
const block_inst: Air.Inst.Index = @enumFromInt(sema.air_instructions.len);
const cond_br_inst: Air.Inst.Index = @enumFromInt(@intFromEnum(block_inst) + 1);
const br_inst: Air.Inst.Index = @enumFromInt(@intFromEnum(cond_br_inst) + 1);
sema.air_instructions.appendAssumeCapacity(.{
.tag = .block,
.data = .{ .ty_pl = .{
.ty = .void_type,
.payload = sema.addExtraAssumeCapacity(Air.Block{
.body_len = 1,
}),
} },
});
sema.air_extra.appendAssumeCapacity(@intFromEnum(cond_br_inst));
sema.air_instructions.appendAssumeCapacity(.{
.tag = .cond_br,
.data = .{
.pl_op = .{
.operand = ok,
.payload = sema.addExtraAssumeCapacity(Air.CondBr{
.then_body_len = 1,
.else_body_len = @intCast(fail_block.instructions.items.len),
.branch_hints = .{
// Safety check failure branch is cold.
.true = .likely,
.false = .cold,
// Code coverage not wanted for panic branches.
.then_cov = .none,
.else_cov = .none,
},
}),
},
},
});
sema.air_extra.appendAssumeCapacity(@intFromEnum(br_inst));
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(fail_block.instructions.items));
sema.air_instructions.appendAssumeCapacity(.{
.tag = .br,
.data = .{ .br = .{
.block_inst = block_inst,
.operand = .void_value,
} },
});
parent_block.instructions.appendAssumeCapacity(block_inst);
}
fn addSafetyCheckUnwrapError(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
unwrap_err_tag: Air.Inst.Tag,
is_non_err_tag: Air.Inst.Tag,
) !void {
assert(!parent_block.isComptime());
const ok = try parent_block.addUnOp(is_non_err_tag, operand);
const gpa = sema.gpa;
var fail_block: Block = .{
.parent = parent_block,
.sema = sema,
.namespace = parent_block.namespace,
.instructions = .{},
.inlining = parent_block.inlining,
.comptime_reason = null,
.src_base_inst = parent_block.src_base_inst,
.type_name_ctx = parent_block.type_name_ctx,
};
defer fail_block.instructions.deinit(gpa);
const err = try fail_block.addTyOp(unwrap_err_tag, .anyerror, operand);
try safetyPanicUnwrapError(sema, &fail_block, src, err);
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
}
fn safetyPanicUnwrapError(sema: *Sema, block: *Block, src: LazySrcLoc, err: Air.Inst.Ref) !void {
const pt = sema.pt;
const zcu = pt.zcu;
if (!zcu.backendSupportsFeature(.panic_fn)) {
_ = try block.addNoOp(.trap);
} else {
const panic_fn = try getBuiltin(sema, src, .@"panic.unwrapError");
try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{err}, .@"safety check");
}
}
fn addSafetyCheckIndexOob(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
index: Air.Inst.Ref,
len: Air.Inst.Ref,
cmp_op: Air.Inst.Tag,
) !void {
assert(!parent_block.isComptime());
const ok = try parent_block.addBinOp(cmp_op, index, len);
return addSafetyCheckCall(sema, parent_block, src, ok, .@"panic.outOfBounds", &.{ index, len });
}
fn addSafetyCheckInactiveUnionField(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
active_tag: Air.Inst.Ref,
wanted_tag: Air.Inst.Ref,
) !void {
assert(!parent_block.isComptime());
const ok = try parent_block.addBinOp(.cmp_eq, active_tag, wanted_tag);
return addSafetyCheckCall(sema, parent_block, src, ok, .@"panic.inactiveUnionField", &.{ active_tag, wanted_tag });
}
fn addSafetyCheckSentinelMismatch(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
maybe_sentinel: ?Value,
sentinel_ty: Type,
ptr: Air.Inst.Ref,
sentinel_index: Air.Inst.Ref,
) !void {
assert(!parent_block.isComptime());
const pt = sema.pt;
const zcu = pt.zcu;
const expected_sentinel_val = maybe_sentinel orelse return;
const expected_sentinel = Air.internedToRef(expected_sentinel_val.toIntern());
const ptr_ty = sema.typeOf(ptr);
const actual_sentinel = if (ptr_ty.isSlice(zcu))
try parent_block.addBinOp(.slice_elem_val, ptr, sentinel_index)
else blk: {
const elem_ptr_ty = try ptr_ty.elemPtrType(null, pt);
const sentinel_ptr = try parent_block.addPtrElemPtr(ptr, sentinel_index, elem_ptr_ty);
break :blk try parent_block.addTyOp(.load, sentinel_ty, sentinel_ptr);
};
const ok = if (sentinel_ty.zigTypeTag(zcu) == .vector) ok: {
const eql = try parent_block.addCmpVector(expected_sentinel, actual_sentinel, .eq);
break :ok try parent_block.addReduce(eql, .And);
} else ok: {
assert(sentinel_ty.isSelfComparable(zcu, true));
break :ok try parent_block.addBinOp(.cmp_eq, expected_sentinel, actual_sentinel);
};
return addSafetyCheckCall(sema, parent_block, src, ok, .@"panic.sentinelMismatch", &.{
expected_sentinel, actual_sentinel,
});
}
fn addSafetyCheckCall(
sema: *Sema,
parent_block: *Block,
src: LazySrcLoc,
ok: Air.Inst.Ref,
comptime func_decl: Zcu.BuiltinDecl,
args: []const Air.Inst.Ref,
) !void {
assert(!parent_block.isComptime());
const gpa = sema.gpa;
const pt = sema.pt;
const zcu = pt.zcu;
var fail_block: Block = .{
.parent = parent_block,
.sema = sema,
.namespace = parent_block.namespace,
.instructions = .{},
.inlining = parent_block.inlining,
.comptime_reason = null,
.src_base_inst = parent_block.src_base_inst,
.type_name_ctx = parent_block.type_name_ctx,
};
defer fail_block.instructions.deinit(gpa);
if (!zcu.backendSupportsFeature(.panic_fn)) {
_ = try fail_block.addNoOp(.trap);
} else {
const panic_fn = try getBuiltin(sema, src, func_decl);
try sema.callBuiltin(&fail_block, src, Air.internedToRef(panic_fn), .auto, args, .@"safety check");
}
try sema.addSafetyCheckExtra(parent_block, ok, &fail_block);
}
/// This does not set `sema.branch_hint`.
fn safetyPanic(sema: *Sema, block: *Block, src: LazySrcLoc, panic_id: Zcu.SimplePanicId) CompileError!void {
if (!sema.pt.zcu.backendSupportsFeature(.panic_fn)) {
_ = try block.addNoOp(.trap);
} else {
const panic_fn = try sema.getPanicIdFunc(src, panic_id);
try sema.callBuiltin(block, src, Air.internedToRef(panic_fn), .auto, &.{}, .@"safety check");
}
}
fn emitBackwardBranch(sema: *Sema, block: *Block, src: LazySrcLoc) !void {
sema.branch_count += 1;
if (sema.branch_count > sema.branch_quota) {
const msg = try sema.errMsg(
src,
"evaluation exceeded {d} backwards branches",
.{sema.branch_quota},
);
try sema.errNote(
src,
msg,
"use @setEvalBranchQuota() to raise the branch limit from {d}",
.{sema.branch_quota},
);
return sema.failWithOwnedErrorMsg(block, msg);
}
}
fn fieldPtrLoad(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
object_ptr: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const object_ptr_ty = sema.typeOf(object_ptr);
const pointee_ty = object_ptr_ty.childType(zcu);
if (try typeHasOnePossibleValue(sema, pointee_ty)) |opv| {
const object: Air.Inst.Ref = .fromValue(opv);
return fieldVal(sema, block, src, object, field_name, field_name_src);
}
if (try sema.resolveDefinedValue(block, src, object_ptr)) |object_ptr_val| {
if (try sema.pointerDeref(block, src, object_ptr_val, object_ptr_ty)) |object_val| {
const object: Air.Inst.Ref = .fromValue(object_val);
return fieldVal(sema, block, src, object, field_name, field_name_src);
}
}
const field_ptr = try sema.fieldPtr(block, src, object_ptr, field_name, field_name_src, false);
return analyzeLoad(sema, block, src, field_ptr, field_name_src);
}
fn fieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
object: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
// When editing this function, note that there is corresponding logic to be edited
// in `fieldPtr`. This function takes a value and returns a value.
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const object_src = src; // TODO better source location
const object_ty = sema.typeOf(object);
// Zig allows dereferencing a single pointer during field lookup. Note that
// we don't actually need to generate the dereference some field lookups, like the
// length of arrays and other comptime operations.
const is_pointer_to = object_ty.isSinglePointer(zcu);
const inner_ty = if (is_pointer_to)
object_ty.childType(zcu)
else
object_ty;
switch (inner_ty.zigTypeTag(zcu)) {
.array => {
if (field_name.eqlSlice("len", ip)) {
return Air.internedToRef((try pt.intValue(.usize, inner_ty.arrayLen(zcu))).toIntern());
} else if (field_name.eqlSlice("ptr", ip) and is_pointer_to) {
const ptr_info = object_ty.ptrInfo(zcu);
const result_ty = try pt.ptrTypeSema(.{
.child = Type.fromInterned(ptr_info.child).childType(zcu).toIntern(),
.sentinel = if (inner_ty.sentinel(zcu)) |s| s.toIntern() else .none,
.flags = .{
.size = .many,
.alignment = ptr_info.flags.alignment,
.is_const = ptr_info.flags.is_const,
.is_volatile = ptr_info.flags.is_volatile,
.is_allowzero = ptr_info.flags.is_allowzero,
.address_space = ptr_info.flags.address_space,
.vector_index = ptr_info.flags.vector_index,
},
.packed_offset = ptr_info.packed_offset,
});
return sema.coerce(block, result_ty, object, src);
} else {
return sema.fail(
block,
field_name_src,
"no member named '{f}' in '{f}'",
.{ field_name.fmt(ip), object_ty.fmt(pt) },
);
}
},
.pointer => {
const ptr_info = inner_ty.ptrInfo(zcu);
if (ptr_info.flags.size == .slice) {
if (field_name.eqlSlice("ptr", ip)) {
const slice = if (is_pointer_to)
try sema.analyzeLoad(block, src, object, object_src)
else
object;
return sema.analyzeSlicePtr(block, object_src, slice, inner_ty);
} else if (field_name.eqlSlice("len", ip)) {
const slice = if (is_pointer_to)
try sema.analyzeLoad(block, src, object, object_src)
else
object;
return sema.analyzeSliceLen(block, src, slice);
} else {
return sema.fail(
block,
field_name_src,
"no member named '{f}' in '{f}'",
.{ field_name.fmt(ip), object_ty.fmt(pt) },
);
}
}
},
.type => {
const dereffed_type = if (is_pointer_to)
try sema.analyzeLoad(block, src, object, object_src)
else
object;
const val = (try sema.resolveDefinedValue(block, object_src, dereffed_type)).?;
const child_type = val.toType();
switch (child_type.zigTypeTag(zcu)) {
.error_set => {
switch (ip.indexToKey(child_type.toIntern())) {
.error_set_type => |error_set_type| blk: {
if (error_set_type.nameIndex(ip, field_name) != null) break :blk;
return sema.fail(block, src, "no error named '{f}' in '{f}'", .{
field_name.fmt(ip), child_type.fmt(pt),
});
},
.inferred_error_set_type => {
return sema.fail(block, src, "TODO handle inferred error sets here", .{});
},
.simple_type => |t| {
assert(t == .anyerror);
_ = try pt.getErrorValue(field_name);
},
else => unreachable,
}
const error_set_type = if (!child_type.isAnyError(zcu))
child_type
else
try pt.singleErrorSetType(field_name);
return Air.internedToRef((try pt.intern(.{ .err = .{
.ty = error_set_type.toIntern(),
.name = field_name,
} })));
},
.@"union" => {
if (try sema.namespaceLookupVal(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| {
return inst;
}
try child_type.resolveFields(pt);
if (child_type.unionTagType(zcu)) |enum_ty| {
if (enum_ty.enumFieldIndex(field_name, zcu)) |field_index_usize| {
const field_index: u32 = @intCast(field_index_usize);
return Air.internedToRef((try pt.enumValueFieldIndex(enum_ty, field_index)).toIntern());
}
}
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
},
.@"enum" => {
if (try sema.namespaceLookupVal(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| {
return inst;
}
const field_index_usize = child_type.enumFieldIndex(field_name, zcu) orelse
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
const field_index: u32 = @intCast(field_index_usize);
const enum_val = try pt.enumValueFieldIndex(child_type, field_index);
return Air.internedToRef(enum_val.toIntern());
},
.@"struct", .@"opaque" => {
if (!child_type.isTuple(zcu) and child_type.toIntern() != .anyopaque_type) {
if (try sema.namespaceLookupVal(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| {
return inst;
}
}
return sema.failWithBadMemberAccess(block, child_type, src, field_name);
},
else => return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "type '{f}' has no members", .{child_type.fmt(pt)});
errdefer msg.destroy(sema.gpa);
if (child_type.isSlice(zcu)) try sema.errNote(src, msg, "slice values have 'len' and 'ptr' members", .{});
if (child_type.zigTypeTag(zcu) == .array) try sema.errNote(src, msg, "array values have 'len' member", .{});
break :msg msg;
}),
}
},
.@"struct" => if (is_pointer_to) {
// Avoid loading the entire struct by fetching a pointer and loading that
const field_ptr = try sema.structFieldPtr(block, src, object, field_name, field_name_src, inner_ty, false);
return sema.analyzeLoad(block, src, field_ptr, object_src);
} else {
return sema.structFieldVal(block, object, field_name, field_name_src, inner_ty);
},
.@"union" => if (is_pointer_to) {
// Avoid loading the entire union by fetching a pointer and loading that
const field_ptr = try sema.unionFieldPtr(block, src, object, field_name, field_name_src, inner_ty, false);
return sema.analyzeLoad(block, src, field_ptr, object_src);
} else {
return sema.unionFieldVal(block, src, object, field_name, field_name_src, inner_ty);
},
else => {},
}
return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name);
}
fn fieldPtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
object_ptr: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
initializing: bool,
) CompileError!Air.Inst.Ref {
// When editing this function, note that there is corresponding logic to be edited
// in `fieldVal`. This function takes a pointer and returns a pointer.
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const object_ptr_src = src; // TODO better source location
const object_ptr_ty = sema.typeOf(object_ptr);
const object_ty = switch (object_ptr_ty.zigTypeTag(zcu)) {
.pointer => object_ptr_ty.childType(zcu),
else => return sema.fail(block, object_ptr_src, "expected pointer, found '{f}'", .{object_ptr_ty.fmt(pt)}),
};
// Zig allows dereferencing a single pointer during field lookup. Note that
// we don't actually need to generate the dereference some field lookups, like the
// length of arrays and other comptime operations.
const is_pointer_to = object_ty.isSinglePointer(zcu);
const inner_ty = if (is_pointer_to)
object_ty.childType(zcu)
else
object_ty;
switch (inner_ty.zigTypeTag(zcu)) {
.array => {
if (field_name.eqlSlice("len", ip)) {
const int_val = try pt.intValue(.usize, inner_ty.arrayLen(zcu));
return uavRef(sema, int_val.toIntern());
} else if (field_name.eqlSlice("ptr", ip) and is_pointer_to) {
const ptr_info = object_ty.ptrInfo(zcu);
const new_ptr_ty = try pt.ptrTypeSema(.{
.child = Type.fromInterned(ptr_info.child).childType(zcu).toIntern(),
.sentinel = if (inner_ty.sentinel(zcu)) |s| s.toIntern() else .none,
.flags = .{
.size = .many,
.alignment = ptr_info.flags.alignment,
.is_const = ptr_info.flags.is_const,
.is_volatile = ptr_info.flags.is_volatile,
.is_allowzero = ptr_info.flags.is_allowzero,
.address_space = ptr_info.flags.address_space,
.vector_index = ptr_info.flags.vector_index,
},
.packed_offset = ptr_info.packed_offset,
});
const ptr_ptr_info = object_ptr_ty.ptrInfo(zcu);
const result_ty = try pt.ptrTypeSema(.{
.child = new_ptr_ty.toIntern(),
.sentinel = if (object_ptr_ty.sentinel(zcu)) |s| s.toIntern() else .none,
.flags = .{
.alignment = ptr_ptr_info.flags.alignment,
.is_const = ptr_ptr_info.flags.is_const,
.is_volatile = ptr_ptr_info.flags.is_volatile,
.is_allowzero = ptr_ptr_info.flags.is_allowzero,
.address_space = ptr_ptr_info.flags.address_space,
.vector_index = ptr_ptr_info.flags.vector_index,
},
.packed_offset = ptr_ptr_info.packed_offset,
});
return sema.bitCast(block, result_ty, object_ptr, src, null);
} else {
return sema.fail(
block,
field_name_src,
"no member named '{f}' in '{f}'",
.{ field_name.fmt(ip), object_ty.fmt(pt) },
);
}
},
.pointer => if (inner_ty.isSlice(zcu)) {
const inner_ptr = if (is_pointer_to)
try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)
else
object_ptr;
const attr_ptr_ty = if (is_pointer_to) object_ty else object_ptr_ty;
if (field_name.eqlSlice("ptr", ip)) {
const slice_ptr_ty = inner_ty.slicePtrFieldType(zcu);
const result_ty = try pt.ptrTypeSema(.{
.child = slice_ptr_ty.toIntern(),
.flags = .{
.is_const = !attr_ptr_ty.ptrIsMutable(zcu),
.is_volatile = attr_ptr_ty.isVolatilePtr(zcu),
.address_space = attr_ptr_ty.ptrAddressSpace(zcu),
},
});
if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| {
return Air.internedToRef((try val.ptrField(Value.slice_ptr_index, pt)).toIntern());
}
try sema.requireRuntimeBlock(block, src, null);
const field_ptr = try block.addTyOp(.ptr_slice_ptr_ptr, result_ty, inner_ptr);
try sema.checkKnownAllocPtr(block, inner_ptr, field_ptr);
return field_ptr;
} else if (field_name.eqlSlice("len", ip)) {
const result_ty = try pt.ptrTypeSema(.{
.child = .usize_type,
.flags = .{
.is_const = !attr_ptr_ty.ptrIsMutable(zcu),
.is_volatile = attr_ptr_ty.isVolatilePtr(zcu),
.address_space = attr_ptr_ty.ptrAddressSpace(zcu),
},
});
if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| {
return Air.internedToRef((try val.ptrField(Value.slice_len_index, pt)).toIntern());
}
try sema.requireRuntimeBlock(block, src, null);
const field_ptr = try block.addTyOp(.ptr_slice_len_ptr, result_ty, inner_ptr);
try sema.checkKnownAllocPtr(block, inner_ptr, field_ptr);
return field_ptr;
} else {
return sema.fail(
block,
field_name_src,
"no member named '{f}' in '{f}'",
.{ field_name.fmt(ip), object_ty.fmt(pt) },
);
}
},
.type => {
_ = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, object_ptr, undefined);
const result = try sema.analyzeLoad(block, src, object_ptr, object_ptr_src);
const inner = if (is_pointer_to)
try sema.analyzeLoad(block, src, result, object_ptr_src)
else
result;
const val = (sema.resolveDefinedValue(block, src, inner) catch unreachable).?;
const child_type = val.toType();
switch (child_type.zigTypeTag(zcu)) {
.error_set => {
switch (ip.indexToKey(child_type.toIntern())) {
.error_set_type => |error_set_type| blk: {
if (error_set_type.nameIndex(ip, field_name) != null) {
break :blk;
}
return sema.fail(block, src, "no error named '{f}' in '{f}'", .{
field_name.fmt(ip), child_type.fmt(pt),
});
},
.inferred_error_set_type => {
return sema.fail(block, src, "TODO handle inferred error sets here", .{});
},
.simple_type => |t| {
assert(t == .anyerror);
_ = try pt.getErrorValue(field_name);
},
else => unreachable,
}
const error_set_type = if (!child_type.isAnyError(zcu))
child_type
else
try pt.singleErrorSetType(field_name);
return uavRef(sema, try pt.intern(.{ .err = .{
.ty = error_set_type.toIntern(),
.name = field_name,
} }));
},
.@"union" => {
if (try sema.namespaceLookupRef(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| {
return inst;
}
try child_type.resolveFields(pt);
if (child_type.unionTagType(zcu)) |enum_ty| {
if (enum_ty.enumFieldIndex(field_name, zcu)) |field_index| {
const field_index_u32: u32 = @intCast(field_index);
const idx_val = try pt.enumValueFieldIndex(enum_ty, field_index_u32);
return uavRef(sema, idx_val.toIntern());
}
}
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
},
.@"enum" => {
if (try sema.namespaceLookupRef(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| {
return inst;
}
const field_index = child_type.enumFieldIndex(field_name, zcu) orelse {
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
};
const field_index_u32: u32 = @intCast(field_index);
const idx_val = try pt.enumValueFieldIndex(child_type, field_index_u32);
return uavRef(sema, idx_val.toIntern());
},
.@"struct", .@"opaque" => {
if (try sema.namespaceLookupRef(block, src, child_type.getNamespaceIndex(zcu), field_name)) |inst| {
return inst;
}
return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name);
},
else => return sema.fail(block, src, "type '{f}' has no members", .{child_type.fmt(pt)}),
}
},
.@"struct" => {
const inner_ptr = if (is_pointer_to)
try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)
else
object_ptr;
const field_ptr = try sema.structFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing);
try sema.checkKnownAllocPtr(block, inner_ptr, field_ptr);
return field_ptr;
},
.@"union" => {
const inner_ptr = if (is_pointer_to)
try sema.analyzeLoad(block, src, object_ptr, object_ptr_src)
else
object_ptr;
const field_ptr = try sema.unionFieldPtr(block, src, inner_ptr, field_name, field_name_src, inner_ty, initializing);
try sema.checkKnownAllocPtr(block, inner_ptr, field_ptr);
return field_ptr;
},
else => {},
}
return sema.failWithInvalidFieldAccess(block, src, object_ty, field_name);
}
const ResolvedFieldCallee = union(enum) {
/// The LHS of the call was an actual field with this value.
direct: Air.Inst.Ref,
/// This is a method call, with the function and first argument given.
method: struct {
func_inst: Air.Inst.Ref,
arg0_inst: Air.Inst.Ref,
},
};
fn fieldCallBind(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
raw_ptr: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
) CompileError!ResolvedFieldCallee {
// When editing this function, note that there is corresponding logic to be edited
// in `fieldVal`. This function takes a pointer and returns a pointer.
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const raw_ptr_src = src; // TODO better source location
const raw_ptr_ty = sema.typeOf(raw_ptr);
const inner_ty = if (raw_ptr_ty.zigTypeTag(zcu) == .pointer and (raw_ptr_ty.ptrSize(zcu) == .one or raw_ptr_ty.ptrSize(zcu) == .c))
raw_ptr_ty.childType(zcu)
else
return sema.fail(block, raw_ptr_src, "expected single pointer, found '{f}'", .{raw_ptr_ty.fmt(pt)});
// Optionally dereference a second pointer to get the concrete type.
const is_double_ptr = inner_ty.zigTypeTag(zcu) == .pointer and inner_ty.ptrSize(zcu) == .one;
const concrete_ty = if (is_double_ptr) inner_ty.childType(zcu) else inner_ty;
const ptr_ty = if (is_double_ptr) inner_ty else raw_ptr_ty;
const object_ptr = if (is_double_ptr)
try sema.analyzeLoad(block, src, raw_ptr, src)
else
raw_ptr;
find_field: {
switch (concrete_ty.zigTypeTag(zcu)) {
.@"struct" => {
try concrete_ty.resolveFields(pt);
if (zcu.typeToStruct(concrete_ty)) |struct_type| {
const field_index = struct_type.nameIndex(ip, field_name) orelse
break :find_field;
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
return sema.finishFieldCallBind(block, src, ptr_ty, field_ty, field_index, object_ptr);
} else if (concrete_ty.isTuple(zcu)) {
if (field_name.eqlSlice("len", ip)) {
return .{ .direct = try pt.intRef(.usize, concrete_ty.structFieldCount(zcu)) };
}
if (field_name.toUnsigned(ip)) |field_index| {
if (field_index >= concrete_ty.structFieldCount(zcu)) break :find_field;
return sema.finishFieldCallBind(block, src, ptr_ty, concrete_ty.fieldType(field_index, zcu), field_index, object_ptr);
}
} else {
const max = concrete_ty.structFieldCount(zcu);
for (0..max) |i_usize| {
const i: u32 = @intCast(i_usize);
if (field_name == concrete_ty.structFieldName(i, zcu).unwrap().?) {
return sema.finishFieldCallBind(block, src, ptr_ty, concrete_ty.fieldType(i, zcu), i, object_ptr);
}
}
}
},
.@"union" => {
try concrete_ty.resolveFields(pt);
const union_obj = zcu.typeToUnion(concrete_ty).?;
_ = union_obj.loadTagType(ip).nameIndex(ip, field_name) orelse break :find_field;
const field_ptr = try unionFieldPtr(sema, block, src, object_ptr, field_name, field_name_src, concrete_ty, false);
return .{ .direct = try sema.analyzeLoad(block, src, field_ptr, src) };
},
.type => {
const namespace = try sema.analyzeLoad(block, src, object_ptr, src);
return .{ .direct = try sema.fieldVal(block, src, namespace, field_name, field_name_src) };
},
else => {},
}
}
// If we get here, we need to look for a decl in the struct type instead.
const found_nav = found_nav: {
const namespace = concrete_ty.getNamespace(zcu).unwrap() orelse
break :found_nav null;
const nav_index = try sema.namespaceLookup(block, src, namespace, field_name) orelse
break :found_nav null;
const decl_val = try sema.analyzeNavVal(block, src, nav_index);
const decl_type = sema.typeOf(decl_val);
if (zcu.typeToFunc(decl_type)) |func_type| f: {
if (func_type.param_types.len == 0) break :f;
const first_param_type: Type = .fromInterned(func_type.param_types.get(ip)[0]);
if (first_param_type.isGenericPoison() or
(first_param_type.zigTypeTag(zcu) == .pointer and
(first_param_type.ptrSize(zcu) == .one or
first_param_type.ptrSize(zcu) == .c) and
first_param_type.childType(zcu).eql(concrete_ty, zcu)))
{
// Note that if the param type is generic poison, we know that it must
// specifically be `anytype` since it's the first parameter, meaning we
// can safely assume it can be a pointer.
// TODO: bound fn calls on rvalues should probably
// generate a by-value argument somehow.
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = object_ptr,
} };
} else if (first_param_type.eql(concrete_ty, zcu)) {
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = deref,
} };
} else if (first_param_type.zigTypeTag(zcu) == .optional) {
const child = first_param_type.optionalChild(zcu);
if (child.eql(concrete_ty, zcu)) {
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = deref,
} };
} else if (child.zigTypeTag(zcu) == .pointer and
child.ptrSize(zcu) == .one and
child.childType(zcu).eql(concrete_ty, zcu))
{
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = object_ptr,
} };
}
} else if (first_param_type.zigTypeTag(zcu) == .error_union and
first_param_type.errorUnionPayload(zcu).eql(concrete_ty, zcu))
{
const deref = try sema.analyzeLoad(block, src, object_ptr, src);
return .{ .method = .{
.func_inst = decl_val,
.arg0_inst = deref,
} };
}
}
break :found_nav nav_index;
};
const msg = msg: {
const msg = try sema.errMsg(src, "no field or member function named '{f}' in '{f}'", .{
field_name.fmt(ip),
concrete_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, concrete_ty);
if (found_nav) |nav_index| {
try sema.errNote(
zcu.navSrcLoc(nav_index),
msg,
"'{f}' is not a member function",
.{field_name.fmt(ip)},
);
}
if (concrete_ty.zigTypeTag(zcu) == .error_union) {
try sema.errNote(src, msg, "consider using 'try', 'catch', or 'if'", .{});
}
if (is_double_ptr) {
try sema.errNote(src, msg, "method invocation only supports up to one level of implicit pointer dereferencing", .{});
try sema.errNote(src, msg, "use '.*' to dereference pointer", .{});
}
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn finishFieldCallBind(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ptr_ty: Type,
field_ty: Type,
field_index: u32,
object_ptr: Air.Inst.Ref,
) CompileError!ResolvedFieldCallee {
const pt = sema.pt;
const zcu = pt.zcu;
const ptr_field_ty = try pt.ptrTypeSema(.{
.child = field_ty.toIntern(),
.flags = .{
.is_const = !ptr_ty.ptrIsMutable(zcu),
.address_space = ptr_ty.ptrAddressSpace(zcu),
},
});
const container_ty = ptr_ty.childType(zcu);
if (container_ty.zigTypeTag(zcu) == .@"struct") {
if (container_ty.structFieldIsComptime(field_index, zcu)) {
try container_ty.resolveStructFieldInits(pt);
const default_val = (try container_ty.structFieldValueComptime(pt, field_index)).?;
return .{ .direct = Air.internedToRef(default_val.toIntern()) };
}
}
if (try sema.resolveDefinedValue(block, src, object_ptr)) |struct_ptr_val| {
const ptr_val = try struct_ptr_val.ptrField(field_index, pt);
const pointer = Air.internedToRef(ptr_val.toIntern());
return .{ .direct = try sema.analyzeLoad(block, src, pointer, src) };
}
try sema.requireRuntimeBlock(block, src, null);
const ptr_inst = try block.addStructFieldPtr(object_ptr, field_index, ptr_field_ty);
return .{ .direct = try sema.analyzeLoad(block, src, ptr_inst, src) };
}
fn namespaceLookup(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
namespace: InternPool.NamespaceIndex,
decl_name: InternPool.NullTerminatedString,
) CompileError!?InternPool.Nav.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
if (try sema.lookupInNamespace(block, namespace, decl_name)) |lookup| {
if (!lookup.accessible) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "'{f}' is not marked 'pub'", .{
decl_name.fmt(&zcu.intern_pool),
});
errdefer msg.destroy(gpa);
try sema.errNote(zcu.navSrcLoc(lookup.nav), msg, "declared here", .{});
break :msg msg;
});
}
return lookup.nav;
}
return null;
}
fn namespaceLookupRef(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
namespace: InternPool.NamespaceIndex,
decl_name: InternPool.NullTerminatedString,
) CompileError!?Air.Inst.Ref {
const nav = try sema.namespaceLookup(block, src, namespace, decl_name) orelse return null;
return try sema.analyzeNavRef(block, src, nav);
}
fn namespaceLookupVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
namespace: InternPool.NamespaceIndex,
decl_name: InternPool.NullTerminatedString,
) CompileError!?Air.Inst.Ref {
const nav = try sema.namespaceLookup(block, src, namespace, decl_name) orelse return null;
return try sema.analyzeNavVal(block, src, nav);
}
fn structFieldPtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
struct_ptr: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
struct_ty: Type,
initializing: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(struct_ty.zigTypeTag(zcu) == .@"struct");
try struct_ty.resolveFields(pt);
try struct_ty.resolveLayout(pt);
if (struct_ty.isTuple(zcu)) {
if (field_name.eqlSlice("len", ip)) {
const len_inst = try pt.intRef(.usize, struct_ty.structFieldCount(zcu));
return sema.analyzeRef(block, src, len_inst);
}
const field_index = try sema.tupleFieldIndex(block, struct_ty, field_name, field_name_src);
return sema.tupleFieldPtr(block, src, struct_ptr, field_name_src, field_index, initializing);
}
const struct_type = zcu.typeToStruct(struct_ty).?;
const field_index = struct_type.nameIndex(ip, field_name) orelse
return sema.failWithBadStructFieldAccess(block, struct_ty, struct_type, field_name_src, field_name);
return sema.structFieldPtrByIndex(block, src, struct_ptr, field_index, struct_ty);
}
fn structFieldPtrByIndex(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
struct_ptr: Air.Inst.Ref,
field_index: u32,
struct_ty: Type,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const struct_type = zcu.typeToStruct(struct_ty).?;
const field_is_comptime = struct_type.fieldIsComptime(ip, field_index);
// Comptime fields are handled later
if (!field_is_comptime) {
if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| {
const val = try struct_ptr_val.ptrField(field_index, pt);
return Air.internedToRef(val.toIntern());
}
}
const field_ty = struct_type.field_types.get(ip)[field_index];
const struct_ptr_ty = sema.typeOf(struct_ptr);
const struct_ptr_ty_info = struct_ptr_ty.ptrInfo(zcu);
var ptr_ty_data: InternPool.Key.PtrType = .{
.child = field_ty,
.flags = .{
.is_const = struct_ptr_ty_info.flags.is_const,
.is_volatile = struct_ptr_ty_info.flags.is_volatile,
.address_space = struct_ptr_ty_info.flags.address_space,
},
};
const parent_align = if (struct_ptr_ty_info.flags.alignment != .none)
struct_ptr_ty_info.flags.alignment
else
try Type.fromInterned(struct_ptr_ty_info.child).abiAlignmentSema(pt);
if (struct_type.layout == .@"packed") {
assert(!field_is_comptime);
const packed_offset = struct_ty.packedStructFieldPtrInfo(struct_ptr_ty, field_index, pt);
ptr_ty_data.flags.alignment = parent_align;
ptr_ty_data.packed_offset = packed_offset;
} else if (struct_type.layout == .@"extern") {
assert(!field_is_comptime);
// For extern structs, field alignment might be bigger than type's
// natural alignment. Eg, in `extern struct { x: u32, y: u16 }` the
// second field is aligned as u32.
const field_offset = struct_ty.structFieldOffset(field_index, zcu);
ptr_ty_data.flags.alignment = if (parent_align == .none)
.none
else
@enumFromInt(@min(@intFromEnum(parent_align), @ctz(field_offset)));
} else {
// Our alignment is capped at the field alignment.
const field_align = try Type.fromInterned(field_ty).structFieldAlignmentSema(
struct_type.fieldAlign(ip, field_index),
struct_type.layout,
pt,
);
ptr_ty_data.flags.alignment = if (struct_ptr_ty_info.flags.alignment == .none)
field_align
else
field_align.min(parent_align);
}
const ptr_field_ty = try pt.ptrTypeSema(ptr_ty_data);
if (field_is_comptime) {
try struct_ty.resolveStructFieldInits(pt);
const val = try pt.intern(.{ .ptr = .{
.ty = ptr_field_ty.toIntern(),
.base_addr = .{ .comptime_field = struct_type.field_inits.get(ip)[field_index] },
.byte_offset = 0,
} });
return Air.internedToRef(val);
}
return block.addStructFieldPtr(struct_ptr, field_index, ptr_field_ty);
}
fn structFieldVal(
sema: *Sema,
block: *Block,
struct_byval: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
struct_ty: Type,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(struct_ty.zigTypeTag(zcu) == .@"struct");
try struct_ty.resolveFields(pt);
switch (ip.indexToKey(struct_ty.toIntern())) {
.struct_type => {
const struct_type = ip.loadStructType(struct_ty.toIntern());
const field_index = struct_type.nameIndex(ip, field_name) orelse
return sema.failWithBadStructFieldAccess(block, struct_ty, struct_type, field_name_src, field_name);
if (struct_type.fieldIsComptime(ip, field_index)) {
try struct_ty.resolveStructFieldInits(pt);
return Air.internedToRef(struct_type.field_inits.get(ip)[field_index]);
}
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
if (try sema.typeHasOnePossibleValue(field_ty)) |field_val|
return Air.internedToRef(field_val.toIntern());
if (try sema.resolveValue(struct_byval)) |struct_val| {
if (struct_val.isUndef(zcu)) return pt.undefRef(field_ty);
if ((try sema.typeHasOnePossibleValue(field_ty))) |opv| {
return Air.internedToRef(opv.toIntern());
}
return Air.internedToRef((try struct_val.fieldValue(pt, field_index)).toIntern());
}
try field_ty.resolveLayout(pt);
return block.addStructFieldVal(struct_byval, field_index, field_ty);
},
.tuple_type => {
return sema.tupleFieldVal(block, struct_byval, field_name, field_name_src, struct_ty);
},
else => unreachable,
}
}
fn tupleFieldVal(
sema: *Sema,
block: *Block,
tuple_byval: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
tuple_ty: Type,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
if (field_name.eqlSlice("len", &zcu.intern_pool)) {
return pt.intRef(.usize, tuple_ty.structFieldCount(zcu));
}
const field_index = try sema.tupleFieldIndex(block, tuple_ty, field_name, field_name_src);
return sema.tupleFieldValByIndex(block, tuple_byval, field_index, tuple_ty);
}
/// Asserts that `field_name` is not "len".
fn tupleFieldIndex(
sema: *Sema,
block: *Block,
tuple_ty: Type,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
) CompileError!u32 {
const pt = sema.pt;
const ip = &pt.zcu.intern_pool;
assert(!field_name.eqlSlice("len", ip));
if (field_name.toUnsigned(ip)) |field_index| {
if (field_index < tuple_ty.structFieldCount(pt.zcu)) return field_index;
return sema.fail(block, field_name_src, "index '{f}' out of bounds of tuple '{f}'", .{
field_name.fmt(ip), tuple_ty.fmt(pt),
});
}
return sema.fail(block, field_name_src, "no field named '{f}' in tuple '{f}'", .{
field_name.fmt(ip), tuple_ty.fmt(pt),
});
}
fn tupleFieldValByIndex(
sema: *Sema,
block: *Block,
tuple_byval: Air.Inst.Ref,
field_index: u32,
tuple_ty: Type,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const field_ty = tuple_ty.fieldType(field_index, zcu);
if (tuple_ty.structFieldIsComptime(field_index, zcu))
try tuple_ty.resolveStructFieldInits(pt);
if (try tuple_ty.structFieldValueComptime(pt, field_index)) |default_value| {
return Air.internedToRef(default_value.toIntern());
}
if (try sema.resolveValue(tuple_byval)) |tuple_val| {
if ((try sema.typeHasOnePossibleValue(field_ty))) |opv| {
return Air.internedToRef(opv.toIntern());
}
return switch (zcu.intern_pool.indexToKey(tuple_val.toIntern())) {
.undef => pt.undefRef(field_ty),
.aggregate => |aggregate| Air.internedToRef(switch (aggregate.storage) {
.bytes => |bytes| try pt.intValue(.u8, bytes.at(field_index, &zcu.intern_pool)),
.elems => |elems| Value.fromInterned(elems[field_index]),
.repeated_elem => |elem| Value.fromInterned(elem),
}.toIntern()),
else => unreachable,
};
}
try field_ty.resolveLayout(pt);
return block.addStructFieldVal(tuple_byval, field_index, field_ty);
}
fn unionFieldPtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
union_ptr: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
union_ty: Type,
initializing: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(union_ty.zigTypeTag(zcu) == .@"union");
const union_ptr_ty = sema.typeOf(union_ptr);
const union_ptr_info = union_ptr_ty.ptrInfo(zcu);
try union_ty.resolveFields(pt);
const union_obj = zcu.typeToUnion(union_ty).?;
const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src);
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
const ptr_field_ty = try pt.ptrTypeSema(.{
.child = field_ty.toIntern(),
.flags = .{
.is_const = union_ptr_info.flags.is_const,
.is_volatile = union_ptr_info.flags.is_volatile,
.address_space = union_ptr_info.flags.address_space,
.alignment = if (union_obj.flagsUnordered(ip).layout == .auto) blk: {
const union_align = if (union_ptr_info.flags.alignment != .none)
union_ptr_info.flags.alignment
else
try union_ty.abiAlignmentSema(pt);
const field_align = try union_ty.fieldAlignmentSema(field_index, pt);
break :blk union_align.min(field_align);
} else union_ptr_info.flags.alignment,
},
.packed_offset = union_ptr_info.packed_offset,
});
const enum_field_index: u32 = @intCast(Type.fromInterned(union_obj.enum_tag_ty).enumFieldIndex(field_name, zcu).?);
if (initializing and field_ty.zigTypeTag(zcu) == .noreturn) {
const msg = msg: {
const msg = try sema.errMsg(src, "cannot initialize 'noreturn' field of union", .{});
errdefer msg.destroy(sema.gpa);
try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' declared here", .{
field_name.fmt(ip),
});
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| ct: {
switch (union_obj.flagsUnordered(ip).layout) {
.auto => if (initializing) {
if (!sema.isComptimeMutablePtr(union_ptr_val)) {
// The initialization is a runtime operation.
break :ct;
}
// Store to the union to initialize the tag.
const field_tag = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), enum_field_index);
const payload_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
const new_union_val = try pt.unionValue(union_ty, field_tag, try pt.undefValue(payload_ty));
try sema.storePtrVal(block, src, union_ptr_val, new_union_val, union_ty);
} else {
const union_val = (try sema.pointerDeref(block, src, union_ptr_val, union_ptr_ty)) orelse
break :ct;
if (union_val.isUndef(zcu)) {
return sema.failWithUseOfUndef(block, src, null);
}
const un = ip.indexToKey(union_val.toIntern()).un;
const field_tag = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), enum_field_index);
const tag_matches = un.tag == field_tag.toIntern();
if (!tag_matches) {
const msg = msg: {
const active_index = Type.fromInterned(union_obj.enum_tag_ty).enumTagFieldIndex(Value.fromInterned(un.tag), zcu).?;
const active_field_name = Type.fromInterned(union_obj.enum_tag_ty).enumFieldName(active_index, zcu);
const msg = try sema.errMsg(src, "access of union field '{f}' while field '{f}' is active", .{
field_name.fmt(ip),
active_field_name.fmt(ip),
});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
},
.@"packed", .@"extern" => {},
}
const field_ptr_val = try union_ptr_val.ptrField(field_index, pt);
return Air.internedToRef(field_ptr_val.toIntern());
}
// If the union has a tag, we must either set or or safety check it depending on `initializing`.
tag: {
if (union_ty.containerLayout(zcu) != .auto) break :tag;
const tag_ty: Type = .fromInterned(union_obj.enum_tag_ty);
if (try sema.typeHasOnePossibleValue(tag_ty) != null) break :tag;
// There is a hypothetical non-trivial tag. We must set it even if not there at runtime, but
// only emit a safety check if it's available at runtime (i.e. it's safety-tagged).
const want_tag = try pt.enumValueFieldIndex(tag_ty, enum_field_index);
if (initializing) {
const set_tag_inst = try block.addBinOp(.set_union_tag, union_ptr, .fromValue(want_tag));
try sema.checkComptimeKnownStore(block, set_tag_inst, .unneeded); // `unneeded` since this isn't a "proper" store
} else if (block.wantSafety() and union_obj.hasTag(ip)) {
// The tag exists at runtime (safety tag), so emit a safety check.
// TODO would it be better if get_union_tag supported pointers to unions?
const union_val = try block.addTyOp(.load, union_ty, union_ptr);
const active_tag = try block.addTyOp(.get_union_tag, tag_ty, union_val);
try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, .fromValue(want_tag));
}
}
if (field_ty.zigTypeTag(zcu) == .noreturn) {
_ = try block.addNoOp(.unreach);
return .unreachable_value;
}
return block.addStructFieldPtr(union_ptr, field_index, ptr_field_ty);
}
fn unionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
union_byval: Air.Inst.Ref,
field_name: InternPool.NullTerminatedString,
field_name_src: LazySrcLoc,
union_ty: Type,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(union_ty.zigTypeTag(zcu) == .@"union");
try union_ty.resolveFields(pt);
const union_obj = zcu.typeToUnion(union_ty).?;
const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src);
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
const enum_field_index: u32 = @intCast(Type.fromInterned(union_obj.enum_tag_ty).enumFieldIndex(field_name, zcu).?);
if (try sema.resolveValue(union_byval)) |union_val| {
if (union_val.isUndef(zcu)) return pt.undefRef(field_ty);
const un = ip.indexToKey(union_val.toIntern()).un;
const field_tag = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), enum_field_index);
const tag_matches = un.tag == field_tag.toIntern();
switch (union_obj.flagsUnordered(ip).layout) {
.auto => {
if (tag_matches) {
return Air.internedToRef(un.val);
} else {
const msg = msg: {
const active_index = Type.fromInterned(union_obj.enum_tag_ty).enumTagFieldIndex(Value.fromInterned(un.tag), zcu).?;
const active_field_name = Type.fromInterned(union_obj.enum_tag_ty).enumFieldName(active_index, zcu);
const msg = try sema.errMsg(src, "access of union field '{f}' while field '{f}' is active", .{
field_name.fmt(ip), active_field_name.fmt(ip),
});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
},
.@"extern" => if (tag_matches) {
// Fast path - no need to use bitcast logic.
return Air.internedToRef(un.val);
} else if (try sema.bitCastVal(union_val, field_ty, 0, 0, 0)) |field_val| {
return Air.internedToRef(field_val.toIntern());
},
.@"packed" => if (tag_matches) {
// Fast path - no need to use bitcast logic.
return Air.internedToRef(un.val);
} else if (try sema.bitCastVal(union_val, field_ty, 0, try union_ty.bitSizeSema(pt), 0)) |field_val| {
return Air.internedToRef(field_val.toIntern());
},
}
}
if (union_obj.flagsUnordered(ip).layout == .auto and block.wantSafety() and
union_ty.unionTagTypeSafety(zcu) != null and union_obj.field_types.len > 1)
{
const wanted_tag_val = try pt.enumValueFieldIndex(.fromInterned(union_obj.enum_tag_ty), enum_field_index);
const wanted_tag = Air.internedToRef(wanted_tag_val.toIntern());
const active_tag = try block.addTyOp(.get_union_tag, .fromInterned(union_obj.enum_tag_ty), union_byval);
try sema.addSafetyCheckInactiveUnionField(block, src, active_tag, wanted_tag);
}
if (field_ty.zigTypeTag(zcu) == .noreturn) {
_ = try block.addNoOp(.unreach);
return .unreachable_value;
}
if (try sema.typeHasOnePossibleValue(field_ty)) |field_only_value| {
return Air.internedToRef(field_only_value.toIntern());
}
try field_ty.resolveLayout(pt);
return block.addStructFieldVal(union_byval, field_index, field_ty);
}
fn elemPtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
indexable_ptr: Air.Inst.Ref,
elem_index: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
init: bool,
oob_safety: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const indexable_ptr_src = src; // TODO better source location
const indexable_ptr_ty = sema.typeOf(indexable_ptr);
const indexable_ty = switch (indexable_ptr_ty.zigTypeTag(zcu)) {
.pointer => indexable_ptr_ty.childType(zcu),
else => return sema.fail(block, indexable_ptr_src, "expected pointer, found '{f}'", .{indexable_ptr_ty.fmt(pt)}),
};
try sema.checkIndexable(block, src, indexable_ty);
const elem_ptr = switch (indexable_ty.zigTypeTag(zcu)) {
.array, .vector => try sema.elemPtrArray(block, src, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init, oob_safety),
.@"struct" => blk: {
// Tuple field access.
const index_val = try sema.resolveConstDefinedValue(block, elem_index_src, elem_index, .{ .simple = .tuple_field_index });
const index: u32 = @intCast(try index_val.toUnsignedIntSema(pt));
break :blk try sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index, init);
},
else => {
const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src);
return elemPtrOneLayerOnly(sema, block, src, indexable, elem_index, elem_index_src, init, oob_safety);
},
};
try sema.checkKnownAllocPtr(block, indexable_ptr, elem_ptr);
return elem_ptr;
}
/// Asserts that the type of indexable is pointer.
fn elemPtrOneLayerOnly(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
indexable: Air.Inst.Ref,
elem_index: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
init: bool,
oob_safety: bool,
) CompileError!Air.Inst.Ref {
const indexable_src = src; // TODO better source location
const indexable_ty = sema.typeOf(indexable);
const pt = sema.pt;
const zcu = pt.zcu;
try sema.checkIndexable(block, src, indexable_ty);
switch (indexable_ty.ptrSize(zcu)) {
.slice => return sema.elemPtrSlice(block, src, indexable_src, indexable, elem_index_src, elem_index, oob_safety),
.many, .c => {
const maybe_ptr_val = try sema.resolveDefinedValue(block, indexable_src, indexable);
const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
ct: {
const ptr_val = maybe_ptr_val orelse break :ct;
const index_val = maybe_index_val orelse break :ct;
const index: usize = @intCast(try index_val.toUnsignedIntSema(pt));
const elem_ptr = try ptr_val.ptrElem(index, pt);
return Air.internedToRef(elem_ptr.toIntern());
}
try sema.checkLogicalPtrOperation(block, src, indexable_ty);
const result_ty = try indexable_ty.elemPtrType(null, pt);
try sema.validateRuntimeElemAccess(block, elem_index_src, result_ty, indexable_ty, indexable_src);
try sema.validateRuntimeValue(block, indexable_src, indexable);
if (!try result_ty.childType(zcu).hasRuntimeBitsIgnoreComptimeSema(pt)) {
// zero-bit child type; just bitcast the pointer
return block.addBitCast(result_ty, indexable);
}
return block.addPtrElemPtr(indexable, elem_index, result_ty);
},
.one => {
const child_ty = indexable_ty.childType(zcu);
const elem_ptr = switch (child_ty.zigTypeTag(zcu)) {
.array, .vector => try sema.elemPtrArray(block, src, indexable_src, indexable, elem_index_src, elem_index, init, oob_safety),
.@"struct" => blk: {
assert(child_ty.isTuple(zcu));
const index_val = try sema.resolveConstDefinedValue(block, elem_index_src, elem_index, .{ .simple = .tuple_field_index });
const index: u32 = @intCast(try index_val.toUnsignedIntSema(pt));
break :blk try sema.tupleFieldPtr(block, indexable_src, indexable, elem_index_src, index, false);
},
else => unreachable, // Guaranteed by checkIndexable
};
try sema.checkKnownAllocPtr(block, indexable, elem_ptr);
return elem_ptr;
},
}
}
fn elemVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
indexable: Air.Inst.Ref,
elem_index_uncasted: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
oob_safety: bool,
) CompileError!Air.Inst.Ref {
const indexable_src = src; // TODO better source location
const indexable_ty = sema.typeOf(indexable);
const pt = sema.pt;
const zcu = pt.zcu;
try sema.checkIndexable(block, src, indexable_ty);
// TODO in case of a vector of pointers, we need to detect whether the element
// index is a scalar or vector instead of unconditionally casting to usize.
const elem_index = try sema.coerce(block, .usize, elem_index_uncasted, elem_index_src);
switch (indexable_ty.zigTypeTag(zcu)) {
.pointer => switch (indexable_ty.ptrSize(zcu)) {
.slice => return sema.elemValSlice(block, src, indexable_src, indexable, elem_index_src, elem_index, oob_safety),
.many, .c => {
const maybe_indexable_val = try sema.resolveDefinedValue(block, indexable_src, indexable);
const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
const elem_ty = indexable_ty.elemType2(zcu);
ct: {
const indexable_val = maybe_indexable_val orelse break :ct;
const index_val = maybe_index_val orelse break :ct;
const index: usize = @intCast(try index_val.toUnsignedIntSema(pt));
const many_ptr_ty = try pt.manyConstPtrType(elem_ty);
const many_ptr_val = try pt.getCoerced(indexable_val, many_ptr_ty);
const elem_ptr_ty = try pt.singleConstPtrType(elem_ty);
const elem_ptr_val = try many_ptr_val.ptrElem(index, pt);
const elem_val = try sema.pointerDeref(block, indexable_src, elem_ptr_val, elem_ptr_ty) orelse break :ct;
return Air.internedToRef((try pt.getCoerced(elem_val, elem_ty)).toIntern());
}
if (try sema.typeHasOnePossibleValue(elem_ty)) |elem_only_value| {
return Air.internedToRef(elem_only_value.toIntern());
}
try sema.checkLogicalPtrOperation(block, src, indexable_ty);
return block.addBinOp(.ptr_elem_val, indexable, elem_index);
},
.one => {
arr_sent: {
const inner_ty = indexable_ty.childType(zcu);
if (inner_ty.zigTypeTag(zcu) != .array) break :arr_sent;
const sentinel = inner_ty.sentinel(zcu) orelse break :arr_sent;
const index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index) orelse break :arr_sent;
const index = try sema.usizeCast(block, src, try index_val.toUnsignedIntSema(pt));
if (index != inner_ty.arrayLen(zcu)) break :arr_sent;
return Air.internedToRef(sentinel.toIntern());
}
const elem_ptr = try sema.elemPtr(block, indexable_src, indexable, elem_index, elem_index_src, false, oob_safety);
return sema.analyzeLoad(block, indexable_src, elem_ptr, elem_index_src);
},
},
.array => return sema.elemValArray(block, src, indexable_src, indexable, elem_index_src, elem_index, oob_safety),
.vector => {
// TODO: If the index is a vector, the result should be a vector.
return sema.elemValArray(block, src, indexable_src, indexable, elem_index_src, elem_index, oob_safety);
},
.@"struct" => {
// Tuple field access.
const index_val = try sema.resolveConstDefinedValue(block, elem_index_src, elem_index, .{ .simple = .tuple_field_index });
const index: u32 = @intCast(try index_val.toUnsignedIntSema(pt));
return sema.tupleField(block, indexable_src, indexable, elem_index_src, index);
},
else => unreachable,
}
}
/// Called when the index or indexable is runtime known.
fn validateRuntimeElemAccess(
sema: *Sema,
block: *Block,
elem_index_src: LazySrcLoc,
elem_ty: Type,
parent_ty: Type,
parent_src: LazySrcLoc,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
if (try elem_ty.comptimeOnlySema(sema.pt)) {
const msg = msg: {
const msg = try sema.errMsg(
elem_index_src,
"values of type '{f}' must be comptime-known, but index value is runtime-known",
.{parent_ty.fmt(sema.pt)},
);
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsComptime(msg, parent_src, parent_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
if (zcu.intern_pool.indexToKey(parent_ty.toIntern()) == .ptr_type) {
const target = zcu.getTarget();
const as = parent_ty.ptrAddressSpace(zcu);
if (target_util.shouldBlockPointerOps(target, as)) {
return sema.fail(block, elem_index_src, "cannot access element of logical pointer '{f}'", .{parent_ty.fmt(pt)});
}
}
}
fn tupleFieldPtr(
sema: *Sema,
block: *Block,
tuple_ptr_src: LazySrcLoc,
tuple_ptr: Air.Inst.Ref,
field_index_src: LazySrcLoc,
field_index: u32,
init: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const tuple_ptr_ty = sema.typeOf(tuple_ptr);
const tuple_ptr_info = tuple_ptr_ty.ptrInfo(zcu);
const tuple_ty: Type = .fromInterned(tuple_ptr_info.child);
try tuple_ty.resolveFields(pt);
const field_count = tuple_ty.structFieldCount(zcu);
if (field_count == 0) {
return sema.fail(block, tuple_ptr_src, "indexing into empty tuple is not allowed", .{});
}
if (field_index >= field_count) {
return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{
field_index, field_count,
});
}
const field_ty = tuple_ty.fieldType(field_index, zcu);
const ptr_field_ty = try pt.ptrTypeSema(.{
.child = field_ty.toIntern(),
.flags = .{
.is_const = tuple_ptr_info.flags.is_const,
.is_volatile = tuple_ptr_info.flags.is_volatile,
.address_space = tuple_ptr_info.flags.address_space,
.alignment = a: {
if (tuple_ptr_info.flags.alignment == .none) break :a .none;
// The tuple pointer isn't naturally aligned, so the field pointer might be underaligned.
const tuple_align = tuple_ptr_info.flags.alignment;
const field_align = try field_ty.abiAlignmentSema(pt);
break :a tuple_align.min(field_align);
},
},
});
if (tuple_ty.structFieldIsComptime(field_index, zcu))
try tuple_ty.resolveStructFieldInits(pt);
if (try tuple_ty.structFieldValueComptime(pt, field_index)) |default_val| {
return Air.internedToRef((try pt.intern(.{ .ptr = .{
.ty = ptr_field_ty.toIntern(),
.base_addr = .{ .comptime_field = default_val.toIntern() },
.byte_offset = 0,
} })));
}
if (try sema.resolveValue(tuple_ptr)) |tuple_ptr_val| {
const field_ptr_val = try tuple_ptr_val.ptrField(field_index, pt);
return Air.internedToRef(field_ptr_val.toIntern());
}
if (!init) {
try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_ptr_src);
}
return block.addStructFieldPtr(tuple_ptr, field_index, ptr_field_ty);
}
fn tupleField(
sema: *Sema,
block: *Block,
tuple_src: LazySrcLoc,
tuple: Air.Inst.Ref,
field_index_src: LazySrcLoc,
field_index: u32,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const tuple_ty = sema.typeOf(tuple);
try tuple_ty.resolveFields(pt);
const field_count = tuple_ty.structFieldCount(zcu);
if (field_count == 0) {
return sema.fail(block, tuple_src, "indexing into empty tuple is not allowed", .{});
}
if (field_index >= field_count) {
return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{
field_index, field_count,
});
}
const field_ty = tuple_ty.fieldType(field_index, zcu);
if (tuple_ty.structFieldIsComptime(field_index, zcu))
try tuple_ty.resolveStructFieldInits(pt);
if (try tuple_ty.structFieldValueComptime(pt, field_index)) |default_value| {
return Air.internedToRef(default_value.toIntern()); // comptime field
}
if (try sema.resolveValue(tuple)) |tuple_val| {
if (tuple_val.isUndef(zcu)) return pt.undefRef(field_ty);
return Air.internedToRef((try tuple_val.fieldValue(pt, field_index)).toIntern());
}
try sema.validateRuntimeElemAccess(block, field_index_src, field_ty, tuple_ty, tuple_src);
try field_ty.resolveLayout(pt);
return block.addStructFieldVal(tuple, field_index, field_ty);
}
fn elemValArray(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
array_src: LazySrcLoc,
array: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
elem_index: Air.Inst.Ref,
oob_safety: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const array_ty = sema.typeOf(array);
const array_sent = array_ty.sentinel(zcu);
const array_len = array_ty.arrayLen(zcu);
const array_len_s = array_len + @intFromBool(array_sent != null);
const elem_ty = array_ty.childType(zcu);
if (array_len_s == 0) {
return sema.fail(block, array_src, "indexing into empty array is not allowed", .{});
}
const maybe_undef_array_val = try sema.resolveValue(array);
// index must be defined since it can access out of bounds
const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
if (maybe_index_val) |index_val| {
const index: usize = @intCast(try index_val.toUnsignedIntSema(pt));
if (array_sent) |s| {
if (index == array_len) {
return Air.internedToRef(s.toIntern());
}
}
if (index >= array_len_s) {
const sentinel_label: []const u8 = if (array_sent != null) " +1 (sentinel)" else "";
return sema.fail(block, elem_index_src, "index {d} outside array of length {d}{s}", .{ index, array_len, sentinel_label });
}
}
if (maybe_undef_array_val) |array_val| {
if (array_val.isUndef(zcu)) {
return pt.undefRef(elem_ty);
}
if (maybe_index_val) |index_val| {
const index: usize = @intCast(try index_val.toUnsignedIntSema(pt));
const elem_val = try array_val.elemValue(pt, index);
return Air.internedToRef(elem_val.toIntern());
}
}
try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, array_ty, array_src);
try sema.validateRuntimeValue(block, array_src, array);
if (oob_safety and block.wantSafety()) {
// Runtime check is only needed if unable to comptime check.
if (maybe_index_val == null) {
const len_inst = try pt.intRef(.usize, array_len);
const cmp_op: Air.Inst.Tag = if (array_sent != null) .cmp_lte else .cmp_lt;
try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
}
}
if (try sema.typeHasOnePossibleValue(elem_ty)) |elem_val|
return Air.internedToRef(elem_val.toIntern());
return block.addBinOp(.array_elem_val, array, elem_index);
}
fn elemPtrArray(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
array_ptr_src: LazySrcLoc,
array_ptr: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
elem_index: Air.Inst.Ref,
init: bool,
oob_safety: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const array_ptr_ty = sema.typeOf(array_ptr);
const array_ty = array_ptr_ty.childType(zcu);
const array_sent = array_ty.sentinel(zcu) != null;
const array_len = array_ty.arrayLen(zcu);
const array_len_s = array_len + @intFromBool(array_sent);
if (array_len_s == 0) {
return sema.fail(block, array_ptr_src, "indexing into empty array is not allowed", .{});
}
const maybe_undef_array_ptr_val = try sema.resolveValue(array_ptr);
// The index must not be undefined since it can be out of bounds.
const offset: ?usize = if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| o: {
const index = try sema.usizeCast(block, elem_index_src, try index_val.toUnsignedIntSema(pt));
if (index >= array_len_s) {
const sentinel_label: []const u8 = if (array_sent) " +1 (sentinel)" else "";
return sema.fail(block, elem_index_src, "index {d} outside array of length {d}{s}", .{ index, array_len, sentinel_label });
}
break :o index;
} else null;
if (offset == null and array_ty.zigTypeTag(zcu) == .vector) {
return sema.fail(block, elem_index_src, "vector index not comptime known", .{});
}
const elem_ptr_ty = try array_ptr_ty.elemPtrType(offset, pt);
if (maybe_undef_array_ptr_val) |array_ptr_val| {
if (array_ptr_val.isUndef(zcu)) {
return pt.undefRef(elem_ptr_ty);
}
if (offset) |index| {
const elem_ptr = try array_ptr_val.ptrElem(index, pt);
return Air.internedToRef(elem_ptr.toIntern());
}
}
if (!init) {
try sema.validateRuntimeElemAccess(block, elem_index_src, array_ty.elemType2(zcu), array_ty, array_ptr_src);
try sema.validateRuntimeValue(block, array_ptr_src, array_ptr);
}
// Runtime check is only needed if unable to comptime check.
if (oob_safety and block.wantSafety() and offset == null) {
const len_inst = try pt.intRef(.usize, array_len);
const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt;
try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
}
return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty);
}
fn elemValSlice(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
slice_src: LazySrcLoc,
slice: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
elem_index: Air.Inst.Ref,
oob_safety: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const slice_ty = sema.typeOf(slice);
const slice_sent = slice_ty.sentinel(zcu) != null;
const elem_ty = slice_ty.elemType2(zcu);
var runtime_src = slice_src;
// slice must be defined since it can dereferenced as null
const maybe_slice_val = try sema.resolveDefinedValue(block, slice_src, slice);
// index must be defined since it can index out of bounds
const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index);
if (maybe_slice_val) |slice_val| {
runtime_src = elem_index_src;
const slice_len = try slice_val.sliceLen(pt);
const slice_len_s = slice_len + @intFromBool(slice_sent);
if (slice_len_s == 0) {
return sema.fail(block, slice_src, "indexing into empty slice is not allowed", .{});
}
if (maybe_index_val) |index_val| {
const index: usize = @intCast(try index_val.toUnsignedIntSema(pt));
if (index >= slice_len_s) {
const sentinel_label: []const u8 = if (slice_sent) " +1 (sentinel)" else "";
return sema.fail(block, elem_index_src, "index {d} outside slice of length {d}{s}", .{ index, slice_len, sentinel_label });
}
const elem_ptr_ty = try slice_ty.elemPtrType(index, pt);
const elem_ptr_val = try slice_val.ptrElem(index, pt);
if (try sema.pointerDeref(block, slice_src, elem_ptr_val, elem_ptr_ty)) |elem_val| {
return Air.internedToRef(elem_val.toIntern());
}
runtime_src = slice_src;
}
}
if (try sema.typeHasOnePossibleValue(elem_ty)) |elem_only_value| {
return Air.internedToRef(elem_only_value.toIntern());
}
try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ty, slice_ty, slice_src);
try sema.validateRuntimeValue(block, slice_src, slice);
if (oob_safety and block.wantSafety()) {
const len_inst = if (maybe_slice_val) |slice_val|
try pt.intRef(.usize, try slice_val.sliceLen(pt))
else
try block.addTyOp(.slice_len, .usize, slice);
const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
}
return block.addBinOp(.slice_elem_val, slice, elem_index);
}
fn elemPtrSlice(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
slice_src: LazySrcLoc,
slice: Air.Inst.Ref,
elem_index_src: LazySrcLoc,
elem_index: Air.Inst.Ref,
oob_safety: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const slice_ty = sema.typeOf(slice);
const slice_sent = slice_ty.sentinel(zcu) != null;
const maybe_undef_slice_val = try sema.resolveValue(slice);
// The index must not be undefined since it can be out of bounds.
const offset: ?usize = if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| o: {
const index = try sema.usizeCast(block, elem_index_src, try index_val.toUnsignedIntSema(pt));
break :o index;
} else null;
const elem_ptr_ty = try slice_ty.elemPtrType(offset, pt);
if (maybe_undef_slice_val) |slice_val| {
if (slice_val.isUndef(zcu)) {
return pt.undefRef(elem_ptr_ty);
}
const slice_len = try slice_val.sliceLen(pt);
const slice_len_s = slice_len + @intFromBool(slice_sent);
if (slice_len_s == 0) {
return sema.fail(block, slice_src, "indexing into empty slice is not allowed", .{});
}
if (offset) |index| {
if (index >= slice_len_s) {
const sentinel_label: []const u8 = if (slice_sent) " +1 (sentinel)" else "";
return sema.fail(block, elem_index_src, "index {d} outside slice of length {d}{s}", .{ index, slice_len, sentinel_label });
}
const elem_ptr_val = try slice_val.ptrElem(index, pt);
return Air.internedToRef(elem_ptr_val.toIntern());
}
}
try sema.validateRuntimeElemAccess(block, elem_index_src, elem_ptr_ty, slice_ty, slice_src);
try sema.validateRuntimeValue(block, slice_src, slice);
if (oob_safety and block.wantSafety()) {
const len_inst = len: {
if (maybe_undef_slice_val) |slice_val|
if (!slice_val.isUndef(zcu))
break :len try pt.intRef(.usize, try slice_val.sliceLen(pt));
break :len try block.addTyOp(.slice_len, .usize, slice);
};
const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt;
try sema.addSafetyCheckIndexOob(block, src, elem_index, len_inst, cmp_op);
}
if (!try slice_ty.childType(zcu).hasRuntimeBitsIgnoreComptimeSema(pt)) {
// zero-bit child type; just extract the pointer and bitcast it
const slice_ptr = try block.addTyOp(.slice_ptr, slice_ty.slicePtrFieldType(zcu), slice);
return block.addBitCast(elem_ptr_ty, slice_ptr);
}
return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty);
}
pub fn coerce(
sema: *Sema,
block: *Block,
dest_ty_unresolved: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
return sema.coerceExtra(block, dest_ty_unresolved, inst, inst_src, .{}) catch |err| switch (err) {
error.NotCoercible => unreachable,
else => |e| return e,
};
}
const CoercionError = CompileError || error{
/// When coerce is called recursively, this error should be returned instead of using `fail`
/// to ensure correct types in compile errors.
NotCoercible,
};
const CoerceOpts = struct {
/// Should coerceExtra emit error messages.
report_err: bool = true,
/// Ignored if `report_err == false`.
is_ret: bool = false,
/// Should coercion to comptime_int emit an error message.
no_cast_to_comptime_int: bool = false,
param_src: struct {
func_inst: Air.Inst.Ref = .none,
param_i: u32 = undefined,
fn get(info: @This(), sema: *Sema) !?LazySrcLoc {
if (info.func_inst == .none) return null;
const func_inst = try sema.funcDeclSrcInst(info.func_inst) orelse return null;
return .{
.base_node_inst = func_inst,
.offset = .{ .fn_proto_param_type = .{
.fn_proto_node_offset = .zero,
.param_index = info.param_i,
} },
};
}
} = .{ .func_inst = .none, .param_i = undefined },
};
fn coerceExtra(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
opts: CoerceOpts,
) CoercionError!Air.Inst.Ref {
if (dest_ty.isGenericPoison()) return inst;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const dest_ty_src = inst_src; // TODO better source location
try dest_ty.resolveFields(pt);
const inst_ty = sema.typeOf(inst);
try inst_ty.resolveFields(pt);
const target = zcu.getTarget();
// If the types are the same, we can return the operand.
if (dest_ty.eql(inst_ty, zcu))
return inst;
const maybe_inst_val = try sema.resolveValue(inst);
var in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src, maybe_inst_val);
if (in_memory_result == .ok) {
if (maybe_inst_val) |val| {
return sema.coerceInMemory(val, dest_ty);
}
try sema.requireRuntimeBlock(block, inst_src, null);
const new_val = try block.addBitCast(dest_ty, inst);
try sema.checkKnownAllocPtr(block, inst, new_val);
return new_val;
}
switch (dest_ty.zigTypeTag(zcu)) {
.optional => optional: {
if (maybe_inst_val) |val| {
// undefined sets the optional bit also to undefined.
if (val.toIntern() == .undef) {
return pt.undefRef(dest_ty);
}
// null to ?T
if (val.toIntern() == .null_value) {
return Air.internedToRef((try pt.intern(.{ .opt = .{
.ty = dest_ty.toIntern(),
.val = .none,
} })));
}
}
// cast from ?*T and ?[*]T to ?*anyopaque
// but don't do it if the source type is a double pointer
if (dest_ty.isPtrLikeOptional(zcu) and
dest_ty.elemType2(zcu).toIntern() == .anyopaque_type and
inst_ty.isPtrAtRuntime(zcu))
anyopaque_check: {
if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :optional;
const elem_ty = inst_ty.elemType2(zcu);
if (elem_ty.zigTypeTag(zcu) == .pointer or elem_ty.isPtrLikeOptional(zcu)) {
in_memory_result = .{ .double_ptr_to_anyopaque = .{
.actual = inst_ty,
.wanted = dest_ty,
} };
break :optional;
}
// Let the logic below handle wrapping the optional now that
// it has been checked to correctly coerce.
if (!inst_ty.isPtrLikeOptional(zcu)) break :anyopaque_check;
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
}
// T to ?T
const child_type = dest_ty.optionalChild(zcu);
const intermediate = sema.coerceExtra(block, child_type, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) {
error.NotCoercible => {
if (in_memory_result == .no_match) {
// Try to give more useful notes
in_memory_result = try sema.coerceInMemoryAllowed(block, child_type, inst_ty, false, target, dest_ty_src, inst_src, maybe_inst_val);
}
break :optional;
},
else => |e| return e,
};
return try sema.wrapOptional(block, dest_ty, intermediate, inst_src);
},
.pointer => pointer: {
const dest_info = dest_ty.ptrInfo(zcu);
// Function body to function pointer.
if (inst_ty.zigTypeTag(zcu) == .@"fn") {
const fn_val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
const fn_nav = switch (zcu.intern_pool.indexToKey(fn_val.toIntern())) {
.func => |f| f.owner_nav,
.@"extern" => |e| e.owner_nav,
else => unreachable,
};
const inst_as_ptr = try sema.analyzeNavRef(block, inst_src, fn_nav);
return sema.coerce(block, dest_ty, inst_as_ptr, inst_src);
}
// *T to *[1]T
single_item: {
if (dest_info.flags.size != .one) break :single_item;
if (!inst_ty.isSinglePointer(zcu)) break :single_item;
if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer;
const ptr_elem_ty = inst_ty.childType(zcu);
const array_ty: Type = .fromInterned(dest_info.child);
if (array_ty.zigTypeTag(zcu) != .array) break :single_item;
const array_elem_ty = array_ty.childType(zcu);
if (array_ty.arrayLen(zcu) != 1) break :single_item;
const dest_is_mut = !dest_info.flags.is_const;
switch (try sema.coerceInMemoryAllowed(block, array_elem_ty, ptr_elem_ty, dest_is_mut, target, dest_ty_src, inst_src, maybe_inst_val)) {
.ok => {},
else => break :single_item,
}
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
}
// Coercions where the source is a single pointer to an array.
src_array_ptr: {
if (!inst_ty.isSinglePointer(zcu)) break :src_array_ptr;
if (dest_info.flags.size == .one) break :src_array_ptr; // `*[n]T` -> `*T` isn't valid
if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer;
const array_ty = inst_ty.childType(zcu);
if (array_ty.zigTypeTag(zcu) != .array) break :src_array_ptr;
const array_elem_type = array_ty.childType(zcu);
const dest_is_mut = !dest_info.flags.is_const;
const dst_elem_type: Type = .fromInterned(dest_info.child);
const elem_res = try sema.coerceInMemoryAllowed(block, dst_elem_type, array_elem_type, dest_is_mut, target, dest_ty_src, inst_src, maybe_inst_val);
switch (elem_res) {
.ok => {},
else => {
in_memory_result = .{ .ptr_child = .{
.child = try elem_res.dupe(sema.arena),
.actual = array_elem_type,
.wanted = dst_elem_type,
} };
break :src_array_ptr;
},
}
if (dest_info.sentinel != .none) {
if (array_ty.sentinel(zcu)) |inst_sent| {
if (Air.internedToRef(dest_info.sentinel) !=
try sema.coerceInMemory(inst_sent, dst_elem_type))
{
in_memory_result = .{ .ptr_sentinel = .{
.actual = inst_sent,
.wanted = Value.fromInterned(dest_info.sentinel),
.ty = dst_elem_type,
} };
break :src_array_ptr;
}
} else {
in_memory_result = .{ .ptr_sentinel = .{
.actual = Value.@"unreachable",
.wanted = Value.fromInterned(dest_info.sentinel),
.ty = dst_elem_type,
} };
break :src_array_ptr;
}
}
switch (dest_info.flags.size) {
.slice => {
// *[N]T to []T
return sema.coerceArrayPtrToSlice(block, dest_ty, inst, inst_src);
},
.c => {
// *[N]T to [*c]T
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
},
.many => {
// *[N]T to [*]T
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
},
.one => unreachable, // early exit at top of block
}
}
// coercion from C pointer
if (inst_ty.isCPtr(zcu)) src_c_ptr: {
if (dest_info.flags.size == .slice) break :src_c_ptr;
if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :src_c_ptr;
// In this case we must add a safety check because the C pointer
// could be null.
const src_elem_ty = inst_ty.childType(zcu);
const dest_is_mut = !dest_info.flags.is_const;
const dst_elem_type: Type = .fromInterned(dest_info.child);
switch (try sema.coerceInMemoryAllowed(block, dst_elem_type, src_elem_ty, dest_is_mut, target, dest_ty_src, inst_src, maybe_inst_val)) {
.ok => {},
else => break :src_c_ptr,
}
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
}
// cast from *T and [*]T to *anyopaque
// but don't do it if the source type is a double pointer
if (dest_info.child == .anyopaque_type and inst_ty.zigTypeTag(zcu) == .pointer) to_anyopaque: {
if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :pointer;
const elem_ty = inst_ty.elemType2(zcu);
if (elem_ty.zigTypeTag(zcu) == .pointer or elem_ty.isPtrLikeOptional(zcu)) {
in_memory_result = .{ .double_ptr_to_anyopaque = .{
.actual = inst_ty,
.wanted = dest_ty,
} };
break :pointer;
}
if (dest_ty.isSlice(zcu)) break :to_anyopaque;
if (inst_ty.isSlice(zcu)) {
in_memory_result = .{ .slice_to_anyopaque = .{
.actual = inst_ty,
.wanted = dest_ty,
} };
break :pointer;
}
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
}
switch (dest_info.flags.size) {
// coercion to C pointer
.c => switch (inst_ty.zigTypeTag(zcu)) {
.null => return Air.internedToRef(try pt.intern(.{ .ptr = .{
.ty = dest_ty.toIntern(),
.base_addr = .int,
.byte_offset = 0,
} })),
.comptime_int => {
const addr = sema.coerceExtra(block, .usize, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) {
error.NotCoercible => break :pointer,
else => |e| return e,
};
return try sema.coerceCompatiblePtrs(block, dest_ty, addr, inst_src);
},
.int => {
const ptr_size_ty: Type = switch (inst_ty.intInfo(zcu).signedness) {
.signed => .isize,
.unsigned => .usize,
};
const addr = sema.coerceExtra(block, ptr_size_ty, inst, inst_src, .{ .report_err = false }) catch |err| switch (err) {
error.NotCoercible => {
// Try to give more useful notes
in_memory_result = try sema.coerceInMemoryAllowed(block, ptr_size_ty, inst_ty, false, target, dest_ty_src, inst_src, maybe_inst_val);
break :pointer;
},
else => |e| return e,
};
return try sema.coerceCompatiblePtrs(block, dest_ty, addr, inst_src);
},
.pointer => p: {
if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :p;
const inst_info = inst_ty.ptrInfo(zcu);
switch (try sema.coerceInMemoryAllowed(
block,
.fromInterned(dest_info.child),
.fromInterned(inst_info.child),
!dest_info.flags.is_const,
target,
dest_ty_src,
inst_src,
maybe_inst_val,
)) {
.ok => {},
else => break :p,
}
if (inst_info.flags.size == .slice) {
assert(dest_info.sentinel == .none);
if (inst_info.sentinel == .none or
inst_info.sentinel != (try pt.intValue(.fromInterned(inst_info.child), 0)).toIntern())
break :p;
const slice_ptr = try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty);
return sema.coerceCompatiblePtrs(block, dest_ty, slice_ptr, inst_src);
}
return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src);
},
else => {},
},
.one => {},
.slice => to_slice: {
if (inst_ty.zigTypeTag(zcu) == .array) {
return sema.fail(
block,
inst_src,
"array literal requires address-of operator (&) to coerce to slice type '{f}'",
.{dest_ty.fmt(pt)},
);
}
if (!inst_ty.isSinglePointer(zcu)) break :to_slice;
const inst_child_ty = inst_ty.childType(zcu);
if (!inst_child_ty.isTuple(zcu)) break :to_slice;
// empty tuple to zero-length slice
// note that this allows coercing to a mutable slice.
if (inst_child_ty.structFieldCount(zcu) == 0) {
const align_val = try dest_ty.ptrAlignmentSema(pt);
return Air.internedToRef(try pt.intern(.{ .slice = .{
.ty = dest_ty.toIntern(),
.ptr = try pt.intern(.{ .ptr = .{
.ty = dest_ty.slicePtrFieldType(zcu).toIntern(),
.base_addr = .int,
.byte_offset = align_val.toByteUnits().?,
} }),
.len = .zero_usize,
} }));
}
// pointer to tuple to slice
if (!dest_info.flags.is_const) {
const err_msg = err_msg: {
const err_msg = try sema.errMsg(inst_src, "cannot cast pointer to tuple to '{f}'", .{dest_ty.fmt(pt)});
errdefer err_msg.destroy(sema.gpa);
try sema.errNote(dest_ty_src, err_msg, "pointers to tuples can only coerce to constant pointers", .{});
break :err_msg err_msg;
};
return sema.failWithOwnedErrorMsg(block, err_msg);
}
return sema.coerceTupleToSlicePtrs(block, dest_ty, dest_ty_src, inst, inst_src);
},
.many => p: {
if (!inst_ty.isSlice(zcu)) break :p;
if (!sema.checkPtrAttributes(dest_ty, inst_ty, &in_memory_result)) break :p;
const inst_info = inst_ty.ptrInfo(zcu);
switch (try sema.coerceInMemoryAllowed(
block,
.fromInterned(dest_info.child),
.fromInterned(inst_info.child),
!dest_info.flags.is_const,
target,
dest_ty_src,
inst_src,
maybe_inst_val,
)) {
.ok => {},
else => break :p,
}
if (dest_info.sentinel == .none or inst_info.sentinel == .none or
Air.internedToRef(dest_info.sentinel) !=
try sema.coerceInMemory(Value.fromInterned(inst_info.sentinel), .fromInterned(dest_info.child)))
break :p;
const slice_ptr = try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty);
return sema.coerceCompatiblePtrs(block, dest_ty, slice_ptr, inst_src);
},
}
},
.int, .comptime_int => switch (inst_ty.zigTypeTag(zcu)) {
.float, .comptime_float => float: {
const val = maybe_inst_val orelse {
if (dest_ty.zigTypeTag(zcu) == .comptime_int) {
if (!opts.report_err) return error.NotCoercible;
return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_int });
}
break :float;
};
const result_val = try sema.intFromFloat(block, inst_src, val, inst_ty, dest_ty, .exact);
return Air.internedToRef(result_val.toIntern());
},
.int, .comptime_int => {
if (maybe_inst_val) |val| {
// comptime-known integer to other number
if (!(try sema.intFitsInType(val, dest_ty, null))) {
if (!opts.report_err) return error.NotCoercible;
return sema.fail(block, inst_src, "type '{f}' cannot represent integer value '{f}'", .{ dest_ty.fmt(pt), val.fmtValueSema(pt, sema) });
}
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.undef => try pt.undefRef(dest_ty),
.int => |int| Air.internedToRef(
try zcu.intern_pool.getCoercedInts(zcu.gpa, pt.tid, int, dest_ty.toIntern()),
),
else => unreachable,
};
}
if (dest_ty.zigTypeTag(zcu) == .comptime_int) {
if (!opts.report_err) return error.NotCoercible;
if (opts.no_cast_to_comptime_int) return inst;
return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_int });
}
// integer widening
const dst_info = dest_ty.intInfo(zcu);
const src_info = inst_ty.intInfo(zcu);
if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or
// small enough unsigned ints can get casted to large enough signed ints
(dst_info.signedness == .signed and dst_info.bits > src_info.bits))
{
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.intcast, dest_ty, inst);
}
},
else => {},
},
.float, .comptime_float => switch (inst_ty.zigTypeTag(zcu)) {
.comptime_float => {
const val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
const result_val = try val.floatCast(dest_ty, pt);
return Air.internedToRef(result_val.toIntern());
},
.float => {
if (maybe_inst_val) |val| {
const result_val = try val.floatCast(dest_ty, pt);
if (!val.eql(try result_val.floatCast(inst_ty, pt), inst_ty, zcu)) {
return sema.fail(
block,
inst_src,
"type '{f}' cannot represent float value '{f}'",
.{ dest_ty.fmt(pt), val.fmtValueSema(pt, sema) },
);
}
return Air.internedToRef(result_val.toIntern());
} else if (dest_ty.zigTypeTag(zcu) == .comptime_float) {
if (!opts.report_err) return error.NotCoercible;
return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float });
}
// float widening
const src_bits = inst_ty.floatBits(target);
const dst_bits = dest_ty.floatBits(target);
if (dst_bits >= src_bits) {
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.fpext, dest_ty, inst);
}
},
.int, .comptime_int => int: {
const val = maybe_inst_val orelse {
if (dest_ty.zigTypeTag(zcu) == .comptime_float) {
if (!opts.report_err) return error.NotCoercible;
return sema.failWithNeededComptime(block, inst_src, .{ .simple = .casted_to_comptime_float });
}
const int_info = inst_ty.intInfo(zcu);
const int_precision = int_info.bits - @intFromBool(int_info.signedness == .signed);
const float_precision: u8 = switch (dest_ty.toIntern()) {
.f16_type => 11,
.f32_type => 24,
.f64_type => 53,
.f80_type => 64,
.f128_type => 113,
else => unreachable,
};
if (int_precision <= float_precision) {
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.float_from_int, dest_ty, inst);
}
break :int;
};
const result_val = try val.floatFromIntAdvanced(sema.arena, inst_ty, dest_ty, pt, .sema);
const fits: bool = switch (ip.indexToKey(result_val.toIntern())) {
else => unreachable,
.undef => true,
.float => |float| fits: {
var buffer: InternPool.Key.Int.Storage.BigIntSpace = undefined;
const operand_big_int = val.toBigInt(&buffer, zcu);
switch (float.storage) {
inline else => |x| {
if (!std.math.isFinite(x)) break :fits false;
var result_big_int: std.math.big.int.Mutable = .{
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(x)),
.len = undefined,
.positive = undefined,
};
switch (result_big_int.setFloat(x, .nearest_even)) {
.inexact => break :fits false,
.exact => {},
}
break :fits result_big_int.toConst().eql(operand_big_int);
},
}
},
};
if (!fits) return sema.fail(
block,
inst_src,
"type '{f}' cannot represent integer value '{f}'",
.{ dest_ty.fmt(pt), val.fmtValue(pt) },
);
return .fromValue(result_val);
},
else => {},
},
.@"enum" => switch (inst_ty.zigTypeTag(zcu)) {
.enum_literal => {
// enum literal to enum
const val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
const string = zcu.intern_pool.indexToKey(val.toIntern()).enum_literal;
const field_index = dest_ty.enumFieldIndex(string, zcu) orelse {
return sema.fail(block, inst_src, "no field named '{f}' in enum '{f}'", .{
string.fmt(&zcu.intern_pool), dest_ty.fmt(pt),
});
};
return Air.internedToRef((try pt.enumValueFieldIndex(dest_ty, @intCast(field_index))).toIntern());
},
.@"union" => blk: {
// union to its own tag type
const union_tag_ty = inst_ty.unionTagType(zcu) orelse break :blk;
if (union_tag_ty.eql(dest_ty, zcu)) {
return sema.unionToTag(block, dest_ty, inst, inst_src);
}
},
else => {},
},
.error_union => switch (inst_ty.zigTypeTag(zcu)) {
.error_union => eu: {
if (maybe_inst_val) |inst_val| {
switch (inst_val.toIntern()) {
.undef => return pt.undefRef(dest_ty),
else => switch (zcu.intern_pool.indexToKey(inst_val.toIntern())) {
.error_union => |error_union| switch (error_union.val) {
.err_name => |err_name| {
const error_set_ty = inst_ty.errorUnionSet(zcu);
const error_set_val = Air.internedToRef((try pt.intern(.{ .err = .{
.ty = error_set_ty.toIntern(),
.name = err_name,
} })));
return sema.wrapErrorUnionSet(block, dest_ty, error_set_val, inst_src);
},
.payload => |payload| {
const payload_val = Air.internedToRef(payload);
return sema.wrapErrorUnionPayload(block, dest_ty, payload_val, inst_src) catch |err| switch (err) {
error.NotCoercible => break :eu,
else => |e| return e,
};
},
},
else => unreachable,
},
}
}
},
.error_set => {
// E to E!T
return sema.wrapErrorUnionSet(block, dest_ty, inst, inst_src);
},
else => eu: {
// T to E!T
return sema.wrapErrorUnionPayload(block, dest_ty, inst, inst_src) catch |err| switch (err) {
error.NotCoercible => {
if (in_memory_result == .no_match) {
const payload_type = dest_ty.errorUnionPayload(zcu);
// Try to give more useful notes
in_memory_result = try sema.coerceInMemoryAllowed(block, payload_type, inst_ty, false, target, dest_ty_src, inst_src, maybe_inst_val);
}
break :eu;
},
else => |e| return e,
};
},
},
.@"union" => switch (inst_ty.zigTypeTag(zcu)) {
.@"enum", .enum_literal => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src),
else => {},
},
.array => switch (inst_ty.zigTypeTag(zcu)) {
.array => array_to_array: {
// Array coercions are allowed only if the child is IMC and the sentinel is unchanged or removed.
if (.ok != try sema.coerceInMemoryAllowed(
block,
dest_ty.childType(zcu),
inst_ty.childType(zcu),
false,
target,
dest_ty_src,
inst_src,
maybe_inst_val,
)) {
break :array_to_array;
}
if (dest_ty.sentinel(zcu)) |dest_sent| {
const src_sent = inst_ty.sentinel(zcu) orelse break :array_to_array;
if (dest_sent.toIntern() != (try pt.getCoerced(src_sent, dest_ty.childType(zcu))).toIntern()) {
break :array_to_array;
}
}
return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src);
},
.vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src),
.@"struct" => {
if (inst_ty.isTuple(zcu)) {
return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src);
}
},
else => {},
},
.vector => switch (inst_ty.zigTypeTag(zcu)) {
.array, .vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src),
.@"struct" => {
if (inst_ty.isTuple(zcu)) {
return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src);
}
},
else => {},
},
.@"struct" => blk: {
if (dest_ty.isTuple(zcu) and inst_ty.isTuple(zcu)) {
return sema.coerceTupleToTuple(block, dest_ty, inst, inst_src) catch |err| switch (err) {
error.NotCoercible => break :blk,
else => |e| return e,
};
}
},
else => {},
}
const can_coerce_to = switch (dest_ty.zigTypeTag(zcu)) {
.noreturn, .@"opaque" => false,
else => true,
};
if (can_coerce_to) {
// undefined to anything. We do this after the big switch above so that
// special logic has a chance to run first, such as `*[N]T` to `[]T` which
// should initialize the length field of the slice.
if (maybe_inst_val) |val| if (val.toIntern() == .undef) return pt.undefRef(dest_ty);
}
if (!opts.report_err) return error.NotCoercible;
if (opts.is_ret and dest_ty.zigTypeTag(zcu) == .noreturn) {
const msg = msg: {
const msg = try sema.errMsg(inst_src, "function declared 'noreturn' returns", .{});
errdefer msg.destroy(sema.gpa);
const ret_ty_src: LazySrcLoc = .{
.base_node_inst = ip.getNav(zcu.funcInfo(sema.func_index).owner_nav).srcInst(ip),
.offset = .{ .node_offset_fn_type_ret_ty = .zero },
};
try sema.errNote(ret_ty_src, msg, "'noreturn' declared here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const msg = msg: {
const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
if (!can_coerce_to) {
try sema.errNote(inst_src, msg, "cannot coerce to '{f}'", .{dest_ty.fmt(pt)});
}
// E!T to T
if (inst_ty.zigTypeTag(zcu) == .error_union and
(try sema.coerceInMemoryAllowed(block, inst_ty.errorUnionPayload(zcu), dest_ty, false, target, dest_ty_src, inst_src, maybe_inst_val)) == .ok)
{
try sema.errNote(inst_src, msg, "cannot convert error union to payload type", .{});
try sema.errNote(inst_src, msg, "consider using 'try', 'catch', or 'if'", .{});
}
// ?T to T
if (inst_ty.zigTypeTag(zcu) == .optional and
(try sema.coerceInMemoryAllowed(block, inst_ty.optionalChild(zcu), dest_ty, false, target, dest_ty_src, inst_src, maybe_inst_val)) == .ok)
{
try sema.errNote(inst_src, msg, "cannot convert optional to payload type", .{});
try sema.errNote(inst_src, msg, "consider using '.?', 'orelse', or 'if'", .{});
}
try in_memory_result.report(sema, inst_src, msg);
// Add notes about function return type
if (opts.is_ret and
!zcu.test_functions.contains(zcu.funcInfo(sema.func_index).owner_nav))
{
const ret_ty_src: LazySrcLoc = .{
.base_node_inst = ip.getNav(zcu.funcInfo(sema.func_index).owner_nav).srcInst(ip),
.offset = .{ .node_offset_fn_type_ret_ty = .zero },
};
if (inst_ty.isError(zcu) and !dest_ty.isError(zcu)) {
try sema.errNote(ret_ty_src, msg, "function cannot return an error", .{});
} else {
try sema.errNote(ret_ty_src, msg, "function return type declared here", .{});
}
}
if (try opts.param_src.get(sema)) |param_src| {
try sema.errNote(param_src, msg, "parameter type declared here", .{});
}
// TODO maybe add "cannot store an error in type '{f}'" note
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
fn coerceInMemory(
sema: *Sema,
val: Value,
dst_ty: Type,
) CompileError!Air.Inst.Ref {
return Air.internedToRef((try sema.pt.getCoerced(val, dst_ty)).toIntern());
}
const InMemoryCoercionResult = union(enum) {
ok,
no_match: Pair,
int_not_coercible: Int,
comptime_int_not_coercible: TypeValuePair,
error_union_payload: PairAndChild,
array_len: IntPair,
array_sentinel: Sentinel,
array_elem: PairAndChild,
vector_len: IntPair,
vector_elem: PairAndChild,
optional_shape: Pair,
optional_child: PairAndChild,
from_anyerror,
missing_error: []const InternPool.NullTerminatedString,
/// true if wanted is var args
fn_var_args: bool,
/// true if wanted is generic
fn_generic: bool,
fn_param_count: IntPair,
fn_param_noalias: IntPair,
fn_param_comptime: ComptimeParam,
fn_param: Param,
fn_cc: CC,
fn_return_type: PairAndChild,
ptr_child: PairAndChild,
ptr_addrspace: AddressSpace,
ptr_sentinel: Sentinel,
ptr_size: Size,
ptr_const: Pair,
ptr_volatile: Pair,
ptr_allowzero: Pair,
ptr_bit_range: BitRange,
ptr_alignment: AlignPair,
double_ptr_to_anyopaque: Pair,
slice_to_anyopaque: Pair,
const Pair = struct {
actual: Type,
wanted: Type,
};
const TypeValuePair = struct {
actual: Value,
wanted: Type,
};
const PairAndChild = struct {
child: *InMemoryCoercionResult,
actual: Type,
wanted: Type,
};
const Param = struct {
child: *InMemoryCoercionResult,
actual: Type,
wanted: Type,
index: u64,
};
const ComptimeParam = struct {
index: u64,
wanted: bool,
};
const Sentinel = struct {
// unreachable_value indicates no sentinel
actual: Value,
wanted: Value,
ty: Type,
};
const Int = struct {
actual_signedness: std.builtin.Signedness,
wanted_signedness: std.builtin.Signedness,
actual_bits: u16,
wanted_bits: u16,
};
const IntPair = struct {
actual: u64,
wanted: u64,
};
const AlignPair = struct {
actual: Alignment,
wanted: Alignment,
};
const Size = struct {
actual: std.builtin.Type.Pointer.Size,
wanted: std.builtin.Type.Pointer.Size,
};
const AddressSpace = struct {
actual: std.builtin.AddressSpace,
wanted: std.builtin.AddressSpace,
};
const CC = struct {
actual: std.builtin.CallingConvention,
wanted: std.builtin.CallingConvention,
};
const BitRange = struct {
actual_host: u16,
wanted_host: u16,
actual_offset: u16,
wanted_offset: u16,
};
fn dupe(child: *const InMemoryCoercionResult, arena: Allocator) !*InMemoryCoercionResult {
const res = try arena.create(InMemoryCoercionResult);
res.* = child.*;
return res;
}
fn report(res: *const InMemoryCoercionResult, sema: *Sema, src: LazySrcLoc, msg: *Zcu.ErrorMsg) !void {
const pt = sema.pt;
var cur = res;
while (true) switch (cur.*) {
.ok => unreachable,
.no_match => |types| {
try sema.addDeclaredHereNote(msg, types.wanted);
try sema.addDeclaredHereNote(msg, types.actual);
break;
},
.int_not_coercible => |int| {
try sema.errNote(src, msg, "{s} {d}-bit int cannot represent all possible {s} {d}-bit values", .{
@tagName(int.wanted_signedness), int.wanted_bits, @tagName(int.actual_signedness), int.actual_bits,
});
break;
},
.comptime_int_not_coercible => |int| {
try sema.errNote(src, msg, "type '{f}' cannot represent value '{f}'", .{
int.wanted.fmt(pt), int.actual.fmtValueSema(pt, sema),
});
break;
},
.error_union_payload => |pair| {
try sema.errNote(src, msg, "error union payload '{f}' cannot cast into error union payload '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
cur = pair.child;
},
.array_len => |lens| {
try sema.errNote(src, msg, "array of length {d} cannot cast into an array of length {d}", .{
lens.actual, lens.wanted,
});
break;
},
.array_sentinel => |sentinel| {
if (sentinel.wanted.toIntern() == .unreachable_value) {
try sema.errNote(src, msg, "source array cannot be guaranteed to maintain '{f}' sentinel", .{
sentinel.actual.fmtValueSema(pt, sema),
});
} else if (sentinel.actual.toIntern() == .unreachable_value) {
try sema.errNote(src, msg, "destination array requires '{f}' sentinel", .{
sentinel.wanted.fmtValueSema(pt, sema),
});
} else {
try sema.errNote(src, msg, "array sentinel '{f}' cannot cast into array sentinel '{f}'", .{
sentinel.actual.fmtValueSema(pt, sema), sentinel.wanted.fmtValueSema(pt, sema),
});
}
break;
},
.array_elem => |pair| {
try sema.errNote(src, msg, "array element type '{f}' cannot cast into array element type '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
cur = pair.child;
},
.vector_len => |lens| {
try sema.errNote(src, msg, "vector of length {d} cannot cast into a vector of length {d}", .{
lens.actual, lens.wanted,
});
break;
},
.vector_elem => |pair| {
try sema.errNote(src, msg, "vector element type '{f}' cannot cast into vector element type '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
cur = pair.child;
},
.optional_shape => |pair| {
try sema.errNote(src, msg, "optional type child '{f}' cannot cast into optional type child '{f}'", .{
pair.actual.optionalChild(pt.zcu).fmt(pt), pair.wanted.optionalChild(pt.zcu).fmt(pt),
});
break;
},
.optional_child => |pair| {
try sema.errNote(src, msg, "optional type child '{f}' cannot cast into optional type child '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
cur = pair.child;
},
.from_anyerror => {
try sema.errNote(src, msg, "global error set cannot cast into a smaller set", .{});
break;
},
.missing_error => |missing_errors| {
for (missing_errors) |err| {
try sema.errNote(src, msg, "'error.{f}' not a member of destination error set", .{err.fmt(&pt.zcu.intern_pool)});
}
break;
},
.fn_var_args => |wanted_var_args| {
if (wanted_var_args) {
try sema.errNote(src, msg, "non-variadic function cannot cast into a variadic function", .{});
} else {
try sema.errNote(src, msg, "variadic function cannot cast into a non-variadic function", .{});
}
break;
},
.fn_generic => |wanted_generic| {
if (wanted_generic) {
try sema.errNote(src, msg, "non-generic function cannot cast into a generic function", .{});
} else {
try sema.errNote(src, msg, "generic function cannot cast into a non-generic function", .{});
}
break;
},
.fn_param_count => |lens| {
try sema.errNote(src, msg, "function with {d} parameters cannot cast into a function with {d} parameters", .{
lens.actual, lens.wanted,
});
break;
},
.fn_param_noalias => |param| {
var index: u6 = 0;
var actual_noalias = false;
while (true) : (index += 1) {
const actual: u1 = @truncate(param.actual >> index);
const wanted: u1 = @truncate(param.wanted >> index);
if (actual != wanted) {
actual_noalias = actual == 1;
break;
}
}
if (!actual_noalias) {
try sema.errNote(src, msg, "regular parameter {d} cannot cast into a noalias parameter", .{index});
} else {
try sema.errNote(src, msg, "noalias parameter {d} cannot cast into a regular parameter", .{index});
}
break;
},
.fn_param_comptime => |param| {
if (param.wanted) {
try sema.errNote(src, msg, "non-comptime parameter {d} cannot cast into a comptime parameter", .{param.index});
} else {
try sema.errNote(src, msg, "comptime parameter {d} cannot cast into a non-comptime parameter", .{param.index});
}
break;
},
.fn_param => |param| {
try sema.errNote(src, msg, "parameter {d} '{f}' cannot cast into '{f}'", .{
param.index, param.actual.fmt(pt), param.wanted.fmt(pt),
});
cur = param.child;
},
.fn_cc => |cc| {
try sema.errNote(src, msg, "calling convention '{s}' cannot cast into calling convention '{s}'", .{ @tagName(cc.actual), @tagName(cc.wanted) });
break;
},
.fn_return_type => |pair| {
try sema.errNote(src, msg, "return type '{f}' cannot cast into return type '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
cur = pair.child;
},
.ptr_child => |pair| {
try sema.errNote(src, msg, "pointer type child '{f}' cannot cast into pointer type child '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
cur = pair.child;
},
.ptr_addrspace => |@"addrspace"| {
try sema.errNote(src, msg, "address space '{s}' cannot cast into address space '{s}'", .{ @tagName(@"addrspace".actual), @tagName(@"addrspace".wanted) });
break;
},
.ptr_sentinel => |sentinel| {
if (sentinel.actual.toIntern() != .unreachable_value) {
try sema.errNote(src, msg, "pointer sentinel '{f}' cannot cast into pointer sentinel '{f}'", .{
sentinel.actual.fmtValueSema(pt, sema), sentinel.wanted.fmtValueSema(pt, sema),
});
} else {
try sema.errNote(src, msg, "destination pointer requires '{f}' sentinel", .{
sentinel.wanted.fmtValueSema(pt, sema),
});
}
break;
},
.ptr_size => |size| {
try sema.errNote(src, msg, "a {s} cannot cast into a {s}", .{ pointerSizeString(size.actual), pointerSizeString(size.wanted) });
break;
},
.ptr_allowzero => |pair| {
const wanted_allow_zero = pair.wanted.ptrAllowsZero(pt.zcu);
const actual_allow_zero = pair.actual.ptrAllowsZero(pt.zcu);
if (actual_allow_zero and !wanted_allow_zero) {
try sema.errNote(src, msg, "'{f}' could have null values which are illegal in type '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
} else {
try sema.errNote(src, msg, "mutable '{f}' would allow illegal null values stored to type '{f}'", .{
pair.wanted.fmt(pt), pair.actual.fmt(pt),
});
}
break;
},
.ptr_const => |pair| {
const wanted_const = pair.wanted.isConstPtr(pt.zcu);
const actual_const = pair.actual.isConstPtr(pt.zcu);
if (actual_const and !wanted_const) {
try sema.errNote(src, msg, "cast discards const qualifier", .{});
} else {
try sema.errNote(src, msg, "mutable '{f}' would allow illegal const pointers stored to type '{f}'", .{
pair.wanted.fmt(pt), pair.actual.fmt(pt),
});
}
break;
},
.ptr_volatile => |pair| {
const wanted_volatile = pair.wanted.isVolatilePtr(pt.zcu);
const actual_volatile = pair.actual.isVolatilePtr(pt.zcu);
if (actual_volatile and !wanted_volatile) {
try sema.errNote(src, msg, "cast discards volatile qualifier", .{});
} else {
try sema.errNote(src, msg, "mutable '{f}' would allow illegal volatile pointers stored to type '{f}'", .{
pair.wanted.fmt(pt), pair.actual.fmt(pt),
});
}
break;
},
.ptr_bit_range => |bit_range| {
if (bit_range.actual_host != bit_range.wanted_host) {
try sema.errNote(src, msg, "pointer host size '{d}' cannot cast into pointer host size '{d}'", .{
bit_range.actual_host, bit_range.wanted_host,
});
}
if (bit_range.actual_offset != bit_range.wanted_offset) {
try sema.errNote(src, msg, "pointer bit offset '{d}' cannot cast into pointer bit offset '{d}'", .{
bit_range.actual_offset, bit_range.wanted_offset,
});
}
break;
},
.ptr_alignment => |pair| {
try sema.errNote(src, msg, "pointer alignment '{d}' cannot cast into pointer alignment '{d}'", .{
pair.actual.toByteUnits() orelse 0, pair.wanted.toByteUnits() orelse 0,
});
break;
},
.double_ptr_to_anyopaque => |pair| {
try sema.errNote(src, msg, "cannot implicitly cast double pointer '{f}' to anyopaque pointer '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
break;
},
.slice_to_anyopaque => |pair| {
try sema.errNote(src, msg, "cannot implicitly cast slice '{f}' to anyopaque pointer '{f}'", .{
pair.actual.fmt(pt), pair.wanted.fmt(pt),
});
try sema.errNote(src, msg, "consider using '.ptr'", .{});
break;
},
};
}
};
fn pointerSizeString(size: std.builtin.Type.Pointer.Size) []const u8 {
return switch (size) {
.one => "single pointer",
.many => "many pointer",
.c => "C pointer",
.slice => "slice",
};
}
/// If types `A` and `B` have identical representations in runtime memory, they are considered
/// "in-memory coercible". This is a subset of normal coercions. Not only can `A` coerce to `B`, but
/// also, coercions can happen through pointers. For instance, `*const A` can coerce to `*const B`.
///
/// If this function is called, the coercion must be applied, or a compile error emitted if `.ok`
/// is not returned. This is because this function may modify inferred error sets to make a
/// coercion possible, even if `.ok` is not returned.
pub fn coerceInMemoryAllowed(
sema: *Sema,
block: *Block,
dest_ty: Type,
src_ty: Type,
/// If `true`, this query comes from an attempted coercion of the form `*Src` -> `*Dest`, where
/// both pointers are mutable. If this coercion is allowed, one could store to the `*Dest` and
/// load from the `*Src` to effectively perform an in-memory coercion from `Dest` to `Src`.
/// Therefore, when `dest_is_mut`, the in-memory coercion must be valid in *both directions*.
dest_is_mut: bool,
target: *const std.Target,
dest_src: LazySrcLoc,
src_src: LazySrcLoc,
src_val: ?Value,
) CompileError!InMemoryCoercionResult {
const pt = sema.pt;
const zcu = pt.zcu;
if (dest_ty.eql(src_ty, zcu))
return .ok;
const dest_tag = dest_ty.zigTypeTag(zcu);
const src_tag = src_ty.zigTypeTag(zcu);
// Differently-named integers with the same number of bits.
if (dest_tag == .int and src_tag == .int) {
const dest_info = dest_ty.intInfo(zcu);
const src_info = src_ty.intInfo(zcu);
if (dest_info.signedness == src_info.signedness and
dest_info.bits == src_info.bits)
{
return .ok;
}
if ((src_info.signedness == dest_info.signedness and dest_info.bits < src_info.bits) or
// small enough unsigned ints can get casted to large enough signed ints
(dest_info.signedness == .signed and src_info.signedness == .unsigned and dest_info.bits <= src_info.bits) or
(dest_info.signedness == .unsigned and src_info.signedness == .signed))
{
return InMemoryCoercionResult{ .int_not_coercible = .{
.actual_signedness = src_info.signedness,
.wanted_signedness = dest_info.signedness,
.actual_bits = src_info.bits,
.wanted_bits = dest_info.bits,
} };
}
}
// Comptime int to regular int.
if (dest_tag == .int and src_tag == .comptime_int) {
if (src_val) |val| {
if (!(try sema.intFitsInType(val, dest_ty, null))) {
return .{ .comptime_int_not_coercible = .{ .wanted = dest_ty, .actual = val } };
}
}
}
// Differently-named floats with the same number of bits.
if (dest_tag == .float and src_tag == .float) {
const dest_bits = dest_ty.floatBits(target);
const src_bits = src_ty.floatBits(target);
if (dest_bits == src_bits) {
return .ok;
}
}
// Pointers / Pointer-like Optionals
const maybe_dest_ptr_ty = try sema.typePtrOrOptionalPtrTy(dest_ty);
const maybe_src_ptr_ty = try sema.typePtrOrOptionalPtrTy(src_ty);
if (maybe_dest_ptr_ty) |dest_ptr_ty| {
if (maybe_src_ptr_ty) |src_ptr_ty| {
return try sema.coerceInMemoryAllowedPtrs(block, dest_ty, src_ty, dest_ptr_ty, src_ptr_ty, dest_is_mut, target, dest_src, src_src);
}
}
// Slices
if (dest_ty.isSlice(zcu) and src_ty.isSlice(zcu)) {
return try sema.coerceInMemoryAllowedPtrs(block, dest_ty, src_ty, dest_ty, src_ty, dest_is_mut, target, dest_src, src_src);
}
// Functions
if (dest_tag == .@"fn" and src_tag == .@"fn") {
return try sema.coerceInMemoryAllowedFns(block, dest_ty, src_ty, dest_is_mut, target, dest_src, src_src);
}
// Error Unions
if (dest_tag == .error_union and src_tag == .error_union) {
const dest_payload = dest_ty.errorUnionPayload(zcu);
const src_payload = src_ty.errorUnionPayload(zcu);
const child = try sema.coerceInMemoryAllowed(block, dest_payload, src_payload, dest_is_mut, target, dest_src, src_src, null);
if (child != .ok) {
return .{ .error_union_payload = .{
.child = try child.dupe(sema.arena),
.actual = src_payload,
.wanted = dest_payload,
} };
}
return try sema.coerceInMemoryAllowed(block, dest_ty.errorUnionSet(zcu), src_ty.errorUnionSet(zcu), dest_is_mut, target, dest_src, src_src, null);
}
// Error Sets
if (dest_tag == .error_set and src_tag == .error_set) {
const res1 = try sema.coerceInMemoryAllowedErrorSets(block, dest_ty, src_ty, dest_src, src_src);
if (!dest_is_mut or res1 != .ok) return res1;
// src -> dest is okay, but `dest_is_mut`, so it needs to be allowed in the other direction.
const res2 = try sema.coerceInMemoryAllowedErrorSets(block, src_ty, dest_ty, src_src, dest_src);
return res2;
}
// Arrays
if (dest_tag == .array and src_tag == .array) {
const dest_info = dest_ty.arrayInfo(zcu);
const src_info = src_ty.arrayInfo(zcu);
if (dest_info.len != src_info.len) {
return .{ .array_len = .{
.actual = src_info.len,
.wanted = dest_info.len,
} };
}
const child = try sema.coerceInMemoryAllowed(block, dest_info.elem_type, src_info.elem_type, dest_is_mut, target, dest_src, src_src, null);
switch (child) {
.ok => {},
.no_match => return child,
else => {
return .{ .array_elem = .{
.child = try child.dupe(sema.arena),
.actual = src_info.elem_type,
.wanted = dest_info.elem_type,
} };
},
}
const ok_sent = (dest_info.sentinel == null and src_info.sentinel == null) or
(src_info.sentinel != null and
dest_info.sentinel != null and
dest_info.sentinel.?.eql(
try pt.getCoerced(src_info.sentinel.?, dest_info.elem_type),
dest_info.elem_type,
zcu,
));
if (!ok_sent) {
return .{ .array_sentinel = .{
.actual = src_info.sentinel orelse Value.@"unreachable",
.wanted = dest_info.sentinel orelse Value.@"unreachable",
.ty = dest_info.elem_type,
} };
}
return .ok;
}
// Vectors
if (dest_tag == .vector and src_tag == .vector) {
const dest_len = dest_ty.vectorLen(zcu);
const src_len = src_ty.vectorLen(zcu);
if (dest_len != src_len) {
return .{ .vector_len = .{
.actual = src_len,
.wanted = dest_len,
} };
}
const dest_elem_ty = dest_ty.scalarType(zcu);
const src_elem_ty = src_ty.scalarType(zcu);
const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src, null);
if (child != .ok) {
return .{ .vector_elem = .{
.child = try child.dupe(sema.arena),
.actual = src_elem_ty,
.wanted = dest_elem_ty,
} };
}
return .ok;
}
// Optionals
if (dest_tag == .optional and src_tag == .optional) {
if ((maybe_dest_ptr_ty != null) != (maybe_src_ptr_ty != null)) {
return .{ .optional_shape = .{
.actual = src_ty,
.wanted = dest_ty,
} };
}
const dest_child_type = dest_ty.optionalChild(zcu);
const src_child_type = src_ty.optionalChild(zcu);
const child = try sema.coerceInMemoryAllowed(block, dest_child_type, src_child_type, dest_is_mut, target, dest_src, src_src, null);
if (child != .ok) {
return .{ .optional_child = .{
.child = try child.dupe(sema.arena),
.actual = src_child_type,
.wanted = dest_child_type,
} };
}
return .ok;
}
// Tuples (with in-memory-coercible fields)
if (dest_ty.isTuple(zcu) and src_ty.isTuple(zcu)) tuple: {
if (dest_ty.structFieldCount(zcu) != src_ty.structFieldCount(zcu)) break :tuple;
const field_count = dest_ty.structFieldCount(zcu);
for (0..field_count) |field_idx| {
if (dest_ty.structFieldIsComptime(field_idx, zcu) != src_ty.structFieldIsComptime(field_idx, zcu)) break :tuple;
if (dest_ty.fieldAlignment(field_idx, zcu) != src_ty.fieldAlignment(field_idx, zcu)) break :tuple;
const dest_field_ty = dest_ty.fieldType(field_idx, zcu);
const src_field_ty = src_ty.fieldType(field_idx, zcu);
const field = try sema.coerceInMemoryAllowed(block, dest_field_ty, src_field_ty, dest_is_mut, target, dest_src, src_src, null);
if (field != .ok) break :tuple;
}
return .ok;
}
return .{ .no_match = .{
.actual = dest_ty,
.wanted = src_ty,
} };
}
fn coerceInMemoryAllowedErrorSets(
sema: *Sema,
block: *Block,
dest_ty: Type,
src_ty: Type,
dest_src: LazySrcLoc,
src_src: LazySrcLoc,
) !InMemoryCoercionResult {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
// Coercion to `anyerror`. Note that this check can return false negatives
// in case the error sets did not get resolved.
if (dest_ty.isAnyError(zcu)) {
return .ok;
}
if (dest_ty.toIntern() == .adhoc_inferred_error_set_type) {
// We are trying to coerce an error set to the current function's
// inferred error set.
const dst_ies = sema.fn_ret_ty_ies.?;
try dst_ies.addErrorSet(src_ty, ip, sema.arena);
return .ok;
}
if (ip.isInferredErrorSetType(dest_ty.toIntern())) {
const dst_ies_func_index = ip.iesFuncIndex(dest_ty.toIntern());
if (sema.fn_ret_ty_ies) |dst_ies| {
if (dst_ies.func == dst_ies_func_index) {
// We are trying to coerce an error set to the current function's
// inferred error set.
try dst_ies.addErrorSet(src_ty, ip, sema.arena);
return .ok;
}
}
switch (try sema.resolveInferredErrorSet(block, dest_src, dest_ty.toIntern())) {
// isAnyError might have changed from a false negative to a true
// positive after resolution.
.anyerror_type => return .ok,
else => {},
}
}
var missing_error_buf = std.array_list.Managed(InternPool.NullTerminatedString).init(gpa);
defer missing_error_buf.deinit();
switch (src_ty.toIntern()) {
.anyerror_type => switch (ip.indexToKey(dest_ty.toIntern())) {
.simple_type => unreachable, // filtered out above
.error_set_type, .inferred_error_set_type => return .from_anyerror,
else => unreachable,
},
else => switch (ip.indexToKey(src_ty.toIntern())) {
.inferred_error_set_type => {
const resolved_src_ty = try sema.resolveInferredErrorSet(block, src_src, src_ty.toIntern());
// src anyerror status might have changed after the resolution.
if (resolved_src_ty == .anyerror_type) {
// dest_ty.isAnyError(zcu) == true is already checked for at this point.
return .from_anyerror;
}
for (ip.indexToKey(resolved_src_ty).error_set_type.names.get(ip)) |key| {
if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), key)) {
try missing_error_buf.append(key);
}
}
if (missing_error_buf.items.len != 0) {
return InMemoryCoercionResult{
.missing_error = try sema.arena.dupe(InternPool.NullTerminatedString, missing_error_buf.items),
};
}
return .ok;
},
.error_set_type => |error_set_type| {
for (error_set_type.names.get(ip)) |name| {
if (!Type.errorSetHasFieldIp(ip, dest_ty.toIntern(), name)) {
try missing_error_buf.append(name);
}
}
if (missing_error_buf.items.len != 0) {
return InMemoryCoercionResult{
.missing_error = try sema.arena.dupe(InternPool.NullTerminatedString, missing_error_buf.items),
};
}
return .ok;
},
else => unreachable,
},
}
}
fn coerceInMemoryAllowedFns(
sema: *Sema,
block: *Block,
dest_ty: Type,
src_ty: Type,
/// If set, the coercion must be valid in both directions.
dest_is_mut: bool,
target: *const std.Target,
dest_src: LazySrcLoc,
src_src: LazySrcLoc,
) !InMemoryCoercionResult {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const dest_info = zcu.typeToFunc(dest_ty).?;
const src_info = zcu.typeToFunc(src_ty).?;
{
if (dest_info.is_var_args != src_info.is_var_args) {
return InMemoryCoercionResult{ .fn_var_args = dest_info.is_var_args };
}
if (dest_info.is_generic != src_info.is_generic) {
return InMemoryCoercionResult{ .fn_generic = dest_info.is_generic };
}
const callconv_ok = callconvCoerceAllowed(target, src_info.cc, dest_info.cc) and
(!dest_is_mut or callconvCoerceAllowed(target, dest_info.cc, src_info.cc));
if (!callconv_ok) {
return .{ .fn_cc = .{
.actual = src_info.cc,
.wanted = dest_info.cc,
} };
}
if (!switch (src_info.return_type) {
.generic_poison_type => true,
.noreturn_type => !dest_is_mut,
else => false,
}) {
const rt = try sema.coerceInMemoryAllowed(
block,
.fromInterned(dest_info.return_type),
.fromInterned(src_info.return_type),
dest_is_mut,
target,
dest_src,
src_src,
null,
);
if (rt != .ok) return .{ .fn_return_type = .{
.child = try rt.dupe(sema.arena),
.actual = .fromInterned(src_info.return_type),
.wanted = .fromInterned(dest_info.return_type),
} };
}
}
const params_len = params_len: {
if (dest_info.param_types.len != src_info.param_types.len) {
return .{ .fn_param_count = .{
.actual = src_info.param_types.len,
.wanted = dest_info.param_types.len,
} };
}
if (dest_info.noalias_bits != src_info.noalias_bits) {
return .{ .fn_param_noalias = .{
.actual = src_info.noalias_bits,
.wanted = dest_info.noalias_bits,
} };
}
break :params_len dest_info.param_types.len;
};
for (0..params_len) |param_i| {
const dest_param_ty: Type = .fromInterned(dest_info.param_types.get(ip)[param_i]);
const src_param_ty: Type = .fromInterned(src_info.param_types.get(ip)[param_i]);
comptime_param: {
const src_is_comptime = src_info.paramIsComptime(@intCast(param_i));
const dest_is_comptime = dest_info.paramIsComptime(@intCast(param_i));
if (src_is_comptime == dest_is_comptime) break :comptime_param;
if (!dest_is_mut and src_is_comptime and !dest_is_comptime and try dest_param_ty.comptimeOnlySema(pt)) {
// A parameter which is marked `comptime` can drop that annotation if the type is comptime-only.
// The function remains generic, and the parameter is going to be comptime-resolved either way,
// so this just affects whether or not the argument is comptime-evaluated at the call site.
break :comptime_param;
}
return .{ .fn_param_comptime = .{
.index = param_i,
.wanted = dest_is_comptime,
} };
}
if (!src_param_ty.isGenericPoison() and !dest_param_ty.isGenericPoison()) {
// Note: Cast direction is reversed here.
const param = try sema.coerceInMemoryAllowed(block, src_param_ty, dest_param_ty, dest_is_mut, target, dest_src, src_src, null);
if (param != .ok) {
return .{ .fn_param = .{
.child = try param.dupe(sema.arena),
.actual = src_param_ty,
.wanted = dest_param_ty,
.index = param_i,
} };
}
}
}
return .ok;
}
fn callconvCoerceAllowed(
target: *const std.Target,
src_cc: std.builtin.CallingConvention,
dest_cc: std.builtin.CallingConvention,
) bool {
const Tag = std.builtin.CallingConvention.Tag;
if (@as(Tag, src_cc) != @as(Tag, dest_cc)) return false;
switch (src_cc) {
inline else => |src_data, tag| {
const dest_data = @field(dest_cc, @tagName(tag));
if (@TypeOf(src_data) != void) {
const default_stack_align = target.stackAlignment();
const src_stack_align = src_data.incoming_stack_alignment orelse default_stack_align;
const dest_stack_align = src_data.incoming_stack_alignment orelse default_stack_align;
if (dest_stack_align < src_stack_align) return false;
}
switch (@TypeOf(src_data)) {
void, std.builtin.CallingConvention.CommonOptions => {},
std.builtin.CallingConvention.X86RegparmOptions => {
if (src_data.register_params != dest_data.register_params) return false;
},
std.builtin.CallingConvention.ArcInterruptOptions => {
if (src_data.type != dest_data.type) return false;
},
std.builtin.CallingConvention.ArmInterruptOptions => {
if (src_data.type != dest_data.type) return false;
},
std.builtin.CallingConvention.MicroblazeInterruptOptions => {
if (src_data.type != dest_data.type) return false;
},
std.builtin.CallingConvention.MipsInterruptOptions => {
if (src_data.mode != dest_data.mode) return false;
},
std.builtin.CallingConvention.RiscvInterruptOptions => {
if (src_data.mode != dest_data.mode) return false;
},
std.builtin.CallingConvention.ShInterruptOptions => {
if (src_data.save != dest_data.save) return false;
},
else => comptime unreachable,
}
},
}
return true;
}
fn coerceInMemoryAllowedPtrs(
sema: *Sema,
block: *Block,
dest_ty: Type,
src_ty: Type,
dest_ptr_ty: Type,
src_ptr_ty: Type,
/// If set, the coercion must be valid in both directions.
dest_is_mut: bool,
target: *const std.Target,
dest_src: LazySrcLoc,
src_src: LazySrcLoc,
) !InMemoryCoercionResult {
const pt = sema.pt;
const zcu = pt.zcu;
const dest_info = dest_ptr_ty.ptrInfo(zcu);
const src_info = src_ptr_ty.ptrInfo(zcu);
const ok_ptr_size = src_info.flags.size == dest_info.flags.size or
src_info.flags.size == .c or dest_info.flags.size == .c;
if (!ok_ptr_size) {
return InMemoryCoercionResult{ .ptr_size = .{
.actual = src_info.flags.size,
.wanted = dest_info.flags.size,
} };
}
const ok_const = src_info.flags.is_const == dest_info.flags.is_const or
(!dest_is_mut and dest_info.flags.is_const);
if (!ok_const) return .{ .ptr_const = .{
.actual = src_ty,
.wanted = dest_ty,
} };
const ok_volatile = src_info.flags.is_volatile == dest_info.flags.is_volatile or
(!dest_is_mut and dest_info.flags.is_volatile);
if (!ok_volatile) return .{ .ptr_volatile = .{
.actual = src_ty,
.wanted = dest_ty,
} };
const dest_allowzero = dest_ty.ptrAllowsZero(zcu);
const src_allowzero = src_ty.ptrAllowsZero(zcu);
const ok_allowzero = src_allowzero == dest_allowzero or
(!dest_is_mut and dest_allowzero);
if (!ok_allowzero) return .{ .ptr_allowzero = .{
.actual = src_ty,
.wanted = dest_ty,
} };
if (dest_info.flags.address_space != src_info.flags.address_space) {
return .{ .ptr_addrspace = .{
.actual = src_info.flags.address_space,
.wanted = dest_info.flags.address_space,
} };
}
const dest_child: Type = .fromInterned(dest_info.child);
const src_child: Type = .fromInterned(src_info.child);
const child = try sema.coerceInMemoryAllowed(
block,
dest_child,
src_child,
// We must also include `dest_is_mut`.
// Otherwise, this code is valid:
//
// const b: B = ...;
// var pa: *const A = undefined;
// const ppa: **const A = &pa;
// const ppb: **const B = ppa; // <-- this is what that allows
// ppb.* = &b;
// const a: A = pa.*;
//
// ...effectively performing an in-memory coercion from B to A.
dest_is_mut or !dest_info.flags.is_const,
target,
dest_src,
src_src,
null,
);
if (child != .ok) allow: {
// As a special case, we also allow coercing `*[n:s]T` to `*[n]T`, akin to dropping the sentinel from a slice.
// `*[n:s]T` cannot coerce in memory to `*[n]T` since they have different sizes.
//
// We must once again include `dest_is_mut` because `**[n:s]T -> **[n]T`
// is not allowed, as it would make it possible to assign an illegal value
// to the sentinel-terminated side.
if (!dest_is_mut and src_child.zigTypeTag(zcu) == .array and dest_child.zigTypeTag(zcu) == .array and
src_child.arrayLen(zcu) == dest_child.arrayLen(zcu) and
src_child.sentinel(zcu) != null and dest_child.sentinel(zcu) == null and
.ok == try sema.coerceInMemoryAllowed(block, dest_child.childType(zcu), src_child.childType(zcu), !dest_info.flags.is_const, target, dest_src, src_src, null))
{
break :allow;
}
return .{ .ptr_child = .{
.child = try child.dupe(sema.arena),
.actual = .fromInterned(src_info.child),
.wanted = .fromInterned(dest_info.child),
} };
}
if (src_info.packed_offset.host_size != dest_info.packed_offset.host_size or
src_info.packed_offset.bit_offset != dest_info.packed_offset.bit_offset)
{
return .{ .ptr_bit_range = .{
.actual_host = src_info.packed_offset.host_size,
.wanted_host = dest_info.packed_offset.host_size,
.actual_offset = src_info.packed_offset.bit_offset,
.wanted_offset = dest_info.packed_offset.bit_offset,
} };
}
const sentinel_ok = ok: {
const ss = src_info.sentinel;
const ds = dest_info.sentinel;
if (ss == .none and ds == .none) break :ok true;
if (ss != .none and ds != .none) {
if (ds == try zcu.intern_pool.getCoerced(sema.gpa, pt.tid, ss, dest_info.child)) break :ok true;
}
if (src_info.flags.size == .c) break :ok true;
if (!dest_is_mut and dest_info.sentinel == .none) break :ok true;
break :ok false;
};
if (!sentinel_ok) {
return .{ .ptr_sentinel = .{
.actual = switch (src_info.sentinel) {
.none => Value.@"unreachable",
else => Value.fromInterned(src_info.sentinel),
},
.wanted = switch (dest_info.sentinel) {
.none => Value.@"unreachable",
else => Value.fromInterned(dest_info.sentinel),
},
.ty = .fromInterned(dest_info.child),
} };
}
// If both pointers have alignment 0, it means they both want ABI alignment.
// In this case, if they share the same child type, no need to resolve
// pointee type alignment. Otherwise both pointee types must have their alignment
// resolved and we compare the alignment numerically.
if (src_info.flags.alignment != .none or dest_info.flags.alignment != .none or
dest_info.child != src_info.child)
{
const src_align = if (src_info.flags.alignment != .none)
src_info.flags.alignment
else
try Type.fromInterned(src_info.child).abiAlignmentSema(pt);
const dest_align = if (dest_info.flags.alignment != .none)
dest_info.flags.alignment
else
try Type.fromInterned(dest_info.child).abiAlignmentSema(pt);
if (dest_align.compare(if (dest_is_mut) .neq else .gt, src_align)) {
return InMemoryCoercionResult{ .ptr_alignment = .{
.actual = src_align,
.wanted = dest_align,
} };
}
}
return .ok;
}
fn coerceVarArgParam(
sema: *Sema,
block: *Block,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
if (block.is_typeof) return inst;
const pt = sema.pt;
const zcu = pt.zcu;
const uncasted_ty = sema.typeOf(inst);
const coerced = switch (uncasted_ty.zigTypeTag(zcu)) {
// TODO consider casting to c_int/f64 if they fit
.comptime_int, .comptime_float => return sema.fail(
block,
inst_src,
"integer and float literals passed to variadic function must be casted to a fixed-size number type",
.{},
),
.@"fn" => fn_ptr: {
const fn_val = try sema.resolveConstDefinedValue(block, LazySrcLoc.unneeded, inst, undefined);
const fn_nav = zcu.funcInfo(fn_val.toIntern()).owner_nav;
break :fn_ptr try sema.analyzeNavRef(block, inst_src, fn_nav);
},
.array => return sema.fail(block, inst_src, "arrays must be passed by reference to variadic function", .{}),
.float => float: {
const target = zcu.getTarget();
const double_bits = target.cTypeBitSize(.double);
const inst_bits = uncasted_ty.floatBits(target);
if (inst_bits >= double_bits) break :float inst;
switch (double_bits) {
32 => break :float try sema.coerce(block, .f32, inst, inst_src),
64 => break :float try sema.coerce(block, .f64, inst, inst_src),
else => unreachable,
}
},
else => if (uncasted_ty.isAbiInt(zcu)) int: {
if (!try sema.validateExternType(uncasted_ty, .param_ty)) break :int inst;
const target = zcu.getTarget();
const uncasted_info = uncasted_ty.intInfo(zcu);
if (uncasted_info.bits <= target.cTypeBitSize(switch (uncasted_info.signedness) {
.signed => .int,
.unsigned => .uint,
})) break :int try sema.coerce(block, switch (uncasted_info.signedness) {
.signed => .c_int,
.unsigned => .c_uint,
}, inst, inst_src);
if (uncasted_info.bits <= target.cTypeBitSize(switch (uncasted_info.signedness) {
.signed => .long,
.unsigned => .ulong,
})) break :int try sema.coerce(block, switch (uncasted_info.signedness) {
.signed => .c_long,
.unsigned => .c_ulong,
}, inst, inst_src);
if (uncasted_info.bits <= target.cTypeBitSize(switch (uncasted_info.signedness) {
.signed => .longlong,
.unsigned => .ulonglong,
})) break :int try sema.coerce(block, switch (uncasted_info.signedness) {
.signed => .c_longlong,
.unsigned => .c_ulonglong,
}, inst, inst_src);
break :int inst;
} else inst,
};
const coerced_ty = sema.typeOf(coerced);
if (!try sema.validateExternType(coerced_ty, .param_ty)) {
const msg = msg: {
const msg = try sema.errMsg(inst_src, "cannot pass '{f}' to variadic function", .{coerced_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, inst_src, coerced_ty, .param_ty);
try sema.addDeclaredHereNote(msg, coerced_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
return coerced;
}
// TODO migrate callsites to use storePtr2 instead.
fn storePtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ptr: Air.Inst.Ref,
uncasted_operand: Air.Inst.Ref,
) CompileError!void {
const air_tag: Air.Inst.Tag = if (block.wantSafety()) .store_safe else .store;
return sema.storePtr2(block, src, ptr, src, uncasted_operand, src, air_tag);
}
fn storePtr2(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ptr: Air.Inst.Ref,
ptr_src: LazySrcLoc,
uncasted_operand: Air.Inst.Ref,
operand_src: LazySrcLoc,
air_tag: Air.Inst.Tag,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ptr_ty = sema.typeOf(ptr);
if (ptr_ty.isConstPtr(zcu))
return sema.fail(block, ptr_src, "cannot assign to constant", .{});
const elem_ty = ptr_ty.childType(zcu);
// To generate better code for tuples, we detect a tuple operand here, and
// analyze field loads and stores directly. This avoids an extra allocation + memcpy
// which would occur if we used `coerce`.
// However, we avoid this mechanism if the destination element type is a tuple,
// because the regular store will be better for this case.
// If the destination type is a struct we don't want this mechanism to trigger, because
// this code does not handle tuple-to-struct coercion which requires dealing with missing
// fields.
const operand_ty = sema.typeOf(uncasted_operand);
if (operand_ty.isTuple(zcu) and elem_ty.zigTypeTag(zcu) == .array) {
const field_count = operand_ty.structFieldCount(zcu);
var i: u32 = 0;
while (i < field_count) : (i += 1) {
const elem_src = operand_src; // TODO better source location
const elem = try sema.tupleField(block, operand_src, uncasted_operand, elem_src, i);
const elem_index = try pt.intRef(.usize, i);
const elem_ptr = try sema.elemPtr(block, ptr_src, ptr, elem_index, elem_src, false, true);
try sema.storePtr2(block, src, elem_ptr, elem_src, elem, elem_src, .store);
}
return;
}
// TODO do the same thing for anon structs as for tuples above.
// However, beware of the need to handle missing/extra fields.
const is_ret = air_tag == .ret_ptr;
const operand = sema.coerceExtra(block, elem_ty, uncasted_operand, operand_src, .{ .is_ret = is_ret }) catch |err| switch (err) {
error.NotCoercible => unreachable,
else => |e| return e,
};
const maybe_operand_val = try sema.resolveValue(operand);
const runtime_src = rs: {
const ptr_val = try sema.resolveDefinedValue(block, ptr_src, ptr) orelse break :rs ptr_src;
if (!sema.isComptimeMutablePtr(ptr_val)) break :rs ptr_src;
const operand_val = maybe_operand_val orelse return sema.fail(block, ptr_src, "cannot store runtime value in compile time variable", .{});
return sema.storePtrVal(block, src, ptr_val, operand_val, elem_ty);
};
// We're performing the store at runtime; as such, we need to make sure the pointee type
// is not comptime-only. We can hit this case with a `@ptrFromInt` pointer.
if (try elem_ty.comptimeOnlySema(pt)) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "cannot store comptime-only type '{f}' at runtime", .{elem_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(ptr_src, msg, "operation is runtime due to this pointer", .{});
break :msg msg;
});
}
// We do this after the possible comptime store above, for the case of field_ptr stores
// to unions because we want the comptime tag to be set, even if the field type is void.
if ((try sema.typeHasOnePossibleValue(elem_ty)) != null) {
return;
}
try sema.requireRuntimeBlock(block, src, runtime_src);
const store_inst = if (is_ret)
try block.addBinOp(.store, ptr, operand)
else
try block.addBinOp(air_tag, ptr, operand);
try sema.checkComptimeKnownStore(block, store_inst, operand_src);
return;
}
/// Given an AIR store instruction, checks whether we are performing a
/// comptime-known store to a local alloc, and updates `maybe_comptime_allocs`
/// accordingly.
/// Handles calling `validateRuntimeValue` if the store is runtime for any reason.
fn checkComptimeKnownStore(sema: *Sema, block: *Block, store_inst_ref: Air.Inst.Ref, store_src: LazySrcLoc) !void {
const store_inst = store_inst_ref.toIndex().?;
const inst_data = sema.air_instructions.items(.data)[@intFromEnum(store_inst)].bin_op;
const ptr = inst_data.lhs.toIndex() orelse return;
const operand = inst_data.rhs;
known: {
const maybe_base_alloc = sema.base_allocs.get(ptr) orelse break :known;
const maybe_comptime_alloc = sema.maybe_comptime_allocs.getPtr(maybe_base_alloc) orelse break :known;
if ((try sema.resolveValue(operand)) != null and
block.runtime_index == maybe_comptime_alloc.runtime_index)
{
try maybe_comptime_alloc.stores.append(sema.arena, .{
.inst = store_inst,
.src = store_src,
});
return;
}
// We're newly discovering that this alloc is runtime-known.
try sema.markMaybeComptimeAllocRuntime(block, maybe_base_alloc);
}
try sema.validateRuntimeValue(block, store_src, operand);
}
/// Given an AIR instruction transforming a pointer (struct_field_ptr,
/// ptr_elem_ptr, bitcast, etc), checks whether the base pointer refers to a
/// local alloc, and updates `base_allocs` accordingly.
fn checkKnownAllocPtr(sema: *Sema, block: *Block, base_ptr: Air.Inst.Ref, new_ptr: Air.Inst.Ref) !void {
const base_ptr_inst = base_ptr.toIndex() orelse return;
const new_ptr_inst = new_ptr.toIndex() orelse return;
const alloc_inst = sema.base_allocs.get(base_ptr_inst) orelse return;
try sema.base_allocs.put(sema.gpa, new_ptr_inst, alloc_inst);
switch (sema.air_instructions.items(.tag)[@intFromEnum(new_ptr_inst)]) {
.optional_payload_ptr_set, .errunion_payload_ptr_set => {
const maybe_comptime_alloc = sema.maybe_comptime_allocs.getPtr(alloc_inst) orelse return;
// This is functionally a store, since it writes the optional payload bit.
// Thus, if it is behind a runtime condition, we must mark the alloc as runtime appropriately.
if (block.runtime_index != maybe_comptime_alloc.runtime_index) {
return sema.markMaybeComptimeAllocRuntime(block, alloc_inst);
}
try maybe_comptime_alloc.stores.append(sema.arena, .{
.inst = new_ptr_inst,
.src = LazySrcLoc.unneeded,
});
},
.ptr_elem_ptr => {
const tmp_air = sema.getTmpAir();
const pl_idx = tmp_air.instructions.items(.data)[@intFromEnum(new_ptr_inst)].ty_pl.payload;
const bin = tmp_air.extraData(Air.Bin, pl_idx).data;
const index_ref = bin.rhs;
// If the index value is runtime-known, this pointer is also runtime-known, so
// we must in turn make the alloc value runtime-known.
if (null == try sema.resolveValue(index_ref)) {
try sema.markMaybeComptimeAllocRuntime(block, alloc_inst);
}
},
else => {},
}
}
fn markMaybeComptimeAllocRuntime(sema: *Sema, block: *Block, alloc_inst: Air.Inst.Index) CompileError!void {
const maybe_comptime_alloc = (sema.maybe_comptime_allocs.fetchRemove(alloc_inst) orelse return).value;
// Since the alloc has been determined to be runtime, we must check that
// all other stores to it are permitted to be runtime values.
const slice = maybe_comptime_alloc.stores.slice();
for (slice.items(.inst), slice.items(.src)) |other_inst, other_src| {
if (other_src.offset == .unneeded) {
switch (sema.air_instructions.items(.tag)[@intFromEnum(other_inst)]) {
.set_union_tag, .optional_payload_ptr_set, .errunion_payload_ptr_set => continue,
else => unreachable, // assertion failure
}
}
const other_data = sema.air_instructions.items(.data)[@intFromEnum(other_inst)].bin_op;
const other_operand = other_data.rhs;
try sema.validateRuntimeValue(block, other_src, other_operand);
}
}
/// Call when you have Value objects rather than Air instructions, and you want to
/// assert the store must be done at comptime.
fn storePtrVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ptr_val: Value,
operand_val: Value,
operand_ty: Type,
) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
// TODO: audit use sites to eliminate this coercion
const coerced_operand_val = try pt.getCoerced(operand_val, operand_ty);
// TODO: audit use sites to eliminate this coercion
const ptr_ty = try pt.ptrType(info: {
var info = ptr_val.typeOf(zcu).ptrInfo(zcu);
info.child = operand_ty.toIntern();
break :info info;
});
const coerced_ptr_val = try pt.getCoerced(ptr_val, ptr_ty);
switch (try sema.storeComptimePtr(block, src, coerced_ptr_val, coerced_operand_val)) {
.success => {},
.runtime_store => unreachable, // use sites check this
// TODO use failWithInvalidComptimeFieldStore
.comptime_field_mismatch => return sema.fail(
block,
src,
"value stored in comptime field does not match the default value of the field",
.{},
),
.undef => return sema.failWithUseOfUndef(block, src, null),
.err_payload => |err_name| return sema.fail(block, src, "attempt to unwrap error: {f}", .{err_name.fmt(ip)}),
.null_payload => return sema.fail(block, src, "attempt to use null value", .{}),
.inactive_union_field => return sema.fail(block, src, "access of inactive union field", .{}),
.needed_well_defined => |ty| return sema.fail(
block,
src,
"comptime dereference requires '{f}' to have a well-defined layout",
.{ty.fmt(pt)},
),
.out_of_bounds => |ty| return sema.fail(
block,
src,
"dereference of '{f}' exceeds bounds of containing decl of type '{f}'",
.{ ptr_ty.fmt(pt), ty.fmt(pt) },
),
.exceeds_host_size => return sema.fail(block, src, "bit-pointer target exceeds host size", .{}),
}
}
fn bitCast(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
operand_src: ?LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
try dest_ty.resolveLayout(pt);
const old_ty = sema.typeOf(inst);
try old_ty.resolveLayout(pt);
const dest_bits = dest_ty.bitSize(zcu);
const old_bits = old_ty.bitSize(zcu);
if (old_bits != dest_bits) {
return sema.fail(block, inst_src, "@bitCast size mismatch: destination type '{f}' has {d} bits but source type '{f}' has {d} bits", .{
dest_ty.fmt(pt),
dest_bits,
old_ty.fmt(pt),
old_bits,
});
}
if (try sema.resolveValue(inst)) |val| {
if (val.isUndef(zcu))
return pt.undefRef(dest_ty);
if (old_ty.zigTypeTag(zcu) == .error_set and dest_ty.zigTypeTag(zcu) == .error_set) {
// Special case: we sometimes call `bitCast` on error set values, but they
// don't have a well-defined layout, so we can't use `bitCastVal` on them.
return Air.internedToRef((try pt.getCoerced(val, dest_ty)).toIntern());
}
if (try sema.bitCastVal(val, dest_ty, 0, 0, 0)) |result_val| {
return Air.internedToRef(result_val.toIntern());
}
}
try sema.requireRuntimeBlock(block, inst_src, operand_src);
try sema.validateRuntimeValue(block, inst_src, inst);
return block.addBitCast(dest_ty, inst);
}
fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
if (try sema.resolveValue(inst)) |val| {
const ptr_array_ty = sema.typeOf(inst);
const array_ty = ptr_array_ty.childType(zcu);
const slice_ptr_ty = dest_ty.slicePtrFieldType(zcu);
const slice_ptr = try pt.getCoerced(val, slice_ptr_ty);
const slice_val = try pt.intern(.{ .slice = .{
.ty = dest_ty.toIntern(),
.ptr = slice_ptr.toIntern(),
.len = (try pt.intValue(.usize, array_ty.arrayLen(zcu))).toIntern(),
} });
return Air.internedToRef(slice_val);
}
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.array_to_slice, dest_ty, inst);
}
fn checkPtrAttributes(sema: *Sema, dest_ty: Type, inst_ty: Type, in_memory_result: *InMemoryCoercionResult) bool {
const pt = sema.pt;
const zcu = pt.zcu;
const dest_info = dest_ty.ptrInfo(zcu);
const inst_info = inst_ty.ptrInfo(zcu);
const len0 = (Type.fromInterned(inst_info.child).zigTypeTag(zcu) == .array and (Type.fromInterned(inst_info.child).arrayLenIncludingSentinel(zcu) == 0 or
(Type.fromInterned(inst_info.child).arrayLen(zcu) == 0 and dest_info.sentinel == .none and dest_info.flags.size != .c and dest_info.flags.size != .many))) or
(Type.fromInterned(inst_info.child).isTuple(zcu) and Type.fromInterned(inst_info.child).structFieldCount(zcu) == 0);
const ok_const = (!inst_info.flags.is_const or dest_info.flags.is_const) or len0;
const ok_volatile = !inst_info.flags.is_volatile or dest_info.flags.is_volatile;
if (!ok_const) {
in_memory_result.* = .{ .ptr_const = .{
.actual = inst_ty,
.wanted = dest_ty,
} };
return false;
}
if (!ok_volatile) {
in_memory_result.* = .{ .ptr_volatile = .{
.actual = inst_ty,
.wanted = dest_ty,
} };
return false;
}
if (dest_info.flags.address_space != inst_info.flags.address_space) {
in_memory_result.* = .{ .ptr_addrspace = .{
.actual = inst_info.flags.address_space,
.wanted = dest_info.flags.address_space,
} };
return false;
}
if (inst_info.flags.alignment == .none and dest_info.flags.alignment == .none) return true;
if (len0) return true;
const inst_align = if (inst_info.flags.alignment != .none)
inst_info.flags.alignment
else
Type.fromInterned(inst_info.child).abiAlignment(zcu);
const dest_align = if (dest_info.flags.alignment != .none)
dest_info.flags.alignment
else
Type.fromInterned(dest_info.child).abiAlignment(zcu);
if (dest_align.compare(.gt, inst_align)) {
in_memory_result.* = .{ .ptr_alignment = .{
.actual = inst_align,
.wanted = dest_align,
} };
return false;
}
if (inst_info.packed_offset.host_size != dest_info.packed_offset.host_size or
inst_info.packed_offset.bit_offset != dest_info.packed_offset.bit_offset)
{
in_memory_result.* = .{ .ptr_bit_range = .{
.actual_host = inst_info.packed_offset.host_size,
.wanted_host = dest_info.packed_offset.host_size,
.actual_offset = inst_info.packed_offset.bit_offset,
.wanted_offset = dest_info.packed_offset.bit_offset,
} };
return false;
}
return true;
}
fn coerceCompatiblePtrs(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_ty = sema.typeOf(inst);
if (try sema.resolveValue(inst)) |val| {
if (!val.isUndef(zcu) and val.isNull(zcu) and !dest_ty.isAllowzeroPtr(zcu)) {
return sema.fail(block, inst_src, "null pointer casted to type '{f}'", .{dest_ty.fmt(pt)});
}
// The comptime Value representation is compatible with both types.
return Air.internedToRef(
(try pt.getCoerced(val, dest_ty)).toIntern(),
);
}
try sema.requireRuntimeBlock(block, inst_src, null);
const inst_allows_zero = inst_ty.zigTypeTag(zcu) != .pointer or inst_ty.ptrAllowsZero(zcu);
if (block.wantSafety() and inst_allows_zero and !dest_ty.ptrAllowsZero(zcu) and
(try dest_ty.elemType2(zcu).hasRuntimeBitsSema(pt) or dest_ty.elemType2(zcu).zigTypeTag(zcu) == .@"fn"))
{
try sema.checkLogicalPtrOperation(block, inst_src, inst_ty);
const actual_ptr = if (inst_ty.isSlice(zcu))
try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty)
else
inst;
const ptr_int = try block.addBitCast(.usize, actual_ptr);
const is_non_zero = try block.addBinOp(.cmp_neq, ptr_int, .zero_usize);
const ok = if (inst_ty.isSlice(zcu)) ok: {
const len = try sema.analyzeSliceLen(block, inst_src, inst);
const len_zero = try block.addBinOp(.cmp_eq, len, .zero_usize);
break :ok try block.addBinOp(.bool_or, len_zero, is_non_zero);
} else is_non_zero;
try sema.addSafetyCheck(block, inst_src, ok, .cast_to_null);
}
const new_ptr = try sema.bitCast(block, dest_ty, inst, inst_src, null);
try sema.checkKnownAllocPtr(block, inst, new_ptr);
return new_ptr;
}
fn coerceEnumToUnion(
sema: *Sema,
block: *Block,
union_ty: Type,
union_ty_src: LazySrcLoc,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_ty = sema.typeOf(inst);
const tag_ty = union_ty.unionTagType(zcu) orelse {
const msg = msg: {
const msg = try sema.typeMismatchErrMsg(inst_src, union_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
try sema.errNote(union_ty_src, msg, "cannot coerce enum to untagged union", .{});
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
};
const enum_tag = try sema.coerce(block, tag_ty, inst, inst_src);
if (try sema.resolveDefinedValue(block, inst_src, enum_tag)) |val| {
const field_index = union_ty.unionTagFieldIndex(val, pt.zcu) orelse {
return sema.fail(block, inst_src, "union '{f}' has no tag with value '{f}'", .{
union_ty.fmt(pt), val.fmtValueSema(pt, sema),
});
};
const union_obj = zcu.typeToUnion(union_ty).?;
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
try field_ty.resolveFields(pt);
if (field_ty.zigTypeTag(zcu) == .noreturn) {
const msg = msg: {
const msg = try sema.errMsg(inst_src, "cannot initialize 'noreturn' field of union", .{});
errdefer msg.destroy(sema.gpa);
const field_name = union_obj.loadTagType(ip).names.get(ip)[field_index];
try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' declared here", .{
field_name.fmt(ip),
});
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const opv = (try sema.typeHasOnePossibleValue(field_ty)) orelse {
const msg = msg: {
const field_name = union_obj.loadTagType(ip).names.get(ip)[field_index];
const msg = try sema.errMsg(inst_src, "coercion from enum '{f}' to union '{f}' must initialize '{f}' field '{f}'", .{
inst_ty.fmt(pt), union_ty.fmt(pt),
field_ty.fmt(pt), field_name.fmt(ip),
});
errdefer msg.destroy(sema.gpa);
try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' declared here", .{
field_name.fmt(ip),
});
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
};
return Air.internedToRef((try pt.unionValue(union_ty, val, opv)).toIntern());
}
try sema.requireRuntimeBlock(block, inst_src, null);
if (tag_ty.isNonexhaustiveEnum(zcu)) {
const msg = msg: {
const msg = try sema.errMsg(inst_src, "runtime coercion to union '{f}' from non-exhaustive enum", .{
union_ty.fmt(pt),
});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, tag_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const union_obj = zcu.typeToUnion(union_ty).?;
{
var msg: ?*Zcu.ErrorMsg = null;
errdefer if (msg) |some| some.destroy(sema.gpa);
for (union_obj.field_types.get(ip), 0..) |field_ty, field_index| {
if (Type.fromInterned(field_ty).zigTypeTag(zcu) == .noreturn) {
const err_msg = msg orelse try sema.errMsg(
inst_src,
"runtime coercion from enum '{f}' to union '{f}' which has a 'noreturn' field",
.{ tag_ty.fmt(pt), union_ty.fmt(pt) },
);
msg = err_msg;
try sema.addFieldErrNote(union_ty, field_index, err_msg, "'noreturn' field here", .{});
}
}
if (msg) |some| {
msg = null;
try sema.addDeclaredHereNote(some, union_ty);
return sema.failWithOwnedErrorMsg(block, some);
}
}
// If the union has all fields 0 bits, the union value is just the enum value.
if (union_ty.unionHasAllZeroBitFieldTypes(zcu)) {
return block.addBitCast(union_ty, enum_tag);
}
const msg = msg: {
const msg = try sema.errMsg(
inst_src,
"runtime coercion from enum '{f}' to union '{f}' which has non-void fields",
.{ tag_ty.fmt(pt), union_ty.fmt(pt) },
);
errdefer msg.destroy(sema.gpa);
for (0..union_obj.field_types.len) |field_index| {
const field_name = union_obj.loadTagType(ip).names.get(ip)[field_index];
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
if (!(try field_ty.hasRuntimeBitsSema(pt))) continue;
try sema.addFieldErrNote(union_ty, field_index, msg, "field '{f}' has type '{f}'", .{
field_name.fmt(ip),
field_ty.fmt(pt),
});
}
try sema.addDeclaredHereNote(msg, union_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
/// If the lengths match, coerces element-wise.
fn coerceArrayLike(
sema: *Sema,
block: *Block,
dest_ty: Type,
dest_ty_src: LazySrcLoc,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_ty = sema.typeOf(inst);
const target = zcu.getTarget();
// try coercion of the whole array
const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src, null);
if (in_memory_result == .ok) {
if (try sema.resolveValue(inst)) |inst_val| {
// These types share the same comptime value representation.
return sema.coerceInMemory(inst_val, dest_ty);
}
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addBitCast(dest_ty, inst);
}
// otherwise, try element by element
const inst_len = inst_ty.arrayLen(zcu);
const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen(zcu));
if (dest_len != inst_len) {
const msg = msg: {
const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
try sema.errNote(dest_ty_src, msg, "destination has length {d}", .{dest_len});
try sema.errNote(inst_src, msg, "source has length {d}", .{inst_len});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const dest_elem_ty = dest_ty.childType(zcu);
if (dest_ty.isVector(zcu) and inst_ty.isVector(zcu) and (try sema.resolveValue(inst)) == null) {
const inst_elem_ty = inst_ty.childType(zcu);
switch (dest_elem_ty.zigTypeTag(zcu)) {
.int => if (inst_elem_ty.isInt(zcu)) {
// integer widening
const dst_info = dest_elem_ty.intInfo(zcu);
const src_info = inst_elem_ty.intInfo(zcu);
if ((src_info.signedness == dst_info.signedness and dst_info.bits >= src_info.bits) or
// small enough unsigned ints can get casted to large enough signed ints
(dst_info.signedness == .signed and dst_info.bits > src_info.bits))
{
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.intcast, dest_ty, inst);
}
},
.float => if (inst_elem_ty.isRuntimeFloat()) {
// float widening
const src_bits = inst_elem_ty.floatBits(target);
const dst_bits = dest_elem_ty.floatBits(target);
if (dst_bits >= src_bits) {
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.fpext, dest_ty, inst);
}
},
else => {},
}
}
const element_vals = try sema.arena.alloc(InternPool.Index, dest_len);
const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len);
var runtime_src: ?LazySrcLoc = null;
for (element_vals, element_refs, 0..) |*val, *ref, i| {
const index_ref = Air.internedToRef((try pt.intValue(.usize, i)).toIntern());
const src = inst_src; // TODO better source location
const elem_src = inst_src; // TODO better source location
const elem_ref = try sema.elemValArray(block, src, inst_src, inst, elem_src, index_ref, true);
const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src);
ref.* = coerced;
if (runtime_src == null) {
if (try sema.resolveValue(coerced)) |elem_val| {
val.* = elem_val.toIntern();
} else {
runtime_src = elem_src;
}
}
}
if (runtime_src) |rs| {
try sema.requireRuntimeBlock(block, inst_src, rs);
return block.addAggregateInit(dest_ty, element_refs);
}
return Air.internedToRef((try pt.aggregateValue(dest_ty, element_vals)).toIntern());
}
/// If the lengths match, coerces element-wise.
fn coerceTupleToArray(
sema: *Sema,
block: *Block,
dest_ty: Type,
dest_ty_src: LazySrcLoc,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const inst_ty = sema.typeOf(inst);
const inst_len = inst_ty.arrayLen(zcu);
const dest_len = dest_ty.arrayLen(zcu);
if (dest_len != inst_len) {
const msg = msg: {
const msg = try sema.typeMismatchErrMsg(inst_src, dest_ty, inst_ty);
errdefer msg.destroy(sema.gpa);
try sema.errNote(dest_ty_src, msg, "destination has length {d}", .{dest_len});
try sema.errNote(inst_src, msg, "source has length {d}", .{inst_len});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
const dest_elems = try sema.usizeCast(block, dest_ty_src, dest_len);
const element_vals = try sema.arena.alloc(InternPool.Index, dest_elems);
const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_elems);
const dest_elem_ty = dest_ty.childType(zcu);
var runtime_src: ?LazySrcLoc = null;
for (element_vals, element_refs, 0..) |*val, *ref, i_usize| {
const i: u32 = @intCast(i_usize);
if (i_usize == inst_len) {
const sentinel_val = dest_ty.sentinel(zcu).?;
val.* = sentinel_val.toIntern();
ref.* = Air.internedToRef(sentinel_val.toIntern());
break;
}
const elem_src = inst_src; // TODO better source location
const elem_ref = try sema.tupleField(block, inst_src, inst, elem_src, i);
const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src);
ref.* = coerced;
if (runtime_src == null) {
if (try sema.resolveValue(coerced)) |elem_val| {
val.* = elem_val.toIntern();
} else {
runtime_src = elem_src;
}
}
}
if (runtime_src) |rs| {
try sema.requireRuntimeBlock(block, inst_src, rs);
return block.addAggregateInit(dest_ty, element_refs);
}
return Air.internedToRef((try pt.aggregateValue(dest_ty, element_vals)).toIntern());
}
/// If the lengths match, coerces element-wise.
fn coerceTupleToSlicePtrs(
sema: *Sema,
block: *Block,
slice_ty: Type,
slice_ty_src: LazySrcLoc,
ptr_tuple: Air.Inst.Ref,
tuple_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const tuple_ty = sema.typeOf(ptr_tuple).childType(zcu);
const tuple = try sema.analyzeLoad(block, tuple_src, ptr_tuple, tuple_src);
const slice_info = slice_ty.ptrInfo(zcu);
const array_ty = try pt.arrayType(.{
.len = tuple_ty.structFieldCount(zcu),
.sentinel = slice_info.sentinel,
.child = slice_info.child,
});
const array_inst = try sema.coerceTupleToArray(block, array_ty, slice_ty_src, tuple, tuple_src);
if (slice_info.flags.alignment != .none) {
return sema.fail(block, slice_ty_src, "TODO: override the alignment of the array decl we create here", .{});
}
const ptr_array = try sema.analyzeRef(block, slice_ty_src, array_inst);
return sema.coerceArrayPtrToSlice(block, slice_ty, ptr_array, slice_ty_src);
}
/// If the lengths match, coerces element-wise.
fn coerceTupleToArrayPtrs(
sema: *Sema,
block: *Block,
ptr_array_ty: Type,
array_ty_src: LazySrcLoc,
ptr_tuple: Air.Inst.Ref,
tuple_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const tuple = try sema.analyzeLoad(block, tuple_src, ptr_tuple, tuple_src);
const ptr_info = ptr_array_ty.ptrInfo(zcu);
const array_ty: Type = .fromInterned(ptr_info.child);
const array_inst = try sema.coerceTupleToArray(block, array_ty, array_ty_src, tuple, tuple_src);
if (ptr_info.flags.alignment != .none) {
return sema.fail(block, array_ty_src, "TODO: override the alignment of the array decl we create here", .{});
}
const ptr_array = try sema.analyzeRef(block, array_ty_src, array_inst);
return ptr_array;
}
fn coerceTupleToTuple(
sema: *Sema,
block: *Block,
tuple_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const dest_field_count = switch (ip.indexToKey(tuple_ty.toIntern())) {
.tuple_type => |tuple_type| tuple_type.types.len,
else => unreachable,
};
const field_vals = try sema.arena.alloc(InternPool.Index, dest_field_count);
const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len);
@memset(field_refs, .none);
const inst_ty = sema.typeOf(inst);
const src_field_count = switch (ip.indexToKey(inst_ty.toIntern())) {
.tuple_type => |tuple_type| tuple_type.types.len,
else => unreachable,
};
if (src_field_count > dest_field_count) return error.NotCoercible;
var runtime_src: ?LazySrcLoc = null;
for (0..dest_field_count) |field_index_usize| {
const field_i: u32 = @intCast(field_index_usize);
const field_src = inst_src; // TODO better source location
const field_ty = switch (ip.indexToKey(tuple_ty.toIntern())) {
.tuple_type => |tuple_type| tuple_type.types.get(ip)[field_index_usize],
.struct_type => ip.loadStructType(tuple_ty.toIntern()).field_types.get(ip)[field_index_usize],
else => unreachable,
};
const default_val = switch (ip.indexToKey(tuple_ty.toIntern())) {
.tuple_type => |tuple_type| tuple_type.values.get(ip)[field_index_usize],
.struct_type => ip.loadStructType(tuple_ty.toIntern()).fieldInit(ip, field_index_usize),
else => unreachable,
};
const field_index: u32 = @intCast(field_index_usize);
const elem_ref = try sema.tupleField(block, inst_src, inst, field_src, field_i);
const coerced = try sema.coerce(block, .fromInterned(field_ty), elem_ref, field_src);
field_refs[field_index] = coerced;
if (default_val != .none) {
const init_val = (try sema.resolveValue(coerced)) orelse {
return sema.failWithNeededComptime(block, field_src, .{ .simple = .stored_to_comptime_field });
};
if (!init_val.eql(Value.fromInterned(default_val), .fromInterned(field_ty), pt.zcu)) {
return sema.failWithInvalidComptimeFieldStore(block, field_src, inst_ty, field_i);
}
}
if (runtime_src == null) {
if (try sema.resolveValue(coerced)) |field_val| {
field_vals[field_index] = field_val.toIntern();
} else {
runtime_src = field_src;
}
}
}
// Populate default field values and report errors for missing fields.
var root_msg: ?*Zcu.ErrorMsg = null;
errdefer if (root_msg) |msg| msg.destroy(sema.gpa);
for (field_refs, 0..) |*field_ref, i_usize| {
const i: u32 = @intCast(i_usize);
if (field_ref.* != .none) continue;
const default_val = switch (ip.indexToKey(tuple_ty.toIntern())) {
.tuple_type => |tuple_type| tuple_type.values.get(ip)[i],
.struct_type => ip.loadStructType(tuple_ty.toIntern()).fieldInit(ip, i),
else => unreachable,
};
const field_src = inst_src; // TODO better source location
if (default_val == .none) {
const template = "missing tuple field: {d}";
if (root_msg) |msg| {
try sema.errNote(field_src, msg, template, .{i});
} else {
root_msg = try sema.errMsg(field_src, template, .{i});
}
continue;
}
if (runtime_src == null) {
field_vals[i] = default_val;
} else {
field_ref.* = Air.internedToRef(default_val);
}
}
if (root_msg) |msg| {
try sema.addDeclaredHereNote(msg, tuple_ty);
root_msg = null;
return sema.failWithOwnedErrorMsg(block, msg);
}
if (runtime_src) |rs| {
try sema.requireRuntimeBlock(block, inst_src, rs);
return block.addAggregateInit(tuple_ty, field_refs);
}
return Air.internedToRef((try pt.aggregateValue(tuple_ty, field_vals)).toIntern());
}
fn analyzeNavVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
nav_index: InternPool.Nav.Index,
) CompileError!Air.Inst.Ref {
const ref = try sema.analyzeNavRefInner(block, src, nav_index, false);
return sema.analyzeLoad(block, src, ref, src);
}
fn addReferenceEntry(
sema: *Sema,
opt_block: ?*Block,
src: LazySrcLoc,
referenced_unit: AnalUnit,
) !void {
const zcu = sema.pt.zcu;
const ip = &zcu.intern_pool;
switch (referenced_unit.unwrap()) {
.func => |f| assert(ip.unwrapCoercedFunc(f) == f), // for `.{ .func = f }`, `f` must be uncoerced
else => {},
}
if (!zcu.comp.config.incremental and zcu.comp.reference_trace == 0) return;
const gop = try sema.references.getOrPut(sema.gpa, referenced_unit);
if (gop.found_existing) return;
try zcu.addUnitReference(sema.owner, referenced_unit, src, inline_frame: {
const block = opt_block orelse break :inline_frame .none;
const inlining = block.inlining orelse break :inline_frame .none;
const frame = try inlining.refFrame(zcu);
break :inline_frame frame.toOptional();
});
}
pub fn addTypeReferenceEntry(
sema: *Sema,
src: LazySrcLoc,
referenced_type: InternPool.Index,
) !void {
const zcu = sema.pt.zcu;
if (!zcu.comp.config.incremental and zcu.comp.reference_trace == 0) return;
const gop = try sema.type_references.getOrPut(sema.gpa, referenced_type);
if (gop.found_existing) return;
try zcu.addTypeReference(sema.owner, referenced_type, src);
}
fn ensureMemoizedStateResolved(sema: *Sema, src: LazySrcLoc, stage: InternPool.MemoizedStateStage) SemaError!void {
const pt = sema.pt;
const unit: AnalUnit = .wrap(.{ .memoized_state = stage });
try sema.addReferenceEntry(null, src, unit);
try sema.declareDependency(.{ .memoized_state = stage });
if (pt.zcu.analysis_in_progress.contains(unit)) {
return sema.failWithOwnedErrorMsg(null, try sema.errMsg(src, "dependency loop detected", .{}));
}
try pt.ensureMemoizedStateUpToDate(stage);
}
pub fn ensureNavResolved(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index, kind: enum { type, fully }) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const nav = ip.getNav(nav_index);
if (nav.analysis == null) {
assert(nav.status == .fully_resolved);
return;
}
try sema.declareDependency(switch (kind) {
.type => .{ .nav_ty = nav_index },
.fully => .{ .nav_val = nav_index },
});
// Note that even if `nav.status == .resolved`, we must still trigger `ensureNavValUpToDate`
// to make sure the value is up-to-date on incremental updates.
const anal_unit: AnalUnit = .wrap(switch (kind) {
.type => .{ .nav_ty = nav_index },
.fully => .{ .nav_val = nav_index },
});
try sema.addReferenceEntry(block, src, anal_unit);
if (zcu.analysis_in_progress.contains(anal_unit)) {
return sema.failWithOwnedErrorMsg(null, try sema.errMsg(.{
.base_node_inst = nav.analysis.?.zir_index,
.offset = LazySrcLoc.Offset.nodeOffset(.zero),
}, "dependency loop detected", .{}));
}
switch (kind) {
.type => {
try zcu.ensureNavValAnalysisQueued(nav_index);
return pt.ensureNavTypeUpToDate(nav_index);
},
.fully => return pt.ensureNavValUpToDate(nav_index),
}
}
fn optRefValue(sema: *Sema, opt_val: ?Value) !Value {
const pt = sema.pt;
const ptr_anyopaque_ty = try pt.singleConstPtrType(.anyopaque);
return Value.fromInterned(try pt.intern(.{ .opt = .{
.ty = (try pt.optionalType(ptr_anyopaque_ty.toIntern())).toIntern(),
.val = if (opt_val) |val| (try pt.getCoerced(
Value.fromInterned(try pt.refValue(val.toIntern())),
ptr_anyopaque_ty,
)).toIntern() else .none,
} }));
}
fn analyzeNavRef(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index) CompileError!Air.Inst.Ref {
return sema.analyzeNavRefInner(block, src, nav_index, true);
}
/// Analyze a reference to the `Nav` at the given index. Ensures the underlying `Nav` is analyzed.
/// If this pointer will be used directly, `is_ref` must be `true`.
/// If this pointer will be immediately loaded (i.e. a `decl_val` instruction), `is_ref` must be `false`.
fn analyzeNavRefInner(sema: *Sema, block: *Block, src: LazySrcLoc, orig_nav_index: InternPool.Nav.Index, is_ref: bool) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
try sema.ensureNavResolved(block, src, orig_nav_index, if (is_ref) .type else .fully);
const nav_index = nav: {
if (ip.getNav(orig_nav_index).isExternOrFn(ip)) {
// Getting a pointer to this `Nav` might mean we actually get a pointer to something else!
// We need to resolve the value to know for sure.
if (is_ref) try sema.ensureNavResolved(block, src, orig_nav_index, .fully);
switch (ip.indexToKey(ip.getNav(orig_nav_index).status.fully_resolved.val)) {
.func => |f| break :nav f.owner_nav,
.@"extern" => |e| break :nav e.owner_nav,
else => {},
}
}
break :nav orig_nav_index;
};
const nav_status = ip.getNav(nav_index).status;
const is_runtime = switch (nav_status) {
.unresolved => unreachable,
// dllimports go straight to `fully_resolved`; the only option is threadlocal
.type_resolved => |r| r.is_threadlocal,
.fully_resolved => |r| switch (ip.indexToKey(r.val)) {
.@"extern" => |e| e.is_threadlocal or e.is_dll_import or switch (e.relocation) {
.any => false,
.pcrel => true,
},
.variable => |v| v.is_threadlocal,
else => false,
},
};
const ty, const alignment, const @"addrspace", const is_const = switch (nav_status) {
.unresolved => unreachable,
.type_resolved => |r| .{ r.type, r.alignment, r.@"addrspace", r.is_const },
.fully_resolved => |r| .{ ip.typeOf(r.val), r.alignment, r.@"addrspace", r.is_const },
};
const ptr_ty = try pt.ptrTypeSema(.{
.child = ty,
.flags = .{
.alignment = alignment,
.is_const = is_const,
.address_space = @"addrspace",
},
});
if (is_runtime) {
// This pointer is runtime-known; we need to emit an AIR instruction to create it.
return block.addInst(.{
.tag = .runtime_nav_ptr,
.data = .{ .ty_nav = .{
.ty = ptr_ty.toIntern(),
.nav = nav_index,
} },
});
}
if (is_ref) {
try sema.maybeQueueFuncBodyAnalysis(block, src, nav_index);
}
return Air.internedToRef((try pt.intern(.{ .ptr = .{
.ty = ptr_ty.toIntern(),
.base_addr = .{ .nav = nav_index },
.byte_offset = 0,
} })));
}
fn maybeQueueFuncBodyAnalysis(sema: *Sema, block: *Block, src: LazySrcLoc, nav_index: InternPool.Nav.Index) !void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
// To avoid forcing too much resolution, let's first resolve the type, and check if it's a function.
// If it is, we can resolve the *value*, and queue analysis as needed.
try sema.ensureNavResolved(block, src, nav_index, .type);
const nav_ty: Type = .fromInterned(ip.getNav(nav_index).typeOf(ip));
if (nav_ty.zigTypeTag(zcu) != .@"fn") return;
if (!try nav_ty.fnHasRuntimeBitsSema(pt)) return;
try sema.ensureNavResolved(block, src, nav_index, .fully);
const nav_val = zcu.navValue(nav_index);
if (!ip.isFuncBody(nav_val.toIntern())) return;
const orig_fn_index = ip.unwrapCoercedFunc(nav_val.toIntern());
try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_fn_index }));
try zcu.ensureFuncBodyAnalysisQueued(orig_fn_index);
}
fn analyzeRef(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const operand_ty = sema.typeOf(operand);
if (try sema.resolveValue(operand)) |val| {
switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.@"extern" => |e| return sema.analyzeNavRef(block, src, e.owner_nav),
.func => |f| return sema.analyzeNavRef(block, src, f.owner_nav),
else => return uavRef(sema, val.toIntern()),
}
}
// No `requireRuntimeBlock`; it's okay to `ref` to a runtime value in a comptime context,
// it's just that we can only use the *type* of the result, since the value is runtime-known.
const address_space = target_util.defaultAddressSpace(zcu.getTarget(), .local);
const ptr_type = try pt.ptrTypeSema(.{
.child = operand_ty.toIntern(),
.flags = .{
.is_const = true,
.address_space = address_space,
},
});
const mut_ptr_type = try pt.ptrTypeSema(.{
.child = operand_ty.toIntern(),
.flags = .{ .address_space = address_space },
});
const alloc = try block.addTy(.alloc, mut_ptr_type);
// In a comptime context, the store would fail, since the operand is runtime-known. But that's
// okay; we don't actually need this store to succeed, since we're creating a runtime value in a
// comptime scope, so the value can never be used aside from to get its type.
if (!block.isComptime()) {
try sema.storePtr(block, src, alloc, operand);
}
// Cast to the constant pointer type. We do this directly rather than going via `coerce` to
// avoid errors in the `block.isComptime()` case.
return block.addBitCast(ptr_type, alloc);
}
fn analyzeLoad(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ptr: Air.Inst.Ref,
ptr_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ptr_ty = sema.typeOf(ptr);
const elem_ty = switch (ptr_ty.zigTypeTag(zcu)) {
.pointer => ptr_ty.childType(zcu),
else => return sema.fail(block, ptr_src, "expected pointer, found '{f}'", .{ptr_ty.fmt(pt)}),
};
if (elem_ty.zigTypeTag(zcu) == .@"opaque") {
return sema.fail(block, ptr_src, "cannot load opaque type '{f}'", .{elem_ty.fmt(pt)});
}
if (try sema.typeHasOnePossibleValue(elem_ty)) |opv| {
return Air.internedToRef(opv.toIntern());
}
if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {
if (try sema.pointerDeref(block, src, ptr_val, ptr_ty)) |elem_val| {
return Air.internedToRef(elem_val.toIntern());
}
}
return block.addTyOp(.load, elem_ty, ptr);
}
fn analyzeSlicePtr(
sema: *Sema,
block: *Block,
slice_src: LazySrcLoc,
slice: Air.Inst.Ref,
slice_ty: Type,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const result_ty = slice_ty.slicePtrFieldType(zcu);
if (try sema.resolveValue(slice)) |val| {
if (val.isUndef(zcu)) return pt.undefRef(result_ty);
return Air.internedToRef(val.slicePtr(zcu).toIntern());
}
try sema.requireRuntimeBlock(block, slice_src, null);
return block.addTyOp(.slice_ptr, result_ty, slice);
}
fn analyzeOptionalSlicePtr(
sema: *Sema,
block: *Block,
opt_slice_src: LazySrcLoc,
opt_slice: Air.Inst.Ref,
opt_slice_ty: Type,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const slice_ty = opt_slice_ty.optionalChild(zcu);
const result_ty = slice_ty.slicePtrFieldType(zcu);
if (try sema.resolveValue(opt_slice)) |opt_val| {
if (opt_val.isUndef(zcu)) return pt.undefRef(result_ty);
const slice_ptr: InternPool.Index = if (opt_val.optionalValue(zcu)) |val|
val.slicePtr(zcu).toIntern()
else
.null_value;
return Air.internedToRef(slice_ptr);
}
try sema.requireRuntimeBlock(block, opt_slice_src, null);
const slice = try block.addTyOp(.optional_payload, slice_ty, opt_slice);
return block.addTyOp(.slice_ptr, result_ty, slice);
}
fn analyzeSliceLen(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
slice_inst: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
if (try sema.resolveValue(slice_inst)) |slice_val| {
if (slice_val.isUndef(zcu)) {
return .undef_usize;
}
return pt.intRef(.usize, try slice_val.sliceLen(pt));
}
try sema.requireRuntimeBlock(block, src, null);
return block.addTyOp(.slice_len, .usize, slice_inst);
}
fn analyzeIsNull(
sema: *Sema,
block: *Block,
operand: Air.Inst.Ref,
invert_logic: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const result_ty: Type = .bool;
if (try sema.resolveValue(operand)) |opt_val| {
if (opt_val.isUndef(zcu)) {
return pt.undefRef(result_ty);
}
const is_null = opt_val.isNull(zcu);
const bool_value = if (invert_logic) !is_null else is_null;
return if (bool_value) .bool_true else .bool_false;
}
if (sema.typeOf(operand).isNullFromType(zcu)) |is_null| {
const result = is_null != invert_logic;
return if (result) .bool_true else .bool_false;
}
const air_tag: Air.Inst.Tag = if (invert_logic) .is_non_null else .is_null;
return block.addUnOp(air_tag, operand);
}
fn analyzePtrIsNonErrComptimeOnly(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ptr_ty = sema.typeOf(operand);
assert(ptr_ty.zigTypeTag(zcu) == .pointer);
const child_ty = ptr_ty.childType(zcu);
const child_tag = child_ty.zigTypeTag(zcu);
if (child_tag != .error_set and child_tag != .error_union) return .bool_true;
if (child_tag == .error_set) return .bool_false;
assert(child_tag == .error_union);
_ = block;
_ = src;
return .none;
}
fn analyzeIsNonErrComptimeOnly(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const operand_ty = sema.typeOf(operand);
const ot = operand_ty.zigTypeTag(zcu);
if (ot != .error_set and ot != .error_union) return .bool_true;
if (ot == .error_set) return .bool_false;
assert(ot == .error_union);
const payload_ty = operand_ty.errorUnionPayload(zcu);
if (payload_ty.zigTypeTag(zcu) == .noreturn) {
return .bool_false;
}
if (operand.toIndex()) |operand_inst| {
switch (sema.air_instructions.items(.tag)[@intFromEnum(operand_inst)]) {
.wrap_errunion_payload => return .bool_true,
.wrap_errunion_err => return .bool_false,
else => {},
}
} else if (operand == .undef) {
return .undef_bool;
} else if (@intFromEnum(operand) < InternPool.static_len) {
// None of the ref tags can be errors.
return .bool_true;
}
const maybe_operand_val = try sema.resolveValue(operand);
// exception if the error union error set is known to be empty,
// we allow the comparison but always make it comptime-known.
const set_ty = ip.errorUnionSet(operand_ty.toIntern());
switch (set_ty) {
.anyerror_type => {},
.adhoc_inferred_error_set_type => if (sema.fn_ret_ty_ies) |ies| blk: {
// If the error set is empty, we must return a comptime true or false.
// However we want to avoid unnecessarily resolving an inferred error set
// in case it is already non-empty.
switch (ies.resolved) {
.anyerror_type => break :blk,
.none => {},
else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,
}
if (maybe_operand_val != null) break :blk;
// Try to avoid resolving inferred error set if possible.
if (ies.errors.count() != 0) return .none;
switch (ies.resolved) {
.anyerror_type => return .none,
.none => {},
else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) {
0 => return .bool_true,
else => return .none,
},
}
// We do not have a comptime answer because this inferred error
// set is not resolved, and an instruction later in this function
// body may or may not cause an error to be added to this set.
return .none;
},
else => switch (ip.indexToKey(set_ty)) {
.error_set_type => |error_set_type| {
if (error_set_type.names.len == 0) return .bool_true;
},
.inferred_error_set_type => |func_index| blk: {
// If the error set is empty, we must return a comptime true or false.
// However we want to avoid unnecessarily resolving an inferred error set
// in case it is already non-empty.
try zcu.maybeUnresolveIes(func_index);
switch (ip.funcIesResolvedUnordered(func_index)) {
.anyerror_type => break :blk,
.none => {},
else => |i| if (ip.indexToKey(i).error_set_type.names.len != 0) break :blk,
}
if (maybe_operand_val != null) break :blk;
if (sema.fn_ret_ty_ies) |ies| {
if (ies.func == func_index) {
// Try to avoid resolving inferred error set if possible.
if (ies.errors.count() != 0) return .none;
switch (ies.resolved) {
.anyerror_type => return .none,
.none => {},
else => switch (ip.indexToKey(ies.resolved).error_set_type.names.len) {
0 => return .bool_true,
else => return .none,
},
}
// We do not have a comptime answer because this inferred error
// set is not resolved, and an instruction later in this function
// body may or may not cause an error to be added to this set.
return .none;
}
}
const resolved_ty = try sema.resolveInferredErrorSet(block, src, set_ty);
if (resolved_ty == .anyerror_type)
break :blk;
if (ip.indexToKey(resolved_ty).error_set_type.names.len == 0)
return .bool_true;
},
else => unreachable,
},
}
if (maybe_operand_val) |err_union| {
return if (err_union.isUndef(zcu)) .undef_bool else if (err_union.getErrorName(zcu) == .none) .bool_true else .bool_false;
}
return .none;
}
fn analyzeIsNonErr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const result = try sema.analyzeIsNonErrComptimeOnly(block, src, operand);
if (result == .none) {
try sema.requireRuntimeBlock(block, src, null);
return block.addUnOp(.is_non_err, operand);
} else {
return result;
}
}
fn analyzePtrIsNonErr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref {
const result = try sema.analyzePtrIsNonErrComptimeOnly(block, src, operand);
if (result == .none) {
try sema.requireRuntimeBlock(block, src, null);
return block.addUnOp(.is_non_err_ptr, operand);
} else {
return result;
}
}
fn analyzeSlice(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ptr_ptr: Air.Inst.Ref,
uncasted_start: Air.Inst.Ref,
uncasted_end_opt: Air.Inst.Ref,
sentinel_opt: Air.Inst.Ref,
sentinel_src: LazySrcLoc,
ptr_src: LazySrcLoc,
start_src: LazySrcLoc,
end_src: LazySrcLoc,
by_length: bool,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
// Slice expressions can operate on a variable whose type is an array. This requires
// the slice operand to be a pointer. In the case of a non-array, it will be a double pointer.
const ptr_ptr_ty = sema.typeOf(ptr_ptr);
const ptr_ptr_child_ty = switch (ptr_ptr_ty.zigTypeTag(zcu)) {
.pointer => ptr_ptr_ty.childType(zcu),
else => return sema.fail(block, ptr_src, "expected pointer, found '{f}'", .{ptr_ptr_ty.fmt(pt)}),
};
var array_ty = ptr_ptr_child_ty;
var slice_ty = ptr_ptr_ty;
var ptr_or_slice = ptr_ptr;
var elem_ty: Type = undefined;
var ptr_sentinel: ?Value = null;
switch (ptr_ptr_child_ty.zigTypeTag(zcu)) {
.array => {
ptr_sentinel = ptr_ptr_child_ty.sentinel(zcu);
elem_ty = ptr_ptr_child_ty.childType(zcu);
},
.pointer => switch (ptr_ptr_child_ty.ptrSize(zcu)) {
.one => {
const double_child_ty = ptr_ptr_child_ty.childType(zcu);
ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src);
if (double_child_ty.zigTypeTag(zcu) == .array) {
ptr_sentinel = double_child_ty.sentinel(zcu);
slice_ty = ptr_ptr_child_ty;
array_ty = double_child_ty;
elem_ty = double_child_ty.childType(zcu);
} else {
if (uncasted_end_opt == .none) {
return sema.fail(block, src, "slice of single-item pointer must be bounded", .{});
}
const start_value = try sema.resolveConstDefinedValue(
block,
start_src,
uncasted_start,
.{ .simple = .slice_single_item_ptr_bounds },
);
const end_value = try sema.resolveConstDefinedValue(
block,
end_src,
uncasted_end_opt,
.{ .simple = .slice_single_item_ptr_bounds },
);
const bounds_error_message = "slice of single-item pointer must have bounds [0..0], [0..1], or [1..1]";
if (try sema.compareScalar(start_value, .neq, end_value, .comptime_int)) {
if (try sema.compareScalar(start_value, .neq, Value.zero_comptime_int, .comptime_int)) {
const msg = msg: {
const msg = try sema.errMsg(start_src, bounds_error_message, .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(
start_src,
msg,
"expected '{f}', found '{f}'",
.{
Value.zero_comptime_int.fmtValueSema(pt, sema),
start_value.fmtValueSema(pt, sema),
},
);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
} else if (try sema.compareScalar(end_value, .neq, Value.one_comptime_int, .comptime_int)) {
const msg = msg: {
const msg = try sema.errMsg(end_src, bounds_error_message, .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(
end_src,
msg,
"expected '{f}', found '{f}'",
.{
Value.one_comptime_int.fmtValueSema(pt, sema),
end_value.fmtValueSema(pt, sema),
},
);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
} else {
if (try sema.compareScalar(end_value, .gt, Value.one_comptime_int, .comptime_int)) {
return sema.fail(
block,
end_src,
"end index {f} out of bounds for slice of single-item pointer",
.{end_value.fmtValueSema(pt, sema)},
);
}
}
array_ty = try pt.arrayType(.{
.len = 1,
.child = double_child_ty.toIntern(),
});
const ptr_info = ptr_ptr_child_ty.ptrInfo(zcu);
slice_ty = try pt.ptrType(.{
.child = array_ty.toIntern(),
.flags = .{
.alignment = ptr_info.flags.alignment,
.is_const = ptr_info.flags.is_const,
.is_allowzero = ptr_info.flags.is_allowzero,
.is_volatile = ptr_info.flags.is_volatile,
.address_space = ptr_info.flags.address_space,
},
});
elem_ty = double_child_ty;
}
},
.many, .c => {
ptr_sentinel = ptr_ptr_child_ty.sentinel(zcu);
ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src);
slice_ty = ptr_ptr_child_ty;
array_ty = ptr_ptr_child_ty;
elem_ty = ptr_ptr_child_ty.childType(zcu);
if (ptr_ptr_child_ty.ptrSize(zcu) == .c) {
if (try sema.resolveDefinedValue(block, ptr_src, ptr_or_slice)) |ptr_val| {
if (ptr_val.isNull(zcu)) {
return sema.fail(block, src, "slice of null pointer", .{});
}
}
}
},
.slice => {
ptr_sentinel = ptr_ptr_child_ty.sentinel(zcu);
ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src);
slice_ty = ptr_ptr_child_ty;
array_ty = ptr_ptr_child_ty;
elem_ty = ptr_ptr_child_ty.childType(zcu);
},
},
else => return sema.fail(block, src, "slice of non-array type '{f}'", .{ptr_ptr_child_ty.fmt(pt)}),
}
const ptr = if (slice_ty.isSlice(zcu))
try sema.analyzeSlicePtr(block, ptr_src, ptr_or_slice, slice_ty)
else if (array_ty.zigTypeTag(zcu) == .array) ptr: {
var manyptr_ty_key = zcu.intern_pool.indexToKey(slice_ty.toIntern()).ptr_type;
assert(manyptr_ty_key.child == array_ty.toIntern());
assert(manyptr_ty_key.flags.size == .one);
manyptr_ty_key.child = elem_ty.toIntern();
manyptr_ty_key.flags.size = .many;
break :ptr try sema.coerceCompatiblePtrs(block, try pt.ptrTypeSema(manyptr_ty_key), ptr_or_slice, ptr_src);
} else ptr_or_slice;
const start = try sema.coerce(block, .usize, uncasted_start, start_src);
const new_ptr = try sema.analyzePtrArithmetic(block, src, ptr, start, .ptr_add, ptr_src, start_src);
const new_ptr_ty = sema.typeOf(new_ptr);
// true if and only if the end index of the slice, implicitly or explicitly, equals
// the length of the underlying object being sliced. we might learn the length of the
// underlying object because it is an array (which has the length in the type), or
// we might learn of the length because it is a comptime-known slice value.
var end_is_len = uncasted_end_opt == .none;
const end = e: {
if (array_ty.zigTypeTag(zcu) == .array) {
const len_val = try pt.intValue(.usize, array_ty.arrayLen(zcu));
if (!end_is_len) {
const end = if (by_length) end: {
const len = try sema.coerce(block, .usize, uncasted_end_opt, end_src);
const uncasted_end = try sema.analyzeArithmetic(block, .add, start, len, src, start_src, end_src, false);
break :end try sema.coerce(block, .usize, uncasted_end, end_src);
} else try sema.coerce(block, .usize, uncasted_end_opt, end_src);
if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| {
const len_s_val = try pt.intValue(
.usize,
array_ty.arrayLenIncludingSentinel(zcu),
);
if (!(try sema.compareAll(end_val, .lte, len_s_val, .usize))) {
const sentinel_label: []const u8 = if (array_ty.sentinel(zcu) != null)
" +1 (sentinel)"
else
"";
return sema.fail(
block,
end_src,
"end index {f} out of bounds for array of length {f}{s}",
.{
end_val.fmtValueSema(pt, sema),
len_val.fmtValueSema(pt, sema),
sentinel_label,
},
);
}
// end_is_len is only true if we are NOT using the sentinel
// length. For sentinel-length, we don't want the type to
// contain the sentinel.
if (end_val.eql(len_val, .usize, zcu)) {
end_is_len = true;
}
}
break :e end;
}
break :e Air.internedToRef(len_val.toIntern());
} else if (slice_ty.isSlice(zcu)) {
if (!end_is_len) {
const end = if (by_length) end: {
const len = try sema.coerce(block, .usize, uncasted_end_opt, end_src);
const uncasted_end = try sema.analyzeArithmetic(block, .add, start, len, src, start_src, end_src, false);
break :end try sema.coerce(block, .usize, uncasted_end, end_src);
} else try sema.coerce(block, .usize, uncasted_end_opt, end_src);
if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| {
if (try sema.resolveValue(ptr_or_slice)) |slice_val| {
if (slice_val.isUndef(zcu)) {
return sema.fail(block, src, "slice of undefined", .{});
}
const has_sentinel = slice_ty.sentinel(zcu) != null;
const slice_len = try slice_val.sliceLen(pt);
const len_plus_sent = slice_len + @intFromBool(has_sentinel);
const slice_len_val_with_sentinel = try pt.intValue(.usize, len_plus_sent);
if (!(try sema.compareAll(end_val, .lte, slice_len_val_with_sentinel, .usize))) {
const sentinel_label: []const u8 = if (has_sentinel)
" +1 (sentinel)"
else
"";
return sema.fail(
block,
end_src,
"end index {f} out of bounds for slice of length {d}{s}",
.{
end_val.fmtValueSema(pt, sema),
try slice_val.sliceLen(pt),
sentinel_label,
},
);
}
// If the slice has a sentinel, we consider end_is_len
// is only true if it equals the length WITHOUT the
// sentinel, so we don't add a sentinel type.
const slice_len_val = try pt.intValue(.usize, slice_len);
if (end_val.eql(slice_len_val, .usize, zcu)) {
end_is_len = true;
}
}
}
break :e end;
}
break :e try sema.analyzeSliceLen(block, src, ptr_or_slice);
}
if (!end_is_len) {
if (by_length) {
const len = try sema.coerce(block, .usize, uncasted_end_opt, end_src);
const uncasted_end = try sema.analyzeArithmetic(block, .add, start, len, src, start_src, end_src, false);
break :e try sema.coerce(block, .usize, uncasted_end, end_src);
} else break :e try sema.coerce(block, .usize, uncasted_end_opt, end_src);
}
// when slicing a many-item pointer, if a sentinel `S` is provided as in `ptr[a.. :S]`, it
// must match the sentinel of `@TypeOf(ptr)`.
sentinel_check: {
if (sentinel_opt == .none) break :sentinel_check;
const provided = provided: {
const casted = try sema.coerce(block, elem_ty, sentinel_opt, sentinel_src);
try checkSentinelType(sema, block, sentinel_src, elem_ty);
break :provided try sema.resolveConstDefinedValue(
block,
sentinel_src,
casted,
.{ .simple = .slice_sentinel },
);
};
if (ptr_sentinel) |current| {
if (provided.toIntern() == current.toIntern()) break :sentinel_check;
}
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(sentinel_src, "sentinel-terminated slicing of many-item pointer must match existing sentinel", .{});
errdefer msg.destroy(sema.gpa);
if (ptr_sentinel) |current| {
try sema.errNote(sentinel_src, msg, "expected sentinel '{f}', found '{f}'", .{ current.fmtValue(pt), provided.fmtValue(pt) });
} else {
try sema.errNote(ptr_src, msg, "type '{f}' does not have a sentinel", .{slice_ty.fmt(pt)});
}
try sema.errNote(src, msg, "use @ptrCast to cast pointer sentinel", .{});
break :msg msg;
});
}
return sema.analyzePtrArithmetic(block, src, ptr, start, .ptr_add, ptr_src, start_src);
};
const sentinel = s: {
if (sentinel_opt != .none) {
const casted = try sema.coerce(block, elem_ty, sentinel_opt, sentinel_src);
try checkSentinelType(sema, block, sentinel_src, elem_ty);
break :s try sema.resolveConstDefinedValue(block, sentinel_src, casted, .{ .simple = .slice_sentinel });
}
// If we are slicing to the end of something that is sentinel-terminated
// then the resulting slice type is also sentinel-terminated.
if (end_is_len) {
if (ptr_sentinel) |sent| {
break :s sent;
}
}
break :s null;
};
const slice_sentinel = if (sentinel_opt != .none) sentinel else null;
var checked_start_lte_end = by_length;
var runtime_src: ?LazySrcLoc = null;
// requirement: start <= end
if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| {
if (try sema.resolveDefinedValue(block, start_src, start)) |start_val| {
if (!by_length and !(try sema.compareAll(start_val, .lte, end_val, .usize))) {
return sema.fail(
block,
start_src,
"start index {f} is larger than end index {f}",
.{
start_val.fmtValueSema(pt, sema),
end_val.fmtValueSema(pt, sema),
},
);
}
checked_start_lte_end = true;
if (try sema.resolveValue(new_ptr)) |ptr_val| sentinel_check: {
const expected_sentinel = sentinel orelse break :sentinel_check;
const start_int = start_val.toUnsignedInt(zcu);
const end_int = end_val.toUnsignedInt(zcu);
const sentinel_index = try sema.usizeCast(block, end_src, end_int - start_int);
const many_ptr_ty = try pt.manyConstPtrType(elem_ty);
const many_ptr_val = try pt.getCoerced(ptr_val, many_ptr_ty);
const elem_ptr = try many_ptr_val.ptrElem(sentinel_index, pt);
const res = try sema.pointerDerefExtra(block, src, elem_ptr);
const actual_sentinel = switch (res) {
.runtime_load => break :sentinel_check,
.val => |v| v,
.needed_well_defined => |ty| return sema.fail(
block,
src,
"comptime dereference requires '{f}' to have a well-defined layout",
.{ty.fmt(pt)},
),
.out_of_bounds => |ty| return sema.fail(
block,
end_src,
"slice end index {d} exceeds bounds of containing decl of type '{f}'",
.{ end_int, ty.fmt(pt) },
),
};
if (!actual_sentinel.eql(expected_sentinel, elem_ty, zcu)) {
const msg = msg: {
const msg = try sema.errMsg(src, "value in memory does not match slice sentinel", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "expected '{f}', found '{f}'", .{
expected_sentinel.fmtValueSema(pt, sema),
actual_sentinel.fmtValueSema(pt, sema),
});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
} else {
runtime_src = ptr_src;
}
} else {
runtime_src = start_src;
}
} else {
runtime_src = end_src;
}
if (!checked_start_lte_end and block.wantSafety() and !block.isComptime()) {
// requirement: start <= end
assert(!block.isComptime());
try sema.requireRuntimeBlock(block, src, runtime_src.?);
const ok = try block.addBinOp(.cmp_lte, start, end);
try sema.addSafetyCheckCall(block, src, ok, .@"panic.startGreaterThanEnd", &.{ start, end });
}
const new_len = if (by_length)
try sema.coerce(block, .usize, uncasted_end_opt, end_src)
else
try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src, false);
const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len);
const new_ptr_ty_info = new_ptr_ty.ptrInfo(zcu);
const new_allowzero = new_ptr_ty_info.flags.is_allowzero and sema.typeOf(ptr).ptrSize(zcu) != .c;
if (opt_new_len_val) |new_len_val| {
const new_len_int = try new_len_val.toUnsignedIntSema(pt);
const return_ty = try pt.ptrTypeSema(.{
.child = (try pt.arrayType(.{
.len = new_len_int,
.sentinel = if (sentinel) |s| s.toIntern() else .none,
.child = elem_ty.toIntern(),
})).toIntern(),
.flags = .{
.alignment = new_ptr_ty_info.flags.alignment,
.is_const = new_ptr_ty_info.flags.is_const,
.is_allowzero = new_allowzero,
.is_volatile = new_ptr_ty_info.flags.is_volatile,
.address_space = new_ptr_ty_info.flags.address_space,
},
});
const opt_new_ptr_val = try sema.resolveValue(new_ptr);
const new_ptr_val = opt_new_ptr_val orelse {
const result = try block.addBitCast(return_ty, new_ptr);
if (block.wantSafety()) {
// requirement: slicing C ptr is non-null
if (ptr_ptr_child_ty.isCPtr(zcu)) {
const is_non_null = try sema.analyzeIsNull(block, ptr, true);
try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
}
bounds_check: {
const actual_len = if (array_ty.zigTypeTag(zcu) == .array)
try pt.intRef(.usize, array_ty.arrayLenIncludingSentinel(zcu))
else if (slice_ty.isSlice(zcu)) l: {
const slice_len = try sema.analyzeSliceLen(block, src, ptr_or_slice);
break :l if (slice_ty.sentinel(zcu) == null)
slice_len
else
try sema.analyzeArithmetic(block, .add, slice_len, .one, src, end_src, end_src, true);
} else break :bounds_check;
const actual_end = if (slice_sentinel != null)
try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src, true)
else
end;
try sema.addSafetyCheckIndexOob(block, src, actual_end, actual_len, .cmp_lte);
}
// requirement: result[new_len] == slice_sentinel
try sema.addSafetyCheckSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
}
return result;
};
if (!new_ptr_val.isUndef(zcu)) {
return Air.internedToRef((try pt.getCoerced(new_ptr_val, return_ty)).toIntern());
}
// Special case: @as([]i32, undefined)[x..x]
if (new_len_int == 0) {
return pt.undefRef(return_ty);
}
return sema.fail(block, src, "non-zero length slice of undefined pointer", .{});
}
const return_ty = try pt.ptrTypeSema(.{
.child = elem_ty.toIntern(),
.sentinel = if (sentinel) |s| s.toIntern() else .none,
.flags = .{
.size = .slice,
.alignment = new_ptr_ty_info.flags.alignment,
.is_const = new_ptr_ty_info.flags.is_const,
.is_volatile = new_ptr_ty_info.flags.is_volatile,
.is_allowzero = new_allowzero,
.address_space = new_ptr_ty_info.flags.address_space,
},
});
try sema.requireRuntimeBlock(block, src, runtime_src.?);
if (block.wantSafety()) {
// requirement: slicing C ptr is non-null
if (ptr_ptr_child_ty.isCPtr(zcu)) {
const is_non_null = try sema.analyzeIsNull(block, ptr, true);
try sema.addSafetyCheck(block, src, is_non_null, .unwrap_null);
}
// requirement: end <= len
const opt_len_inst = if (array_ty.zigTypeTag(zcu) == .array)
try pt.intRef(.usize, array_ty.arrayLenIncludingSentinel(zcu))
else if (slice_ty.isSlice(zcu)) blk: {
if (try sema.resolveDefinedValue(block, src, ptr_or_slice)) |slice_val| {
// we don't need to add one for sentinels because the
// underlying value data includes the sentinel
break :blk try pt.intRef(.usize, try slice_val.sliceLen(pt));
}
const slice_len_inst = try block.addTyOp(.slice_len, .usize, ptr_or_slice);
if (slice_ty.sentinel(zcu) == null) break :blk slice_len_inst;
// we have to add one because slice lengths don't include the sentinel
break :blk try sema.analyzeArithmetic(block, .add, slice_len_inst, .one, src, end_src, end_src, true);
} else null;
if (opt_len_inst) |len_inst| {
const actual_end = if (slice_sentinel != null)
try sema.analyzeArithmetic(block, .add, end, .one, src, end_src, end_src, true)
else
end;
try sema.addSafetyCheckIndexOob(block, src, actual_end, len_inst, .cmp_lte);
}
// requirement: start <= end
try sema.addSafetyCheckIndexOob(block, src, start, end, .cmp_lte);
}
const result = try block.addInst(.{
.tag = .slice,
.data = .{ .ty_pl = .{
.ty = Air.internedToRef(return_ty.toIntern()),
.payload = try sema.addExtra(Air.Bin{
.lhs = new_ptr,
.rhs = new_len,
}),
} },
});
if (block.wantSafety()) {
// requirement: result[new_len] == slice_sentinel
try sema.addSafetyCheckSentinelMismatch(block, src, slice_sentinel, elem_ty, result, new_len);
}
return result;
}
/// Asserts that lhs and rhs types are both numeric.
fn cmpNumeric(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
uncasted_lhs: Air.Inst.Ref,
uncasted_rhs: Air.Inst.Ref,
op: std.math.CompareOperator,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs_ty = sema.typeOf(uncasted_lhs);
const rhs_ty = sema.typeOf(uncasted_rhs);
assert(lhs_ty.isNumeric(zcu));
assert(rhs_ty.isNumeric(zcu));
const lhs_ty_tag = lhs_ty.zigTypeTag(zcu);
const rhs_ty_tag = rhs_ty.zigTypeTag(zcu);
const target = zcu.getTarget();
// One exception to heterogeneous comparison: comptime_float needs to
// coerce to fixed-width float.
const lhs = if (lhs_ty_tag == .comptime_float and rhs_ty_tag == .float)
try sema.coerce(block, rhs_ty, uncasted_lhs, lhs_src)
else
uncasted_lhs;
const rhs = if (lhs_ty_tag == .float and rhs_ty_tag == .comptime_float)
try sema.coerce(block, lhs_ty, uncasted_rhs, rhs_src)
else
uncasted_rhs;
const maybe_lhs_val = try sema.resolveValue(lhs);
const maybe_rhs_val = try sema.resolveValue(rhs);
// If the LHS is const, check if there is a guaranteed result which does not depend on ths RHS value.
if (maybe_lhs_val) |lhs_val| {
// Result based on comparison exceeding type bounds
if (!lhs_val.isUndef(zcu) and (lhs_ty_tag == .int or lhs_ty_tag == .comptime_int) and rhs_ty.isInt(zcu)) {
if (try sema.compareIntsOnlyPossibleResult(lhs_val, op, rhs_ty)) |res| {
return if (res) .bool_true else .bool_false;
}
}
// Result based on NaN comparison
if (lhs_val.isNan(zcu)) {
return if (op == .neq) .bool_true else .bool_false;
}
// Result based on inf comparison to int
if (lhs_val.isInf(zcu) and rhs_ty_tag == .int) return switch (op) {
.neq => .bool_true,
.eq => .bool_false,
.gt, .gte => if (lhs_val.isNegativeInf(zcu)) .bool_false else .bool_true,
.lt, .lte => if (lhs_val.isNegativeInf(zcu)) .bool_true else .bool_false,
};
}
// If the RHS is const, check if there is a guaranteed result which does not depend on ths LHS value.
if (maybe_rhs_val) |rhs_val| {
// Result based on comparison exceeding type bounds
if (!rhs_val.isUndef(zcu) and (rhs_ty_tag == .int or rhs_ty_tag == .comptime_int) and lhs_ty.isInt(zcu)) {
if (try sema.compareIntsOnlyPossibleResult(rhs_val, op.reverse(), lhs_ty)) |res| {
return if (res) .bool_true else .bool_false;
}
}
// Result based on NaN comparison
if (rhs_val.isNan(zcu)) {
return if (op == .neq) .bool_true else .bool_false;
}
// Result based on inf comparison to int
if (rhs_val.isInf(zcu) and lhs_ty_tag == .int) return switch (op) {
.neq => .bool_true,
.eq => .bool_false,
.gt, .gte => if (rhs_val.isNegativeInf(zcu)) .bool_true else .bool_false,
.lt, .lte => if (rhs_val.isNegativeInf(zcu)) .bool_false else .bool_true,
};
}
// Any other comparison depends on both values, so the result is undef if either is undef.
if (maybe_lhs_val) |v| if (v.isUndef(zcu)) return .undef_bool;
if (maybe_rhs_val) |v| if (v.isUndef(zcu)) return .undef_bool;
const runtime_src: LazySrcLoc = if (maybe_lhs_val) |lhs_val| rs: {
if (maybe_rhs_val) |rhs_val| {
const res = try Value.compareHeteroSema(lhs_val, op, rhs_val, pt);
return if (res) .bool_true else .bool_false;
} else break :rs rhs_src;
} else lhs_src;
// TODO handle comparisons against lazy zero values
// Some values can be compared against zero without being runtime-known or without forcing
// a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to
// always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout
// of this function if we don't need to.
try sema.requireRuntimeBlock(block, src, runtime_src);
// For floats, emit a float comparison instruction.
const lhs_is_float = switch (lhs_ty_tag) {
.float, .comptime_float => true,
else => false,
};
const rhs_is_float = switch (rhs_ty_tag) {
.float, .comptime_float => true,
else => false,
};
if (lhs_is_float and rhs_is_float) {
// Smaller fixed-width floats coerce to larger fixed-width floats.
// comptime_float coerces to fixed-width float.
const dest_ty = x: {
if (lhs_ty_tag == .comptime_float) {
break :x rhs_ty;
} else if (rhs_ty_tag == .comptime_float) {
break :x lhs_ty;
}
if (lhs_ty.floatBits(target) >= rhs_ty.floatBits(target)) {
break :x lhs_ty;
} else {
break :x rhs_ty;
}
};
const casted_lhs = try sema.coerce(block, dest_ty, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, dest_ty, rhs, rhs_src);
return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .optimized), casted_lhs, casted_rhs);
}
// For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
// For mixed signed and unsigned integers, implicit cast both operands to a signed
// integer with + 1 bit.
// For mixed floats and integers, extract the integer part from the float, cast that to
// a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
// add/subtract 1.
const lhs_is_signed = if (maybe_lhs_val) |lhs_val|
!(try lhs_val.compareAllWithZeroSema(.gte, pt))
else
(lhs_ty.isRuntimeFloat() or lhs_ty.isSignedInt(zcu));
const rhs_is_signed = if (maybe_rhs_val) |rhs_val|
!(try rhs_val.compareAllWithZeroSema(.gte, pt))
else
(rhs_ty.isRuntimeFloat() or rhs_ty.isSignedInt(zcu));
const dest_int_is_signed = lhs_is_signed or rhs_is_signed;
var dest_float_type: ?Type = null;
var lhs_bits: usize = undefined;
if (maybe_lhs_val) |unresolved_lhs_val| {
const lhs_val = try sema.resolveLazyValue(unresolved_lhs_val);
if (!rhs_is_signed) {
switch (lhs_val.orderAgainstZero(zcu)) {
.gt => {},
.eq => switch (op) { // LHS = 0, RHS is unsigned
.lte => return .bool_true,
.gt => return .bool_false,
else => {},
},
.lt => switch (op) { // LHS < 0, RHS is unsigned
.neq, .lt, .lte => return .bool_true,
.eq, .gt, .gte => return .bool_false,
},
}
}
if (lhs_is_float) {
const float = lhs_val.toFloat(f128, zcu);
var big_int: std.math.big.int.Mutable = .{
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
.len = undefined,
.positive = undefined,
};
switch (big_int.setFloat(float, .away)) {
.inexact => switch (op) {
.eq => return .bool_false,
.neq => return .bool_true,
else => {},
},
.exact => {},
}
lhs_bits = big_int.toConst().bitCountTwosComp();
} else {
lhs_bits = lhs_val.intBitCountTwosComp(zcu);
}
lhs_bits += @intFromBool(!lhs_is_signed and dest_int_is_signed);
} else if (lhs_is_float) {
dest_float_type = lhs_ty;
} else {
const int_info = lhs_ty.intInfo(zcu);
lhs_bits = int_info.bits + @intFromBool(int_info.signedness == .unsigned and dest_int_is_signed);
}
var rhs_bits: usize = undefined;
if (maybe_rhs_val) |unresolved_rhs_val| {
const rhs_val = try sema.resolveLazyValue(unresolved_rhs_val);
if (!lhs_is_signed) {
switch (rhs_val.orderAgainstZero(zcu)) {
.gt => {},
.eq => switch (op) { // RHS = 0, LHS is unsigned
.gte => return .bool_true,
.lt => return .bool_false,
else => {},
},
.lt => switch (op) { // RHS < 0, LHS is unsigned
.neq, .gt, .gte => return .bool_true,
.eq, .lt, .lte => return .bool_false,
},
}
}
if (rhs_is_float) {
const float = rhs_val.toFloat(f128, zcu);
var big_int: std.math.big.int.Mutable = .{
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
.len = undefined,
.positive = undefined,
};
switch (big_int.setFloat(float, .away)) {
.inexact => switch (op) {
.eq => return .bool_false,
.neq => return .bool_true,
else => {},
},
.exact => {},
}
rhs_bits = big_int.toConst().bitCountTwosComp();
} else {
rhs_bits = rhs_val.intBitCountTwosComp(zcu);
}
rhs_bits += @intFromBool(!rhs_is_signed and dest_int_is_signed);
} else if (rhs_is_float) {
dest_float_type = rhs_ty;
} else {
const int_info = rhs_ty.intInfo(zcu);
rhs_bits = int_info.bits + @intFromBool(int_info.signedness == .unsigned and dest_int_is_signed);
}
const dest_ty = if (dest_float_type) |ft| ft else blk: {
const max_bits = @max(lhs_bits, rhs_bits);
const casted_bits = std.math.cast(u16, max_bits) orelse return sema.fail(block, src, "{d} exceeds maximum integer bit count", .{max_bits});
const signedness: std.builtin.Signedness = if (dest_int_is_signed) .signed else .unsigned;
break :blk try pt.intType(signedness, casted_bits);
};
const casted_lhs = try sema.coerce(block, dest_ty, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, dest_ty, rhs, rhs_src);
return block.addBinOp(Air.Inst.Tag.fromCmpOp(op, block.float_mode == .optimized), casted_lhs, casted_rhs);
}
/// Asserts that LHS value is an int or comptime int and not undefined, and
/// that RHS type is an int. Given a const LHS and an unknown RHS, attempt to
/// determine whether `op` has a guaranteed result.
/// If it cannot be determined, returns null.
/// Otherwise returns a bool for the guaranteed comparison operation.
fn compareIntsOnlyPossibleResult(
sema: *Sema,
lhs_val: Value,
op: std.math.CompareOperator,
rhs_ty: Type,
) SemaError!?bool {
const pt = sema.pt;
const zcu = pt.zcu;
const min_rhs = try rhs_ty.minInt(pt, rhs_ty);
const max_rhs = try rhs_ty.maxInt(pt, rhs_ty);
if (min_rhs.toIntern() == max_rhs.toIntern()) {
// RHS is effectively comptime-known.
return try Value.compareHeteroSema(lhs_val, op, min_rhs, pt);
}
const against_min = try lhs_val.orderAdvanced(min_rhs, .sema, zcu, pt.tid);
const against_max = try lhs_val.orderAdvanced(max_rhs, .sema, zcu, pt.tid);
switch (op) {
.eq => {
if (against_min.compare(.lt)) return false;
if (against_max.compare(.gt)) return false;
},
.neq => {
if (against_min.compare(.lt)) return true;
if (against_max.compare(.gt)) return true;
},
.lt => {
if (against_min.compare(.lt)) return true;
if (against_max.compare(.gte)) return false;
},
.gt => {
if (against_max.compare(.gt)) return true;
if (against_min.compare(.lte)) return false;
},
.lte => {
if (against_min.compare(.lte)) return true;
if (against_max.compare(.gt)) return false;
},
.gte => {
if (against_max.compare(.gte)) return true;
if (against_min.compare(.lt)) return false;
},
}
return null;
}
/// Asserts that lhs and rhs types are both vectors.
fn cmpVector(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
lhs: Air.Inst.Ref,
rhs: Air.Inst.Ref,
op: std.math.CompareOperator,
lhs_src: LazySrcLoc,
rhs_src: LazySrcLoc,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const lhs_ty = sema.typeOf(lhs);
const rhs_ty = sema.typeOf(rhs);
assert(lhs_ty.zigTypeTag(zcu) == .vector);
assert(rhs_ty.zigTypeTag(zcu) == .vector);
try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src);
const resolved_ty = try sema.resolvePeerTypes(block, src, &.{ lhs, rhs }, .{ .override = &.{ lhs_src, rhs_src } });
const casted_lhs = try sema.coerce(block, resolved_ty, lhs, lhs_src);
const casted_rhs = try sema.coerce(block, resolved_ty, rhs, rhs_src);
const result_ty = try pt.vectorType(.{
.len = lhs_ty.vectorLen(zcu),
.child = .bool_type,
});
const maybe_lhs_val = try sema.resolveValue(casted_lhs);
const maybe_rhs_val = try sema.resolveValue(casted_rhs);
if (maybe_lhs_val) |v| if (v.isUndef(zcu)) return pt.undefRef(result_ty);
if (maybe_rhs_val) |v| if (v.isUndef(zcu)) return pt.undefRef(result_ty);
const runtime_src: LazySrcLoc = if (maybe_lhs_val) |lhs_val| src: {
if (maybe_rhs_val) |rhs_val| {
const cmp_val = try sema.compareVector(lhs_val, op, rhs_val, resolved_ty);
return Air.internedToRef(cmp_val.toIntern());
} else break :src rhs_src;
} else lhs_src;
try sema.requireRuntimeBlock(block, src, runtime_src);
return block.addCmpVector(casted_lhs, casted_rhs, op);
}
fn wrapOptional(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
if (try sema.resolveValue(inst)) |val| {
return Air.internedToRef((try sema.pt.intern(.{ .opt = .{
.ty = dest_ty.toIntern(),
.val = val.toIntern(),
} })));
}
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.wrap_optional, dest_ty, inst);
}
fn wrapErrorUnionPayload(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const dest_payload_ty = dest_ty.errorUnionPayload(zcu);
const coerced = try sema.coerceExtra(block, dest_payload_ty, inst, inst_src, .{ .report_err = false });
if (try sema.resolveValue(coerced)) |val| {
return Air.internedToRef((try pt.intern(.{ .error_union = .{
.ty = dest_ty.toIntern(),
.val = .{ .payload = val.toIntern() },
} })));
}
try sema.requireRuntimeBlock(block, inst_src, null);
return block.addTyOp(.wrap_errunion_payload, dest_ty, coerced);
}
fn wrapErrorUnionSet(
sema: *Sema,
block: *Block,
dest_ty: Type,
inst: Air.Inst.Ref,
inst_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const inst_ty = sema.typeOf(inst);
const dest_err_set_ty = dest_ty.errorUnionSet(zcu);
if (try sema.resolveValue(inst)) |val| {
const expected_name = zcu.intern_pool.indexToKey(val.toIntern()).err.name;
switch (dest_err_set_ty.toIntern()) {
.anyerror_type => {},
.adhoc_inferred_error_set_type => ok: {
const ies = sema.fn_ret_ty_ies.?;
switch (ies.resolved) {
.anyerror_type => break :ok,
.none => if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) {
break :ok;
},
else => |i| if (ip.indexToKey(i).error_set_type.nameIndex(ip, expected_name) != null) {
break :ok;
},
}
return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
},
else => switch (ip.indexToKey(dest_err_set_ty.toIntern())) {
.error_set_type => |error_set_type| ok: {
if (error_set_type.nameIndex(ip, expected_name) != null) break :ok;
return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
},
.inferred_error_set_type => |func_index| ok: {
// We carefully do this in an order that avoids unnecessarily
// resolving the destination error set type.
try zcu.maybeUnresolveIes(func_index);
switch (ip.funcIesResolvedUnordered(func_index)) {
.anyerror_type => break :ok,
.none => if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) {
break :ok;
},
else => |i| if (ip.indexToKey(i).error_set_type.nameIndex(ip, expected_name) != null) {
break :ok;
},
}
return sema.failWithTypeMismatch(block, inst_src, dest_err_set_ty, inst_ty);
},
else => unreachable,
},
}
return Air.internedToRef((try pt.intern(.{ .error_union = .{
.ty = dest_ty.toIntern(),
.val = .{ .err_name = expected_name },
} })));
}
try sema.requireRuntimeBlock(block, inst_src, null);
const coerced = try sema.coerce(block, dest_err_set_ty, inst, inst_src);
return block.addTyOp(.wrap_errunion_err, dest_ty, coerced);
}
fn unionToTag(
sema: *Sema,
block: *Block,
enum_ty: Type,
un: Air.Inst.Ref,
un_src: LazySrcLoc,
) !Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
if ((try sema.typeHasOnePossibleValue(enum_ty))) |opv| {
return Air.internedToRef(opv.toIntern());
}
if (try sema.resolveValue(un)) |un_val| {
const tag_val = un_val.unionTag(zcu).?;
if (tag_val.isUndef(zcu))
return try pt.undefRef(enum_ty);
return Air.internedToRef(tag_val.toIntern());
}
try sema.requireRuntimeBlock(block, un_src, null);
return block.addTyOp(.get_union_tag, enum_ty, un);
}
const PeerResolveStrategy = enum {
/// The type is not known.
/// If refined no further, this is equivalent to `exact`.
unknown,
/// The type may be an error set or error union.
/// If refined no further, it is an error set.
error_set,
/// The type must be some error union.
error_union,
/// The type may be @TypeOf(null), an optional or a C pointer.
/// If refined no further, it is @TypeOf(null).
nullable,
/// The type must be some optional or a C pointer.
/// If refined no further, it is an optional.
optional,
/// The type must be either an array or a vector.
/// If refined no further, it is an array.
array,
/// The type must be a vector.
vector,
/// The type must be a C pointer.
c_ptr,
/// The type must be a pointer (C or not).
/// If refined no further, it is a non-C pointer.
ptr,
/// The type must be a function or a pointer to a function.
/// If refined no further, it is a function.
func,
/// The type must be an enum literal, or some specific enum or union. Which one is decided
/// afterwards based on the types in question.
enum_or_union,
/// The type must be some integer or float type.
/// If refined no further, it is `comptime_int`.
comptime_int,
/// The type must be some float type.
/// If refined no further, it is `comptime_float`.
comptime_float,
/// The type must be some float or fixed-width integer type.
/// If refined no further, it is some fixed-width integer type.
fixed_int,
/// The type must be some fixed-width float type.
fixed_float,
/// The type must be a tuple.
tuple,
/// The peers must all be of the same type.
exact,
/// Given two strategies, find a strategy that satisfies both, if one exists. If no such
/// strategy exists, any strategy may be returned; an error will be emitted when the caller
/// attempts to use the strategy to resolve the type.
/// Strategy `a` comes from the peer in `reason_peer`, while strategy `b` comes from the peer at
/// index `b_peer_idx`. `reason_peer` is updated to reflect the reason for the new strategy.
fn merge(a: PeerResolveStrategy, b: PeerResolveStrategy, reason_peer: *usize, b_peer_idx: usize) PeerResolveStrategy {
// Our merging should be order-independent. Thus, even though the union order is arbitrary,
// by sorting the tags and switching first on the smaller, we have half as many cases to
// worry about (since we avoid the duplicates).
const s0_is_a = @intFromEnum(a) <= @intFromEnum(b);
const s0 = if (s0_is_a) a else b;
const s1 = if (s0_is_a) b else a;
const ReasonMethod = enum {
all_s0,
all_s1,
either,
};
const reason_method: ReasonMethod, const strat: PeerResolveStrategy = switch (s0) {
.unknown => .{ .all_s1, s1 },
.error_set => switch (s1) {
.error_set => .{ .either, .error_set },
else => .{ .all_s0, .error_union },
},
.error_union => switch (s1) {
.error_union => .{ .either, .error_union },
else => .{ .all_s0, .error_union },
},
.nullable => switch (s1) {
.nullable => .{ .either, .nullable },
.c_ptr => .{ .all_s1, .c_ptr },
else => .{ .all_s0, .optional },
},
.optional => switch (s1) {
.optional => .{ .either, .optional },
.c_ptr => .{ .all_s1, .c_ptr },
else => .{ .all_s0, .optional },
},
.array => switch (s1) {
.array => .{ .either, .array },
.vector => .{ .all_s1, .vector },
else => .{ .all_s0, .array },
},
.vector => switch (s1) {
.vector => .{ .either, .vector },
else => .{ .all_s0, .vector },
},
.c_ptr => switch (s1) {
.c_ptr => .{ .either, .c_ptr },
else => .{ .all_s0, .c_ptr },
},
.ptr => switch (s1) {
.ptr => .{ .either, .ptr },
else => .{ .all_s0, .ptr },
},
.func => switch (s1) {
.func => .{ .either, .func },
else => .{ .all_s1, s1 }, // doesn't override anything later
},
.enum_or_union => switch (s1) {
.enum_or_union => .{ .either, .enum_or_union },
else => .{ .all_s0, .enum_or_union },
},
.comptime_int => switch (s1) {
.comptime_int => .{ .either, .comptime_int },
else => .{ .all_s1, s1 }, // doesn't override anything later
},
.comptime_float => switch (s1) {
.comptime_float => .{ .either, .comptime_float },
else => .{ .all_s1, s1 }, // doesn't override anything later
},
.fixed_int => switch (s1) {
.fixed_int => .{ .either, .fixed_int },
else => .{ .all_s1, s1 }, // doesn't override anything later
},
.fixed_float => switch (s1) {
.fixed_float => .{ .either, .fixed_float },
else => .{ .all_s1, s1 }, // doesn't override anything later
},
.tuple => switch (s1) {
.exact => .{ .all_s1, .exact },
else => .{ .all_s0, .tuple },
},
.exact => .{ .all_s0, .exact },
};
switch (reason_method) {
.all_s0 => {
if (!s0_is_a) {
reason_peer.* = b_peer_idx;
}
},
.all_s1 => {
if (s0_is_a) {
reason_peer.* = b_peer_idx;
}
},
.either => {
// Prefer the earliest peer
reason_peer.* = @min(reason_peer.*, b_peer_idx);
},
}
return strat;
}
fn select(ty: Type, zcu: *Zcu) PeerResolveStrategy {
return switch (ty.zigTypeTag(zcu)) {
.type, .void, .bool, .@"opaque", .frame, .@"anyframe" => .exact,
.noreturn, .undefined => .unknown,
.null => .nullable,
.comptime_int => .comptime_int,
.int => .fixed_int,
.comptime_float => .comptime_float,
.float => .fixed_float,
.pointer => if (ty.ptrInfo(zcu).flags.size == .c) .c_ptr else .ptr,
.array => .array,
.vector => .vector,
.optional => .optional,
.error_set => .error_set,
.error_union => .error_union,
.enum_literal, .@"enum", .@"union" => .enum_or_union,
.@"struct" => if (ty.isTuple(zcu)) .tuple else .exact,
.@"fn" => .func,
};
}
};
const PeerTypeCandidateSrc = union(enum) {
/// Do not print out error notes for candidate sources
none: void,
/// When we want to know the the src of candidate i, look up at
/// index i in this slice
override: []const ?LazySrcLoc,
/// resolvePeerTypes originates from a @TypeOf(...) call
typeof_builtin_call_node_offset: std.zig.Ast.Node.Offset,
pub fn resolve(
self: PeerTypeCandidateSrc,
block: *Block,
candidate_i: usize,
) ?LazySrcLoc {
return switch (self) {
.none => null,
.override => |candidate_srcs| if (candidate_i >= candidate_srcs.len)
null
else
candidate_srcs[candidate_i],
.typeof_builtin_call_node_offset => |node_offset| block.builtinCallArgSrc(node_offset, @intCast(candidate_i)),
};
}
};
const PeerResolveResult = union(enum) {
/// The peer type resolution was successful, and resulted in the given type.
success: Type,
/// There was some generic conflict between two peers.
conflict: struct {
peer_idx_a: usize,
peer_idx_b: usize,
},
/// There was an error when resolving the type of a struct or tuple field.
field_error: struct {
/// The name of the field which caused the failure.
field_name: InternPool.NullTerminatedString,
/// The type of this field in each peer.
field_types: []Type,
/// The error from resolving the field type. Guaranteed not to be `success`.
sub_result: *PeerResolveResult,
},
fn report(
result: PeerResolveResult,
sema: *Sema,
block: *Block,
src: LazySrcLoc,
instructions: []const Air.Inst.Ref,
candidate_srcs: PeerTypeCandidateSrc,
) !*Zcu.ErrorMsg {
const pt = sema.pt;
var opt_msg: ?*Zcu.ErrorMsg = null;
errdefer if (opt_msg) |msg| msg.destroy(sema.gpa);
// If we mention fields we'll want to include field types, so put peer types in a buffer
var peer_tys = try sema.arena.alloc(Type, instructions.len);
for (peer_tys, instructions) |*ty, inst| {
ty.* = sema.typeOf(inst);
}
var cur = result;
while (true) {
var conflict_idx: [2]usize = undefined;
switch (cur) {
.success => unreachable,
.conflict => |conflict| {
// Fall through to two-peer conflict handling below
conflict_idx = .{
conflict.peer_idx_a,
conflict.peer_idx_b,
};
},
.field_error => |field_error| {
const fmt = "struct field '{f}' has conflicting types";
const args = .{field_error.field_name.fmt(&pt.zcu.intern_pool)};
if (opt_msg) |msg| {
try sema.errNote(src, msg, fmt, args);
} else {
opt_msg = try sema.errMsg(src, fmt, args);
}
// Continue on to child error
cur = field_error.sub_result.*;
peer_tys = field_error.field_types;
continue;
},
}
// This is the path for reporting a generic conflict between two peers.
if (conflict_idx[1] < conflict_idx[0]) {
// b comes first in source, so it's better if it comes first in the error
std.mem.swap(usize, &conflict_idx[0], &conflict_idx[1]);
}
const conflict_tys: [2]Type = .{
peer_tys[conflict_idx[0]],
peer_tys[conflict_idx[1]],
};
const conflict_srcs: [2]?LazySrcLoc = .{
candidate_srcs.resolve(block, conflict_idx[0]),
candidate_srcs.resolve(block, conflict_idx[1]),
};
const fmt = "incompatible types: '{f}' and '{f}'";
const args = .{
conflict_tys[0].fmt(pt),
conflict_tys[1].fmt(pt),
};
const msg = if (opt_msg) |msg| msg: {
try sema.errNote(src, msg, fmt, args);
break :msg msg;
} else msg: {
const msg = try sema.errMsg(src, fmt, args);
opt_msg = msg;
break :msg msg;
};
if (conflict_srcs[0]) |src_loc| try sema.errNote(src_loc, msg, "type '{f}' here", .{conflict_tys[0].fmt(pt)});
if (conflict_srcs[1]) |src_loc| try sema.errNote(src_loc, msg, "type '{f}' here", .{conflict_tys[1].fmt(pt)});
// No child error
break;
}
return opt_msg.?;
}
};
fn resolvePeerTypes(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
instructions: []const Air.Inst.Ref,
candidate_srcs: PeerTypeCandidateSrc,
) !Type {
switch (instructions.len) {
0 => return .noreturn,
1 => return sema.typeOf(instructions[0]),
else => {},
}
// Fast path: check if everything has the same type to bypass the main PTR logic.
same_type: {
const ty = sema.typeOf(instructions[0]);
for (instructions[1..]) |inst| {
if (sema.typeOf(inst).toIntern() != ty.toIntern()) {
break :same_type;
}
}
return ty;
}
const peer_tys = try sema.arena.alloc(?Type, instructions.len);
const peer_vals = try sema.arena.alloc(?Value, instructions.len);
for (instructions, peer_tys, peer_vals) |inst, *ty, *val| {
ty.* = sema.typeOf(inst);
val.* = try sema.resolveValue(inst);
}
switch (try sema.resolvePeerTypesInner(block, src, peer_tys, peer_vals)) {
.success => |ty| return ty,
else => |result| {
const msg = try result.report(sema, block, src, instructions, candidate_srcs);
return sema.failWithOwnedErrorMsg(block, msg);
},
}
}
fn resolvePeerTypesInner(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
peer_tys: []?Type,
peer_vals: []?Value,
) !PeerResolveResult {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
var strat_reason: usize = 0;
var s: PeerResolveStrategy = .unknown;
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
s = s.merge(PeerResolveStrategy.select(ty, zcu), &strat_reason, i);
}
if (s == .unknown) {
// The whole thing was noreturn or undefined - try to do an exact match
s = .exact;
} else {
// There was something other than noreturn and undefined, so we can ignore those peers
for (peer_tys) |*ty_ptr| {
const ty = ty_ptr.* orelse continue;
switch (ty.zigTypeTag(zcu)) {
.noreturn, .undefined => ty_ptr.* = null,
else => {},
}
}
}
const target = zcu.getTarget();
switch (s) {
.unknown => unreachable,
.error_set => {
var final_set: ?Type = null;
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
if (ty.zigTypeTag(zcu) != .error_set) return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
if (final_set) |cur_set| {
final_set = try sema.maybeMergeErrorSets(block, src, cur_set, ty);
} else {
final_set = ty;
}
}
return .{ .success = final_set.? };
},
.error_union => {
var final_set: ?Type = null;
for (peer_tys, peer_vals) |*ty_ptr, *val_ptr| {
const ty = ty_ptr.* orelse continue;
const set_ty = switch (ty.zigTypeTag(zcu)) {
.error_set => blk: {
ty_ptr.* = null; // no payload to decide on
val_ptr.* = null;
break :blk ty;
},
.error_union => blk: {
const set_ty = ty.errorUnionSet(zcu);
ty_ptr.* = ty.errorUnionPayload(zcu);
if (val_ptr.*) |eu_val| switch (ip.indexToKey(eu_val.toIntern())) {
.error_union => |eu| switch (eu.val) {
.payload => |payload_ip| val_ptr.* = Value.fromInterned(payload_ip),
.err_name => val_ptr.* = null,
},
.undef => val_ptr.* = Value.fromInterned(try pt.intern(.{ .undef = ty_ptr.*.?.toIntern() })),
else => unreachable,
};
break :blk set_ty;
},
else => continue, // whole type is the payload
};
if (final_set) |cur_set| {
final_set = try sema.maybeMergeErrorSets(block, src, cur_set, set_ty);
} else {
final_set = set_ty;
}
}
assert(final_set != null);
const final_payload = switch (try sema.resolvePeerTypesInner(
block,
src,
peer_tys,
peer_vals,
)) {
.success => |ty| ty,
else => |result| return result,
};
return .{ .success = try pt.errorUnionType(final_set.?, final_payload) };
},
.nullable => {
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
if (!ty.eql(.null, zcu)) return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
}
return .{ .success = .null };
},
.optional => {
for (peer_tys, peer_vals) |*ty_ptr, *val_ptr| {
const ty = ty_ptr.* orelse continue;
switch (ty.zigTypeTag(zcu)) {
.null => {
ty_ptr.* = null;
val_ptr.* = null;
},
.optional => {
ty_ptr.* = ty.optionalChild(zcu);
if (val_ptr.*) |opt_val| val_ptr.* = if (!opt_val.isUndef(zcu)) opt_val.optionalValue(zcu) else null;
},
else => {},
}
}
const child_ty = switch (try sema.resolvePeerTypesInner(
block,
src,
peer_tys,
peer_vals,
)) {
.success => |ty| ty,
else => |result| return result,
};
return .{ .success = try pt.optionalType(child_ty.toIntern()) };
},
.array => {
// Index of the first non-null peer
var opt_first_idx: ?usize = null;
// Index of the first array or vector peer (i.e. not a tuple)
var opt_first_arr_idx: ?usize = null;
// Set to non-null once we see any peer, even a tuple
var len: u64 = undefined;
var sentinel: ?Value = undefined;
// Only set once we see a non-tuple peer
var elem_ty: Type = undefined;
for (peer_tys, 0..) |*ty_ptr, i| {
const ty = ty_ptr.* orelse continue;
if (!ty.isArrayOrVector(zcu)) {
// We allow tuples of the correct length. We won't validate their elem type, since the elements can be coerced.
const arr_like = sema.typeIsArrayLike(ty) orelse return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
if (opt_first_idx) |first_idx| {
if (arr_like.len != len) return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
} else {
opt_first_idx = i;
len = arr_like.len;
}
sentinel = null;
continue;
}
const first_arr_idx = opt_first_arr_idx orelse {
if (opt_first_idx == null) {
opt_first_idx = i;
len = ty.arrayLen(zcu);
sentinel = ty.sentinel(zcu);
}
opt_first_arr_idx = i;
elem_ty = ty.childType(zcu);
continue;
};
if (ty.arrayLen(zcu) != len) return .{ .conflict = .{
.peer_idx_a = first_arr_idx,
.peer_idx_b = i,
} };
const peer_elem_ty = ty.childType(zcu);
if (!peer_elem_ty.eql(elem_ty, zcu)) coerce: {
const peer_elem_coerces_to_elem =
try sema.coerceInMemoryAllowed(block, elem_ty, peer_elem_ty, false, zcu.getTarget(), src, src, null);
if (peer_elem_coerces_to_elem == .ok) {
break :coerce;
}
const elem_coerces_to_peer_elem =
try sema.coerceInMemoryAllowed(block, peer_elem_ty, elem_ty, false, zcu.getTarget(), src, src, null);
if (elem_coerces_to_peer_elem == .ok) {
elem_ty = peer_elem_ty;
break :coerce;
}
return .{ .conflict = .{
.peer_idx_a = first_arr_idx,
.peer_idx_b = i,
} };
}
if (sentinel) |cur_sent| {
if (ty.sentinel(zcu)) |peer_sent| {
if (!peer_sent.eql(cur_sent, elem_ty, zcu)) sentinel = null;
} else {
sentinel = null;
}
}
}
// There should always be at least one array or vector peer
assert(opt_first_arr_idx != null);
return .{ .success = try pt.arrayType(.{
.len = len,
.child = elem_ty.toIntern(),
.sentinel = if (sentinel) |sent_val| sent_val.toIntern() else .none,
}) };
},
.vector => {
var len: ?u64 = null;
var first_idx: usize = undefined;
for (peer_tys, peer_vals, 0..) |*ty_ptr, *val_ptr, i| {
const ty = ty_ptr.* orelse continue;
if (!ty.isArrayOrVector(zcu)) {
// Allow tuples of the correct length
const arr_like = sema.typeIsArrayLike(ty) orelse return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
if (len) |expect_len| {
if (arr_like.len != expect_len) return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
} else {
len = arr_like.len;
first_idx = i;
}
// Tuples won't participate in the child type resolution. We'll resolve without
// them, and if the tuples have a bad type, we'll get a coercion error later.
ty_ptr.* = null;
val_ptr.* = null;
continue;
}
if (len) |expect_len| {
if (ty.arrayLen(zcu) != expect_len) return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
} else {
len = ty.arrayLen(zcu);
first_idx = i;
}
ty_ptr.* = ty.childType(zcu);
val_ptr.* = null; // multiple child vals, so we can't easily use them in PTR
}
const child_ty = switch (try sema.resolvePeerTypesInner(
block,
src,
peer_tys,
peer_vals,
)) {
.success => |ty| ty,
else => |result| return result,
};
return .{ .success = try pt.vectorType(.{
.len = @intCast(len.?),
.child = child_ty.toIntern(),
}) };
},
.c_ptr => {
var opt_ptr_info: ?InternPool.Key.PtrType = null;
var first_idx: usize = undefined;
for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| {
const ty = opt_ty orelse continue;
switch (ty.zigTypeTag(zcu)) {
.comptime_int => continue, // comptime-known integers can always coerce to C pointers
.int => {
if (opt_val != null) {
// Always allow the coercion for comptime-known ints
continue;
} else {
// Runtime-known, so check if the type is no bigger than a usize
const ptr_bits = target.ptrBitWidth();
const bits = ty.intInfo(zcu).bits;
if (bits <= ptr_bits) continue;
}
},
.null => continue,
else => {},
}
if (!ty.isPtrAtRuntime(zcu)) return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
// Goes through optionals
const peer_info = ty.ptrInfo(zcu);
var ptr_info = opt_ptr_info orelse {
opt_ptr_info = peer_info;
opt_ptr_info.?.flags.size = .c;
first_idx = i;
continue;
};
// Try peer -> cur, then cur -> peer
ptr_info.child = ((try sema.resolvePairInMemoryCoercible(block, src, .fromInterned(ptr_info.child), .fromInterned(peer_info.child))) orelse {
return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
}).toIntern();
if (ptr_info.sentinel != .none and peer_info.sentinel != .none) {
const peer_sent = try ip.getCoerced(sema.gpa, pt.tid, ptr_info.sentinel, ptr_info.child);
const ptr_sent = try ip.getCoerced(sema.gpa, pt.tid, peer_info.sentinel, ptr_info.child);
if (ptr_sent == peer_sent) {
ptr_info.sentinel = ptr_sent;
} else {
ptr_info.sentinel = .none;
}
} else {
ptr_info.sentinel = .none;
}
// Note that the align can be always non-zero; Zcu.ptrType will canonicalize it
ptr_info.flags.alignment = InternPool.Alignment.min(
if (ptr_info.flags.alignment != .none)
ptr_info.flags.alignment
else
Type.fromInterned(ptr_info.child).abiAlignment(zcu),
if (peer_info.flags.alignment != .none)
peer_info.flags.alignment
else
Type.fromInterned(peer_info.child).abiAlignment(zcu),
);
if (ptr_info.flags.address_space != peer_info.flags.address_space) {
return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
}
if (ptr_info.packed_offset.bit_offset != peer_info.packed_offset.bit_offset or
ptr_info.packed_offset.host_size != peer_info.packed_offset.host_size)
{
return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
}
ptr_info.flags.is_const = ptr_info.flags.is_const or peer_info.flags.is_const;
ptr_info.flags.is_volatile = ptr_info.flags.is_volatile or peer_info.flags.is_volatile;
opt_ptr_info = ptr_info;
}
return .{ .success = try pt.ptrTypeSema(opt_ptr_info.?) };
},
.ptr => {
// If we've resolved to a `[]T` but then see a `[*]T`, we can resolve to a `[*]T` only
// if there were no actual slices. Else, we want the slice index to report a conflict.
var opt_slice_idx: ?usize = null;
var any_abi_aligned = false;
var opt_ptr_info: ?InternPool.Key.PtrType = null;
var first_idx: usize = undefined;
var other_idx: usize = undefined; // We sometimes need a second peer index to report a generic error
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
const peer_info: InternPool.Key.PtrType = switch (ty.zigTypeTag(zcu)) {
.pointer => ty.ptrInfo(zcu),
.@"fn" => .{
.child = ty.toIntern(),
.flags = .{
.address_space = target_util.defaultAddressSpace(target, .global_constant),
},
},
else => return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} },
};
switch (peer_info.flags.size) {
.one, .many => {},
.slice => opt_slice_idx = i,
.c => return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} },
}
var ptr_info = opt_ptr_info orelse {
opt_ptr_info = peer_info;
first_idx = i;
continue;
};
other_idx = i;
// We want to return this in a lot of cases, so alias it here for convenience
const generic_err: PeerResolveResult = .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
// Note that the align can be always non-zero; Type.ptr will canonicalize it
if (peer_info.flags.alignment == .none) {
any_abi_aligned = true;
} else if (ptr_info.flags.alignment == .none) {
any_abi_aligned = true;
ptr_info.flags.alignment = peer_info.flags.alignment;
} else {
ptr_info.flags.alignment = ptr_info.flags.alignment.minStrict(peer_info.flags.alignment);
}
if (ptr_info.flags.address_space != peer_info.flags.address_space) {
return generic_err;
}
if (ptr_info.packed_offset.bit_offset != peer_info.packed_offset.bit_offset or
ptr_info.packed_offset.host_size != peer_info.packed_offset.host_size)
{
return generic_err;
}
ptr_info.flags.is_const = ptr_info.flags.is_const or peer_info.flags.is_const;
ptr_info.flags.is_volatile = ptr_info.flags.is_volatile or peer_info.flags.is_volatile;
ptr_info.flags.is_allowzero = ptr_info.flags.is_allowzero or peer_info.flags.is_allowzero;
const peer_sentinel: InternPool.Index = switch (peer_info.flags.size) {
.one => switch (ip.indexToKey(peer_info.child)) {
.array_type => |array_type| array_type.sentinel,
else => .none,
},
.many, .slice => peer_info.sentinel,
.c => unreachable,
};
const cur_sentinel: InternPool.Index = switch (ptr_info.flags.size) {
.one => switch (ip.indexToKey(ptr_info.child)) {
.array_type => |array_type| array_type.sentinel,
else => .none,
},
.many, .slice => ptr_info.sentinel,
.c => unreachable,
};
// We abstract array handling slightly so that tuple pointers can work like array pointers
const peer_pointee_array = sema.typeIsArrayLike(.fromInterned(peer_info.child));
const cur_pointee_array = sema.typeIsArrayLike(.fromInterned(ptr_info.child));
// This switch is just responsible for deciding the size and pointee (not including
// single-pointer array sentinel).
good: {
switch (peer_info.flags.size) {
.one => switch (ptr_info.flags.size) {
.one => {
if (try sema.resolvePairInMemoryCoercible(block, src, .fromInterned(ptr_info.child), .fromInterned(peer_info.child))) |pointee| {
ptr_info.child = pointee.toIntern();
break :good;
}
const cur_arr = cur_pointee_array orelse return generic_err;
const peer_arr = peer_pointee_array orelse return generic_err;
if (try sema.resolvePairInMemoryCoercible(block, src, cur_arr.elem_ty, peer_arr.elem_ty)) |elem_ty| {
// *[n:x]T + *[n:y]T = *[n]T
if (cur_arr.len == peer_arr.len) {
ptr_info.child = (try pt.arrayType(.{
.len = cur_arr.len,
.child = elem_ty.toIntern(),
})).toIntern();
break :good;
}
// *[a]T + *[b]T = []T
ptr_info.flags.size = .slice;
ptr_info.child = elem_ty.toIntern();
break :good;
}
if (peer_arr.elem_ty.toIntern() == .noreturn_type) {
// *struct{} + *[a]T = []T
ptr_info.flags.size = .slice;
ptr_info.child = cur_arr.elem_ty.toIntern();
break :good;
}
if (cur_arr.elem_ty.toIntern() == .noreturn_type) {
// *[a]T + *struct{} = []T
ptr_info.flags.size = .slice;
ptr_info.child = peer_arr.elem_ty.toIntern();
break :good;
}
return generic_err;
},
.many => {
// Only works for *[n]T + [*]T -> [*]T
const arr = peer_pointee_array orelse return generic_err;
if (try sema.resolvePairInMemoryCoercible(block, src, .fromInterned(ptr_info.child), arr.elem_ty)) |pointee| {
ptr_info.child = pointee.toIntern();
break :good;
}
if (arr.elem_ty.toIntern() == .noreturn_type) {
// *struct{} + [*]T -> [*]T
break :good;
}
return generic_err;
},
.slice => {
// Only works for *[n]T + []T -> []T
const arr = peer_pointee_array orelse return generic_err;
if (try sema.resolvePairInMemoryCoercible(block, src, .fromInterned(ptr_info.child), arr.elem_ty)) |pointee| {
ptr_info.child = pointee.toIntern();
break :good;
}
if (arr.elem_ty.toIntern() == .noreturn_type) {
// *struct{} + []T -> []T
break :good;
}
return generic_err;
},
.c => unreachable,
},
.many => switch (ptr_info.flags.size) {
.one => {
// Only works for [*]T + *[n]T -> [*]T
const arr = cur_pointee_array orelse return generic_err;
if (try sema.resolvePairInMemoryCoercible(block, src, arr.elem_ty, .fromInterned(peer_info.child))) |pointee| {
ptr_info.flags.size = .many;
ptr_info.child = pointee.toIntern();
break :good;
}
if (arr.elem_ty.toIntern() == .noreturn_type) {
// [*]T + *struct{} -> [*]T
ptr_info.flags.size = .many;
ptr_info.child = peer_info.child;
break :good;
}
return generic_err;
},
.many => {
if (try sema.resolvePairInMemoryCoercible(block, src, .fromInterned(ptr_info.child), .fromInterned(peer_info.child))) |pointee| {
ptr_info.child = pointee.toIntern();
break :good;
}
return generic_err;
},
.slice => {
// Only works if no peers are actually slices
if (opt_slice_idx) |slice_idx| {
return .{ .conflict = .{
.peer_idx_a = slice_idx,
.peer_idx_b = i,
} };
}
// Okay, then works for [*]T + "[]T" -> [*]T
if (try sema.resolvePairInMemoryCoercible(block, src, .fromInterned(ptr_info.child), .fromInterned(peer_info.child))) |pointee| {
ptr_info.flags.size = .many;
ptr_info.child = pointee.toIntern();
break :good;
}
return generic_err;
},
.c => unreachable,
},
.slice => switch (ptr_info.flags.size) {
.one => {
// Only works for []T + *[n]T -> []T
const arr = cur_pointee_array orelse return generic_err;
if (try sema.resolvePairInMemoryCoercible(block, src, arr.elem_ty, .fromInterned(peer_info.child))) |pointee| {
ptr_info.flags.size = .slice;
ptr_info.child = pointee.toIntern();
break :good;
}
if (arr.elem_ty.toIntern() == .noreturn_type) {
// []T + *struct{} -> []T
ptr_info.flags.size = .slice;
ptr_info.child = peer_info.child;
break :good;
}
return generic_err;
},
.many => {
// Impossible! (current peer is an actual slice)
return generic_err;
},
.slice => {
if (try sema.resolvePairInMemoryCoercible(block, src, .fromInterned(ptr_info.child), .fromInterned(peer_info.child))) |pointee| {
ptr_info.child = pointee.toIntern();
break :good;
}
return generic_err;
},
.c => unreachable,
},
.c => unreachable,
}
}
const sentinel_ty = switch (ptr_info.flags.size) {
.one => switch (ip.indexToKey(ptr_info.child)) {
.array_type => |array_type| array_type.child,
else => ptr_info.child,
},
.many, .slice, .c => ptr_info.child,
};
sentinel: {
no_sentinel: {
if (peer_sentinel == .none) break :no_sentinel;
if (cur_sentinel == .none) break :no_sentinel;
const peer_sent_coerced = try ip.getCoerced(sema.gpa, pt.tid, peer_sentinel, sentinel_ty);
const cur_sent_coerced = try ip.getCoerced(sema.gpa, pt.tid, cur_sentinel, sentinel_ty);
if (peer_sent_coerced != cur_sent_coerced) break :no_sentinel;
// Sentinels match
if (ptr_info.flags.size == .one) switch (ip.indexToKey(ptr_info.child)) {
.array_type => |array_type| ptr_info.child = (try pt.arrayType(.{
.len = array_type.len,
.child = array_type.child,
.sentinel = cur_sent_coerced,
})).toIntern(),
else => unreachable,
} else {
ptr_info.sentinel = cur_sent_coerced;
}
break :sentinel;
}
// Clear existing sentinel
ptr_info.sentinel = .none;
if (ptr_info.flags.size == .one) switch (ip.indexToKey(ptr_info.child)) {
.array_type => |array_type| ptr_info.child = (try pt.arrayType(.{
.len = array_type.len,
.child = array_type.child,
.sentinel = .none,
})).toIntern(),
else => {},
};
}
opt_ptr_info = ptr_info;
}
// Before we succeed, check the pointee type. If we tried to apply PTR to (for instance)
// &.{} and &.{}, we'll currently have a pointer type of `*[0]noreturn` - we wanted to
// coerce the empty struct to a specific type, but no peer provided one. We need to
// detect this case and emit an error.
const pointee = opt_ptr_info.?.child;
switch (pointee) {
.noreturn_type => return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = other_idx,
} },
else => switch (ip.indexToKey(pointee)) {
.array_type => |array_type| if (array_type.child == .noreturn_type) return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = other_idx,
} },
else => {},
},
}
if (any_abi_aligned and opt_ptr_info.?.flags.alignment != .none) {
opt_ptr_info.?.flags.alignment = opt_ptr_info.?.flags.alignment.minStrict(
try Type.fromInterned(pointee).abiAlignmentSema(pt),
);
}
return .{ .success = try pt.ptrTypeSema(opt_ptr_info.?) };
},
.func => {
var opt_cur_ty: ?Type = null;
var first_idx: usize = undefined;
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
const cur_ty = opt_cur_ty orelse {
opt_cur_ty = ty;
first_idx = i;
continue;
};
if (ty.zigTypeTag(zcu) != .@"fn") return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
// ty -> cur_ty
if (.ok == try sema.coerceInMemoryAllowedFns(block, cur_ty, ty, false, target, src, src)) {
continue;
}
// cur_ty -> ty
if (.ok == try sema.coerceInMemoryAllowedFns(block, ty, cur_ty, false, target, src, src)) {
opt_cur_ty = ty;
continue;
}
return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
}
return .{ .success = opt_cur_ty.? };
},
.enum_or_union => {
var opt_cur_ty: ?Type = null;
// The peer index which gave the current type
var cur_ty_idx: usize = undefined;
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
switch (ty.zigTypeTag(zcu)) {
.enum_literal, .@"enum", .@"union" => {},
else => return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} },
}
const cur_ty = opt_cur_ty orelse {
opt_cur_ty = ty;
cur_ty_idx = i;
continue;
};
// We want to return this in a lot of cases, so alias it here for convenience
const generic_err: PeerResolveResult = .{ .conflict = .{
.peer_idx_a = cur_ty_idx,
.peer_idx_b = i,
} };
switch (cur_ty.zigTypeTag(zcu)) {
.enum_literal => {
opt_cur_ty = ty;
cur_ty_idx = i;
},
.@"enum" => switch (ty.zigTypeTag(zcu)) {
.enum_literal => {},
.@"enum" => {
if (!ty.eql(cur_ty, zcu)) return generic_err;
},
.@"union" => {
const tag_ty = ty.unionTagTypeHypothetical(zcu);
if (!tag_ty.eql(cur_ty, zcu)) return generic_err;
opt_cur_ty = ty;
cur_ty_idx = i;
},
else => unreachable,
},
.@"union" => switch (ty.zigTypeTag(zcu)) {
.enum_literal => {},
.@"enum" => {
const cur_tag_ty = cur_ty.unionTagTypeHypothetical(zcu);
if (!ty.eql(cur_tag_ty, zcu)) return generic_err;
},
.@"union" => {
if (!ty.eql(cur_ty, zcu)) return generic_err;
},
else => unreachable,
},
else => unreachable,
}
}
return .{ .success = opt_cur_ty.? };
},
.comptime_int => {
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
switch (ty.zigTypeTag(zcu)) {
.comptime_int => {},
else => return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} },
}
}
return .{ .success = .comptime_int };
},
.comptime_float => {
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
switch (ty.zigTypeTag(zcu)) {
.comptime_int, .comptime_float => {},
else => return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} },
}
}
return .{ .success = .comptime_float };
},
.fixed_int => {
var idx_unsigned: ?usize = null;
var idx_signed: ?usize = null;
// TODO: this is for compatibility with legacy behavior. See beneath the loop.
var any_comptime_known = false;
for (peer_tys, peer_vals, 0..) |opt_ty, *ptr_opt_val, i| {
const ty = opt_ty orelse continue;
const opt_val = ptr_opt_val.*;
const peer_tag = ty.zigTypeTag(zcu);
switch (peer_tag) {
.comptime_int => {
// If the value is undefined, we can't refine to a fixed-width int
if (opt_val == null or opt_val.?.isUndef(zcu)) return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
any_comptime_known = true;
ptr_opt_val.* = try sema.resolveLazyValue(opt_val.?);
continue;
},
.int => {},
else => return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} },
}
if (opt_val != null) any_comptime_known = true;
const info = ty.intInfo(zcu);
const idx_ptr = switch (info.signedness) {
.unsigned => &idx_unsigned,
.signed => &idx_signed,
};
const largest_idx = idx_ptr.* orelse {
idx_ptr.* = i;
continue;
};
const cur_info = peer_tys[largest_idx].?.intInfo(zcu);
if (info.bits > cur_info.bits) {
idx_ptr.* = i;
}
}
if (idx_signed == null) {
return .{ .success = peer_tys[idx_unsigned.?].? };
}
if (idx_unsigned == null) {
return .{ .success = peer_tys[idx_signed.?].? };
}
const unsigned_info = peer_tys[idx_unsigned.?].?.intInfo(zcu);
const signed_info = peer_tys[idx_signed.?].?.intInfo(zcu);
if (signed_info.bits > unsigned_info.bits) {
return .{ .success = peer_tys[idx_signed.?].? };
}
// TODO: this is for compatibility with legacy behavior. Before this version of PTR was
// implemented, the algorithm very often returned false positives, with the expectation
// that you'd just hit a coercion error later. One of these was that for integers, the
// largest type would always be returned, even if it couldn't fit everything. This had
// an unintentional consequence to semantics, which is that if values were known at
// comptime, they would be coerced down to the smallest type where possible. This
// behavior is unintuitive and order-dependent, so in my opinion should be eliminated,
// but for now we'll retain compatibility.
if (any_comptime_known) {
if (unsigned_info.bits > signed_info.bits) {
return .{ .success = peer_tys[idx_unsigned.?].? };
}
const idx = @min(idx_unsigned.?, idx_signed.?);
return .{ .success = peer_tys[idx].? };
}
return .{ .conflict = .{
.peer_idx_a = idx_unsigned.?,
.peer_idx_b = idx_signed.?,
} };
},
.fixed_float => {
var opt_cur_ty: ?Type = null;
for (peer_tys, peer_vals, 0..) |opt_ty, opt_val, i| {
const ty = opt_ty orelse continue;
switch (ty.zigTypeTag(zcu)) {
.comptime_float, .comptime_int => {},
.int => {
if (opt_val == null) return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
},
.float => {
if (opt_cur_ty) |cur_ty| {
if (cur_ty.eql(ty, zcu)) continue;
// Recreate the type so we eliminate any c_longdouble
const bits = @max(cur_ty.floatBits(target), ty.floatBits(target));
opt_cur_ty = switch (bits) {
16 => .f16,
32 => .f32,
64 => .f64,
80 => .f80,
128 => .f128,
else => unreachable,
};
} else {
opt_cur_ty = ty;
}
},
else => return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} },
}
}
// Note that fixed_float is only chosen if there is at least one fixed-width float peer,
// so opt_cur_ty must be non-null.
return .{ .success = opt_cur_ty.? };
},
.tuple => {
// First, check that every peer has the same approximate structure (field count)
var opt_first_idx: ?usize = null;
var is_tuple: bool = undefined;
var field_count: usize = undefined;
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
if (!ty.isTuple(zcu)) {
return .{ .conflict = .{
.peer_idx_a = strat_reason,
.peer_idx_b = i,
} };
}
const first_idx = opt_first_idx orelse {
opt_first_idx = i;
is_tuple = ty.isTuple(zcu);
field_count = ty.structFieldCount(zcu);
continue;
};
if (ty.structFieldCount(zcu) != field_count) {
return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
}
}
assert(opt_first_idx != null);
// Now, we'll recursively resolve the field types
const field_types = try sema.arena.alloc(InternPool.Index, field_count);
// Values for `comptime` fields - `.none` used for non-comptime fields
const field_vals = try sema.arena.alloc(InternPool.Index, field_count);
const sub_peer_tys = try sema.arena.alloc(?Type, peer_tys.len);
const sub_peer_vals = try sema.arena.alloc(?Value, peer_vals.len);
for (field_types, field_vals, 0..) |*field_ty, *field_val, field_index| {
// Fill buffers with types and values of the field
for (peer_tys, peer_vals, sub_peer_tys, sub_peer_vals) |opt_ty, opt_val, *peer_field_ty, *peer_field_val| {
const ty = opt_ty orelse {
peer_field_ty.* = null;
peer_field_val.* = null;
continue;
};
peer_field_ty.* = ty.fieldType(field_index, zcu);
peer_field_val.* = if (opt_val) |val| try val.fieldValue(pt, field_index) else null;
}
// Resolve field type recursively
field_ty.* = switch (try sema.resolvePeerTypesInner(block, src, sub_peer_tys, sub_peer_vals)) {
.success => |ty| ty.toIntern(),
else => |result| {
const result_buf = try sema.arena.create(PeerResolveResult);
result_buf.* = result;
const field_name = try ip.getOrPutStringFmt(sema.gpa, pt.tid, "{d}", .{field_index}, .no_embedded_nulls);
// The error info needs the field types, but we can't reuse sub_peer_tys
// since the recursive call may have clobbered it.
const peer_field_tys = try sema.arena.alloc(Type, peer_tys.len);
for (peer_tys, peer_field_tys) |opt_ty, *peer_field_ty| {
// Already-resolved types won't be referenced by the error so it's fine
// to leave them undefined.
const ty = opt_ty orelse continue;
peer_field_ty.* = ty.fieldType(field_index, zcu);
}
return .{ .field_error = .{
.field_name = field_name,
.field_types = peer_field_tys,
.sub_result = result_buf,
} };
},
};
// Decide if this is a comptime field. If it is comptime in all peers, and the
// coerced comptime values are all the same, we say it is comptime, else not.
var comptime_val: ?Value = null;
for (peer_tys) |opt_ty| {
const struct_ty = opt_ty orelse continue;
try struct_ty.resolveStructFieldInits(pt);
const uncoerced_field_val = try struct_ty.structFieldValueComptime(pt, field_index) orelse {
comptime_val = null;
break;
};
const uncoerced_field = Air.internedToRef(uncoerced_field_val.toIntern());
const coerced_inst = sema.coerceExtra(block, .fromInterned(field_ty.*), uncoerced_field, src, .{ .report_err = false }) catch |err| switch (err) {
// It's possible for PTR to give false positives. Just give up on making this a comptime field, we'll get an error later anyway
error.NotCoercible => {
comptime_val = null;
break;
},
else => |e| return e,
};
const coerced_val = (try sema.resolveValue(coerced_inst)) orelse continue;
const existing = comptime_val orelse {
comptime_val = coerced_val;
continue;
};
if (!coerced_val.eql(existing, .fromInterned(field_ty.*), zcu)) {
comptime_val = null;
break;
}
}
field_val.* = if (comptime_val) |v| v.toIntern() else .none;
}
const final_ty = try ip.getTupleType(zcu.gpa, pt.tid, .{
.types = field_types,
.values = field_vals,
});
return .{ .success = .fromInterned(final_ty) };
},
.exact => {
var expect_ty: ?Type = null;
var first_idx: usize = undefined;
for (peer_tys, 0..) |opt_ty, i| {
const ty = opt_ty orelse continue;
if (expect_ty) |expect| {
if (!ty.eql(expect, zcu)) return .{ .conflict = .{
.peer_idx_a = first_idx,
.peer_idx_b = i,
} };
} else {
expect_ty = ty;
first_idx = i;
}
}
return .{ .success = expect_ty.? };
},
}
}
fn maybeMergeErrorSets(sema: *Sema, block: *Block, src: LazySrcLoc, e0: Type, e1: Type) !Type {
// e0 -> e1
if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, e1, e0, src, src)) {
return e1;
}
// e1 -> e0
if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, e0, e1, src, src)) {
return e0;
}
return sema.errorSetMerge(e0, e1);
}
fn resolvePairInMemoryCoercible(sema: *Sema, block: *Block, src: LazySrcLoc, ty_a: Type, ty_b: Type) !?Type {
const target = sema.pt.zcu.getTarget();
// ty_b -> ty_a
if (.ok == try sema.coerceInMemoryAllowed(block, ty_a, ty_b, false, target, src, src, null)) {
return ty_a;
}
// ty_a -> ty_b
if (.ok == try sema.coerceInMemoryAllowed(block, ty_b, ty_a, false, target, src, src, null)) {
return ty_b;
}
return null;
}
const ArrayLike = struct {
len: u64,
/// `noreturn` indicates that this type is `struct{}` so can coerce to anything
elem_ty: Type,
};
fn typeIsArrayLike(sema: *Sema, ty: Type) ?ArrayLike {
const pt = sema.pt;
const zcu = pt.zcu;
return switch (ty.zigTypeTag(zcu)) {
.array => .{
.len = ty.arrayLen(zcu),
.elem_ty = ty.childType(zcu),
},
.@"struct" => {
const field_count = ty.structFieldCount(zcu);
if (field_count == 0) return .{
.len = 0,
.elem_ty = .noreturn,
};
if (!ty.isTuple(zcu)) return null;
const elem_ty = ty.fieldType(0, zcu);
for (1..field_count) |i| {
if (!ty.fieldType(i, zcu).eql(elem_ty, zcu)) {
return null;
}
}
return .{
.len = field_count,
.elem_ty = elem_ty,
};
},
else => null,
};
}
pub fn resolveIes(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
if (sema.fn_ret_ty_ies) |ies| {
try sema.resolveInferredErrorSetPtr(block, src, ies);
assert(ies.resolved != .none);
ip.funcIesResolved(sema.func_index).* = ies.resolved;
}
}
pub fn resolveFnTypes(sema: *Sema, fn_ty: Type, src: LazySrcLoc) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const fn_ty_info = zcu.typeToFunc(fn_ty).?;
try Type.fromInterned(fn_ty_info.return_type).resolveFully(pt);
if (zcu.comp.config.any_error_tracing and
Type.fromInterned(fn_ty_info.return_type).isError(zcu))
{
// Ensure the type exists so that backends can assume that.
_ = try sema.getBuiltinType(src, .StackTrace);
}
for (0..fn_ty_info.param_types.len) |i| {
try Type.fromInterned(fn_ty_info.param_types.get(ip)[i]).resolveFully(pt);
}
}
fn resolveLazyValue(sema: *Sema, val: Value) CompileError!Value {
return val.resolveLazy(sema.arena, sema.pt);
}
/// Resolve a struct's alignment only without triggering resolution of its layout.
/// Asserts that the alignment is not yet resolved and the layout is non-packed.
pub fn resolveStructAlignment(
sema: *Sema,
ty: InternPool.Index,
struct_type: InternPool.LoadedStructType,
) SemaError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const target = zcu.getTarget();
assert(sema.owner.unwrap().type == ty);
assert(struct_type.layout != .@"packed");
assert(struct_type.flagsUnordered(ip).alignment == .none);
const ptr_align = Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8));
// We'll guess "pointer-aligned", if the struct has an
// underaligned pointer field then some allocations
// might require explicit alignment.
if (struct_type.assumePointerAlignedIfFieldTypesWip(ip, ptr_align)) return;
try sema.resolveStructFieldTypes(ty, struct_type);
// We'll guess "pointer-aligned", if the struct has an
// underaligned pointer field then some allocations
// might require explicit alignment.
if (struct_type.assumePointerAlignedIfWip(ip, ptr_align)) return;
defer struct_type.clearAlignmentWip(ip);
// No `zcu.trackUnitSema` calls, since this phase isn't really doing any semantic analysis.
// It's just triggering *other* analysis, alongside a simple loop over already-resolved info.
var alignment: Alignment = .@"1";
for (0..struct_type.field_types.len) |i| {
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
if (struct_type.fieldIsComptime(ip, i) or try field_ty.comptimeOnlySema(pt))
continue;
const field_align = try field_ty.structFieldAlignmentSema(
struct_type.fieldAlign(ip, i),
struct_type.layout,
pt,
);
alignment = alignment.maxStrict(field_align);
}
struct_type.setAlignment(ip, alignment);
}
pub fn resolveStructLayout(sema: *Sema, ty: Type) SemaError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const struct_type = zcu.typeToStruct(ty) orelse return;
assert(sema.owner.unwrap().type == ty.toIntern());
if (struct_type.haveLayout(ip))
return;
try sema.resolveStructFieldTypes(ty.toIntern(), struct_type);
// No `zcu.trackUnitSema` calls, since this phase isn't really doing any semantic analysis.
// It's just triggering *other* analysis, alongside a simple loop over already-resolved info.
if (struct_type.layout == .@"packed") {
sema.backingIntType(struct_type) catch |err| switch (err) {
error.AnalysisFail, error.OutOfMemory, error.Canceled => |e| return e,
error.ComptimeBreak, error.ComptimeReturn => unreachable,
};
return;
}
if (struct_type.setLayoutWip(ip)) {
const msg = try sema.errMsg(
ty.srcLoc(zcu),
"struct '{f}' depends on itself",
.{ty.fmt(pt)},
);
return sema.failWithOwnedErrorMsg(null, msg);
}
defer struct_type.clearLayoutWip(ip);
const aligns = try sema.arena.alloc(Alignment, struct_type.field_types.len);
const sizes = try sema.arena.alloc(u64, struct_type.field_types.len);
var big_align: Alignment = .@"1";
for (aligns, sizes, 0..) |*field_align, *field_size, i| {
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
if (struct_type.fieldIsComptime(ip, i) or try field_ty.comptimeOnlySema(pt)) {
struct_type.offsets.get(ip)[i] = 0;
field_size.* = 0;
field_align.* = .none;
continue;
}
field_size.* = field_ty.abiSizeSema(pt) catch |err| switch (err) {
error.AnalysisFail => {
const msg = sema.err orelse return err;
try sema.addFieldErrNote(ty, i, msg, "while checking this field", .{});
return err;
},
else => return err,
};
field_align.* = try field_ty.structFieldAlignmentSema(
struct_type.fieldAlign(ip, i),
struct_type.layout,
pt,
);
big_align = big_align.maxStrict(field_align.*);
}
if (struct_type.flagsUnordered(ip).assumed_runtime_bits and !(try ty.hasRuntimeBitsSema(pt))) {
const msg = try sema.errMsg(
ty.srcLoc(zcu),
"struct layout depends on it having runtime bits",
.{},
);
return sema.failWithOwnedErrorMsg(null, msg);
}
if (struct_type.flagsUnordered(ip).assumed_pointer_aligned and
big_align.compareStrict(.neq, Alignment.fromByteUnits(@divExact(zcu.getTarget().ptrBitWidth(), 8))))
{
const msg = try sema.errMsg(
ty.srcLoc(zcu),
"struct layout depends on being pointer aligned",
.{},
);
return sema.failWithOwnedErrorMsg(null, msg);
}
if (struct_type.hasReorderedFields()) {
const runtime_order = struct_type.runtime_order.get(ip);
for (runtime_order, 0..) |*ro, i| {
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
if (struct_type.fieldIsComptime(ip, i) or try field_ty.comptimeOnlySema(pt)) {
ro.* = .omitted;
} else {
ro.* = @enumFromInt(i);
}
}
const RuntimeOrder = InternPool.LoadedStructType.RuntimeOrder;
const AlignSortContext = struct {
aligns: []const Alignment,
fn lessThan(ctx: @This(), a: RuntimeOrder, b: RuntimeOrder) bool {
if (a == .omitted) return false;
if (b == .omitted) return true;
const a_align = ctx.aligns[@intFromEnum(a)];
const b_align = ctx.aligns[@intFromEnum(b)];
return a_align.compare(.gt, b_align);
}
};
if (!zcu.backendSupportsFeature(.field_reordering)) {
// TODO: we should probably also reorder tuple fields? This is a bit weird because it'll involve
// mutating the `InternPool` for a non-container type.
//
// TODO: implement field reordering support in all the backends!
//
// This logic does not reorder fields; it only moves the omitted ones to the end
// so that logic elsewhere does not need to special-case here.
var i: usize = 0;
var off: usize = 0;
while (i + off < runtime_order.len) {
if (runtime_order[i + off] == .omitted) {
off += 1;
continue;
}
runtime_order[i] = runtime_order[i + off];
i += 1;
}
@memset(runtime_order[i..], .omitted);
} else {
mem.sortUnstable(RuntimeOrder, runtime_order, AlignSortContext{
.aligns = aligns,
}, AlignSortContext.lessThan);
}
}
// Calculate size, alignment, and field offsets.
const offsets = struct_type.offsets.get(ip);
var it = struct_type.iterateRuntimeOrder(ip);
var offset: u64 = 0;
while (it.next()) |i| {
offsets[i] = @intCast(aligns[i].forward(offset));
offset = offsets[i] + sizes[i];
}
const size = std.math.cast(u32, big_align.forward(offset)) orelse {
const msg = try sema.errMsg(
ty.srcLoc(zcu),
"struct layout requires size {d}, this compiler implementation supports up to {d}",
.{ big_align.forward(offset), std.math.maxInt(u32) },
);
return sema.failWithOwnedErrorMsg(null, msg);
};
struct_type.setLayoutResolved(ip, size, big_align);
_ = try ty.comptimeOnlySema(pt);
}
fn backingIntType(
sema: *Sema,
struct_type: InternPool.LoadedStructType,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
var analysis_arena = std.heap.ArenaAllocator.init(gpa);
defer analysis_arena.deinit();
var block: Block = .{
.parent = null,
.sema = sema,
.namespace = struct_type.namespace,
.instructions = .{},
.inlining = null,
.comptime_reason = null, // set below if needed
.src_base_inst = struct_type.zir_index,
.type_name_ctx = struct_type.name,
};
defer assert(block.instructions.items.len == 0);
const fields_bit_sum = blk: {
var accumulator: u64 = 0;
for (0..struct_type.field_types.len) |i| {
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
accumulator += try field_ty.bitSizeSema(pt);
}
break :blk accumulator;
};
const zir = zcu.namespacePtr(struct_type.namespace).fileScope(zcu).zir.?;
const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail;
const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended;
assert(extended.opcode == .struct_decl);
const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
if (small.has_backing_int) {
var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len;
const captures_len = if (small.has_captures_len) blk: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :blk captures_len;
} else 0;
extra_index += @intFromBool(small.has_fields_len);
extra_index += @intFromBool(small.has_decls_len);
extra_index += captures_len * 2;
const backing_int_body_len = zir.extra[extra_index];
extra_index += 1;
const backing_int_src: LazySrcLoc = .{
.base_node_inst = struct_type.zir_index,
.offset = .{ .node_offset_container_tag = .zero },
};
block.comptime_reason = .{ .reason = .{
.src = backing_int_src,
.r = .{ .simple = .type },
} };
const backing_int_ty = blk: {
if (backing_int_body_len == 0) {
const backing_int_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
break :blk try sema.resolveType(&block, backing_int_src, backing_int_ref);
} else {
const body = zir.bodySlice(extra_index, backing_int_body_len);
const ty_ref = try sema.resolveInlineBody(&block, body, zir_index);
break :blk try sema.analyzeAsType(&block, backing_int_src, ty_ref);
}
};
try sema.checkBackingIntType(&block, backing_int_src, backing_int_ty, fields_bit_sum);
struct_type.setBackingIntType(ip, backing_int_ty.toIntern());
} else {
if (fields_bit_sum > std.math.maxInt(u16)) {
return sema.fail(&block, block.nodeOffset(.zero), "size of packed struct '{d}' exceeds maximum bit width of 65535", .{fields_bit_sum});
}
const backing_int_ty = try pt.intType(.unsigned, @intCast(fields_bit_sum));
struct_type.setBackingIntType(ip, backing_int_ty.toIntern());
}
try sema.flushExports();
}
fn checkBackingIntType(sema: *Sema, block: *Block, src: LazySrcLoc, backing_int_ty: Type, fields_bit_sum: u64) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
if (!backing_int_ty.isInt(zcu)) {
return sema.fail(block, src, "expected backing integer type, found '{f}'", .{backing_int_ty.fmt(pt)});
}
if (backing_int_ty.bitSize(zcu) != fields_bit_sum) {
return sema.fail(
block,
src,
"backing integer type '{f}' has bit size {d} but the struct fields have a total bit size of {d}",
.{ backing_int_ty.fmt(pt), backing_int_ty.bitSize(zcu), fields_bit_sum },
);
}
}
fn checkIndexable(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
const pt = sema.pt;
if (!ty.isIndexable(pt.zcu)) {
const msg = msg: {
const msg = try sema.errMsg(src, "type '{f}' does not support indexing", .{ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "operand must be an array, slice, tuple, or vector", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
}
fn checkMemOperand(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !void {
const pt = sema.pt;
const zcu = pt.zcu;
if (ty.zigTypeTag(zcu) == .pointer) {
switch (ty.ptrSize(zcu)) {
.slice, .many, .c => return,
.one => {
const elem_ty = ty.childType(zcu);
if (elem_ty.zigTypeTag(zcu) == .array) return;
// TODO https://github.com/ziglang/zig/issues/15479
// if (elem_ty.isTuple()) return;
},
}
}
const msg = msg: {
const msg = try sema.errMsg(src, "type '{f}' is not an indexable pointer", .{ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.errNote(src, msg, "operand must be a slice, a many pointer or a pointer to an array", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
/// Resolve a unions's alignment only without triggering resolution of its layout.
/// Asserts that the alignment is not yet resolved.
pub fn resolveUnionAlignment(
sema: *Sema,
ty: Type,
union_type: InternPool.LoadedUnionType,
) SemaError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const target = zcu.getTarget();
assert(sema.owner.unwrap().type == ty.toIntern());
assert(!union_type.haveLayout(ip));
const ptr_align = Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8));
// We'll guess "pointer-aligned", if the union has an
// underaligned pointer field then some allocations
// might require explicit alignment.
if (union_type.assumePointerAlignedIfFieldTypesWip(ip, ptr_align)) return;
try sema.resolveUnionFieldTypes(ty, union_type);
// No `zcu.trackUnitSema` calls, since this phase isn't really doing any semantic analysis.
// It's just triggering *other* analysis, alongside a simple loop over already-resolved info.
var max_align: Alignment = .@"1";
for (0..union_type.field_types.len) |field_index| {
const field_ty: Type = .fromInterned(union_type.field_types.get(ip)[field_index]);
if (!(try field_ty.hasRuntimeBitsSema(pt))) continue;
const explicit_align = union_type.fieldAlign(ip, field_index);
const field_align = if (explicit_align != .none)
explicit_align
else
try field_ty.abiAlignmentSema(sema.pt);
max_align = max_align.max(field_align);
}
union_type.setAlignment(ip, max_align);
}
/// This logic must be kept in sync with `Type.getUnionLayout`.
pub fn resolveUnionLayout(sema: *Sema, ty: Type) SemaError!void {
const pt = sema.pt;
const ip = &pt.zcu.intern_pool;
try sema.resolveUnionFieldTypes(ty, ip.loadUnionType(ty.ip_index));
// Load again, since the tag type might have changed due to resolution.
const union_type = ip.loadUnionType(ty.ip_index);
assert(sema.owner.unwrap().type == ty.toIntern());
const old_flags = union_type.flagsUnordered(ip);
switch (old_flags.status) {
.none, .have_field_types => {},
.field_types_wip, .layout_wip => {
const msg = try sema.errMsg(
ty.srcLoc(pt.zcu),
"union '{f}' depends on itself",
.{ty.fmt(pt)},
);
return sema.failWithOwnedErrorMsg(null, msg);
},
.have_layout, .fully_resolved_wip, .fully_resolved => return,
}
errdefer union_type.setStatusIfLayoutWip(ip, old_flags.status);
union_type.setStatus(ip, .layout_wip);
// No `zcu.trackUnitSema` calls, since this phase isn't really doing any semantic analysis.
// It's just triggering *other* analysis, alongside a simple loop over already-resolved info.
var max_size: u64 = 0;
var max_align: Alignment = .@"1";
for (0..union_type.field_types.len) |field_index| {
const field_ty: Type = .fromInterned(union_type.field_types.get(ip)[field_index]);
if (field_ty.isNoReturn(pt.zcu)) continue;
// We need to call `hasRuntimeBits` before calling `abiSize` to prevent reachable `unreachable`s,
// but `hasRuntimeBits` only resolves field types and so may infinite recurse on a layout wip type,
// so we must resolve the layout manually first, instead of waiting for `abiSize` to do it for us.
// This is arguably just hacking around bugs in both `abiSize` for not allowing arbitrary types to
// be queried, enabling failures to be handled with the emission of a compile error, and also in
// `hasRuntimeBits` for ever being able to infinite recurse in the first place.
try field_ty.resolveLayout(pt);
if (try field_ty.hasRuntimeBitsSema(pt)) {
max_size = @max(max_size, field_ty.abiSizeSema(pt) catch |err| switch (err) {
error.AnalysisFail => {
const msg = sema.err orelse return err;
try sema.addFieldErrNote(ty, field_index, msg, "while checking this field", .{});
return err;
},
else => return err,
});
}
const explicit_align = union_type.fieldAlign(ip, field_index);
const field_align = if (explicit_align != .none)
explicit_align
else
try field_ty.abiAlignmentSema(pt);
max_align = max_align.max(field_align);
}
const has_runtime_tag = union_type.flagsUnordered(ip).runtime_tag.hasTag() and
try Type.fromInterned(union_type.enum_tag_ty).hasRuntimeBitsSema(pt);
const size, const alignment, const padding = if (has_runtime_tag) layout: {
const enum_tag_type: Type = .fromInterned(union_type.enum_tag_ty);
const tag_align = try enum_tag_type.abiAlignmentSema(pt);
const tag_size = try enum_tag_type.abiSizeSema(pt);
// Put the tag before or after the payload depending on which one's
// alignment is greater.
var size: u64 = 0;
var padding: u32 = 0;
if (tag_align.order(max_align).compare(.gte)) {
// {Tag, Payload}
size += tag_size;
size = max_align.forward(size);
size += max_size;
const prev_size = size;
size = tag_align.forward(size);
padding = @intCast(size - prev_size);
} else {
// {Payload, Tag}
size += max_size;
size = switch (pt.zcu.getTarget().ofmt) {
.c => max_align,
else => tag_align,
}.forward(size);
size += tag_size;
const prev_size = size;
size = max_align.forward(size);
padding = @intCast(size - prev_size);
}
break :layout .{ size, max_align.max(tag_align), padding };
} else .{ max_align.forward(max_size), max_align, 0 };
const casted_size = std.math.cast(u32, size) orelse {
const msg = try sema.errMsg(
ty.srcLoc(pt.zcu),
"union layout requires size {d}, this compiler implementation supports up to {d}",
.{ size, std.math.maxInt(u32) },
);
return sema.failWithOwnedErrorMsg(null, msg);
};
union_type.setHaveLayout(ip, casted_size, padding, alignment);
if (union_type.flagsUnordered(ip).assumed_runtime_bits and !(try ty.hasRuntimeBitsSema(pt))) {
const msg = try sema.errMsg(
ty.srcLoc(pt.zcu),
"union layout depends on it having runtime bits",
.{},
);
return sema.failWithOwnedErrorMsg(null, msg);
}
if (union_type.flagsUnordered(ip).assumed_pointer_aligned and
alignment.compareStrict(.neq, Alignment.fromByteUnits(@divExact(pt.zcu.getTarget().ptrBitWidth(), 8))))
{
const msg = try sema.errMsg(
ty.srcLoc(pt.zcu),
"union layout depends on being pointer aligned",
.{},
);
return sema.failWithOwnedErrorMsg(null, msg);
}
_ = try ty.comptimeOnlySema(pt);
}
/// Returns `error.AnalysisFail` if any of the types (recursively) failed to
/// be resolved.
pub fn resolveStructFully(sema: *Sema, ty: Type) SemaError!void {
try sema.resolveStructLayout(ty);
try sema.resolveStructFieldInits(ty);
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const struct_type = zcu.typeToStruct(ty).?;
assert(sema.owner.unwrap().type == ty.toIntern());
if (struct_type.setFullyResolved(ip)) return;
errdefer struct_type.clearFullyResolved(ip);
// No `zcu.trackUnitSema` calls, since this phase isn't really doing any semantic analysis.
// It's just triggering *other* analysis, alongside a simple loop over already-resolved info.
// After we have resolve struct layout we have to go over the fields again to
// make sure pointer fields get their child types resolved as well.
// See also similar code for unions.
for (0..struct_type.field_types.len) |i| {
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
try field_ty.resolveFully(pt);
}
}
pub fn resolveUnionFully(sema: *Sema, ty: Type) SemaError!void {
try sema.resolveUnionLayout(ty);
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const union_obj = zcu.typeToUnion(ty).?;
assert(sema.owner.unwrap().type == ty.toIntern());
switch (union_obj.flagsUnordered(ip).status) {
.none, .have_field_types, .field_types_wip, .layout_wip, .have_layout => {},
.fully_resolved_wip, .fully_resolved => return,
}
// No `zcu.trackUnitSema` calls, since this phase isn't really doing any semantic analysis.
// It's just triggering *other* analysis, alongside a simple loop over already-resolved info.
{
// After we have resolve union layout we have to go over the fields again to
// make sure pointer fields get their child types resolved as well.
// See also similar code for structs.
const prev_status = union_obj.flagsUnordered(ip).status;
errdefer union_obj.setStatus(ip, prev_status);
union_obj.setStatus(ip, .fully_resolved_wip);
for (0..union_obj.field_types.len) |field_index| {
const field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[field_index]);
try field_ty.resolveFully(pt);
}
union_obj.setStatus(ip, .fully_resolved);
}
// And let's not forget comptime-only status.
_ = try ty.comptimeOnlySema(pt);
}
pub fn resolveStructFieldTypes(
sema: *Sema,
ty: InternPool.Index,
struct_type: InternPool.LoadedStructType,
) SemaError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(sema.owner.unwrap().type == ty);
if (struct_type.haveFieldTypes(ip)) return;
if (struct_type.setFieldTypesWip(ip)) {
const msg = try sema.errMsg(
Type.fromInterned(ty).srcLoc(zcu),
"struct '{f}' depends on itself",
.{Type.fromInterned(ty).fmt(pt)},
);
return sema.failWithOwnedErrorMsg(null, msg);
}
defer struct_type.clearFieldTypesWip(ip);
// can't happen earlier than this because we only want the progress node if not already resolved
const tracked_unit = zcu.trackUnitSema(struct_type.name.toSlice(ip), null);
defer tracked_unit.end(zcu);
sema.structFields(struct_type) catch |err| switch (err) {
error.AnalysisFail, error.OutOfMemory, error.Canceled => |e| return e,
error.ComptimeBreak, error.ComptimeReturn => unreachable,
};
}
pub fn resolveStructFieldInits(sema: *Sema, ty: Type) SemaError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const struct_type = zcu.typeToStruct(ty) orelse return;
assert(sema.owner.unwrap().type == ty.toIntern());
// Inits can start as resolved
if (struct_type.haveFieldInits(ip)) return;
try sema.resolveStructLayout(ty);
if (struct_type.setInitsWip(ip)) {
const msg = try sema.errMsg(
ty.srcLoc(zcu),
"struct '{f}' depends on itself",
.{ty.fmt(pt)},
);
return sema.failWithOwnedErrorMsg(null, msg);
}
defer struct_type.clearInitsWip(ip);
// can't happen earlier than this because we only want the progress node if not already resolved
const tracked_unit = zcu.trackUnitSema(struct_type.name.toSlice(ip), null);
defer tracked_unit.end(zcu);
sema.structFieldInits(struct_type) catch |err| switch (err) {
error.AnalysisFail, error.OutOfMemory, error.Canceled => |e| return e,
error.ComptimeBreak, error.ComptimeReturn => unreachable,
};
struct_type.setHaveFieldInits(ip);
}
pub fn resolveUnionFieldTypes(sema: *Sema, ty: Type, union_type: InternPool.LoadedUnionType) SemaError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(sema.owner.unwrap().type == ty.toIntern());
switch (union_type.flagsUnordered(ip).status) {
.none => {},
.field_types_wip => {
const msg = try sema.errMsg(ty.srcLoc(zcu), "union '{f}' depends on itself", .{ty.fmt(pt)});
return sema.failWithOwnedErrorMsg(null, msg);
},
.have_field_types,
.have_layout,
.layout_wip,
.fully_resolved_wip,
.fully_resolved,
=> return,
}
// can't happen earlier than this because we only want the progress node if not already resolved
const tracked_unit = zcu.trackUnitSema(union_type.name.toSlice(ip), null);
defer tracked_unit.end(zcu);
union_type.setStatus(ip, .field_types_wip);
errdefer union_type.setStatus(ip, .none);
sema.unionFields(ty.toIntern(), union_type) catch |err| switch (err) {
error.AnalysisFail, error.OutOfMemory, error.Canceled => |e| return e,
error.ComptimeBreak, error.ComptimeReturn => unreachable,
};
union_type.setStatus(ip, .have_field_types);
}
/// Returns a normal error set corresponding to the fully populated inferred
/// error set.
fn resolveInferredErrorSet(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ies_index: InternPool.Index,
) CompileError!InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const func_index = ip.iesFuncIndex(ies_index);
const func = zcu.funcInfo(func_index);
try sema.declareDependency(.{ .interned = func_index }); // resolved IES
try zcu.maybeUnresolveIes(func_index);
const resolved_ty = func.resolvedErrorSetUnordered(ip);
if (resolved_ty != .none) return resolved_ty;
if (zcu.analysis_in_progress.contains(.wrap(.{ .func = func_index }))) {
return sema.fail(block, src, "unable to resolve inferred error set", .{});
}
// In order to ensure that all dependencies are properly added to the set,
// we need to ensure the function body is analyzed of the inferred error
// set. However, in the case of comptime/inline function calls with
// inferred error sets, each call gets an adhoc InferredErrorSet object, which
// has no corresponding function body.
const ies_func_info = zcu.typeToFunc(.fromInterned(func.ty)).?;
// if ies declared by a inline function with generic return type, the return_type should be generic_poison,
// because inline function does not create a new declaration, and the ies has been filled with analyzeCall,
// so here we can simply skip this case.
if (ies_func_info.return_type == .generic_poison_type) {
assert(ies_func_info.cc == .@"inline");
} else if (ip.errorUnionSet(ies_func_info.return_type) == ies_index) {
if (ies_func_info.is_generic) {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "unable to resolve inferred error set of generic function", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(zcu.navSrcLoc(func.owner_nav), msg, "generic function declared here", .{});
break :msg msg;
});
}
// In this case we are dealing with the actual InferredErrorSet object that
// corresponds to the function, not one created to track an inline/comptime call.
const orig_func_index = ip.unwrapCoercedFunc(func_index);
try sema.addReferenceEntry(block, src, .wrap(.{ .func = orig_func_index }));
try pt.ensureFuncBodyUpToDate(orig_func_index);
}
// This will now have been resolved by the logic at the end of `Zcu.analyzeFnBody`
// which calls `resolveInferredErrorSetPtr`.
const final_resolved_ty = func.resolvedErrorSetUnordered(ip);
assert(final_resolved_ty != .none);
return final_resolved_ty;
}
pub fn resolveInferredErrorSetPtr(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ies: *InferredErrorSet,
) CompileError!void {
const pt = sema.pt;
const ip = &pt.zcu.intern_pool;
if (ies.resolved != .none) return;
const ies_index = ip.errorUnionSet(sema.fn_ret_ty.toIntern());
for (ies.inferred_error_sets.keys()) |other_ies_index| {
if (ies_index == other_ies_index) continue;
switch (try sema.resolveInferredErrorSet(block, src, other_ies_index)) {
.anyerror_type => {
ies.resolved = .anyerror_type;
return;
},
else => |error_set_ty_index| {
const names = ip.indexToKey(error_set_ty_index).error_set_type.names;
for (names.get(ip)) |name| {
try ies.errors.put(sema.arena, name, {});
}
},
}
}
const resolved_error_set_ty = try pt.errorSetFromUnsortedNames(ies.errors.keys());
ies.resolved = resolved_error_set_ty.toIntern();
}
fn resolveAdHocInferredErrorSet(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
value: InternPool.Index,
) CompileError!InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const new_ty = try resolveAdHocInferredErrorSetTy(sema, block, src, ip.typeOf(value));
if (new_ty == .none) return value;
return ip.getCoerced(gpa, pt.tid, value, new_ty);
}
fn resolveAdHocInferredErrorSetTy(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ty: InternPool.Index,
) CompileError!InternPool.Index {
const ies = sema.fn_ret_ty_ies orelse return .none;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const error_union_info = switch (ip.indexToKey(ty)) {
.error_union_type => |x| x,
else => return .none,
};
if (error_union_info.error_set_type != .adhoc_inferred_error_set_type)
return .none;
try sema.resolveInferredErrorSetPtr(block, src, ies);
const new_ty = try pt.intern(.{ .error_union_type = .{
.error_set_type = ies.resolved,
.payload_type = error_union_info.payload_type,
} });
return new_ty;
}
fn resolveInferredErrorSetTy(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
ty: InternPool.Index,
) CompileError!InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
if (ty == .anyerror_type) return ty;
switch (ip.indexToKey(ty)) {
.error_set_type => return ty,
.inferred_error_set_type => return sema.resolveInferredErrorSet(block, src, ty),
else => unreachable,
}
}
fn structZirInfo(zir: Zir, zir_index: Zir.Inst.Index) struct {
/// fields_len
usize,
Zir.Inst.StructDecl.Small,
/// extra_index
usize,
} {
const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended;
assert(extended.opcode == .struct_decl);
const small: Zir.Inst.StructDecl.Small = @bitCast(extended.small);
var extra_index: usize = extended.operand + @typeInfo(Zir.Inst.StructDecl).@"struct".fields.len;
const captures_len = if (small.has_captures_len) blk: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :blk captures_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = zir.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) decls_len: {
const decls_len = zir.extra[extra_index];
extra_index += 1;
break :decls_len decls_len;
} else 0;
extra_index += captures_len * 2;
// The backing integer cannot be handled until `resolveStructLayout()`.
if (small.has_backing_int) {
const backing_int_body_len = zir.extra[extra_index];
extra_index += 1; // backing_int_body_len
if (backing_int_body_len == 0) {
extra_index += 1; // backing_int_ref
} else {
extra_index += backing_int_body_len; // backing_int_body_inst
}
}
// Skip over decls.
extra_index += decls_len;
return .{ fields_len, small, extra_index };
}
fn structFields(
sema: *Sema,
struct_type: InternPool.LoadedStructType,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const namespace_index = struct_type.namespace;
const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir.?;
const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail;
const fields_len, _, var extra_index = structZirInfo(zir, zir_index);
if (fields_len == 0) switch (struct_type.layout) {
.@"packed" => {
try sema.backingIntType(struct_type);
return;
},
.auto, .@"extern" => {
struct_type.setLayoutResolved(ip, 0, .none);
return;
},
};
var block_scope: Block = .{
.parent = null,
.sema = sema,
.namespace = namespace_index,
.instructions = .{},
.inlining = null,
.comptime_reason = .{ .reason = .{
.src = .{
.base_node_inst = struct_type.zir_index,
.offset = .nodeOffset(.zero),
},
.r = .{ .simple = .type },
} },
.src_base_inst = struct_type.zir_index,
.type_name_ctx = struct_type.name,
};
defer assert(block_scope.instructions.items.len == 0);
const Field = struct {
type_body_len: u32 = 0,
align_body_len: u32 = 0,
init_body_len: u32 = 0,
type_ref: Zir.Inst.Ref = .none,
};
const fields = try sema.arena.alloc(Field, fields_len);
var any_inits = false;
var any_aligned = false;
{
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
const flags_index = extra_index;
var bit_bag_index: usize = flags_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_align = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const has_init = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const is_comptime = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
if (is_comptime) struct_type.setFieldComptime(ip, field_i);
const field_name_zir: [:0]const u8 = zir.nullTerminatedString(@enumFromInt(zir.extra[extra_index]));
extra_index += 1; // field_name
fields[field_i] = .{};
if (has_type_body) {
fields[field_i].type_body_len = zir.extra[extra_index];
} else {
fields[field_i].type_ref = @enumFromInt(zir.extra[extra_index]);
}
extra_index += 1;
// This string needs to outlive the ZIR code.
const field_name = try ip.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls);
assert(struct_type.addFieldName(ip, field_name) == null);
if (has_align) {
fields[field_i].align_body_len = zir.extra[extra_index];
extra_index += 1;
any_aligned = true;
}
if (has_init) {
fields[field_i].init_body_len = zir.extra[extra_index];
extra_index += 1;
any_inits = true;
}
}
}
// Next we do only types and alignments, saving the inits for a second pass,
// so that init values may depend on type layout.
for (fields, 0..) |zir_field, field_i| {
const ty_src: LazySrcLoc = .{
.base_node_inst = struct_type.zir_index,
.offset = .{ .container_field_type = @intCast(field_i) },
};
const field_ty: Type = ty: {
if (zir_field.type_ref != .none) {
break :ty try sema.resolveType(&block_scope, ty_src, zir_field.type_ref);
}
assert(zir_field.type_body_len != 0);
const body = zir.bodySlice(extra_index, zir_field.type_body_len);
extra_index += body.len;
const ty_ref = try sema.resolveInlineBody(&block_scope, body, zir_index);
break :ty try sema.analyzeAsType(&block_scope, ty_src, ty_ref);
};
struct_type.field_types.get(ip)[field_i] = field_ty.toIntern();
if (field_ty.zigTypeTag(zcu) == .@"opaque") {
const msg = msg: {
const msg = try sema.errMsg(ty_src, "opaque types have unknown size and therefore cannot be directly embedded in structs", .{});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
if (field_ty.zigTypeTag(zcu) == .noreturn) {
const msg = msg: {
const msg = try sema.errMsg(ty_src, "struct fields cannot be 'noreturn'", .{});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
switch (struct_type.layout) {
.@"extern" => if (!try sema.validateExternType(field_ty, .struct_field)) {
const msg = msg: {
const msg = try sema.errMsg(ty_src, "extern structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, ty_src, field_ty, .struct_field);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
},
.@"packed" => if (!try sema.validatePackedType(field_ty)) {
const msg = msg: {
const msg = try sema.errMsg(ty_src, "packed structs cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotPacked(msg, ty_src, field_ty);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
},
else => {},
}
if (zir_field.align_body_len > 0) {
const body = zir.bodySlice(extra_index, zir_field.align_body_len);
extra_index += body.len;
const align_ref = try sema.resolveInlineBody(&block_scope, body, zir_index);
const align_src: LazySrcLoc = .{
.base_node_inst = struct_type.zir_index,
.offset = .{ .container_field_align = @intCast(field_i) },
};
const field_align = try sema.analyzeAsAlign(&block_scope, align_src, align_ref);
struct_type.field_aligns.get(ip)[field_i] = field_align;
}
extra_index += zir_field.init_body_len;
}
struct_type.clearFieldTypesWip(ip);
if (!any_inits) struct_type.setHaveFieldInits(ip);
try sema.flushExports();
}
// This logic must be kept in sync with `structFields`
fn structFieldInits(
sema: *Sema,
struct_type: InternPool.LoadedStructType,
) CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
assert(!struct_type.haveFieldInits(ip));
const namespace_index = struct_type.namespace;
const zir = zcu.namespacePtr(namespace_index).fileScope(zcu).zir.?;
const zir_index = struct_type.zir_index.resolve(ip) orelse return error.AnalysisFail;
const fields_len, _, var extra_index = structZirInfo(zir, zir_index);
var block_scope: Block = .{
.parent = null,
.sema = sema,
.namespace = namespace_index,
.instructions = .{},
.inlining = null,
.comptime_reason = undefined, // set when `block_scope` is used
.src_base_inst = struct_type.zir_index,
.type_name_ctx = struct_type.name,
};
defer assert(block_scope.instructions.items.len == 0);
const Field = struct {
type_body_len: u32 = 0,
align_body_len: u32 = 0,
init_body_len: u32 = 0,
};
const fields = try sema.arena.alloc(Field, fields_len);
var any_inits = false;
{
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
const flags_index = extra_index;
var bit_bag_index: usize = flags_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_align = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const has_init = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 2;
const has_type_body = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
extra_index += 1; // field_name
fields[field_i] = .{};
if (has_type_body) fields[field_i].type_body_len = zir.extra[extra_index];
extra_index += 1;
if (has_align) {
fields[field_i].align_body_len = zir.extra[extra_index];
extra_index += 1;
}
if (has_init) {
fields[field_i].init_body_len = zir.extra[extra_index];
extra_index += 1;
any_inits = true;
}
}
}
if (any_inits) {
for (fields, 0..) |zir_field, field_i| {
extra_index += zir_field.type_body_len;
extra_index += zir_field.align_body_len;
const body = zir.bodySlice(extra_index, zir_field.init_body_len);
extra_index += zir_field.init_body_len;
if (body.len == 0) continue;
// Pre-populate the type mapping the body expects to be there.
// In init bodies, the zir index of the struct itself is used
// to refer to the current field type.
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_i]);
const type_ref = Air.internedToRef(field_ty.toIntern());
try sema.inst_map.ensureSpaceForInstructions(sema.gpa, &.{zir_index});
sema.inst_map.putAssumeCapacity(zir_index, type_ref);
const init_src: LazySrcLoc = .{
.base_node_inst = struct_type.zir_index,
.offset = .{ .container_field_value = @intCast(field_i) },
};
block_scope.comptime_reason = .{ .reason = .{
.src = init_src,
.r = .{ .simple = .struct_field_default_value },
} };
const init = try sema.resolveInlineBody(&block_scope, body, zir_index);
const coerced = try sema.coerce(&block_scope, field_ty, init, init_src);
const default_val = try sema.resolveConstValue(&block_scope, init_src, coerced, null);
if (default_val.canMutateComptimeVarState(zcu)) {
return sema.failWithContainsReferenceToComptimeVar(
&block_scope,
init_src,
struct_type.fieldName(ip, field_i),
"field default value",
default_val,
);
}
struct_type.field_inits.get(ip)[field_i] = default_val.toIntern();
}
}
try sema.flushExports();
}
fn unionFields(
sema: *Sema,
union_ty: InternPool.Index,
union_type: InternPool.LoadedUnionType,
) CompileError!void {
const tracy = trace(@src());
defer tracy.end();
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const zir = zcu.namespacePtr(union_type.namespace).fileScope(zcu).zir.?;
const zir_index = union_type.zir_index.resolve(ip) orelse return error.AnalysisFail;
const extended = zir.instructions.items(.data)[@intFromEnum(zir_index)].extended;
assert(extended.opcode == .union_decl);
const small: Zir.Inst.UnionDecl.Small = @bitCast(extended.small);
const extra = zir.extraData(Zir.Inst.UnionDecl, extended.operand);
var extra_index: usize = extra.end;
const tag_type_ref: Zir.Inst.Ref = if (small.has_tag_type) blk: {
const ty_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
extra_index += 1;
break :blk ty_ref;
} else .none;
const captures_len = if (small.has_captures_len) blk: {
const captures_len = zir.extra[extra_index];
extra_index += 1;
break :blk captures_len;
} else 0;
const body_len = if (small.has_body_len) blk: {
const body_len = zir.extra[extra_index];
extra_index += 1;
break :blk body_len;
} else 0;
const fields_len = if (small.has_fields_len) blk: {
const fields_len = zir.extra[extra_index];
extra_index += 1;
break :blk fields_len;
} else 0;
const decls_len = if (small.has_decls_len) decls_len: {
const decls_len = zir.extra[extra_index];
extra_index += 1;
break :decls_len decls_len;
} else 0;
// Skip over captures and decls.
extra_index += captures_len * 2 + decls_len;
const body = zir.bodySlice(extra_index, body_len);
extra_index += body.len;
const src: LazySrcLoc = .{
.base_node_inst = union_type.zir_index,
.offset = .nodeOffset(.zero),
};
var block_scope: Block = .{
.parent = null,
.sema = sema,
.namespace = union_type.namespace,
.instructions = .{},
.inlining = null,
.comptime_reason = .{ .reason = .{
.src = src,
.r = .{ .simple = .type },
} },
.src_base_inst = union_type.zir_index,
.type_name_ctx = union_type.name,
};
defer assert(block_scope.instructions.items.len == 0);
if (body.len != 0) {
_ = try sema.analyzeInlineBody(&block_scope, body, zir_index);
}
var int_tag_ty: Type = undefined;
var enum_field_names: []InternPool.NullTerminatedString = &.{};
var enum_field_vals: std.AutoArrayHashMapUnmanaged(InternPool.Index, void) = .empty;
var explicit_tags_seen: []bool = &.{};
if (tag_type_ref != .none) {
const tag_ty_src: LazySrcLoc = .{
.base_node_inst = union_type.zir_index,
.offset = .{ .node_offset_container_tag = .zero },
};
const provided_ty = try sema.resolveType(&block_scope, tag_ty_src, tag_type_ref);
if (small.auto_enum_tag) {
// The provided type is an integer type and we must construct the enum tag type here.
int_tag_ty = provided_ty;
if (int_tag_ty.zigTypeTag(zcu) != .int and int_tag_ty.zigTypeTag(zcu) != .comptime_int) {
return sema.fail(&block_scope, tag_ty_src, "expected integer tag type, found '{f}'", .{int_tag_ty.fmt(pt)});
}
if (fields_len > 0) {
const field_count_val = try pt.intValue(.comptime_int, fields_len - 1);
if (!(try sema.intFitsInType(field_count_val, int_tag_ty, null))) {
const msg = msg: {
const msg = try sema.errMsg(tag_ty_src, "specified integer tag type cannot represent every field", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(tag_ty_src, msg, "type '{f}' cannot fit values in range 0...{d}", .{
int_tag_ty.fmt(pt),
fields_len - 1,
});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
enum_field_names = try sema.arena.alloc(InternPool.NullTerminatedString, fields_len);
try enum_field_vals.ensureTotalCapacity(sema.arena, fields_len);
}
} else {
// The provided type is the enum tag type.
const enum_type = switch (ip.indexToKey(provided_ty.toIntern())) {
.enum_type => ip.loadEnumType(provided_ty.toIntern()),
else => return sema.fail(&block_scope, tag_ty_src, "expected enum tag type, found '{f}'", .{provided_ty.fmt(pt)}),
};
union_type.setTagType(ip, provided_ty.toIntern());
// The fields of the union must match the enum exactly.
// A flag per field is used to check for missing and extraneous fields.
explicit_tags_seen = try sema.arena.alloc(bool, enum_type.names.len);
@memset(explicit_tags_seen, false);
}
} else {
// If auto_enum_tag is false, this is an untagged union. However, for semantic analysis
// purposes, we still auto-generate an enum tag type the same way. That the union is
// untagged is represented by the Type tag (union vs union_tagged).
enum_field_names = try sema.arena.alloc(InternPool.NullTerminatedString, fields_len);
}
var field_types: std.ArrayList(InternPool.Index) = .empty;
var field_aligns: std.ArrayList(InternPool.Alignment) = .empty;
try field_types.ensureTotalCapacityPrecise(sema.arena, fields_len);
if (small.any_aligned_fields)
try field_aligns.ensureTotalCapacityPrecise(sema.arena, fields_len);
var max_bits: u64 = 0;
var min_bits: u64 = std.math.maxInt(u64);
var max_bits_src: LazySrcLoc = undefined;
var min_bits_src: LazySrcLoc = undefined;
var max_bits_ty: Type = undefined;
var min_bits_ty: Type = undefined;
const bits_per_field = 4;
const fields_per_u32 = 32 / bits_per_field;
const bit_bags_count = std.math.divCeil(usize, fields_len, fields_per_u32) catch unreachable;
var bit_bag_index: usize = extra_index;
extra_index += bit_bags_count;
var cur_bit_bag: u32 = undefined;
var field_i: u32 = 0;
var last_tag_val: ?Value = null;
const layout = union_type.flagsUnordered(ip).layout;
while (field_i < fields_len) : (field_i += 1) {
if (field_i % fields_per_u32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_type = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const has_align = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const has_tag = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const unused = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
_ = unused;
const field_name_index: Zir.NullTerminatedString = @enumFromInt(zir.extra[extra_index]);
const field_name_zir = zir.nullTerminatedString(field_name_index);
extra_index += 1;
const field_type_ref: Zir.Inst.Ref = if (has_type) blk: {
const field_type_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
extra_index += 1;
break :blk field_type_ref;
} else .none;
const align_ref: Zir.Inst.Ref = if (has_align) blk: {
const align_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
extra_index += 1;
break :blk align_ref;
} else .none;
const tag_ref: Air.Inst.Ref = if (has_tag) blk: {
const tag_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
extra_index += 1;
break :blk try sema.resolveInst(tag_ref);
} else .none;
const name_src: LazySrcLoc = .{
.base_node_inst = union_type.zir_index,
.offset = .{ .container_field_name = field_i },
};
const value_src: LazySrcLoc = .{
.base_node_inst = union_type.zir_index,
.offset = .{ .container_field_value = field_i },
};
const align_src: LazySrcLoc = .{
.base_node_inst = union_type.zir_index,
.offset = .{ .container_field_align = field_i },
};
const type_src: LazySrcLoc = .{
.base_node_inst = union_type.zir_index,
.offset = .{ .container_field_type = field_i },
};
if (enum_field_vals.capacity() > 0) {
const enum_tag_val = if (tag_ref != .none) blk: {
const coerced = try sema.coerce(&block_scope, int_tag_ty, tag_ref, value_src);
const val = try sema.resolveConstDefinedValue(&block_scope, value_src, coerced, .{ .simple = .enum_field_tag_value });
last_tag_val = val;
break :blk val;
} else blk: {
if (last_tag_val) |last_tag| {
const result = try arith.incrementDefinedInt(sema, int_tag_ty, last_tag);
if (result.overflow) return sema.fail(
&block_scope,
value_src,
"enumeration value '{f}' too large for type '{f}'",
.{ result.val.fmtValueSema(pt, sema), int_tag_ty.fmt(pt) },
);
last_tag_val = result.val;
} else {
last_tag_val = try pt.intValue(int_tag_ty, 0);
}
break :blk last_tag_val.?;
};
const gop = enum_field_vals.getOrPutAssumeCapacity(enum_tag_val.toIntern());
if (gop.found_existing) {
const other_value_src: LazySrcLoc = .{
.base_node_inst = union_type.zir_index,
.offset = .{ .container_field_value = @intCast(gop.index) },
};
const msg = msg: {
const msg = try sema.errMsg(
value_src,
"enum tag value {f} already taken",
.{enum_tag_val.fmtValueSema(pt, sema)},
);
errdefer msg.destroy(gpa);
try sema.errNote(other_value_src, msg, "other occurrence here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
}
// This string needs to outlive the ZIR code.
const field_name = try ip.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls);
if (enum_field_names.len != 0) {
enum_field_names[field_i] = field_name;
}
const field_ty: Type = if (!has_type)
.void
else if (field_type_ref == .none)
.noreturn
else
try sema.resolveType(&block_scope, type_src, field_type_ref);
if (explicit_tags_seen.len > 0) {
const tag_ty = union_type.tagTypeUnordered(ip);
const tag_info = ip.loadEnumType(tag_ty);
const enum_index = tag_info.nameIndex(ip, field_name) orelse {
return sema.fail(&block_scope, name_src, "no field named '{f}' in enum '{f}'", .{
field_name.fmt(ip), Type.fromInterned(tag_ty).fmt(pt),
});
};
// No check for duplicate because the check already happened in order
// to create the enum type in the first place.
assert(!explicit_tags_seen[enum_index]);
explicit_tags_seen[enum_index] = true;
// Enforce the enum fields and the union fields being in the same order.
if (enum_index != field_i) {
const msg = msg: {
const enum_field_src: LazySrcLoc = .{
.base_node_inst = Type.fromInterned(tag_ty).typeDeclInstAllowGeneratedTag(zcu).?,
.offset = .{ .container_field_name = enum_index },
};
const msg = try sema.errMsg(name_src, "union field '{f}' ordered differently than corresponding enum field", .{
field_name.fmt(ip),
});
errdefer msg.destroy(sema.gpa);
try sema.errNote(enum_field_src, msg, "enum field here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
}
if (field_ty.zigTypeTag(zcu) == .@"opaque") {
const msg = msg: {
const msg = try sema.errMsg(type_src, "opaque types have unknown size and therefore cannot be directly embedded in unions", .{});
errdefer msg.destroy(sema.gpa);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
switch (layout) {
.@"extern" => if (!try sema.validateExternType(field_ty, .union_field)) {
const msg = msg: {
const msg = try sema.errMsg(type_src, "extern unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotExtern(msg, type_src, field_ty, .union_field);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
},
.@"packed" => {
if (!try sema.validatePackedType(field_ty)) {
const msg = msg: {
const msg = try sema.errMsg(type_src, "packed unions cannot contain fields of type '{f}'", .{field_ty.fmt(pt)});
errdefer msg.destroy(sema.gpa);
try sema.explainWhyTypeIsNotPacked(msg, type_src, field_ty);
try sema.addDeclaredHereNote(msg, field_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
const field_bits = try field_ty.bitSizeSema(pt);
if (field_bits >= max_bits) {
max_bits = field_bits;
max_bits_src = type_src;
max_bits_ty = field_ty;
}
if (field_bits <= min_bits) {
min_bits = field_bits;
min_bits_src = type_src;
min_bits_ty = field_ty;
}
},
.auto => {},
}
field_types.appendAssumeCapacity(field_ty.toIntern());
if (small.any_aligned_fields) {
field_aligns.appendAssumeCapacity(if (align_ref != .none)
try sema.resolveAlign(&block_scope, align_src, align_ref)
else
.none);
} else {
assert(align_ref == .none);
}
}
union_type.setFieldTypes(ip, field_types.items);
union_type.setFieldAligns(ip, field_aligns.items);
if (layout == .@"packed" and fields_len != 0 and min_bits != max_bits) {
const msg = msg: {
const msg = try sema.errMsg(src, "packed union has fields with mismatching bit sizes", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(min_bits_src, msg, "{d} bits here", .{min_bits});
try sema.addDeclaredHereNote(msg, min_bits_ty);
try sema.errNote(max_bits_src, msg, "{d} bits here", .{max_bits});
try sema.addDeclaredHereNote(msg, max_bits_ty);
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
if (explicit_tags_seen.len > 0) {
const tag_ty = union_type.tagTypeUnordered(ip);
const tag_info = ip.loadEnumType(tag_ty);
if (tag_info.names.len > fields_len) {
const msg = msg: {
const msg = try sema.errMsg(src, "enum field(s) missing in union", .{});
errdefer msg.destroy(sema.gpa);
for (tag_info.names.get(ip), 0..) |field_name, field_index| {
if (explicit_tags_seen[field_index]) continue;
try sema.addFieldErrNote(.fromInterned(tag_ty), field_index, msg, "field '{f}' missing, declared here", .{
field_name.fmt(ip),
});
}
try sema.addDeclaredHereNote(msg, .fromInterned(tag_ty));
break :msg msg;
};
return sema.failWithOwnedErrorMsg(&block_scope, msg);
}
} else if (enum_field_vals.count() > 0) {
const enum_ty = try sema.generateUnionTagTypeNumbered(&block_scope, enum_field_names, enum_field_vals.keys(), union_ty, union_type.name);
union_type.setTagType(ip, enum_ty);
} else {
const enum_ty = try sema.generateUnionTagTypeSimple(&block_scope, enum_field_names, union_ty, union_type.name);
union_type.setTagType(ip, enum_ty);
}
try sema.flushExports();
}
fn generateUnionTagTypeNumbered(
sema: *Sema,
block: *Block,
enum_field_names: []const InternPool.NullTerminatedString,
enum_field_vals: []const InternPool.Index,
union_type: InternPool.Index,
union_name: InternPool.NullTerminatedString,
) !InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = sema.gpa;
const ip = &zcu.intern_pool;
const name = try ip.getOrPutStringFmt(
gpa,
pt.tid,
"@typeInfo({f}).@\"union\".tag_type.?",
.{union_name.fmt(ip)},
.no_embedded_nulls,
);
const enum_ty = try ip.getGeneratedTagEnumType(gpa, pt.tid, .{
.name = name,
.owner_union_ty = union_type,
.tag_ty = if (enum_field_vals.len == 0)
(try pt.intType(.unsigned, 0)).toIntern()
else
ip.typeOf(enum_field_vals[0]),
.names = enum_field_names,
.values = enum_field_vals,
.tag_mode = .explicit,
.parent_namespace = block.namespace,
});
return enum_ty;
}
fn generateUnionTagTypeSimple(
sema: *Sema,
block: *Block,
enum_field_names: []const InternPool.NullTerminatedString,
union_type: InternPool.Index,
union_name: InternPool.NullTerminatedString,
) !InternPool.Index {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const gpa = sema.gpa;
const name = try ip.getOrPutStringFmt(
gpa,
pt.tid,
"@typeInfo({f}).@\"union\".tag_type.?",
.{union_name.fmt(ip)},
.no_embedded_nulls,
);
const enum_ty = try ip.getGeneratedTagEnumType(gpa, pt.tid, .{
.name = name,
.owner_union_ty = union_type,
.tag_ty = (try pt.smallestUnsignedInt(enum_field_names.len -| 1)).toIntern(),
.names = enum_field_names,
.values = &.{},
.tag_mode = .auto,
.parent_namespace = block.namespace,
});
return enum_ty;
}
/// There is another implementation of this in `Type.onePossibleValue`. This one
/// in `Sema` is for calling during semantic analysis, and performs field resolution
/// to get the answer. The one in `Type` is for calling during codegen and asserts
/// that the types are already resolved.
/// TODO assert the return value matches `ty.onePossibleValue`
pub fn typeHasOnePossibleValue(sema: *Sema, ty: Type) CompileError!?Value {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
return switch (ty.toIntern()) {
.u0_type,
.i0_type,
=> try pt.intValue(ty, 0),
.u1_type,
.u8_type,
.i8_type,
.u16_type,
.i16_type,
.u29_type,
.u32_type,
.i32_type,
.u64_type,
.i64_type,
.u80_type,
.u128_type,
.i128_type,
.u256_type,
.usize_type,
.isize_type,
.c_char_type,
.c_short_type,
.c_ushort_type,
.c_int_type,
.c_uint_type,
.c_long_type,
.c_ulong_type,
.c_longlong_type,
.c_ulonglong_type,
.c_longdouble_type,
.f16_type,
.f32_type,
.f64_type,
.f80_type,
.f128_type,
.anyopaque_type,
.bool_type,
.type_type,
.anyerror_type,
.adhoc_inferred_error_set_type,
.comptime_int_type,
.comptime_float_type,
.enum_literal_type,
.ptr_usize_type,
.ptr_const_comptime_int_type,
.manyptr_u8_type,
.manyptr_const_u8_type,
.manyptr_const_u8_sentinel_0_type,
.manyptr_const_slice_const_u8_type,
.slice_const_u8_type,
.slice_const_u8_sentinel_0_type,
.slice_const_slice_const_u8_type,
.optional_type_type,
.manyptr_const_type_type,
.slice_const_type_type,
.vector_8_i8_type,
.vector_16_i8_type,
.vector_32_i8_type,
.vector_64_i8_type,
.vector_1_u8_type,
.vector_2_u8_type,
.vector_4_u8_type,
.vector_8_u8_type,
.vector_16_u8_type,
.vector_32_u8_type,
.vector_64_u8_type,
.vector_2_i16_type,
.vector_4_i16_type,
.vector_8_i16_type,
.vector_16_i16_type,
.vector_32_i16_type,
.vector_4_u16_type,
.vector_8_u16_type,
.vector_16_u16_type,
.vector_32_u16_type,
.vector_2_i32_type,
.vector_4_i32_type,
.vector_8_i32_type,
.vector_16_i32_type,
.vector_4_u32_type,
.vector_8_u32_type,
.vector_16_u32_type,
.vector_2_i64_type,
.vector_4_i64_type,
.vector_8_i64_type,
.vector_2_u64_type,
.vector_4_u64_type,
.vector_8_u64_type,
.vector_1_u128_type,
.vector_2_u128_type,
.vector_1_u256_type,
.vector_4_f16_type,
.vector_8_f16_type,
.vector_16_f16_type,
.vector_32_f16_type,
.vector_2_f32_type,
.vector_4_f32_type,
.vector_8_f32_type,
.vector_16_f32_type,
.vector_2_f64_type,
.vector_4_f64_type,
.vector_8_f64_type,
.anyerror_void_error_union_type,
=> null,
.void_type => Value.void,
.noreturn_type => Value.@"unreachable",
.anyframe_type => unreachable,
.null_type => Value.null,
.undefined_type => Value.undef,
.optional_noreturn_type => try pt.nullValue(ty),
.generic_poison_type => unreachable,
.empty_tuple_type => Value.empty_tuple,
// values, not types
.undef,
.undef_bool,
.undef_usize,
.undef_u1,
.zero,
.zero_usize,
.zero_u1,
.zero_u8,
.one,
.one_usize,
.one_u1,
.one_u8,
.four_u8,
.negative_one,
.void_value,
.unreachable_value,
.null_value,
.bool_true,
.bool_false,
.empty_tuple,
// invalid
.none,
=> unreachable,
_ => switch (ty.toIntern().unwrap(ip).getTag(ip)) {
.removed => unreachable,
.type_int_signed, // i0 handled above
.type_int_unsigned, // u0 handled above
.type_pointer,
.type_slice,
.type_anyframe,
.type_error_union,
.type_anyerror_union,
.type_error_set,
.type_inferred_error_set,
.type_opaque,
.type_function,
=> null,
.simple_type, // handled above
// values, not types
.undef,
.simple_value,
.ptr_nav,
.ptr_uav,
.ptr_uav_aligned,
.ptr_comptime_alloc,
.ptr_comptime_field,
.ptr_int,
.ptr_eu_payload,
.ptr_opt_payload,
.ptr_elem,
.ptr_field,
.ptr_slice,
.opt_payload,
.opt_null,
.int_u8,
.int_u16,
.int_u32,
.int_i32,
.int_usize,
.int_comptime_int_u32,
.int_comptime_int_i32,
.int_small,
.int_positive,
.int_negative,
.int_lazy_align,
.int_lazy_size,
.error_set_error,
.error_union_error,
.error_union_payload,
.enum_literal,
.enum_tag,
.float_f16,
.float_f32,
.float_f64,
.float_f80,
.float_f128,
.float_c_longdouble_f80,
.float_c_longdouble_f128,
.float_comptime_float,
.variable,
.threadlocal_variable,
.@"extern",
.func_decl,
.func_instance,
.func_coerced,
.only_possible_value,
.union_value,
.bytes,
.aggregate,
.repeated,
// memoized value, not types
.memoized_call,
=> unreachable,
.type_array_big,
.type_array_small,
.type_vector,
.type_enum_auto,
.type_enum_explicit,
.type_enum_nonexhaustive,
.type_struct,
.type_struct_packed,
.type_struct_packed_inits,
.type_tuple,
.type_union,
=> switch (ip.indexToKey(ty.toIntern())) {
inline .array_type, .vector_type => |seq_type, seq_tag| {
const has_sentinel = seq_tag == .array_type and seq_type.sentinel != .none;
if (seq_type.len + @intFromBool(has_sentinel) == 0) return try pt.aggregateValue(ty, &.{});
if (try sema.typeHasOnePossibleValue(.fromInterned(seq_type.child))) |opv| {
return try pt.aggregateSplatValue(ty, opv);
}
return null;
},
.struct_type => {
// Resolving the layout first helps to avoid loops.
// If the type has a coherent layout, we can recurse through fields safely.
try ty.resolveLayout(pt);
const struct_type = ip.loadStructType(ty.toIntern());
if (struct_type.field_types.len == 0) {
// In this case the struct has no fields at all and
// therefore has one possible value.
return try pt.aggregateValue(ty, &.{});
}
const field_vals = try sema.arena.alloc(
InternPool.Index,
struct_type.field_types.len,
);
for (field_vals, 0..) |*field_val, i| {
if (struct_type.fieldIsComptime(ip, i)) {
try ty.resolveStructFieldInits(pt);
field_val.* = struct_type.field_inits.get(ip)[i];
continue;
}
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[i]);
if (try sema.typeHasOnePossibleValue(field_ty)) |field_opv| {
field_val.* = field_opv.toIntern();
} else return null;
}
// In this case the struct has no runtime-known fields and
// therefore has one possible value.
return try pt.aggregateValue(ty, field_vals);
},
.tuple_type => |tuple| {
try ty.resolveLayout(pt);
if (tuple.types.len == 0) {
return try pt.aggregateValue(ty, &.{});
}
const field_vals = try sema.arena.alloc(
InternPool.Index,
tuple.types.len,
);
for (
field_vals,
tuple.types.get(ip),
tuple.values.get(ip),
) |*field_val, field_ty, field_comptime_val| {
if (field_comptime_val != .none) {
field_val.* = field_comptime_val;
continue;
}
if (try sema.typeHasOnePossibleValue(.fromInterned(field_ty))) |opv| {
field_val.* = opv.toIntern();
} else return null;
}
return try pt.aggregateValue(ty, field_vals);
},
.union_type => {
// Resolving the layout first helps to avoid loops.
// If the type has a coherent layout, we can recurse through fields safely.
try ty.resolveLayout(pt);
const union_obj = ip.loadUnionType(ty.toIntern());
const tag_val = (try sema.typeHasOnePossibleValue(.fromInterned(union_obj.tagTypeUnordered(ip)))) orelse
return null;
if (union_obj.field_types.len == 0) {
const only = try pt.intern(.{ .empty_enum_value = ty.toIntern() });
return Value.fromInterned(only);
}
const only_field_ty: Type = .fromInterned(union_obj.field_types.get(ip)[0]);
const val_val = (try sema.typeHasOnePossibleValue(only_field_ty)) orelse
return null;
const only = try pt.internUnion(.{
.ty = ty.toIntern(),
.tag = tag_val.toIntern(),
.val = val_val.toIntern(),
});
return Value.fromInterned(only);
},
.enum_type => {
const enum_type = ip.loadEnumType(ty.toIntern());
switch (enum_type.tag_mode) {
.nonexhaustive => {
if (enum_type.tag_ty == .comptime_int_type) return null;
if (try sema.typeHasOnePossibleValue(.fromInterned(enum_type.tag_ty))) |int_opv| {
const only = try pt.intern(.{ .enum_tag = .{
.ty = ty.toIntern(),
.int = int_opv.toIntern(),
} });
return Value.fromInterned(only);
}
return null;
},
.auto, .explicit => {
if (Type.fromInterned(enum_type.tag_ty).hasRuntimeBits(zcu)) return null;
return Value.fromInterned(switch (enum_type.names.len) {
0 => try pt.intern(.{ .empty_enum_value = ty.toIntern() }),
1 => try pt.intern(.{ .enum_tag = .{
.ty = ty.toIntern(),
.int = if (enum_type.values.len == 0)
(try pt.intValue(.fromInterned(enum_type.tag_ty), 0)).toIntern()
else
try ip.getCoercedInts(
zcu.gpa,
pt.tid,
ip.indexToKey(enum_type.values.get(ip)[0]).int,
enum_type.tag_ty,
),
} }),
else => return null,
});
},
}
},
else => unreachable,
},
.type_optional => {
const payload_ip = ip.indexToKey(ty.toIntern()).opt_type;
// Although ?noreturn is handled above, the element type
// can be effectively noreturn for example via an empty
// enum or error set.
if (ip.isNoReturn(payload_ip)) return try pt.nullValue(ty);
return null;
},
},
};
}
/// Returns the type of the AIR instruction.
fn typeOf(sema: *Sema, inst: Air.Inst.Ref) Type {
return sema.getTmpAir().typeOf(inst, &sema.pt.zcu.intern_pool);
}
pub fn getTmpAir(sema: Sema) Air {
return .{
.instructions = sema.air_instructions.slice(),
.extra = sema.air_extra,
};
}
pub fn addExtra(sema: *Sema, extra: anytype) Allocator.Error!u32 {
const fields = std.meta.fields(@TypeOf(extra));
try sema.air_extra.ensureUnusedCapacity(sema.gpa, fields.len);
return sema.addExtraAssumeCapacity(extra);
}
pub fn addExtraAssumeCapacity(sema: *Sema, extra: anytype) u32 {
const result: u32 = @intCast(sema.air_extra.items.len);
sema.air_extra.appendSliceAssumeCapacity(&payloadToExtraItems(extra));
return result;
}
fn payloadToExtraItems(data: anytype) [@typeInfo(@TypeOf(data)).@"struct".fields.len]u32 {
const fields = @typeInfo(@TypeOf(data)).@"struct".fields;
var result: [fields.len]u32 = undefined;
inline for (&result, fields) |*val, field| {
val.* = switch (field.type) {
u32 => @field(data, field.name),
i32, Air.CondBr.BranchHints, Air.Asm.Flags => @bitCast(@field(data, field.name)),
Air.Inst.Ref, InternPool.Index => @intFromEnum(@field(data, field.name)),
else => @compileError("bad field type: " ++ @typeName(field.type)),
};
}
return result;
}
fn appendRefsAssumeCapacity(sema: *Sema, refs: []const Air.Inst.Ref) void {
sema.air_extra.appendSliceAssumeCapacity(@ptrCast(refs));
}
fn getBreakBlock(sema: *Sema, inst_index: Air.Inst.Index) ?Air.Inst.Index {
const air_datas = sema.air_instructions.items(.data);
const air_tags = sema.air_instructions.items(.tag);
switch (air_tags[@intFromEnum(inst_index)]) {
.br => return air_datas[@intFromEnum(inst_index)].br.block_inst,
else => return null,
}
}
fn isComptimeKnown(
sema: *Sema,
inst: Air.Inst.Ref,
) !bool {
return (try sema.resolveValue(inst)) != null;
}
fn analyzeComptimeAlloc(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
var_type: Type,
alignment: Alignment,
) CompileError!Air.Inst.Ref {
const pt = sema.pt;
const zcu = pt.zcu;
// Needed to make an anon decl with type `var_type` (the `finish()` call below).
_ = try sema.typeHasOnePossibleValue(var_type);
const ptr_type = try pt.ptrTypeSema(.{
.child = var_type.toIntern(),
.flags = .{
.alignment = alignment,
.address_space = target_util.defaultAddressSpace(zcu.getTarget(), .global_constant),
},
});
const alloc = try sema.newComptimeAlloc(block, src, var_type, alignment);
return Air.internedToRef((try pt.intern(.{ .ptr = .{
.ty = ptr_type.toIntern(),
.base_addr = .{ .comptime_alloc = alloc },
.byte_offset = 0,
} })));
}
fn resolveAddressSpace(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
zir_ref: Zir.Inst.Ref,
ctx: std.Target.AddressSpaceContext,
) !std.builtin.AddressSpace {
const air_ref = try sema.resolveInst(zir_ref);
return sema.analyzeAsAddressSpace(block, src, air_ref, ctx);
}
pub fn analyzeAsAddressSpace(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
air_ref: Air.Inst.Ref,
ctx: std.Target.AddressSpaceContext,
) !std.builtin.AddressSpace {
const pt = sema.pt;
const addrspace_ty = try sema.getBuiltinType(src, .AddressSpace);
const coerced = try sema.coerce(block, addrspace_ty, air_ref, src);
const addrspace_val = try sema.resolveConstDefinedValue(block, src, coerced, .{ .simple = .@"addrspace" });
const address_space = try sema.interpretBuiltinType(block, src, addrspace_val, std.builtin.AddressSpace);
const target = pt.zcu.getTarget();
if (!target.supportsAddressSpace(address_space, ctx)) {
// TODO error messages could be made more elaborate here
const entity = switch (ctx) {
.function => "functions",
.variable => "mutable values",
.constant => "constant values",
.pointer => "pointers",
};
return sema.fail(
block,
src,
"{s} with address space '{s}' are not supported on {s}",
.{ entity, @tagName(address_space), @tagName(target.cpu.arch.family()) },
);
}
return address_space;
}
/// Asserts the value is a pointer and dereferences it.
/// Returns `null` if the pointer contents cannot be loaded at comptime.
fn pointerDeref(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, ptr_ty: Type) CompileError!?Value {
// TODO: audit use sites to eliminate this coercion
const pt = sema.pt;
const coerced_ptr_val = try pt.getCoerced(ptr_val, ptr_ty);
switch (try sema.pointerDerefExtra(block, src, coerced_ptr_val)) {
.runtime_load => return null,
.val => |v| return v,
.needed_well_defined => |ty| return sema.fail(
block,
src,
"comptime dereference requires '{f}' to have a well-defined layout",
.{ty.fmt(pt)},
),
.out_of_bounds => |ty| return sema.fail(
block,
src,
"dereference of '{f}' exceeds bounds of containing decl of type '{f}'",
.{ ptr_ty.fmt(pt), ty.fmt(pt) },
),
}
}
const DerefResult = union(enum) {
runtime_load,
val: Value,
needed_well_defined: Type,
out_of_bounds: Type,
};
fn pointerDerefExtra(sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value) CompileError!DerefResult {
const pt = sema.pt;
const ip = &pt.zcu.intern_pool;
switch (try sema.loadComptimePtr(block, src, ptr_val)) {
.success => |mv| return .{ .val = try mv.intern(pt, sema.arena) },
.runtime_load => return .runtime_load,
.undef => return sema.failWithUseOfUndef(block, src, null),
.err_payload => |err_name| return sema.fail(block, src, "attempt to unwrap error: {f}", .{err_name.fmt(ip)}),
.null_payload => return sema.fail(block, src, "attempt to use null value", .{}),
.inactive_union_field => return sema.fail(block, src, "access of inactive union field", .{}),
.needed_well_defined => |ty| return .{ .needed_well_defined = ty },
.out_of_bounds => |ty| return .{ .out_of_bounds = ty },
.exceeds_host_size => return sema.fail(block, src, "bit-pointer target exceeds host size", .{}),
}
}
/// Used to convert a u64 value to a usize value, emitting a compile error if the number
/// is too big to fit.
fn usizeCast(sema: *Sema, block: *Block, src: LazySrcLoc, int: u64) CompileError!usize {
if (@bitSizeOf(u64) <= @bitSizeOf(usize)) return int;
return std.math.cast(usize, int) orelse return sema.fail(block, src, "expression produces integer value '{d}' which is too big for this compiler implementation to handle", .{int});
}
/// For pointer-like optionals, it returns the pointer type. For pointers,
/// the type is returned unmodified.
/// This can return `error.AnalysisFail` because it sometimes requires resolving whether
/// a type has zero bits, which can cause a "foo depends on itself" compile error.
/// This logic must be kept in sync with `Type.isPtrLikeOptional`.
fn typePtrOrOptionalPtrTy(sema: *Sema, ty: Type) !?Type {
const pt = sema.pt;
const zcu = pt.zcu;
return switch (zcu.intern_pool.indexToKey(ty.toIntern())) {
.ptr_type => |ptr_type| switch (ptr_type.flags.size) {
.one, .many, .c => ty,
.slice => null,
},
.opt_type => |opt_child| switch (zcu.intern_pool.indexToKey(opt_child)) {
.ptr_type => |ptr_type| switch (ptr_type.flags.size) {
.slice, .c => null,
.many, .one => {
if (ptr_type.flags.is_allowzero) return null;
// optionals of zero sized types behave like bools, not pointers
const payload_ty: Type = .fromInterned(opt_child);
if ((try sema.typeHasOnePossibleValue(payload_ty)) != null) {
return null;
}
return payload_ty;
},
},
else => null,
},
else => null,
};
}
fn unionFieldIndex(
sema: *Sema,
block: *Block,
union_ty: Type,
field_name: InternPool.NullTerminatedString,
field_src: LazySrcLoc,
) !u32 {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
try union_ty.resolveFields(pt);
const union_obj = zcu.typeToUnion(union_ty).?;
const field_index = union_obj.loadTagType(ip).nameIndex(ip, field_name) orelse
return sema.failWithBadUnionFieldAccess(block, union_ty, union_obj, field_src, field_name);
return @intCast(field_index);
}
fn structFieldIndex(
sema: *Sema,
block: *Block,
struct_ty: Type,
field_name: InternPool.NullTerminatedString,
field_src: LazySrcLoc,
) !u32 {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
try struct_ty.resolveFields(pt);
const struct_type = zcu.typeToStruct(struct_ty).?;
return struct_type.nameIndex(ip, field_name) orelse
return sema.failWithBadStructFieldAccess(block, struct_ty, struct_type, field_src, field_name);
}
const IntFromFloatMode = enum { exact, truncate };
fn intFromFloat(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
val: Value,
float_ty: Type,
int_ty: Type,
mode: IntFromFloatMode,
) CompileError!Value {
const pt = sema.pt;
const zcu = pt.zcu;
if (float_ty.zigTypeTag(zcu) == .vector) {
const result_data = try sema.arena.alloc(InternPool.Index, float_ty.vectorLen(zcu));
for (result_data, 0..) |*scalar, elem_idx| {
const elem_val = try val.elemValue(pt, elem_idx);
scalar.* = (try sema.intFromFloatScalar(block, src, elem_val, int_ty.scalarType(zcu), mode, elem_idx)).toIntern();
}
return pt.aggregateValue(int_ty, result_data);
}
return sema.intFromFloatScalar(block, src, val, int_ty, mode, null);
}
fn intFromFloatScalar(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
val: Value,
int_ty: Type,
mode: IntFromFloatMode,
vec_idx: ?usize,
) CompileError!Value {
const pt = sema.pt;
const zcu = pt.zcu;
if (val.isUndef(zcu)) return sema.failWithUseOfUndef(block, src, vec_idx);
const float = val.toFloat(f128, zcu);
if (std.math.isNan(float)) {
return sema.fail(block, src, "float value NaN cannot be stored in integer type '{f}'", .{
int_ty.fmt(pt),
});
}
if (std.math.isInf(float)) {
return sema.fail(block, src, "float value Inf cannot be stored in integer type '{f}'", .{
int_ty.fmt(pt),
});
}
var big_int: std.math.big.int.Mutable = .{
.limbs = try sema.arena.alloc(std.math.big.Limb, std.math.big.int.calcLimbLen(float)),
.len = undefined,
.positive = undefined,
};
switch (big_int.setFloat(float, .trunc)) {
.inexact => switch (mode) {
.exact => return sema.fail(
block,
src,
"fractional component prevents float value '{f}' from coercion to type '{f}'",
.{ val.fmtValueSema(pt, sema), int_ty.fmt(pt) },
),
.truncate => {},
},
.exact => {},
}
const cti_result = try pt.intValue_big(.comptime_int, big_int.toConst());
if (int_ty.toIntern() == .comptime_int_type) return cti_result;
const int_info = int_ty.intInfo(zcu);
if (!big_int.toConst().fitsInTwosComp(int_info.signedness, int_info.bits)) {
return sema.fail(block, src, "float value '{f}' cannot be stored in integer type '{f}'", .{
val.fmtValueSema(pt, sema), int_ty.fmt(pt),
});
}
return pt.getCoerced(cti_result, int_ty);
}
/// Asserts the value is an integer, and the destination type is ComptimeInt or Int.
/// Vectors are also accepted. Vector results are reduced with AND.
///
/// If provided, `vector_index` reports the first element that failed the range check.
fn intFitsInType(
sema: *Sema,
val: Value,
ty: Type,
vector_index: ?*usize,
) CompileError!bool {
const pt = sema.pt;
const zcu = pt.zcu;
if (ty.toIntern() == .comptime_int_type) return true;
const info = ty.intInfo(zcu);
switch (val.toIntern()) {
.zero_usize, .zero_u8 => return true,
else => switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.undef => return true,
.variable, .@"extern", .func, .ptr => {
const target = zcu.getTarget();
const ptr_bits = target.ptrBitWidth();
return switch (info.signedness) {
.signed => info.bits > ptr_bits,
.unsigned => info.bits >= ptr_bits,
};
},
.int => |int| switch (int.storage) {
.u64, .i64, .big_int => {
var buffer: InternPool.Key.Int.Storage.BigIntSpace = undefined;
const big_int = int.storage.toBigInt(&buffer);
return big_int.fitsInTwosComp(info.signedness, info.bits);
},
.lazy_align => |lazy_ty| {
const max_needed_bits = @as(u16, 16) + @intFromBool(info.signedness == .signed);
// If it is u16 or bigger we know the alignment fits without resolving it.
if (info.bits >= max_needed_bits) return true;
const x = try Type.fromInterned(lazy_ty).abiAlignmentSema(pt);
if (x == .none) return true;
const actual_needed_bits = @as(usize, x.toLog2Units()) + 1 + @intFromBool(info.signedness == .signed);
return info.bits >= actual_needed_bits;
},
.lazy_size => |lazy_ty| {
const max_needed_bits = @as(u16, 64) + @intFromBool(info.signedness == .signed);
// If it is u64 or bigger we know the size fits without resolving it.
if (info.bits >= max_needed_bits) return true;
const x = try Type.fromInterned(lazy_ty).abiSizeSema(pt);
if (x == 0) return true;
const actual_needed_bits = std.math.log2(x) + 1 + @intFromBool(info.signedness == .signed);
return info.bits >= actual_needed_bits;
},
},
.aggregate => |aggregate| {
assert(ty.zigTypeTag(zcu) == .vector);
return switch (aggregate.storage) {
.bytes => |bytes| for (bytes.toSlice(ty.vectorLen(zcu), &zcu.intern_pool), 0..) |byte, i| {
if (byte == 0) continue;
const actual_needed_bits = std.math.log2(byte) + 1 + @intFromBool(info.signedness == .signed);
if (info.bits >= actual_needed_bits) continue;
if (vector_index) |vi| vi.* = i;
break false;
} else true,
.elems, .repeated_elem => for (switch (aggregate.storage) {
.bytes => unreachable,
.elems => |elems| elems,
.repeated_elem => |elem| @as(*const [1]InternPool.Index, &elem),
}, 0..) |elem, i| {
if (try sema.intFitsInType(Value.fromInterned(elem), ty.scalarType(zcu), null)) continue;
if (vector_index) |vi| vi.* = i;
break false;
} else true,
};
},
else => unreachable,
},
}
}
fn intInRange(sema: *Sema, tag_ty: Type, int_val: Value, end: usize) !bool {
const pt = sema.pt;
if (!(try int_val.compareAllWithZeroSema(.gte, pt))) return false;
const end_val = try pt.intValue(tag_ty, end);
if (!(try sema.compareAll(int_val, .lt, end_val, tag_ty))) return false;
return true;
}
/// Asserts the type is an enum.
fn enumHasInt(sema: *Sema, ty: Type, int: Value) CompileError!bool {
const pt = sema.pt;
const zcu = pt.zcu;
const enum_type = zcu.intern_pool.loadEnumType(ty.toIntern());
assert(enum_type.tag_mode != .nonexhaustive);
// The `tagValueIndex` function call below relies on the type being the integer tag type.
// `getCoerced` assumes the value will fit the new type.
if (!(try sema.intFitsInType(int, .fromInterned(enum_type.tag_ty), null))) return false;
const int_coerced = try pt.getCoerced(int, .fromInterned(enum_type.tag_ty));
return enum_type.tagValueIndex(&zcu.intern_pool, int_coerced.toIntern()) != null;
}
/// Asserts the values are comparable. Both operands have type `ty`.
/// For vectors, returns true if the comparison is true for ALL elements.
///
/// Note that `!compareAll(.eq, ...) != compareAll(.neq, ...)`
fn compareAll(
sema: *Sema,
lhs: Value,
op: std.math.CompareOperator,
rhs: Value,
ty: Type,
) CompileError!bool {
const pt = sema.pt;
const zcu = pt.zcu;
if (ty.zigTypeTag(zcu) == .vector) {
var i: usize = 0;
while (i < ty.vectorLen(zcu)) : (i += 1) {
const lhs_elem = try lhs.elemValue(pt, i);
const rhs_elem = try rhs.elemValue(pt, i);
if (!(try sema.compareScalar(lhs_elem, op, rhs_elem, ty.scalarType(zcu)))) {
return false;
}
}
return true;
}
return sema.compareScalar(lhs, op, rhs, ty);
}
/// Asserts the values are comparable. Both operands have type `ty`.
fn compareScalar(
sema: *Sema,
lhs: Value,
op: std.math.CompareOperator,
rhs: Value,
ty: Type,
) CompileError!bool {
const pt = sema.pt;
const coerced_lhs = try pt.getCoerced(lhs, ty);
const coerced_rhs = try pt.getCoerced(rhs, ty);
// Equality comparisons of signed zero and NaN need to use floating point semantics
if (coerced_lhs.isFloat(pt.zcu) or coerced_rhs.isFloat(pt.zcu))
return Value.compareHeteroSema(coerced_lhs, op, coerced_rhs, pt);
switch (op) {
.eq => return sema.valuesEqual(coerced_lhs, coerced_rhs, ty),
.neq => return !(try sema.valuesEqual(coerced_lhs, coerced_rhs, ty)),
else => return Value.compareHeteroSema(coerced_lhs, op, coerced_rhs, pt),
}
}
fn valuesEqual(
sema: *Sema,
lhs: Value,
rhs: Value,
ty: Type,
) CompileError!bool {
return lhs.eql(rhs, ty, sema.pt.zcu);
}
/// Asserts the values are comparable vectors of type `ty`.
fn compareVector(
sema: *Sema,
lhs: Value,
op: std.math.CompareOperator,
rhs: Value,
ty: Type,
) !Value {
const pt = sema.pt;
const zcu = pt.zcu;
assert(ty.zigTypeTag(zcu) == .vector);
const result_data = try sema.arena.alloc(InternPool.Index, ty.vectorLen(zcu));
for (result_data, 0..) |*scalar, i| {
const lhs_elem = try lhs.elemValue(pt, i);
const rhs_elem = try rhs.elemValue(pt, i);
if (lhs_elem.isUndef(zcu) or rhs_elem.isUndef(zcu)) {
scalar.* = .undef_bool;
} else {
const res_bool = try sema.compareScalar(lhs_elem, op, rhs_elem, ty.scalarType(zcu));
scalar.* = Value.makeBool(res_bool).toIntern();
}
}
return pt.aggregateValue(try pt.vectorType(.{
.len = ty.vectorLen(zcu),
.child = .bool_type,
}), result_data);
}
/// Merge lhs with rhs.
/// Asserts that lhs and rhs are both error sets and are resolved.
fn errorSetMerge(sema: *Sema, lhs: Type, rhs: Type) !Type {
const pt = sema.pt;
const ip = &pt.zcu.intern_pool;
const arena = sema.arena;
const lhs_names = lhs.errorSetNames(pt.zcu);
const rhs_names = rhs.errorSetNames(pt.zcu);
var names: InferredErrorSet.NameMap = .{};
try names.ensureUnusedCapacity(arena, lhs_names.len);
for (0..lhs_names.len) |lhs_index| {
names.putAssumeCapacityNoClobber(lhs_names.get(ip)[lhs_index], {});
}
for (0..rhs_names.len) |rhs_index| {
try names.put(arena, rhs_names.get(ip)[rhs_index], {});
}
return pt.errorSetFromUnsortedNames(names.keys());
}
/// Avoids crashing the compiler when asking if inferred allocations are noreturn.
fn isNoReturn(sema: *Sema, ref: Air.Inst.Ref) bool {
if (ref == .unreachable_value) return true;
if (ref.toIndex()) |inst| switch (sema.air_instructions.items(.tag)[@intFromEnum(inst)]) {
.inferred_alloc, .inferred_alloc_comptime => return false,
else => {},
};
return sema.typeOf(ref).isNoReturn(sema.pt.zcu);
}
/// Avoids crashing the compiler when asking if inferred allocations are known to be a certain zig type.
fn isKnownZigType(sema: *Sema, ref: Air.Inst.Ref, tag: std.builtin.TypeId) bool {
if (ref.toIndex()) |inst| switch (sema.air_instructions.items(.tag)[@intFromEnum(inst)]) {
.inferred_alloc, .inferred_alloc_comptime => return false,
else => {},
};
return sema.typeOf(ref).zigTypeTag(sema.pt.zcu) == tag;
}
pub fn declareDependency(sema: *Sema, dependee: InternPool.Dependee) !void {
const pt = sema.pt;
if (!pt.zcu.comp.config.incremental) return;
const gop = try sema.dependencies.getOrPut(sema.gpa, dependee);
if (gop.found_existing) return;
// Avoid creating dependencies on ourselves. This situation can arise when we analyze the fields
// of a type and they use `@This()`. This dependency would be unnecessary, and in fact would
// just result in over-analysis since `Zcu.findOutdatedToAnalyze` would never be able to resolve
// the loop.
// Note that this also disallows a `nav_val`
switch (sema.owner.unwrap()) {
.nav_val => |this_nav| switch (dependee) {
.nav_val => |other_nav| if (this_nav == other_nav) return,
else => {},
},
.nav_ty => |this_nav| switch (dependee) {
.nav_ty => |other_nav| if (this_nav == other_nav) return,
else => {},
},
else => {},
}
try pt.addDependency(sema.owner, dependee);
}
fn isComptimeMutablePtr(sema: *Sema, val: Value) bool {
return switch (sema.pt.zcu.intern_pool.indexToKey(val.toIntern())) {
.slice => |slice| sema.isComptimeMutablePtr(Value.fromInterned(slice.ptr)),
.ptr => |ptr| switch (ptr.base_addr) {
.uav, .nav, .int => false,
.comptime_field => true,
.comptime_alloc => |alloc_index| !sema.getComptimeAlloc(alloc_index).is_const,
.eu_payload, .opt_payload => |base| sema.isComptimeMutablePtr(Value.fromInterned(base)),
.arr_elem, .field => |bi| sema.isComptimeMutablePtr(Value.fromInterned(bi.base)),
},
else => false,
};
}
fn checkRuntimeValue(sema: *Sema, ptr: Air.Inst.Ref) bool {
const val = ptr.toInterned() orelse return true;
return !Value.fromInterned(val).canMutateComptimeVarState(sema.pt.zcu);
}
fn validateRuntimeValue(sema: *Sema, block: *Block, val_src: LazySrcLoc, val: Air.Inst.Ref) CompileError!void {
if (sema.checkRuntimeValue(val)) return;
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(val_src, "runtime value contains reference to comptime var", .{});
errdefer msg.destroy(sema.gpa);
try sema.errNote(val_src, msg, "comptime var pointers are not available at runtime", .{});
const pt = sema.pt;
const zcu = pt.zcu;
const val_str = try zcu.intern_pool.getOrPutString(zcu.gpa, pt.tid, "runtime_value", .no_embedded_nulls);
try sema.explainWhyValueContainsReferenceToComptimeVar(msg, val_src, val_str, .fromInterned(val.toInterned().?));
break :msg msg;
});
}
fn failWithContainsReferenceToComptimeVar(sema: *Sema, block: *Block, src: LazySrcLoc, value_name: InternPool.NullTerminatedString, kind_of_value: []const u8, val: ?Value) CompileError {
return sema.failWithOwnedErrorMsg(block, msg: {
const msg = try sema.errMsg(src, "{s} contains reference to comptime var", .{kind_of_value});
errdefer msg.destroy(sema.gpa);
if (val) |v| try sema.explainWhyValueContainsReferenceToComptimeVar(msg, src, value_name, v);
break :msg msg;
});
}
fn explainWhyValueContainsReferenceToComptimeVar(sema: *Sema, msg: *Zcu.ErrorMsg, src: LazySrcLoc, value_name: InternPool.NullTerminatedString, val: Value) Allocator.Error!void {
// Our goal is something like this:
// note: '(value.? catch unreachable)[0]' points to 'v0.?.foo'
// note: '(v0.?.bar catch unreachable)' points to 'v1'
// note: 'v1.?' points to a comptime var
var intermediate_value_count: u32 = 0;
var cur_val: Value = val;
while (true) {
switch (try sema.notePathToComptimeAllocPtr(msg, src, cur_val, intermediate_value_count, value_name)) {
.done => return,
.new_val => |new_val| {
intermediate_value_count += 1;
cur_val = new_val;
},
}
}
}
fn notePathToComptimeAllocPtr(
sema: *Sema,
msg: *Zcu.ErrorMsg,
src: LazySrcLoc,
val: Value,
intermediate_value_count: u32,
start_value_name: InternPool.NullTerminatedString,
) Allocator.Error!union(enum) {
done,
new_val: Value,
} {
const arena = sema.arena;
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
var first_path: std.ArrayList(u8) = .empty;
if (intermediate_value_count == 0) {
try first_path.print(arena, "{f}", .{start_value_name.fmt(ip)});
} else {
try first_path.print(arena, "v{d}", .{intermediate_value_count - 1});
}
const comptime_ptr = try sema.notePathToComptimeAllocPtrInner(val, &first_path);
switch (ip.indexToKey(comptime_ptr.toIntern()).ptr.base_addr) {
.comptime_field => {
try sema.errNote(src, msg, "'{s}' points to comptime field", .{first_path.items});
return .done;
},
.comptime_alloc => |idx| {
const cta = sema.getComptimeAlloc(idx);
if (!cta.is_const) {
try sema.errNote(cta.src, msg, "'{s}' points to comptime var declared here", .{first_path.items});
return .done;
}
},
else => {}, // there will be another stage
}
const derivation = comptime_ptr.pointerDerivationAdvanced(arena, pt, false, sema) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.Canceled => @panic("TODO"), // pls don't be cancelable mlugg
error.AnalysisFail => unreachable,
};
var second_path_aw: std.Io.Writer.Allocating = .init(arena);
defer second_path_aw.deinit();
const inter_name = try std.fmt.allocPrint(arena, "v{d}", .{intermediate_value_count});
const deriv_start = @import("print_value.zig").printPtrDerivation(
derivation,
&second_path_aw.writer,
pt,
.lvalue,
.{ .str = inter_name },
20,
) catch return error.OutOfMemory;
switch (deriv_start) {
.int, .nav_ptr => unreachable,
.uav_ptr => |uav| {
try sema.errNote(src, msg, "'{s}' points to '{s}', where", .{ first_path.items, second_path_aw.written() });
return .{ .new_val = .fromInterned(uav.val) };
},
.comptime_alloc_ptr => |cta_info| {
try sema.errNote(src, msg, "'{s}' points to '{s}', where", .{ first_path.items, second_path_aw.written() });
const cta = sema.getComptimeAlloc(cta_info.idx);
if (cta.is_const) {
return .{ .new_val = cta_info.val };
} else {
try sema.errNote(cta.src, msg, "'{s}' is a comptime var declared here", .{inter_name});
return .done;
}
},
.comptime_field_ptr => {
try sema.errNote(src, msg, "'{s}' points to '{s}', where", .{ first_path.items, second_path_aw.written() });
try sema.errNote(src, msg, "'{s}' is a comptime field", .{inter_name});
return .done;
},
.eu_payload_ptr,
.opt_payload_ptr,
.field_ptr,
.elem_ptr,
.offset_and_cast,
=> unreachable,
}
}
fn notePathToComptimeAllocPtrInner(sema: *Sema, val: Value, path: *std.ArrayList(u8)) Allocator.Error!Value {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const arena = sema.arena;
assert(val.canMutateComptimeVarState(zcu));
switch (ip.indexToKey(val.toIntern())) {
.ptr => return val,
.error_union => |eu| {
try path.insert(arena, 0, '(');
try path.appendSlice(arena, " catch unreachable)");
return sema.notePathToComptimeAllocPtrInner(.fromInterned(eu.val.payload), path);
},
.slice => |slice| {
try path.appendSlice(arena, ".ptr");
return sema.notePathToComptimeAllocPtrInner(.fromInterned(slice.ptr), path);
},
.opt => |opt| {
try path.appendSlice(arena, ".?");
return sema.notePathToComptimeAllocPtrInner(.fromInterned(opt.val), path);
},
.un => |un| {
assert(un.tag != .none);
const union_ty: Type = .fromInterned(un.ty);
const backing_enum = union_ty.unionTagTypeHypothetical(zcu);
const field_idx = backing_enum.enumTagFieldIndex(.fromInterned(un.tag), zcu).?;
const field_name = backing_enum.enumFieldName(field_idx, zcu);
try path.print(arena, ".{f}", .{field_name.fmt(ip)});
return sema.notePathToComptimeAllocPtrInner(.fromInterned(un.val), path);
},
.aggregate => |agg| {
const elem: InternPool.Index, const elem_idx: usize = switch (agg.storage) {
.bytes => unreachable,
.repeated_elem => |elem| .{ elem, 0 },
.elems => |elems| for (elems, 0..) |elem, elem_idx| {
if (Value.fromInterned(elem).canMutateComptimeVarState(zcu)) {
break .{ elem, elem_idx };
}
} else unreachable,
};
const agg_ty: Type = .fromInterned(agg.ty);
switch (agg_ty.zigTypeTag(zcu)) {
.array, .vector => try path.print(arena, "[{d}]", .{elem_idx}),
.pointer => switch (elem_idx) {
Value.slice_ptr_index => try path.appendSlice(arena, ".ptr"),
Value.slice_len_index => try path.appendSlice(arena, ".len"),
else => unreachable,
},
.@"struct" => if (agg_ty.isTuple(zcu)) {
try path.print(arena, "[{d}]", .{elem_idx});
} else {
const name = agg_ty.structFieldName(elem_idx, zcu).unwrap().?;
try path.print(arena, ".{f}", .{name.fmt(ip)});
},
else => unreachable,
}
return sema.notePathToComptimeAllocPtrInner(.fromInterned(elem), path);
},
else => unreachable,
}
}
/// Returns true if any value contained in `val` is undefined.
fn anyUndef(sema: *Sema, block: *Block, src: LazySrcLoc, val: Value) !bool {
const pt = sema.pt;
const zcu = pt.zcu;
return switch (zcu.intern_pool.indexToKey(val.toIntern())) {
.undef => true,
.simple_value => |v| v == .undefined,
.slice => {
// If the slice contents are runtime-known, reification will fail later on with a
// specific error message.
const arr = try sema.maybeDerefSliceAsArray(block, src, val) orelse return false;
return sema.anyUndef(block, src, arr);
},
.aggregate => |aggregate| for (0..aggregate.storage.values().len) |i| {
const elem = zcu.intern_pool.indexToKey(val.toIntern()).aggregate.storage.values()[i];
if (try sema.anyUndef(block, src, Value.fromInterned(elem))) break true;
} else false,
else => false,
};
}
/// Asserts that `slice_val` is a slice of `u8`.
fn sliceToIpString(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
slice_val: Value,
reason: ComptimeReason,
) CompileError!InternPool.NullTerminatedString {
const pt = sema.pt;
const zcu = pt.zcu;
const slice_ty = slice_val.typeOf(zcu);
assert(slice_ty.isSlice(zcu));
assert(slice_ty.childType(zcu).toIntern() == .u8_type);
const array_val = try sema.derefSliceAsArray(block, src, slice_val, reason);
const array_ty = array_val.typeOf(zcu);
return array_val.toIpString(array_ty, pt);
}
/// Given a slice value, attempts to dereference it into a comptime-known array.
/// Emits a compile error if the contents of the slice are not comptime-known.
/// Asserts that `slice_val` is a slice or a pointer to an array.
fn derefSliceAsArray(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
slice_val: Value,
/// `null` may be passed only if `block.isComptime()`. It indicates that the reason for the value
/// being comptime-resolved is that the block is being comptime-evaluated.
reason: ?ComptimeReason,
) CompileError!Value {
return try sema.maybeDerefSliceAsArray(block, src, slice_val) orelse {
return sema.failWithNeededComptime(block, src, reason);
};
}
/// Given a slice value, attempts to dereference it into a comptime-known array.
/// Returns `null` if the contents of the slice are not comptime-known.
/// Asserts that `slice_val` is a slice or a pointer to an array.
fn maybeDerefSliceAsArray(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
slice_val: Value,
) CompileError!?Value {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const slice_ty = slice_val.typeOf(zcu);
assert(slice_ty.zigTypeTag(zcu) == .pointer);
switch (slice_ty.ptrInfo(zcu).flags.size) {
.slice => {},
.one => return sema.pointerDeref(block, src, slice_val, slice_ty),
.many, .c => unreachable,
}
const slice = switch (ip.indexToKey(slice_val.toIntern())) {
.undef => return sema.failWithUseOfUndef(block, src, null),
.slice => |slice| slice,
else => unreachable,
};
const elem_ty = Type.fromInterned(slice.ty).childType(zcu);
const len = try Value.fromInterned(slice.len).toUnsignedIntSema(pt);
const array_ty = try pt.arrayType(.{
.child = elem_ty.toIntern(),
.len = len,
});
const ptr_ty = try pt.ptrTypeSema(p: {
var p = Type.fromInterned(slice.ty).ptrInfo(zcu);
p.flags.size = .one;
p.child = array_ty.toIntern();
p.sentinel = .none;
break :p p;
});
const casted_ptr = try pt.getCoerced(Value.fromInterned(slice.ptr), ptr_ty);
return sema.pointerDeref(block, src, casted_ptr, ptr_ty);
}
fn analyzeUnreachable(sema: *Sema, block: *Block, src: LazySrcLoc, safety_check: bool) !void {
if (safety_check and block.wantSafety()) {
// We only apply the first hint in a branch.
// This allows user-provided hints to override implicit cold hints.
if (sema.branch_hint == null) {
sema.branch_hint = .cold;
}
try sema.safetyPanic(block, src, .reached_unreachable);
} else {
_ = try block.addNoOp(.unreach);
}
}
/// This should be called exactly once, at the end of a `Sema`'s lifetime.
/// It takes the exports stored in `sema.export` and flushes them to the `Zcu`
/// to be processed by the linker after the update.
pub fn flushExports(sema: *Sema) !void {
if (sema.exports.items.len == 0) return;
const zcu = sema.pt.zcu;
const gpa = zcu.gpa;
// There may be existing exports. For instance, a struct may export
// things during both field type resolution and field default resolution.
//
// So, pick up and delete any existing exports. This strategy performs
// redundant work, but that's okay, because this case is exceedingly rare.
if (zcu.single_exports.get(sema.owner)) |export_idx| {
try sema.exports.append(gpa, export_idx.ptr(zcu).*);
} else if (zcu.multi_exports.get(sema.owner)) |info| {
try sema.exports.appendSlice(gpa, zcu.all_exports.items[info.index..][0..info.len]);
}
zcu.deleteUnitExports(sema.owner);
// `sema.exports` is completed; store the data into the `Zcu`.
if (sema.exports.items.len == 1) {
try zcu.single_exports.ensureUnusedCapacity(gpa, 1);
const export_idx: Zcu.Export.Index = zcu.free_exports.pop() orelse idx: {
_ = try zcu.all_exports.addOne(gpa);
break :idx @enumFromInt(zcu.all_exports.items.len - 1);
};
export_idx.ptr(zcu).* = sema.exports.items[0];
zcu.single_exports.putAssumeCapacityNoClobber(sema.owner, export_idx);
} else {
try zcu.multi_exports.ensureUnusedCapacity(gpa, 1);
const exports_base = zcu.all_exports.items.len;
try zcu.all_exports.appendSlice(gpa, sema.exports.items);
zcu.multi_exports.putAssumeCapacityNoClobber(sema.owner, .{
.index = @intCast(exports_base),
.len = @intCast(sema.exports.items.len),
});
}
}
/// Called as soon as a `declared` enum type is created.
/// Resolves the tag type and field inits.
/// Marks the `src_inst` dependency on the enum's declaration, so call sites need not do this.
pub fn resolveDeclaredEnum(
pt: Zcu.PerThread,
wip_ty: InternPool.WipEnumType,
inst: Zir.Inst.Index,
tracked_inst: InternPool.TrackedInst.Index,
namespace: InternPool.NamespaceIndex,
type_name: InternPool.NullTerminatedString,
small: Zir.Inst.EnumDecl.Small,
body: []const Zir.Inst.Index,
tag_type_ref: Zir.Inst.Ref,
any_values: bool,
fields_len: u32,
zir: Zir,
body_end: usize,
) Zcu.SemaError!void {
const zcu = pt.zcu;
const gpa = zcu.gpa;
const src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = LazySrcLoc.Offset.nodeOffset(.zero) };
var arena: std.heap.ArenaAllocator = .init(gpa);
defer arena.deinit();
var comptime_err_ret_trace: std.array_list.Managed(Zcu.LazySrcLoc) = .init(gpa);
defer comptime_err_ret_trace.deinit();
var sema: Sema = .{
.pt = pt,
.gpa = gpa,
.arena = arena.allocator(),
.code = zir,
.owner = .wrap(.{ .type = wip_ty.index }),
.func_index = .none,
.func_is_naked = false,
.fn_ret_ty = .void,
.fn_ret_ty_ies = null,
.comptime_err_ret_trace = &comptime_err_ret_trace,
};
defer sema.deinit();
if (zcu.comp.debugIncremental()) {
const info = try zcu.incremental_debug_state.getUnitInfo(gpa, sema.owner);
info.last_update_gen = zcu.generation;
}
try sema.declareDependency(.{ .src_hash = tracked_inst });
var block: Block = .{
.parent = null,
.sema = &sema,
.namespace = namespace,
.instructions = .{},
.inlining = null,
.comptime_reason = .{ .reason = .{
.src = src,
.r = .{ .simple = .enum_field_values },
} },
.src_base_inst = tracked_inst,
.type_name_ctx = type_name,
};
defer block.instructions.deinit(gpa);
sema.resolveDeclaredEnumInner(
&block,
wip_ty,
inst,
tracked_inst,
src,
small,
body,
tag_type_ref,
any_values,
fields_len,
zir,
body_end,
) catch |err| switch (err) {
error.ComptimeBreak => unreachable,
error.ComptimeReturn => unreachable,
error.OutOfMemory, error.Canceled => |e| return e,
error.AnalysisFail => {
if (!zcu.failed_analysis.contains(sema.owner)) {
try zcu.transitive_failed_analysis.put(gpa, sema.owner, {});
}
return error.AnalysisFail;
},
};
}
fn resolveDeclaredEnumInner(
sema: *Sema,
block: *Block,
wip_ty: InternPool.WipEnumType,
inst: Zir.Inst.Index,
tracked_inst: InternPool.TrackedInst.Index,
src: LazySrcLoc,
small: Zir.Inst.EnumDecl.Small,
body: []const Zir.Inst.Index,
tag_type_ref: Zir.Inst.Ref,
any_values: bool,
fields_len: u32,
zir: Zir,
body_end: usize,
) Zcu.CompileError!void {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const bit_bags_count = std.math.divCeil(usize, fields_len, 32) catch unreachable;
const tag_ty_src: LazySrcLoc = .{ .base_node_inst = tracked_inst, .offset = .{ .node_offset_container_tag = .zero } };
const int_tag_ty = ty: {
if (body.len != 0) {
_ = try sema.analyzeInlineBody(block, body, inst);
}
if (tag_type_ref != .none) {
const ty = try sema.resolveType(block, tag_ty_src, tag_type_ref);
if (ty.zigTypeTag(zcu) != .int and ty.zigTypeTag(zcu) != .comptime_int) {
return sema.fail(block, tag_ty_src, "expected integer tag type, found '{f}'", .{ty.fmt(pt)});
}
break :ty ty;
} else if (fields_len == 0) {
break :ty try pt.intType(.unsigned, 0);
} else {
const bits = std.math.log2_int_ceil(usize, fields_len);
break :ty try pt.intType(.unsigned, bits);
}
};
wip_ty.setTagTy(ip, int_tag_ty.toIntern());
var extra_index = body_end + bit_bags_count;
var bit_bag_index: usize = body_end;
var cur_bit_bag: u32 = undefined;
var last_tag_val: ?Value = null;
for (0..fields_len) |field_i_usize| {
const field_i: u32 = @intCast(field_i_usize);
if (field_i % 32 == 0) {
cur_bit_bag = zir.extra[bit_bag_index];
bit_bag_index += 1;
}
const has_tag_value = @as(u1, @truncate(cur_bit_bag)) != 0;
cur_bit_bag >>= 1;
const field_name_index: Zir.NullTerminatedString = @enumFromInt(zir.extra[extra_index]);
const field_name_zir = zir.nullTerminatedString(field_name_index);
extra_index += 1; // field name
const field_name = try ip.getOrPutString(gpa, pt.tid, field_name_zir, .no_embedded_nulls);
const value_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .container_field_value = field_i },
};
const tag_overflow = if (has_tag_value) overflow: {
const tag_val_ref: Zir.Inst.Ref = @enumFromInt(zir.extra[extra_index]);
extra_index += 1;
const tag_inst = try sema.resolveInst(tag_val_ref);
last_tag_val = try sema.resolveConstDefinedValue(block, .{
.base_node_inst = tracked_inst,
.offset = .{ .container_field_name = field_i },
}, tag_inst, .{ .simple = .enum_field_tag_value });
if (!(try sema.intFitsInType(last_tag_val.?, int_tag_ty, null))) break :overflow true;
last_tag_val = try pt.getCoerced(last_tag_val.?, int_tag_ty);
if (wip_ty.nextField(ip, field_name, last_tag_val.?.toIntern())) |conflict| {
assert(conflict.kind == .value); // AstGen validated names are unique
const other_field_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .container_field_value = conflict.prev_field_idx },
};
const msg = msg: {
const msg = try sema.errMsg(value_src, "enum tag value {f} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)});
errdefer msg.destroy(gpa);
try sema.errNote(other_field_src, msg, "other occurrence here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
break :overflow false;
} else if (any_values) overflow: {
if (last_tag_val) |last_tag| {
const result = try arith.incrementDefinedInt(sema, int_tag_ty, last_tag);
last_tag_val = result.val;
if (result.overflow) break :overflow true;
} else {
last_tag_val = try pt.intValue(int_tag_ty, 0);
}
if (wip_ty.nextField(ip, field_name, last_tag_val.?.toIntern())) |conflict| {
assert(conflict.kind == .value); // AstGen validated names are unique
const other_field_src: LazySrcLoc = .{
.base_node_inst = tracked_inst,
.offset = .{ .container_field_value = conflict.prev_field_idx },
};
const msg = msg: {
const msg = try sema.errMsg(value_src, "enum tag value {f} already taken", .{last_tag_val.?.fmtValueSema(pt, sema)});
errdefer msg.destroy(gpa);
try sema.errNote(other_field_src, msg, "other occurrence here", .{});
break :msg msg;
};
return sema.failWithOwnedErrorMsg(block, msg);
}
break :overflow false;
} else overflow: {
assert(wip_ty.nextField(ip, field_name, .none) == null);
last_tag_val = try pt.intValue(.comptime_int, field_i);
if (!try sema.intFitsInType(last_tag_val.?, int_tag_ty, null)) break :overflow true;
last_tag_val = try pt.getCoerced(last_tag_val.?, int_tag_ty);
break :overflow false;
};
if (tag_overflow) {
const msg = try sema.errMsg(value_src, "enumeration value '{f}' too large for type '{f}'", .{
last_tag_val.?.fmtValueSema(pt, sema), int_tag_ty.fmt(pt),
});
return sema.failWithOwnedErrorMsg(block, msg);
}
}
if (small.nonexhaustive and int_tag_ty.toIntern() != .comptime_int_type) {
if (fields_len >= 1 and std.math.log2_int(u64, fields_len) == int_tag_ty.bitSize(zcu)) {
return sema.fail(block, src, "non-exhaustive enum specifies every value", .{});
}
}
}
pub const bitCastVal = @import("Sema/bitcast.zig").bitCast;
pub const bitCastSpliceVal = @import("Sema/bitcast.zig").bitCastSplice;
const loadComptimePtr = @import("Sema/comptime_ptr_access.zig").loadComptimePtr;
const ComptimeLoadResult = @import("Sema/comptime_ptr_access.zig").ComptimeLoadResult;
const storeComptimePtr = @import("Sema/comptime_ptr_access.zig").storeComptimePtr;
const ComptimeStoreResult = @import("Sema/comptime_ptr_access.zig").ComptimeStoreResult;
pub fn getBuiltinType(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl) SemaError!Type {
assert(decl.kind() == .type);
try sema.ensureMemoizedStateResolved(src, decl.stage());
return .fromInterned(sema.pt.zcu.builtin_decl_values.get(decl));
}
pub fn getBuiltin(sema: *Sema, src: LazySrcLoc, decl: Zcu.BuiltinDecl) SemaError!InternPool.Index {
assert(decl.kind() != .type);
try sema.ensureMemoizedStateResolved(src, decl.stage());
return sema.pt.zcu.builtin_decl_values.get(decl);
}
pub const NavPtrModifiers = struct {
alignment: Alignment,
@"linksection": InternPool.OptionalNullTerminatedString,
@"addrspace": std.builtin.AddressSpace,
};
pub fn resolveNavPtrModifiers(
sema: *Sema,
block: *Block,
zir_decl: Zir.Inst.Declaration.Unwrapped,
decl_inst: Zir.Inst.Index,
nav_ty: Type,
) CompileError!NavPtrModifiers {
const pt = sema.pt;
const zcu = pt.zcu;
const gpa = zcu.gpa;
const ip = &zcu.intern_pool;
const align_src = block.src(.{ .node_offset_var_decl_align = .zero });
const section_src = block.src(.{ .node_offset_var_decl_section = .zero });
const addrspace_src = block.src(.{ .node_offset_var_decl_addrspace = .zero });
const alignment: InternPool.Alignment = a: {
const align_body = zir_decl.align_body orelse break :a .none;
const align_ref = try sema.resolveInlineBody(block, align_body, decl_inst);
break :a try sema.analyzeAsAlign(block, align_src, align_ref);
};
const @"linksection": InternPool.OptionalNullTerminatedString = ls: {
const linksection_body = zir_decl.linksection_body orelse break :ls .none;
const linksection_ref = try sema.resolveInlineBody(block, linksection_body, decl_inst);
const bytes = try sema.toConstString(block, section_src, linksection_ref, .{ .simple = .@"linksection" });
if (std.mem.indexOfScalar(u8, bytes, 0) != null) {
return sema.fail(block, section_src, "linksection cannot contain null bytes", .{});
} else if (bytes.len == 0) {
return sema.fail(block, section_src, "linksection cannot be empty", .{});
}
break :ls try ip.getOrPutStringOpt(gpa, pt.tid, bytes, .no_embedded_nulls);
};
const @"addrspace": std.builtin.AddressSpace = as: {
const addrspace_ctx: std.Target.AddressSpaceContext = switch (zir_decl.kind) {
.@"var" => .variable,
else => switch (nav_ty.zigTypeTag(zcu)) {
.@"fn" => .function,
else => .constant,
},
};
const target = zcu.getTarget();
const addrspace_body = zir_decl.addrspace_body orelse break :as switch (addrspace_ctx) {
.function => target_util.defaultAddressSpace(target, .function),
.variable => target_util.defaultAddressSpace(target, .global_mutable),
.constant => target_util.defaultAddressSpace(target, .global_constant),
else => unreachable,
};
const addrspace_ref = try sema.resolveInlineBody(block, addrspace_body, decl_inst);
break :as try sema.analyzeAsAddressSpace(block, addrspace_src, addrspace_ref, addrspace_ctx);
};
return .{
.alignment = alignment,
.@"linksection" = @"linksection",
.@"addrspace" = @"addrspace",
};
}
pub fn analyzeMemoizedState(sema: *Sema, block: *Block, simple_src: LazySrcLoc, builtin_namespace: InternPool.NamespaceIndex, stage: InternPool.MemoizedStateStage) CompileError!bool {
const pt = sema.pt;
const zcu = pt.zcu;
const ip = &zcu.intern_pool;
const gpa = zcu.gpa;
var any_changed = false;
inline for (comptime std.enums.values(Zcu.BuiltinDecl)) |builtin_decl| {
if (stage == comptime builtin_decl.stage()) {
const parent_ns: Zcu.Namespace.Index, const parent_name: []const u8, const name: []const u8 = switch (comptime builtin_decl.access()) {
.direct => |name| .{ builtin_namespace, "std.builtin", name },
.nested => |nested| access: {
const parent_ty: Type = .fromInterned(zcu.builtin_decl_values.get(nested[0]));
const parent_ns = parent_ty.getNamespace(zcu).unwrap() orelse {
return sema.fail(block, simple_src, "std.builtin.{s} is not a container type", .{@tagName(nested[0])});
};
break :access .{ parent_ns, "std.builtin." ++ @tagName(nested[0]), nested[1] };
},
};
const name_nts = try ip.getOrPutString(gpa, pt.tid, name, .no_embedded_nulls);
const nav = try sema.namespaceLookup(block, simple_src, parent_ns, name_nts) orelse
return sema.fail(block, simple_src, "{s} missing {s}", .{ parent_name, name });
const src: LazySrcLoc = .{
.base_node_inst = ip.getNav(nav).srcInst(ip),
.offset = .nodeOffset(.zero),
};
const result = try sema.analyzeNavVal(block, src, nav);
const uncoerced_val = try sema.resolveConstDefinedValue(block, src, result, null);
const maybe_lazy_val: Value = switch (builtin_decl.kind()) {
.type => if (uncoerced_val.typeOf(zcu).zigTypeTag(zcu) != .type) {
return sema.fail(block, src, "{s}.{s} is not a type", .{ parent_name, name });
} else val: {
try uncoerced_val.toType().resolveFully(pt);
break :val uncoerced_val;
},
.func => val: {
const func_ty = try sema.getExpectedBuiltinFnType(builtin_decl);
const coerced = try sema.coerce(block, func_ty, Air.internedToRef(uncoerced_val.toIntern()), src);
break :val .fromInterned(coerced.toInterned().?);
},
.string => val: {
const coerced = try sema.coerce(block, .slice_const_u8, Air.internedToRef(uncoerced_val.toIntern()), src);
break :val .fromInterned(coerced.toInterned().?);
},
};
const val = try sema.resolveLazyValue(maybe_lazy_val);
const prev = zcu.builtin_decl_values.get(builtin_decl);
if (val.toIntern() != prev) {
zcu.builtin_decl_values.set(builtin_decl, val.toIntern());
any_changed = true;
}
}
}
return any_changed;
}
/// Given that `decl.kind() == .func`, get the type expected of the function.
fn getExpectedBuiltinFnType(sema: *Sema, decl: Zcu.BuiltinDecl) CompileError!Type {
const pt = sema.pt;
return switch (decl) {
// `noinline fn () void`
.returnError => try pt.funcType(.{
.param_types = &.{},
.return_type = .void_type,
.is_noinline = true,
}),
// `fn ([]const u8, ?usize) noreturn`
.@"panic.call" => try pt.funcType(.{
.param_types = &.{
.slice_const_u8_type,
(try pt.optionalType(.usize_type)).toIntern(),
},
.return_type = .noreturn_type,
}),
// `fn (anytype, anytype) noreturn`
.@"panic.sentinelMismatch",
.@"panic.inactiveUnionField",
=> try pt.funcType(.{
.param_types = &.{ .generic_poison_type, .generic_poison_type },
.return_type = .noreturn_type,
.is_generic = true,
}),
// `fn (anyerror) noreturn`
.@"panic.unwrapError" => try pt.funcType(.{
.param_types = &.{.anyerror_type},
.return_type = .noreturn_type,
}),
// `fn (usize) noreturn`
.@"panic.sliceCastLenRemainder" => try pt.funcType(.{
.param_types = &.{.usize_type},
.return_type = .noreturn_type,
}),
// `fn (usize, usize) noreturn`
.@"panic.outOfBounds",
.@"panic.startGreaterThanEnd",
=> try pt.funcType(.{
.param_types = &.{ .usize_type, .usize_type },
.return_type = .noreturn_type,
}),
// `fn () noreturn`
.@"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",
=> try pt.funcType(.{
.param_types = &.{},
.return_type = .noreturn_type,
}),
else => unreachable,
};
}