diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2022-07-01 15:52:54 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2022-07-01 15:52:54 -0700 |
| commit | c89dd15e1be4959800dc7092d7dd4375253db7bc (patch) | |
| tree | ca184ae53592efa21e67128a5f891d642d7f1118 /src/Sema.zig | |
| parent | 5466e87fce581f2ef90ac23bb80b1dbc05836fc6 (diff) | |
| parent | 2360f8c490f3ec684ed64ff28e8c1fade249070b (diff) | |
| download | zig-c89dd15e1be4959800dc7092d7dd4375253db7bc.tar.gz zig-c89dd15e1be4959800dc7092d7dd4375253db7bc.zip | |
Merge remote-tracking branch 'origin/master' into llvm14
Diffstat (limited to 'src/Sema.zig')
| -rw-r--r-- | src/Sema.zig | 15869 |
1 files changed, 12252 insertions, 3617 deletions
diff --git a/src/Sema.zig b/src/Sema.zig index 94b5a7f1d1..b946e29057 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -24,6 +24,7 @@ inst_map: InstMap = .{}, /// and `src_decl` of `Block` is the `Decl` of the callee. /// This `Decl` owns the arena memory of this `Sema`. owner_decl: *Decl, +owner_decl_index: Decl.Index, /// For an inline or comptime function call, this will be the root parent function /// which contains the callsite. Corresponds to `owner_decl`. owner_func: ?*Module.Fn, @@ -37,7 +38,7 @@ func: ?*Module.Fn, /// 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, -branch_quota: u32 = 1000, +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. @@ -47,7 +48,7 @@ comptime_break_inst: Zir.Inst.Index = undefined, /// access to the source location set by the previous instruction which did /// contain a mapped source location. src: LazySrcLoc = .{ .token_offset = 0 }, -decl_val_table: std.AutoHashMapUnmanaged(*Decl, Air.Inst.Ref) = .{}, +decl_val_table: std.AutoHashMapUnmanaged(Decl.Index, Air.Inst.Ref) = .{}, /// When doing a generic function instantiation, this array collects a /// `Value` object for each parameter that is comptime known and thus elided /// from the generated function. This memory is allocated by a parent `Sema` and @@ -63,6 +64,16 @@ comptime_args_fn_inst: Zir.Inst.Index = 0, /// extra hash table lookup in the `monomorphed_funcs` set. /// Sema will set this to null when it takes ownership. preallocated_new_func: ?*Module.Fn = null, +/// The key is `constant` AIR instructions to types that must be fully resolved +/// after the current function body analysis is done. +/// TODO: after upgrading to use InternPool change the key here to be an +/// InternPool value index. +types_to_resolve: std.ArrayListUnmanaged(Air.Inst.Ref) = .{}, +/// These are lazily created runtime blocks from inline_block instructions. +/// They are created when an inline_break 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) = .{}, const std = @import("std"); const mem = std.mem; @@ -89,6 +100,9 @@ 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"); + +pub const default_branch_quota = 1000; pub const InstMap = std.AutoHashMapUnmanaged(Zir.Inst.Index, Air.Inst.Ref); @@ -100,10 +114,6 @@ pub const Block = struct { parent: ?*Block, /// Shared among all child blocks. sema: *Sema, - /// This Decl is the Decl according to the Zig source code corresponding to this Block. - /// This can vary during inline or comptime function calls. See `Sema.owner_decl` - /// for the one that will be the same for all Block instances. - src_decl: *Decl, /// The namespace to use for lookups from this source block /// When analyzing fields, this is different from src_decl.src_namepsace. namespace: *Namespace, @@ -119,21 +129,31 @@ pub const Block = struct { /// If runtime_index is not 0 then one of these is guaranteed to be non null. runtime_cond: ?LazySrcLoc = null, runtime_loop: ?LazySrcLoc = null, + /// This Decl is the Decl according to the Zig source code corresponding to this Block. + /// This can vary during inline or comptime function calls. See `Sema.owner_decl` + /// for the one that will be the same for all Block instances. + src_decl: Decl.Index, /// Non zero if a non-inline loop or a runtime conditional have been encountered. /// Stores to to comptime variables are only allowed when var.runtime_index <= runtime_index. - runtime_index: u32 = 0, + runtime_index: Value.RuntimeIndex = .zero, is_comptime: bool, + is_typeof: bool = false, + is_coerce_result_ptr: bool = false, /// when null, it is determined by build mode, changed by @setRuntimeSafety want_safety: ?bool = null, c_import_buf: ?*std.ArrayList(u8) = null, + /// type of `err` in `else => |err|` + switch_else_err_ty: ?Type = null, + const Param = struct { /// `noreturn` means `anytype`. ty: Type, is_comptime: bool, + name: []const u8, }; /// This `Block` maps a block ZIR instruction to the corresponding @@ -151,6 +171,7 @@ pub const Block = struct { pub const Inlining = struct { comptime_result: Air.Inst.Ref, merges: Merges, + err: ?*Module.ErrorMsg = null, }; pub const Merges = struct { @@ -180,11 +201,13 @@ pub const Block = struct { .label = null, .inlining = parent.inlining, .is_comptime = parent.is_comptime, + .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, .c_import_buf = parent.c_import_buf, + .switch_else_err_ty = parent.switch_else_err_ty, }; } @@ -201,7 +224,7 @@ pub const Block = struct { return block.namespace.file_scope; } - pub fn addTy( + fn addTy( block: *Block, tag: Air.Inst.Tag, ty: Type, @@ -212,7 +235,7 @@ pub const Block = struct { }); } - pub fn addTyOp( + fn addTyOp( block: *Block, tag: Air.Inst.Tag, ty: Type, @@ -227,7 +250,7 @@ pub const Block = struct { }); } - pub fn addBitCast(block: *Block, ty: Type, operand: Air.Inst.Ref) Allocator.Error!Air.Inst.Ref { + fn addBitCast(block: *Block, ty: Type, operand: Air.Inst.Ref) Allocator.Error!Air.Inst.Ref { return block.addInst(.{ .tag = .bitcast, .data = .{ .ty_op = .{ @@ -237,14 +260,14 @@ pub const Block = struct { }); } - pub fn addNoOp(block: *Block, tag: Air.Inst.Tag) error{OutOfMemory}!Air.Inst.Ref { + fn addNoOp(block: *Block, tag: Air.Inst.Tag) error{OutOfMemory}!Air.Inst.Ref { return block.addInst(.{ .tag = tag, .data = .{ .no_op = {} }, }); } - pub fn addUnOp( + fn addUnOp( block: *Block, tag: Air.Inst.Tag, operand: Air.Inst.Ref, @@ -255,7 +278,7 @@ pub const Block = struct { }); } - pub fn addBr( + fn addBr( block: *Block, target_block: Air.Inst.Index, operand: Air.Inst.Ref, @@ -284,13 +307,10 @@ pub const Block = struct { }); } - fn addArg(block: *Block, ty: Type, name: u32) error{OutOfMemory}!Air.Inst.Ref { + fn addArg(block: *Block, ty: Type) error{OutOfMemory}!Air.Inst.Ref { return block.addInst(.{ .tag = .arg, - .data = .{ .ty_str = .{ - .ty = try block.sema.addType(ty), - .str = name, - } }, + .data = .{ .ty = ty }, }); } @@ -328,7 +348,7 @@ pub const Block = struct { }); } - pub fn addStructFieldVal( + fn addStructFieldVal( block: *Block, struct_val: Air.Inst.Ref, field_index: u32, @@ -346,7 +366,7 @@ pub const Block = struct { }); } - pub fn addSliceElemPtr( + fn addSliceElemPtr( block: *Block, slice: Air.Inst.Ref, elem_index: Air.Inst.Ref, @@ -364,7 +384,7 @@ pub const Block = struct { }); } - pub fn addPtrElemPtr( + fn addPtrElemPtr( block: *Block, array_ptr: Air.Inst.Ref, elem_index: Air.Inst.Ref, @@ -374,7 +394,7 @@ pub const Block = struct { return block.addPtrElemPtrTypeRef(array_ptr, elem_index, ty_ref); } - pub fn addPtrElemPtrTypeRef( + fn addPtrElemPtrTypeRef( block: *Block, array_ptr: Air.Inst.Ref, elem_index: Air.Inst.Ref, @@ -392,19 +412,33 @@ pub const Block = struct { }); } - pub fn addVectorInit( + fn addCmpVector(block: *Block, lhs: Air.Inst.Ref, rhs: Air.Inst.Ref, cmp_op: std.math.CompareOperator, vector_ty: Air.Inst.Ref) !Air.Inst.Ref { + return block.addInst(.{ + .tag = .cmp_vector, + .data = .{ .ty_pl = .{ + .ty = vector_ty, + .payload = try block.sema.addExtra(Air.VectorCmp{ + .lhs = lhs, + .rhs = rhs, + .op = Air.VectorCmp.encodeOp(cmp_op), + }), + } }, + }); + } + + fn addAggregateInit( block: *Block, - vector_ty: Type, + aggregate_ty: Type, elements: []const Air.Inst.Ref, ) !Air.Inst.Ref { const sema = block.sema; - const ty_ref = try sema.addType(vector_ty); + const ty_ref = try sema.addType(aggregate_ty); try sema.air_extra.ensureUnusedCapacity(sema.gpa, elements.len); const extra_index = @intCast(u32, sema.air_extra.items.len); sema.appendRefsAssumeCapacity(elements); return block.addInst(.{ - .tag = .vector_init, + .tag = .aggregate_init, .data = .{ .ty_pl = .{ .ty = ty_ref, .payload = extra_index, @@ -412,6 +446,24 @@ pub const Block = struct { }); } + 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 = try block.sema.addType(union_ty), + .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 Air.indexToRef(try block.addInstAsIndex(inst)); } @@ -463,23 +515,38 @@ pub const Block = struct { wad.* = undefined; } - pub fn finish(wad: *WipAnonDecl, ty: Type, val: Value) !*Decl { + /// `alignment` value of 0 means to use ABI alignment. + pub fn finish(wad: *WipAnonDecl, ty: Type, val: Value, alignment: u32) !Decl.Index { const sema = wad.block.sema; // Do this ahead of time because `createAnonymousDecl` depends on calling // `type.hasRuntimeBits()`. _ = try sema.typeHasRuntimeBits(wad.block, wad.src, ty); - const new_decl = try sema.mod.createAnonymousDecl(wad.block, .{ + const new_decl_index = try sema.mod.createAnonymousDecl(wad.block, .{ .ty = ty, .val = val, }); - errdefer sema.mod.abortAnonDecl(new_decl); + const new_decl = sema.mod.declPtr(new_decl_index); + new_decl.@"align" = alignment; + errdefer sema.mod.abortAnonDecl(new_decl_index); try new_decl.finalizeNewArena(&wad.new_decl_arena); wad.finished = true; - return new_decl; + return new_decl_index; } }; }; +const LabeledBlock = struct { + block: Block, + label: Block.Label, + + fn destroy(lb: *LabeledBlock, gpa: Allocator) void { + lb.block.instructions.deinit(gpa); + lb.label.merges.results.deinit(gpa); + lb.label.merges.br_list.deinit(gpa); + gpa.destroy(lb); + } +}; + pub fn deinit(sema: *Sema) void { const gpa = sema.gpa; sema.air_instructions.deinit(gpa); @@ -487,6 +554,15 @@ pub fn deinit(sema: *Sema) void { sema.air_values.deinit(gpa); sema.inst_map.deinit(gpa); sema.decl_val_table.deinit(gpa); + sema.types_to_resolve.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.* = undefined; } @@ -501,28 +577,54 @@ fn resolveBody( /// use to return from the body. body_inst: Zir.Inst.Index, ) CompileError!Air.Inst.Ref { - const break_inst = try sema.analyzeBody(block, body); - const break_data = sema.code.instructions.items(.data)[break_inst].@"break"; + const break_data = (try sema.analyzeBodyBreak(block, body)) orelse + return Air.Inst.Ref.unreachable_value; // For comptime control flow, we need to detect when `analyzeBody` reports // that we need to break from an outer block. In such case we // use Zig's error mechanism to send control flow up the stack until // we find the corresponding block to this break. if (block.is_comptime and break_data.block_inst != body_inst) { - sema.comptime_break_inst = break_inst; + sema.comptime_break_inst = break_data.inst; return error.ComptimeBreak; } - return sema.resolveInst(break_data.operand); + return try sema.resolveInst(break_data.operand); } pub fn analyzeBody( sema: *Sema, block: *Block, body: []const Zir.Inst.Index, -) CompileError!Zir.Inst.Index { - return sema.analyzeBodyInner(block, body) catch |err| switch (err) { +) !void { + _ = sema.analyzeBodyInner(block, body) catch |err| switch (err) { + error.ComptimeBreak => unreachable, // unexpected comptime control flow + else => |e| return e, + }; +} + +const BreakData = struct { + block_inst: Zir.Inst.Index, + operand: Zir.Inst.Ref, + inst: Zir.Inst.Index, +}; + +pub fn analyzeBodyBreak( + sema: *Sema, + block: *Block, + body: []const Zir.Inst.Index, +) CompileError!?BreakData { + const break_inst = sema.analyzeBodyInner(block, body) catch |err| switch (err) { error.ComptimeBreak => sema.comptime_break_inst, else => |e| return e, }; + if (block.instructions.items.len != 0 and + sema.typeOf(Air.indexToRef(block.instructions.items[block.instructions.items.len - 1])).isNoReturn()) + return null; + const break_data = sema.code.instructions.items(.data)[break_inst].@"break"; + return BreakData{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = break_inst, + }; } /// ZIR instructions which are always `noreturn` return this. This matches the @@ -569,6 +671,8 @@ fn analyzeBodyInner( crash_info.push(); defer crash_info.pop(); + var dbg_block_begins: u32 = 0; + // 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. @@ -577,16 +681,18 @@ fn analyzeBodyInner( crash_info.setBodyIndex(i); const inst = body[i]; std.log.scoped(.sema_zir).debug("sema ZIR {s} %{d}", .{ - block.src_decl.src_namespace.file_scope.sub_file_path, inst, + sema.mod.declPtr(block.src_decl).src_namespace.file_scope.sub_file_path, inst, }); const air_inst: Air.Inst.Ref = switch (tags[inst]) { // zig fmt: off .alloc => try sema.zirAlloc(block, inst), .alloc_inferred => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_const)), .alloc_inferred_mut => try sema.zirAllocInferred(block, inst, Type.initTag(.inferred_alloc_mut)), - .alloc_inferred_comptime => try sema.zirAllocInferredComptime(inst), + .alloc_inferred_comptime => try sema.zirAllocInferredComptime(inst, Type.initTag(.inferred_alloc_const)), + .alloc_inferred_comptime_mut => try sema.zirAllocInferredComptime(inst, Type.initTag(.inferred_alloc_mut)), .alloc_mut => try sema.zirAllocMut(block, inst), - .alloc_comptime => try sema.zirAllocComptime(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), @@ -621,7 +727,7 @@ fn analyzeBodyInner( .elem_ptr_imm => try sema.zirElemPtrImm(block, inst), .elem_val => try sema.zirElemVal(block, inst), .elem_val_node => try sema.zirElemValNode(block, inst), - .elem_type => try sema.zirElemType(block, inst), + .elem_type_index => try sema.zirElemTypeIndex(block, inst), .enum_literal => try sema.zirEnumLiteral(block, inst), .enum_to_int => try sema.zirEnumToInt(block, inst), .int_to_enum => try sema.zirIntToEnum(block, inst), @@ -640,9 +746,9 @@ fn analyzeBodyInner( .field_val => try sema.zirFieldVal(block, inst), .field_val_named => try sema.zirFieldValNamed(block, inst), .field_call_bind => try sema.zirFieldCallBind(block, inst), - .field_call_bind_named => try sema.zirFieldCallBindNamed(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), @@ -655,17 +761,18 @@ fn analyzeBodyInner( .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, .sub), - .negate_wrap => try sema.zirNegate(block, inst, .subwrap), + .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), + .param_type => try sema.zirParamType(block, inst), .ptr_type => try sema.zirPtrType(block, inst), .ptr_type_simple => try sema.zirPtrTypeSimple(block, inst), .ref => try sema.zirRef(block, inst), - .ret_err_value_code => try sema.zirRetErrValueCode(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), @@ -683,6 +790,7 @@ fn analyzeBodyInner( .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), .log2_int_type => try sema.zirLog2IntType(block, inst), .typeof_log2_int_type => try sema.zirTypeofLog2IntType(block, inst), .xor => try sema.zirBitwise(block, inst, .xor), @@ -695,7 +803,7 @@ fn analyzeBodyInner( .array_init_ref => try sema.zirArrayInit(block, inst, true), .array_init_anon => try sema.zirArrayInitAnon(block, inst, false), .array_init_anon_ref => try sema.zirArrayInitAnon(block, inst, true), - .union_init_ptr => try sema.zirUnionInitPtr(block, inst), + .union_init => try sema.zirUnionInit(block, inst), .field_type => try sema.zirFieldType(block, inst), .field_type_ref => try sema.zirFieldTypeRef(block, inst), .ptr_to_int => try sema.zirPtrToInt(block, inst), @@ -713,13 +821,11 @@ fn analyzeBodyInner( .int_to_ptr => try sema.zirIntToPtr(block, inst), .float_cast => try sema.zirFloatCast(block, inst), .int_cast => try sema.zirIntCast(block, inst), - .err_set_cast => try sema.zirErrSetCast(block, inst), .ptr_cast => try sema.zirPtrCast(block, inst), .truncate => try sema.zirTruncate(block, inst), .align_cast => try sema.zirAlignCast(block, inst), .has_decl => try sema.zirHasDecl(block, inst), .has_field => try sema.zirHasField(block, inst), - .pop_count => try sema.zirPopCount(block, inst), .byte_swap => try sema.zirByteSwap(block, inst), .bit_reverse => try sema.zirBitReverse(block, inst), .bit_offset_of => try sema.zirBitOffsetOf(block, inst), @@ -734,30 +840,31 @@ fn analyzeBodyInner( .atomic_rmw => try sema.zirAtomicRmw(block, inst), .mul_add => try sema.zirMulAdd(block, inst), .builtin_call => try sema.zirBuiltinCall(block, inst), - .field_ptr_type => try sema.zirFieldPtrType(block, inst), .field_parent_ptr => try sema.zirFieldParentPtr(block, inst), .builtin_async_call => try sema.zirBuiltinAsyncCall(block, inst), .@"resume" => try sema.zirResume(block, inst), - .@"await" => try sema.zirAwait(block, inst, false), - .await_nosuspend => try sema.zirAwait(block, inst, true), - .extended => try sema.zirExtended(block, inst), - - .clz => try sema.zirClzCtz(block, inst, .clz, Value.clz), - .ctz => try sema.zirClzCtz(block, inst, .ctz, Value.ctz), - - .sqrt => try sema.zirUnaryMath(block, inst), - .sin => try sema.zirUnaryMath(block, inst), - .cos => try sema.zirUnaryMath(block, inst), - .exp => try sema.zirUnaryMath(block, inst), - .exp2 => try sema.zirUnaryMath(block, inst), - .log => try sema.zirUnaryMath(block, inst), - .log2 => try sema.zirUnaryMath(block, inst), - .log10 => try sema.zirUnaryMath(block, inst), - .fabs => try sema.zirUnaryMath(block, inst), - .floor => try sema.zirUnaryMath(block, inst), - .ceil => try sema.zirUnaryMath(block, inst), - .trunc => try sema.zirUnaryMath(block, inst), - .round => try sema.zirUnaryMath(block, inst), + .@"await" => try sema.zirAwait(block, inst), + .array_base_ptr => try sema.zirArrayBasePtr(block, inst), + .field_base_ptr => try sema.zirFieldBasePtr(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), + + .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), + .fabs => try sema.zirUnaryMath(block, inst, .fabs, Value.fabs), + .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(block, inst, .parent), .error_set_decl_anon => try sema.zirErrorSetDecl(block, inst, .anon), @@ -787,11 +894,14 @@ fn analyzeBodyInner( .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 => try sema.zirRetType(block, inst), + // 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 sema.zirCompileError(block, inst), - .ret_coerce => break sema.zirRetCoerce(block, inst), + .ret_tok => break sema.zirRetTok(block, inst), .ret_node => break sema.zirRetNode(block, inst), .ret_load => break sema.zirRetLoad(block, inst), .ret_err_value => break sema.zirRetErrValue(block, inst), @@ -799,25 +909,94 @@ fn analyzeBodyInner( .panic => break sema.zirPanic(block, inst), // zig fmt: on + .extended => ext: { + const extended = datas[inst].extended; + break :ext switch (extended.opcode) { + // zig fmt: off + .variable => try sema.zirVarExtended( block, extended), + .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), + .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, extended), + .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), + .typeof_peer => try sema.zirTypeofPeer( block, extended), + .compile_log => try sema.zirCompileLog( block, extended), + .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), + .field_call_bind_named => try sema.zirFieldCallBindNamed(block, extended), + .err_set_cast => try sema.zirErrSetCast( block, extended), + .await_nosuspend => try sema.zirAwaitNosuspend( block, extended), + // zig fmt: on + .fence => { + try sema.zirFence(block, extended); + i += 1; + continue; + }, + .set_float_mode => { + try sema.zirSetFloatMode(block, extended); + i += 1; + continue; + }, + .set_align_stack => { + try sema.zirSetAlignStack(block, extended); + i += 1; + continue; + }, + .breakpoint => { + if (!block.is_comptime) { + _ = try block.addNoOp(.breakpoint); + } + i += 1; + continue; + }, + }; + }, + // 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. - .breakpoint => { - if (!block.is_comptime) { - _ = try block.addNoOp(.breakpoint); - } + .dbg_stmt => { + try sema.zirDbgStmt(block, inst); i += 1; continue; }, - .fence => { - try sema.zirFence(block, inst); + .dbg_var_ptr => { + try sema.zirDbgVar(block, inst, .dbg_var_ptr); i += 1; continue; }, - .dbg_stmt => { - try sema.zirDbgStmt(block, inst); + .dbg_var_val => { + try sema.zirDbgVar(block, inst, .dbg_var_val); + i += 1; + continue; + }, + .dbg_block_begin => { + dbg_block_begins += 1; + try sema.zirDbgBlockBegin(block); + i += 1; + continue; + }, + .dbg_block_end => { + dbg_block_begins -= 1; + try sema.zirDbgBlockEnd(block); i += 1; continue; }, @@ -871,6 +1050,16 @@ fn analyzeBodyInner( i += 1; continue; }, + .validate_array_init_ty => { + try sema.validateArrayInitTy(block, inst); + i += 1; + continue; + }, + .validate_struct_init_ty => { + try sema.validateStructInitTy(block, inst); + i += 1; + continue; + }, .validate_struct_init => { try sema.zirValidateStructInit(block, inst, false); i += 1; @@ -891,6 +1080,11 @@ fn analyzeBodyInner( i += 1; continue; }, + .validate_deref => { + try sema.zirValidateDeref(block, inst); + i += 1; + continue; + }, .@"export" => { try sema.zirExport(block, inst); i += 1; @@ -901,21 +1095,11 @@ fn analyzeBodyInner( i += 1; continue; }, - .set_align_stack => { - try sema.zirSetAlignStack(block, inst); - i += 1; - continue; - }, .set_cold => { try sema.zirSetCold(block, inst); i += 1; continue; }, - .set_float_mode => { - try sema.zirSetFloatMode(block, inst); - i += 1; - continue; - }, .set_runtime_safety => { try sema.zirSetRuntimeSafety(block, inst); i += 1; @@ -965,11 +1149,18 @@ fn analyzeBodyInner( break sema.zirBreak(block, inst); } }, - .break_inline => break inst, + .break_inline => { + if (block.is_comptime) { + break inst; + } else { + sema.comptime_break_inst = inst; + return error.ComptimeBreak; + } + }, .repeat => { if (block.is_comptime) { // Send comptime control flow back to the beginning of this block. - const src: LazySrcLoc = .{ .node_offset = datas[inst].node }; + const src = LazySrcLoc.nodeOffset(datas[inst].node); try sema.emitBackwardBranch(block, src); if (wip_captures.scope.captures.count() != orig_captures) { try wip_captures.reset(parent_capture_scope); @@ -980,14 +1171,14 @@ fn analyzeBodyInner( continue; } else { const src_node = sema.code.instructions.items(.data)[inst].node; - const src: LazySrcLoc = .{ .node_offset = src_node }; + const src = LazySrcLoc.nodeOffset(src_node); try sema.requireRuntimeBlock(block, src); break always_noreturn; } }, .repeat_inline => { // Send comptime control flow back to the beginning of this block. - const src: LazySrcLoc = .{ .node_offset = datas[inst].node }; + const src = LazySrcLoc.nodeOffset(datas[inst].node); try sema.emitBackwardBranch(block, src); if (wip_captures.scope.captures.count() != orig_captures) { try wip_captures.reset(parent_capture_scope); @@ -1003,12 +1194,12 @@ fn analyzeBodyInner( const inst_data = datas[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index); const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; - const break_inst = try sema.analyzeBody(block, inline_body); - const break_data = datas[break_inst].@"break"; + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; if (inst == break_data.block_inst) { - break :blk sema.resolveInst(break_data.operand); + break :blk try sema.resolveInst(break_data.operand); } else { - break break_inst; + break break_data.inst; } }, .block => blk: { @@ -1026,34 +1217,81 @@ fn analyzeBodyInner( block.params.deinit(sema.gpa); block.params = prev_params; } - const break_inst = try sema.analyzeBody(block, inline_body); - const break_data = datas[break_inst].@"break"; + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; if (inst == break_data.block_inst) { - break :blk sema.resolveInst(break_data.operand); + break :blk try sema.resolveInst(break_data.operand); } else { - break break_inst; + break break_data.inst; } }, .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[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index); const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const gpa = sema.gpa; // 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 prev_params = block.params; block.params = .{}; defer { - block.params.deinit(sema.gpa); + block.params.deinit(gpa); block.params = prev_params; } - const break_inst = try sema.analyzeBody(block, inline_body); - const break_data = datas[break_inst].@"break"; + const opt_break_data = try sema.analyzeBodyBreak(block, inline_body); + // 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 = Air.refToIndex(new_block_ref) 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 are about to look up in the post_hoc_blocks map below. + try sema.addRuntimeBreak(block, break_data); + } else { + // Here the comptime control flow ends with noreturn; however + // we have runtime control flow continuing after this block. + // This branch is therefore handled by the `i += 1; continue;` + // logic below. + } + + try labeled_block.block.instructions.appendSlice(gpa, block.instructions.items[block_index..]); + block.instructions.items.len = block_index; + + const block_result = try sema.analyzeBlockBody(block, inst_data.src(), &labeled_block.block, &labeled_block.label.merges); + { + // 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)); + } + try map.put(gpa, inst, block_result); + i += 1; + continue; + } + + const break_data = opt_break_data orelse break always_noreturn; if (inst == break_data.block_inst) { - break :blk sema.resolveInst(break_data.operand); + break :blk try sema.resolveInst(break_data.operand); } else { - break break_inst; + break break_data.inst; } }, .condbr => blk: { @@ -1066,12 +1304,12 @@ fn analyzeBodyInner( const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; const cond = try sema.resolveInstConst(block, cond_src, extra.data.condition); const inline_body = if (cond.val.toBool()) then_body else else_body; - const break_inst = try sema.analyzeBody(block, inline_body); - const break_data = datas[break_inst].@"break"; + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; if (inst == break_data.block_inst) { - break :blk sema.resolveInst(break_data.operand); + break :blk try sema.resolveInst(break_data.operand); } else { - break break_inst; + break break_data.inst; } }, .condbr_inline => blk: { @@ -1082,14 +1320,114 @@ fn analyzeBodyInner( const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; const cond = try sema.resolveInstConst(block, cond_src, extra.data.condition); const inline_body = if (cond.val.toBool()) then_body else else_body; - const break_inst = try sema.analyzeBody(block, inline_body); - const break_data = datas[break_inst].@"break"; + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; + if (inst == break_data.block_inst) { + break :blk try sema.resolveInst(break_data.operand); + } else { + break break_data.inst; + } + }, + .@"try" => blk: { + if (!block.is_comptime) break :blk try sema.zirTry(block, inst); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + const err_union = try sema.resolveInst(extra.data.operand); + const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union); + assert(is_non_err != .none); + const is_non_err_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + if (is_non_err_tv.val.toBool()) { + const err_union_ty = sema.typeOf(err_union); + break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, err_union, operand_src, false); + } + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; + if (inst == break_data.block_inst) { + break :blk try sema.resolveInst(break_data.operand); + } else { + break break_data.inst; + } + }, + //.try_inline => blk: { + // const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + // const src = inst_data.src(); + // const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + // const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + // const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; + // const operand = try sema.resolveInst(extra.data.operand); + // const operand_ty = sema.typeOf(operand); + // const is_ptr = operand_ty.zigTypeTag() == .Pointer; + // const err_union = if (is_ptr) + // try sema.analyzeLoad(block, src, operand, operand_src) + // else + // operand; + // const is_non_err = try sema.analyzeIsNonErrComptimeOnly(block, operand_src, err_union); + // assert(is_non_err != .none); + // const is_non_err_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + // if (is_non_err_tv.val.toBool()) { + // if (is_ptr) { + // break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); + // } else { + // const err_union_ty = sema.typeOf(err_union); + // break :blk try sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, false); + // } + // } + // const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + // break always_noreturn; + // if (inst == break_data.block_inst) { + // break :blk try sema.resolveInst(break_data.operand); + // } else { + // break break_data.inst; + // } + //}, + .try_ptr => blk: { + if (!block.is_comptime) break :blk try sema.zirTryPtr(block, inst); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const inline_body = sema.code.extra[extra.end..][0..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_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + if (is_non_err_tv.val.toBool()) { + break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); + } + const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + break always_noreturn; if (inst == break_data.block_inst) { - break :blk sema.resolveInst(break_data.operand); + break :blk try sema.resolveInst(break_data.operand); } else { - break break_inst; + break break_data.inst; } }, + //.try_ptr_inline => blk: { + // const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + // const src = inst_data.src(); + // const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + // const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + // const inline_body = sema.code.extra[extra.end..][0..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_tv = try sema.resolveInstConst(block, operand_src, is_non_err); + // if (is_non_err_tv.val.toBool()) { + // break :blk try sema.analyzeErrUnionPayloadPtr(block, src, operand, false, false); + // } + // const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse + // break always_noreturn; + // if (inst == break_data.block_inst) { + // break :blk try sema.resolveInst(break_data.operand); + // } else { + // break break_data.inst; + // } + //}, }; if (sema.typeOf(air_inst).isNoReturn()) break always_noreturn; @@ -1097,6 +1435,19 @@ fn analyzeBodyInner( i += 1; } else unreachable; + // balance out dbg_block_begins in case of early noreturn + const noreturn_inst = block.instructions.popOrNull(); + while (dbg_block_begins > 0) { + dbg_block_begins -= 1; + if (block.is_comptime or sema.mod.comp.bin_file.options.strip) continue; + + _ = try block.addInst(.{ + .tag = .dbg_block_end, + .data = undefined, + }); + } + if (noreturn_inst) |some| try block.instructions.append(sema.gpa, some); + if (!wip_captures.finalized) { try wip_captures.finalize(); block.wip_capture_scope = parent_capture_scope; @@ -1105,44 +1456,7 @@ fn analyzeBodyInner( return result; } -fn zirExtended(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const extended = sema.code.instructions.items(.data)[inst].extended; - switch (extended.opcode) { - // zig fmt: off - .func => return sema.zirFuncExtended( block, extended, inst), - .variable => return sema.zirVarExtended( block, extended), - .struct_decl => return sema.zirStructDecl( block, extended, inst), - .enum_decl => return sema.zirEnumDecl( block, extended), - .union_decl => return sema.zirUnionDecl( block, extended, inst), - .opaque_decl => return sema.zirOpaqueDecl( block, extended), - .ret_ptr => return sema.zirRetPtr( block, extended), - .ret_type => return sema.zirRetType( block, extended), - .this => return sema.zirThis( block, extended), - .ret_addr => return sema.zirRetAddr( block, extended), - .builtin_src => return sema.zirBuiltinSrc( block, extended), - .error_return_trace => return sema.zirErrorReturnTrace( block, extended), - .frame => return sema.zirFrame( block, extended), - .frame_address => return sema.zirFrameAddress( block, extended), - .alloc => return sema.zirAllocExtended( block, extended), - .builtin_extern => return sema.zirBuiltinExtern( block, extended), - .@"asm" => return sema.zirAsm( block, extended, inst), - .typeof_peer => return sema.zirTypeofPeer( block, extended), - .compile_log => return sema.zirCompileLog( block, extended), - .add_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), - .sub_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), - .mul_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), - .shl_with_overflow => return sema.zirOverflowArithmetic(block, extended, extended.opcode), - .c_undef => return sema.zirCUndef( block, extended), - .c_include => return sema.zirCInclude( block, extended), - .c_define => return sema.zirCDefine( block, extended), - .wasm_memory_size => return sema.zirWasmMemorySize( block, extended), - .wasm_memory_grow => return sema.zirWasmMemoryGrow( block, extended), - .prefetch => return sema.zirPrefetch( block, extended), - // zig fmt: on - } -} - -pub fn resolveInst(sema: *Sema, zir_ref: Zir.Inst.Ref) Air.Inst.Ref { +pub fn resolveInst(sema: *Sema, zir_ref: Zir.Inst.Ref) !Air.Inst.Ref { var i: usize = @enumToInt(zir_ref); // First section of indexes correspond to a set number of constant values. @@ -1153,7 +1467,9 @@ pub fn resolveInst(sema: *Sema, zir_ref: Zir.Inst.Ref) Air.Inst.Ref { i -= Zir.Inst.Ref.typed_value_map.len; // Finally, the last section of indexes refers to the map of ZIR=>AIR. - return sema.inst_map.get(@intCast(u32, i)).?; + const inst = sema.inst_map.get(@intCast(u32, i)).?; + if (sema.typeOf(inst).tag() == .generic_poison) return error.GenericPoison; + return inst; } fn resolveConstBool( @@ -1162,28 +1478,28 @@ fn resolveConstBool( src: LazySrcLoc, zir_ref: Zir.Inst.Ref, ) !bool { - const air_inst = sema.resolveInst(zir_ref); + 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.resolveConstValue(block, src, coerced_inst); return val.toBool(); } -fn resolveConstString( +pub fn resolveConstString( sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref, ) ![]u8 { - const air_inst = sema.resolveInst(zir_ref); + const air_inst = try sema.resolveInst(zir_ref); const wanted_type = Type.initTag(.const_slice_u8); const coerced_inst = try sema.coerce(block, wanted_type, air_inst, src); const val = try sema.resolveConstValue(block, src, coerced_inst); - return val.toAllocatedBytes(wanted_type, sema.arena); + return val.toAllocatedBytes(wanted_type, sema.arena, sema.mod); } pub fn resolveType(sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref) !Type { - const air_inst = sema.resolveInst(zir_ref); + const air_inst = try sema.resolveInst(zir_ref); const ty = try sema.analyzeAsType(block, src, air_inst); if (ty.tag() == .generic_poison) return error.GenericPoison; return ty; @@ -1203,6 +1519,45 @@ fn analyzeAsType( return ty.copy(sema.arena); } +pub fn setupErrorReturnTrace(sema: *Sema, block: *Block, last_arg_index: usize) !void { + const backend_supports_error_return_tracing = + sema.mod.comp.bin_file.options.use_llvm; + if (!backend_supports_error_return_tracing) { + // TODO implement this feature in all the backends and then delete this branch + return; + } + + var err_trace_block = block.makeSubBlock(); + err_trace_block.is_comptime = false; + defer err_trace_block.instructions.deinit(sema.gpa); + + const src: LazySrcLoc = .unneeded; + + // var addrs: [err_return_trace_addr_count]usize = undefined; + const err_return_trace_addr_count = 32; + const addr_arr_ty = try Type.array(sema.arena, err_return_trace_addr_count, null, Type.usize, sema.mod); + const addrs_ptr = try err_trace_block.addTy(.alloc, try Type.Tag.single_mut_pointer.create(sema.arena, addr_arr_ty)); + + // var st: StackTrace = undefined; + const unresolved_stack_trace_ty = try sema.getBuiltinType(&err_trace_block, src, "StackTrace"); + const stack_trace_ty = try sema.resolveTypeFields(&err_trace_block, src, unresolved_stack_trace_ty); + const st_ptr = try err_trace_block.addTy(.alloc, try Type.Tag.single_mut_pointer.create(sema.arena, stack_trace_ty)); + + // st.instruction_addresses = &addrs; + const addr_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "instruction_addresses", src); + try sema.storePtr2(&err_trace_block, src, addr_field_ptr, src, addrs_ptr, src, .store); + + // st.index = 0; + const index_field_ptr = try sema.fieldPtr(&err_trace_block, src, st_ptr, "index", src); + const zero = try sema.addConstant(Type.usize, Value.zero); + try sema.storePtr2(&err_trace_block, src, index_field_ptr, src, zero, src, .store); + + // @errorReturnTrace() = &st; + _ = try err_trace_block.addUnOp(.set_err_return_trace, st_ptr); + + try block.instructions.insertSlice(sema.gpa, last_arg_index, err_trace_block.instructions.items); +} + /// May return Value Tags: `variable`, `undef`. /// See `resolveConstValue` for an alternative. /// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. @@ -1266,6 +1621,7 @@ fn resolveDefinedValue( ) CompileError!?Value { if (try sema.resolveMaybeUndefVal(block, src, air_ref)) |val| { if (val.isUndef()) { + if (block.is_typeof) return null; return sema.failWithUseOfUndef(block, src); } return val; @@ -1290,6 +1646,24 @@ fn resolveMaybeUndefVal( } } +/// Value Tag `variable` results in `null`. +/// Value Tag `undef` results in the Value. +/// Value Tag `generic_poison` causes `error.GenericPoison` to be returned. +/// Value Tag `decl_ref` and `decl_ref_mut` or any nested such value results in `null`. +fn resolveMaybeUndefValIntable( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + inst: Air.Inst.Ref, +) CompileError!?Value { + const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; + switch (val.tag()) { + .variable, .decl_ref, .decl_ref_mut => return null, + .generic_poison => return error.GenericPoison, + else => return val, + } +} + /// Returns all Value tags including `variable` and `undef`. fn resolveMaybeUndefValAllowVariables( sema: *Sema, @@ -1333,11 +1707,25 @@ fn failWithDivideByZero(sema: *Sema, block: *Block, src: LazySrcLoc) CompileErro } fn failWithModRemNegative(sema: *Sema, block: *Block, src: LazySrcLoc, lhs_ty: Type, rhs_ty: Type) CompileError { - return sema.fail(block, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ lhs_ty, rhs_ty }); + return sema.fail(block, src, "remainder division with '{}' and '{}': signed integers and floats must use @rem or @mod", .{ + lhs_ty.fmt(sema.mod), rhs_ty.fmt(sema.mod), + }); } fn failWithExpectedOptionalType(sema: *Sema, block: *Block, src: LazySrcLoc, optional_ty: Type) CompileError { - return sema.fail(block, src, "expected optional type, found {}", .{optional_ty}); + return sema.fail(block, src, "expected optional type, found '{}'", .{optional_ty.fmt(sema.mod)}); +} + +fn failWithArrayInitNotSupported(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError { + return sema.fail(block, src, "type '{}' does not support array initialization syntax", .{ + ty.fmt(sema.mod), + }); +} + +fn failWithStructInitNotSupported(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError { + return sema.fail(block, src, "type '{}' does not support struct initialization syntax", .{ + ty.fmt(sema.mod), + }); } fn failWithErrorSetCodeMissing( @@ -1348,7 +1736,24 @@ fn failWithErrorSetCodeMissing( src_err_set_ty: Type, ) CompileError { return sema.fail(block, src, "expected type '{}', found type '{}'", .{ - dest_err_set_ty, src_err_set_ty, + dest_err_set_ty.fmt(sema.mod), src_err_set_ty.fmt(sema.mod), + }); +} + +fn failWithIntegerOverflow(sema: *Sema, block: *Block, src: LazySrcLoc, int_ty: Type, val: Value, vector_index: usize) CompileError { + if (int_ty.zigTypeTag() == .Vector) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "overflow of vector type '{}' with value '{}'", .{ + int_ty.fmt(sema.mod), val.fmtValue(int_ty, sema.mod), + }); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, src, msg, "when computing vector element at index '{d}'", .{vector_index}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + return sema.fail(block, src, "overflow of integer type '{}' with value '{}'", .{ + int_ty.fmt(sema.mod), val.fmtValue(int_ty, sema.mod), }); } @@ -1362,7 +1767,26 @@ fn errNote( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!void { - return sema.mod.errNoteNonLazy(src.toSrcLoc(block.src_decl), parent, format, args); + const mod = sema.mod; + const src_decl = mod.declPtr(block.src_decl); + return mod.errNoteNonLazy(src.toSrcLoc(src_decl), parent, format, args); +} + +fn addFieldErrNote( + sema: *Sema, + block: *Block, + container_ty: Type, + field_index: usize, + parent: *Module.ErrorMsg, + comptime format: []const u8, + args: anytype, +) !void { + const mod = sema.mod; + const decl_index = container_ty.getOwnerDecl(); + const decl = mod.declPtr(decl_index); + const tree = try sema.getAstTree(block); + const field_src = enumFieldSrcLoc(decl, tree.*, container_ty.getNodeOffset(), field_index); + try mod.errNoteNonLazy(field_src.toSrcLoc(decl), parent, format, args); } fn errMsg( @@ -1372,7 +1796,9 @@ fn errMsg( comptime format: []const u8, args: anytype, ) error{OutOfMemory}!*Module.ErrorMsg { - return Module.ErrorMsg.create(sema.gpa, src.toSrcLoc(block.src_decl), format, args); + const mod = sema.mod; + const src_decl = mod.declPtr(block.src_decl); + return Module.ErrorMsg.create(sema.gpa, src.toSrcLoc(src_decl), format, args); } pub fn fail( @@ -1383,10 +1809,10 @@ pub fn fail( args: anytype, ) CompileError { const err_msg = try sema.errMsg(block, src, format, args); - return sema.failWithOwnedErrorMsg(err_msg); + return sema.failWithOwnedErrorMsg(block, err_msg); } -fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { +fn failWithOwnedErrorMsg(sema: *Sema, block: *Block, err_msg: *Module.ErrorMsg) CompileError { @setCold(true); if (crash_report.is_enabled and sema.mod.comp.debug_compile_errors) { @@ -1399,6 +1825,7 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { } const mod = sema.mod; + if (block.inlining) |some| some.err = err_msg; { errdefer err_msg.destroy(mod.gpa); @@ -1414,41 +1841,27 @@ fn failWithOwnedErrorMsg(sema: *Sema, err_msg: *Module.ErrorMsg) CompileError { sema.owner_decl.analysis = .sema_failure; sema.owner_decl.generation = mod.generation; } - mod.failed_decls.putAssumeCapacityNoClobber(sema.owner_decl, err_msg); - return error.AnalysisFail; -} - -/// Appropriate to call when the coercion has already been done by result -/// location semantics. Asserts the value fits in the provided `Int` type. -/// Only supports `Int` types 64 bits or less. -/// TODO don't ever call this since we're migrating towards ResultLoc.coerced_ty. -fn resolveAlreadyCoercedInt( - sema: *Sema, - block: *Block, - src: LazySrcLoc, - zir_ref: Zir.Inst.Ref, - comptime Int: type, -) !Int { - comptime assert(@typeInfo(Int).Int.bits <= 64); - const air_inst = sema.resolveInst(zir_ref); - const val = try sema.resolveConstValue(block, src, air_inst); - switch (@typeInfo(Int).Int.signedness) { - .signed => return @intCast(Int, val.toSignedInt()), - .unsigned => return @intCast(Int, val.toUnsignedInt()), + const gop = mod.failed_decls.getOrPutAssumeCapacity(sema.owner_decl_index); + if (gop.found_existing) { + // If there are multiple errors for the same Decl, prefer the first one added. + err_msg.destroy(mod.gpa); + } else { + gop.value_ptr.* = err_msg; } + return error.AnalysisFail; } -fn resolveAlign( +pub fn resolveAlign( sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref, -) !u16 { - const alignment_big = try sema.resolveInt(block, src, zir_ref, Type.initTag(.u16)); - const alignment = @intCast(u16, alignment_big); // We coerce to u16 in the prev line. +) !u32 { + const alignment_big = try sema.resolveInt(block, src, zir_ref, Type.initTag(.u29)); + const alignment = @intCast(u32, alignment_big); // We coerce to u16 in the prev line. 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", .{ + return sema.fail(block, src, "alignment value '{d}' is not a power of two", .{ alignment, }); } @@ -1462,11 +1875,11 @@ fn resolveInt( zir_ref: Zir.Inst.Ref, dest_ty: Type, ) !u64 { - const air_inst = sema.resolveInst(zir_ref); + const air_inst = try sema.resolveInst(zir_ref); const coerced = try sema.coerce(block, dest_ty, air_inst, src); const val = try sema.resolveConstValue(block, src, coerced); - - return val.toUnsignedInt(); + const target = sema.mod.getTarget(); + return (try val.getUnsignedIntAdvanced(target, sema.kit(block, src))).?; } // Returns a compile error if the value has tag `variable`. See `resolveInstValue` for @@ -1477,7 +1890,7 @@ pub fn resolveInstConst( src: LazySrcLoc, zir_ref: Zir.Inst.Ref, ) CompileError!TypedValue { - const air_ref = sema.resolveInst(zir_ref); + const air_ref = try sema.resolveInst(zir_ref); const val = try sema.resolveConstValue(block, src, air_ref); return TypedValue{ .ty = sema.typeOf(air_ref), @@ -1493,7 +1906,7 @@ pub fn resolveInstValue( src: LazySrcLoc, zir_ref: Zir.Inst.Ref, ) CompileError!TypedValue { - const air_ref = sema.resolveInst(zir_ref); + const air_ref = try sema.resolveInst(zir_ref); const val = try sema.resolveValue(block, src, air_ref); return TypedValue{ .ty = sema.typeOf(air_ref), @@ -1505,11 +1918,13 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE const tracy = trace(@src()); defer tracy.end(); - const src: LazySrcLoc = sema.src; - const bin_inst = sema.code.instructions.items(.data)[inst].bin; - const pointee_ty = try sema.resolveType(block, src, bin_inst.lhs); - const ptr = sema.resolveInst(bin_inst.rhs); - const addr_space = target_util.defaultAddressSpace(sema.mod.getTarget(), .local); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const pointee_ty = try sema.resolveType(block, src, extra.lhs); + const ptr = try sema.resolveInst(extra.rhs); + const target = sema.mod.getTarget(); + const addr_space = target_util.defaultAddressSpace(target, .local); if (Air.refToIndex(ptr)) |ptr_inst| { if (sema.air_instructions.items(.tag)[ptr_inst] == .constant) { @@ -1529,7 +1944,7 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE try inferred_alloc.stored_inst_list.append(sema.arena, operand); try sema.requireRuntimeBlock(block, src); - const ptr_ty = try Type.ptr(sema.arena, .{ + const ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = pointee_ty, .@"align" = inferred_alloc.alignment, .@"addrspace" = addr_space, @@ -1543,11 +1958,15 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE // The alloc will turn into a Decl. var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); - iac.data.decl = try anon_decl.finish( + iac.data.decl_index = try anon_decl.finish( try pointee_ty.copy(anon_decl.arena()), Value.undef, + iac.data.alignment, ); - const ptr_ty = try Type.ptr(sema.arena, .{ + if (iac.data.alignment != 0) { + try sema.resolveTypeLayout(block, src, pointee_ty); + } + const ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = pointee_ty, .@"align" = iac.data.alignment, .@"addrspace" = addr_space, @@ -1555,33 +1974,27 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE return sema.addConstant( ptr_ty, try Value.Tag.decl_ref_mut.create(sema.arena, .{ - .decl = iac.data.decl, + .decl_index = iac.data.decl_index, .runtime_index = block.runtime_index, }), ); }, - .decl_ref_mut => { - const ptr_ty = try Type.ptr(sema.arena, .{ - .pointee_type = pointee_ty, - .@"addrspace" = addr_space, - }); - return sema.addConstant(ptr_ty, ptr_val); - }, else => {}, } } } - try sema.requireRuntimeBlock(block, src); - // Make a dummy store through the pointer to test the coercion. // We will then use the generated instructions to decide what // kind of transformations to make on the result pointer. var trash_block = block.makeSubBlock(); + trash_block.is_comptime = false; + trash_block.is_coerce_result_ptr = true; defer trash_block.instructions.deinit(sema.gpa); + const dummy_ptr = try trash_block.addTy(.alloc, sema.typeOf(ptr)); const dummy_operand = try trash_block.addBitCast(pointee_ty, .void_value); - try sema.storePtr(&trash_block, src, ptr, dummy_operand); + try sema.storePtr2(&trash_block, src, dummy_ptr, src, dummy_operand, src, .bitcast); { const air_tags = sema.air_instructions.items(.tag); @@ -1592,13 +2005,18 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE //} // The last one is always `store`. - const trash_inst = trash_block.instructions.pop(); - assert(air_tags[trash_inst] == .store); - assert(trash_inst == sema.air_instructions.len - 1); - sema.air_instructions.len -= 1; + const trash_inst = trash_block.instructions.items[trash_block.instructions.items.len - 1]; + if (air_tags[trash_inst] != .store) { + // no store instruction is generated for zero sized types + assert((try sema.typeHasOnePossibleValue(block, src, pointee_ty)) != null); + } else { + trash_block.instructions.items.len -= 1; + assert(trash_inst == sema.air_instructions.len - 1); + sema.air_instructions.len -= 1; + } } - const ptr_ty = try Type.ptr(sema.arena, .{ + const ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = pointee_ty, .@"addrspace" = addr_space, }); @@ -1612,30 +2030,31 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE switch (air_tags[trash_inst]) { .bitcast => { if (Air.indexToRef(trash_inst) == dummy_operand) { - return block.addBitCast(ptr_ty, new_ptr); + if (try sema.resolveDefinedValue(block, src, new_ptr)) |ptr_val| { + return sema.addConstant(ptr_ty, ptr_val); + } + return sema.bitCast(block, ptr_ty, new_ptr, src); } const ty_op = air_datas[trash_inst].ty_op; - const operand_ty = sema.getTmpAir().typeOf(ty_op.operand); - const ptr_operand_ty = try Type.ptr(sema.arena, .{ + const operand_ty = sema.typeOf(ty_op.operand); + const ptr_operand_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = operand_ty, .@"addrspace" = addr_space, }); - new_ptr = try block.addBitCast(ptr_operand_ty, new_ptr); + if (try sema.resolveDefinedValue(block, src, new_ptr)) |ptr_val| { + new_ptr = try sema.addConstant(ptr_operand_ty, ptr_val); + } else { + new_ptr = try sema.bitCast(block, ptr_operand_ty, new_ptr, src); + } }, .wrap_optional => { - const ty_op = air_datas[trash_inst].ty_op; - const payload_ty = sema.getTmpAir().typeOf(ty_op.operand); - const ptr_payload_ty = try Type.ptr(sema.arena, .{ - .pointee_type = payload_ty, - .@"addrspace" = addr_space, - }); - new_ptr = try block.addTyOp(.optional_payload_ptr_set, ptr_payload_ty, new_ptr); + new_ptr = try sema.analyzeOptionalPayloadPtr(block, src, new_ptr, false, true); }, .wrap_errunion_err => { return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_err", .{}); }, .wrap_errunion_payload => { - return sema.fail(block, src, "TODO coerce_result_ptr wrap_errunion_payload", .{}); + new_ptr = try sema.analyzeErrUnionPayloadPtr(block, src, new_ptr, false, true); }, else => { if (std.debug.runtime_safety) { @@ -1647,7 +2066,7 @@ fn zirCoerceResultPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE } }, } - } else unreachable; // TODO should not need else unreachable + } } pub fn analyzeStructDecl( @@ -1687,27 +2106,28 @@ fn zirStructDecl( const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small); const src: LazySrcLoc = if (small.has_src_node) blk: { const node_offset = @bitCast(i32, sema.code.extra[extended.operand]); - break :blk .{ .node_offset = node_offset }; + break :blk LazySrcLoc.nodeOffset(node_offset); } else sema.src; var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); errdefer new_decl_arena.deinit(); const new_decl_arena_allocator = new_decl_arena.allocator(); + const mod = sema.mod; const struct_obj = try new_decl_arena_allocator.create(Module.Struct); const struct_ty = try Type.Tag.@"struct".create(new_decl_arena_allocator, struct_obj); const struct_val = try Value.Tag.ty.create(new_decl_arena_allocator, struct_ty); - const type_name = try sema.createTypeName(block, small.name_strategy); - const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{ + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ .ty = Type.type, .val = struct_val, - }, type_name); + }, small.name_strategy, "struct", inst); + const new_decl = mod.declPtr(new_decl_index); new_decl.owns_tv = true; - errdefer sema.mod.abortAnonDecl(new_decl); + errdefer mod.abortAnonDecl(new_decl_index); struct_obj.* = .{ - .owner_decl = new_decl, + .owner_decl = new_decl_index, .fields = .{}, - .node_offset = src.node_offset, + .node_offset = src.node_offset.x, .zir_index = inst, .layout = small.layout, .status = .none, @@ -1723,31 +2143,97 @@ fn zirStructDecl( }); try sema.analyzeStructDecl(new_decl, inst, struct_obj); try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclVal(block, src, new_decl); + return sema.analyzeDeclVal(block, src, new_decl_index); } -fn createTypeName(sema: *Sema, block: *Block, name_strategy: Zir.Inst.NameStrategy) ![:0]u8 { +fn createAnonymousDeclTypeNamed( + sema: *Sema, + block: *Block, + typed_value: TypedValue, + name_strategy: Zir.Inst.NameStrategy, + anon_prefix: []const u8, + inst: ?Zir.Inst.Index, +) !Decl.Index { + const mod = sema.mod; + const namespace = block.namespace; + const src_scope = block.wip_capture_scope; + const src_decl = mod.declPtr(block.src_decl); + const new_decl_index = try mod.allocateNewDecl(namespace, src_decl.src_node, src_scope); + errdefer mod.destroyDecl(new_decl_index); + switch (name_strategy) { .anon => { // 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. - const name_index = sema.mod.getNextAnonNameIndex(); - return std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{ - block.src_decl.name, name_index, + // This name is also used as the key in the parent namespace so it cannot be + // renamed. + const name = try std.fmt.allocPrintZ(sema.gpa, "{s}__{s}_{d}", .{ + src_decl.name, anon_prefix, @enumToInt(new_decl_index), }); + errdefer sema.gpa.free(name); + try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name); + return new_decl_index; + }, + .parent => { + const name = try sema.gpa.dupeZ(u8, mem.sliceTo(sema.mod.declPtr(block.src_decl).name, 0)); + errdefer sema.gpa.free(name); + try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name); + return new_decl_index; }, - .parent => return sema.gpa.dupeZ(u8, mem.sliceTo(block.src_decl.name, 0)), .func => { - const name_index = sema.mod.getNextAnonNameIndex(); - const name = try std.fmt.allocPrintZ(sema.gpa, "{s}__anon_{d}", .{ - block.src_decl.name, name_index, - }); - log.warn("TODO: handle NameStrategy.func correctly instead of using anon name '{s}'", .{ - name, - }); - return name; + const fn_info = sema.code.getFnInfo(sema.func.?.zir_body_inst); + const zir_tags = sema.code.instructions.items(.tag); + + var buf = std.ArrayList(u8).init(sema.gpa); + defer buf.deinit(); + try buf.appendSlice(mem.sliceTo(sema.mod.declPtr(block.src_decl).name, 0)); + try buf.appendSlice("("); + + var arg_i: usize = 0; + for (fn_info.param_body) |zir_inst| switch (zir_tags[zir_inst]) { + .param, .param_comptime, .param_anytype, .param_anytype_comptime => { + const arg = sema.inst_map.get(zir_inst).?; + // The comptime call code in analyzeCall already did this, so we're + // just repeating it here and it's guaranteed to work. + const arg_val = sema.resolveConstMaybeUndefVal(block, .unneeded, arg) catch unreachable; + + if (arg_i != 0) try buf.appendSlice(","); + try buf.writer().print("{}", .{arg_val.fmtValue(sema.typeOf(arg), sema.mod)}); + + arg_i += 1; + continue; + }, + else => continue, + }; + + try buf.appendSlice(")"); + const name = try buf.toOwnedSliceSentinel(0); + errdefer sema.gpa.free(name); + try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name); + return new_decl_index; + }, + .dbg_var => { + const ref = Zir.indexToRef(inst.?); + const zir_tags = sema.code.instructions.items(.tag); + const zir_data = sema.code.instructions.items(.data); + var i = inst.?; + while (i < zir_tags.len) : (i += 1) switch (zir_tags[i]) { + .dbg_var_ptr, .dbg_var_val => { + if (zir_data[i].str_op.operand != ref) continue; + + const name = try std.fmt.allocPrintZ(sema.gpa, "{s}.{s}", .{ + src_decl.name, zir_data[i].str_op.getStr(sema.code), + }); + errdefer sema.gpa.free(name); + + try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, namespace, typed_value, name); + return new_decl_index; + }, + else => {}, + }; + return sema.createAnonymousDeclTypeNamed(block, typed_value, .anon, anon_prefix, null); }, } } @@ -1756,6 +2242,7 @@ 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(); @@ -1768,7 +2255,7 @@ fn zirEnumDecl( const src: LazySrcLoc = if (small.has_src_node) blk: { const node_offset = @bitCast(i32, sema.code.extra[extra_index]); extra_index += 1; - break :blk .{ .node_offset = node_offset }; + break :blk LazySrcLoc.nodeOffset(node_offset); } else sema.src; const tag_type_ref = if (small.has_tag_type) blk: { @@ -1807,20 +2294,21 @@ fn zirEnumDecl( }; const enum_ty = Type.initPayload(&enum_ty_payload.base); const enum_val = try Value.Tag.ty.create(new_decl_arena_allocator, enum_ty); - const type_name = try sema.createTypeName(block, small.name_strategy); - const new_decl = try mod.createAnonymousDeclNamed(block, .{ + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ .ty = Type.type, .val = enum_val, - }, type_name); + }, small.name_strategy, "enum", inst); + const new_decl = mod.declPtr(new_decl_index); new_decl.owns_tv = true; - errdefer mod.abortAnonDecl(new_decl); + errdefer mod.abortAnonDecl(new_decl_index); enum_obj.* = .{ - .owner_decl = new_decl, - .tag_ty = Type.initTag(.@"null"), + .owner_decl = new_decl_index, + .tag_ty = Type.@"null", + .tag_ty_inferred = true, .fields = .{}, .values = .{}, - .node_offset = src.node_offset, + .node_offset = src.node_offset.x, .namespace = .{ .parent = block.namespace, .ty = enum_ty, @@ -1840,9 +2328,10 @@ fn zirEnumDecl( // TODO better source location const ty = try sema.resolveType(block, src, tag_type_ref); enum_obj.tag_ty = try ty.copy(new_decl_arena_allocator); + enum_obj.tag_ty_inferred = false; } try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclVal(block, src, new_decl); + return sema.analyzeDeclVal(block, src, new_decl_index); } extra_index += body.len; @@ -1857,8 +2346,13 @@ fn zirEnumDecl( // should be the enum itself. const prev_owner_decl = sema.owner_decl; + const prev_owner_decl_index = sema.owner_decl_index; sema.owner_decl = new_decl; - defer sema.owner_decl = prev_owner_decl; + sema.owner_decl_index = new_decl_index; + defer { + sema.owner_decl = prev_owner_decl; + sema.owner_decl_index = prev_owner_decl_index; + } const prev_owner_func = sema.owner_func; sema.owner_func = null; @@ -1874,7 +2368,7 @@ fn zirEnumDecl( var enum_block: Block = .{ .parent = null, .sema = sema, - .src_decl = new_decl, + .src_decl = new_decl_index, .namespace = &enum_obj.namespace, .wip_capture_scope = wip_captures.scope, .instructions = .{}, @@ -1884,21 +2378,27 @@ fn zirEnumDecl( defer assert(enum_block.instructions.items.len == 0); // should all be comptime instructions if (body.len != 0) { - _ = try sema.analyzeBody(&enum_block, body); + try sema.analyzeBody(&enum_block, body); } try wip_captures.finalize(); - const tag_ty = blk: { - if (tag_type_ref != .none) { - // TODO better source location - const ty = try sema.resolveType(block, src, tag_type_ref); - break :blk try ty.copy(new_decl_arena_allocator); - } + if (tag_type_ref != .none) { + // TODO better source location + const ty = try sema.resolveType(block, src, tag_type_ref); + enum_obj.tag_ty = try ty.copy(new_decl_arena_allocator); + enum_obj.tag_ty_inferred = false; + } else { const bits = std.math.log2_int_ceil(usize, fields_len); - break :blk try Type.Tag.int_unsigned.create(new_decl_arena_allocator, bits); - }; - enum_obj.tag_ty = tag_ty; + enum_obj.tag_ty = try Type.Tag.int_unsigned.create(new_decl_arena_allocator, bits); + enum_obj.tag_ty_inferred = true; + } + } + + if (small.nonexhaustive) { + if (fields_len > 1 and std.math.log2_int(u64, fields_len) == enum_obj.tag_ty.bitSize(sema.mod.getTarget())) { + return sema.fail(block, src, "non-exhaustive enum specifies every value", .{}); + } } try enum_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); @@ -1908,12 +2408,14 @@ fn zirEnumDecl( if (any_values) { try enum_obj.values.ensureTotalCapacityContext(new_decl_arena_allocator, fields_len, .{ .ty = enum_obj.tag_ty, + .mod = mod, }); } var bit_bag_index: usize = body_end; var cur_bit_bag: u32 = undefined; var field_i: u32 = 0; + var last_tag_val: ?Value = null; while (field_i < fields_len) : (field_i += 1) { if (field_i % 32 == 0) { cur_bit_bag = sema.code.extra[bit_bag_index]; @@ -1934,15 +2436,15 @@ fn zirEnumDecl( const gop = enum_obj.fields.getOrPutAssumeCapacity(field_name); if (gop.found_existing) { const tree = try sema.getAstTree(block); - const field_src = enumFieldSrcLoc(block.src_decl, tree.*, src.node_offset, field_i); - const other_tag_src = enumFieldSrcLoc(block.src_decl, tree.*, src.node_offset, gop.index); + const field_src = enumFieldSrcLoc(sema.mod.declPtr(block.src_decl), tree.*, src.node_offset.x, field_i); + const other_tag_src = enumFieldSrcLoc(sema.mod.declPtr(block.src_decl), tree.*, src.node_offset.x, gop.index); const msg = msg: { - const msg = try sema.errMsg(block, field_src, "duplicate enum tag", .{}); + const msg = try sema.errMsg(block, field_src, "duplicate enum field '{s}'", .{field_name}); errdefer msg.destroy(gpa); - try sema.errNote(block, other_tag_src, msg, "other tag here", .{}); + try sema.errNote(block, other_tag_src, msg, "other field here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } if (has_tag_value) { @@ -1952,18 +2454,28 @@ fn zirEnumDecl( // that points to this default value expression rather than the struct. // But only resolve the source location if we need to emit a compile error. const tag_val = (try sema.resolveInstConst(block, src, tag_val_ref)).val; + last_tag_val = tag_val; const copied_tag_val = try tag_val.copy(new_decl_arena_allocator); enum_obj.values.putAssumeCapacityNoClobberContext(copied_tag_val, {}, .{ .ty = enum_obj.tag_ty, + .mod = mod, }); } else if (any_values) { - const tag_val = try Value.Tag.int_u64.create(new_decl_arena_allocator, field_i); - enum_obj.values.putAssumeCapacityNoClobberContext(tag_val, {}, .{ .ty = enum_obj.tag_ty }); + const tag_val = if (last_tag_val) |val| + try sema.intAdd(block, src, val, Value.one, enum_obj.tag_ty) + else + Value.zero; + last_tag_val = tag_val; + const copied_tag_val = try tag_val.copy(new_decl_arena_allocator); + enum_obj.values.putAssumeCapacityNoClobberContext(copied_tag_val, {}, .{ + .ty = enum_obj.tag_ty, + .mod = mod, + }); } } try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclVal(block, src, new_decl); + return sema.analyzeDeclVal(block, src, new_decl_index); } fn zirUnionDecl( @@ -1981,7 +2493,7 @@ fn zirUnionDecl( const src: LazySrcLoc = if (small.has_src_node) blk: { const node_offset = @bitCast(i32, sema.code.extra[extra_index]); extra_index += 1; - break :blk .{ .node_offset = node_offset }; + break :blk LazySrcLoc.nodeOffset(node_offset); } else sema.src; extra_index += @boolToInt(small.has_tag_type); @@ -2007,18 +2519,19 @@ fn zirUnionDecl( }; const union_ty = Type.initPayload(&union_payload.base); const union_val = try Value.Tag.ty.create(new_decl_arena_allocator, union_ty); - const type_name = try sema.createTypeName(block, small.name_strategy); - const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{ + const mod = sema.mod; + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ .ty = Type.type, .val = union_val, - }, type_name); + }, small.name_strategy, "union", inst); + const new_decl = mod.declPtr(new_decl_index); new_decl.owns_tv = true; - errdefer sema.mod.abortAnonDecl(new_decl); + errdefer mod.abortAnonDecl(new_decl_index); union_obj.* = .{ - .owner_decl = new_decl, + .owner_decl = new_decl_index, .tag_ty = Type.initTag(.@"null"), .fields = .{}, - .node_offset = src.node_offset, + .node_offset = src.node_offset.x, .zir_index = inst, .layout = small.layout, .status = .none, @@ -2032,16 +2545,17 @@ fn zirUnionDecl( &union_obj.namespace, new_decl, new_decl.name, }); - _ = try sema.mod.scanNamespace(&union_obj.namespace, extra_index, decls_len, new_decl); + _ = try mod.scanNamespace(&union_obj.namespace, extra_index, decls_len, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclVal(block, src, new_decl); + return sema.analyzeDeclVal(block, src, new_decl_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(); @@ -2054,7 +2568,7 @@ fn zirOpaqueDecl( const src: LazySrcLoc = if (small.has_src_node) blk: { const node_offset = @bitCast(i32, sema.code.extra[extra_index]); extra_index += 1; - break :blk .{ .node_offset = node_offset }; + break :blk LazySrcLoc.nodeOffset(node_offset); } else sema.src; const decls_len = if (small.has_decls_len) blk: { @@ -2075,17 +2589,17 @@ fn zirOpaqueDecl( }; const opaque_ty = Type.initPayload(&opaque_ty_payload.base); const opaque_val = try Value.Tag.ty.create(new_decl_arena_allocator, opaque_ty); - const type_name = try sema.createTypeName(block, small.name_strategy); - const new_decl = try mod.createAnonymousDeclNamed(block, .{ + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ .ty = Type.type, .val = opaque_val, - }, type_name); + }, small.name_strategy, "opaque", inst); + const new_decl = mod.declPtr(new_decl_index); new_decl.owns_tv = true; - errdefer mod.abortAnonDecl(new_decl); + errdefer mod.abortAnonDecl(new_decl_index); opaque_obj.* = .{ - .owner_decl = new_decl, - .node_offset = src.node_offset, + .owner_decl = new_decl_index, + .node_offset = src.node_offset.x, .namespace = .{ .parent = block.namespace, .ty = opaque_ty, @@ -2099,7 +2613,7 @@ fn zirOpaqueDecl( extra_index = try mod.scanNamespace(&opaque_obj.namespace, extra_index, decls_len, new_decl); try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclVal(block, src, new_decl); + return sema.analyzeDeclVal(block, src, new_decl_index); } fn zirErrorSetDecl( @@ -2123,13 +2637,14 @@ fn zirErrorSetDecl( const error_set = try new_decl_arena_allocator.create(Module.ErrorSet); const error_set_ty = try Type.Tag.error_set.create(new_decl_arena_allocator, error_set); const error_set_val = try Value.Tag.ty.create(new_decl_arena_allocator, error_set_ty); - const type_name = try sema.createTypeName(block, name_strategy); - const new_decl = try sema.mod.createAnonymousDeclNamed(block, .{ + const mod = sema.mod; + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ .ty = Type.type, .val = error_set_val, - }, type_name); + }, name_strategy, "error", inst); + const new_decl = mod.declPtr(new_decl_index); new_decl.owns_tv = true; - errdefer sema.mod.abortAnonDecl(new_decl); + errdefer mod.abortAnonDecl(new_decl_index); var names = Module.ErrorSet.NameMap{}; try names.ensureUnusedCapacity(new_decl_arena_allocator, extra.data.fields_len); @@ -2138,42 +2653,40 @@ fn zirErrorSetDecl( const extra_index_end = extra_index + (extra.data.fields_len * 2); while (extra_index < extra_index_end) : (extra_index += 2) { // +2 to skip over doc_string const str_index = sema.code.extra[extra_index]; - const name = try new_decl_arena_allocator.dupe(u8, sema.code.nullTerminatedString(str_index)); - - // TODO: This check should be performed in AstGen instead. - const result = names.getOrPutAssumeCapacity(name); - if (result.found_existing) { - return sema.fail(block, src, "duplicate error set field {s}", .{name}); - } + const kv = try mod.getErrorValue(sema.code.nullTerminatedString(str_index)); + const result = names.getOrPutAssumeCapacity(kv.key); + assert(!result.found_existing); // verified in AstGen } + + // names must be sorted. + Module.ErrorSet.sortNames(&names); + error_set.* = .{ - .owner_decl = new_decl, + .owner_decl = new_decl_index, .node_offset = inst_data.src_node, .names = names, }; try new_decl.finalizeNewArena(&new_decl_arena); - return sema.analyzeDeclVal(block, src, new_decl); + return sema.analyzeDeclVal(block, src, new_decl_index); } -fn zirRetPtr( - sema: *Sema, - block: *Block, - extended: Zir.Inst.Extended.InstData, -) CompileError!Air.Inst.Ref { +fn zirRetPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + const inst_data = sema.code.instructions.items(.data)[inst].node; + const src = LazySrcLoc.nodeOffset(inst_data); try sema.requireFunctionBlock(block, src); - if (block.is_comptime) { + if (block.is_comptime or try sema.typeRequiresComptime(block, src, sema.fn_ret_ty)) { const fn_ret_ty = try sema.resolveTypeFields(block, src, sema.fn_ret_ty); return sema.analyzeComptimeAlloc(block, fn_ret_ty, 0, src); } - const ptr_type = try Type.ptr(sema.arena, .{ + const target = sema.mod.getTarget(); + const ptr_type = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = sema.fn_ret_ty, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); if (block.inlining != null) { @@ -2191,19 +2704,16 @@ fn zirRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_tok; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); return sema.analyzeRef(block, inst_data.src(), operand); } -fn zirRetType( - sema: *Sema, - block: *Block, - extended: Zir.Inst.Extended.InstData, -) CompileError!Air.Inst.Ref { +fn zirRetType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + const inst_data = sema.code.instructions.items(.data)[inst].node; + const src = LazySrcLoc.nodeOffset(inst_data); try sema.requireFunctionBlock(block, src); return sema.addType(sema.fn_ret_ty); } @@ -2213,7 +2723,7 @@ fn zirEnsureResultUsed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compile defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const src = inst_data.src(); return sema.ensureResultUsed(block, operand, src); @@ -2228,6 +2738,7 @@ fn ensureResultUsed( const operand_ty = sema.typeOf(operand); switch (operand_ty.zigTypeTag()) { .Void, .NoReturn => return, + .ErrorSet, .ErrorUnion => return sema.fail(block, src, "error is ignored. consider using `try`, `catch`, or `if`", .{}), else => return sema.fail(block, src, "expression value is ignored", .{}), } } @@ -2237,11 +2748,11 @@ fn zirEnsureResultNonError(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const src = inst_data.src(); const operand_ty = sema.typeOf(operand); switch (operand_ty.zigTypeTag()) { - .ErrorSet, .ErrorUnion => return sema.fail(block, src, "error is discarded", .{}), + .ErrorSet, .ErrorUnion => return sema.fail(block, src, "error is discarded. consider using `try`, `catch`, or `if`", .{}), else => return, } } @@ -2252,7 +2763,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const object = sema.resolveInst(inst_data.operand); + const object = try sema.resolveInst(inst_data.operand); const object_ty = sema.typeOf(object); const is_pointer_to = object_ty.isSinglePointer(); @@ -2268,7 +2779,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE block, src, "type '{}' does not support indexing", - .{array_ty}, + .{array_ty.fmt(sema.mod)}, ); errdefer msg.destroy(sema.gpa); try sema.errNote( @@ -2280,7 +2791,7 @@ fn zirIndexablePtrLen(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } return sema.fieldVal(block, src, object, "len", src); @@ -2292,7 +2803,7 @@ fn zirAllocExtended( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.AllocExtended, extended.operand); - const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + const src = LazySrcLoc.nodeOffset(extra.data.src_node); const ty_src = src; // TODO better source location const align_src = src; // TODO better source location const small = @bitCast(Zir.Inst.AllocExtended.Small, extended.small); @@ -2305,7 +2816,7 @@ fn zirAllocExtended( break :blk try sema.resolveType(block, ty_src, type_ref); } else undefined; - const alignment: u16 = if (small.has_align) blk: { + const alignment: u32 = if (small.has_align) blk: { const align_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; const alignment = try sema.resolveAlign(block, align_src, align_ref); @@ -2317,14 +2828,14 @@ fn zirAllocExtended( else Type.initTag(.inferred_alloc_mut); - if (small.is_comptime) { + if (block.is_comptime or small.is_comptime) { if (small.has_type) { return sema.analyzeComptimeAlloc(block, var_ty, alignment, ty_src); } else { return sema.addConstant( inferred_alloc_ty, try Value.Tag.inferred_alloc_comptime.create(sema.arena, .{ - .decl = undefined, + .decl_index = undefined, .alignment = alignment, }), ); @@ -2335,13 +2846,14 @@ fn zirAllocExtended( if (!small.is_const) { try sema.validateVarType(block, ty_src, var_ty, false); } - const ptr_type = try Type.ptr(sema.arena, .{ + const target = sema.mod.getTarget(); + try sema.requireRuntimeBlock(block, src); + try sema.resolveTypeLayout(block, src, var_ty); + const ptr_type = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = var_ty, .@"align" = alignment, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); - try sema.requireRuntimeBlock(block, src); - try sema.resolveTypeLayout(block, src, var_ty); return block.addTy(.alloc, ptr_type); } @@ -2368,13 +2880,90 @@ fn zirAllocComptime(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr return sema.analyzeComptimeAlloc(block, var_ty, 0, ty_src); } -fn zirAllocInferredComptime(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirMakePtrConst(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const alloc = try sema.resolveInst(inst_data.operand); + const alloc_ty = sema.typeOf(alloc); + + var ptr_info = alloc_ty.ptrInfo().data; + const elem_ty = ptr_info.pointee_type; + + // Detect if all stores to an `.alloc` were comptime known. + ct: { + var search_index: usize = block.instructions.items.len; + const air_tags = sema.air_instructions.items(.tag); + const air_datas = sema.air_instructions.items(.data); + + const store_inst = while (true) { + if (search_index == 0) break :ct; + search_index -= 1; + + const candidate = block.instructions.items[search_index]; + switch (air_tags[candidate]) { + .dbg_stmt => continue, + .store => break candidate, + else => break :ct, + } + } else unreachable; // TODO shouldn't need this + + while (true) { + if (search_index == 0) break :ct; + search_index -= 1; + + const candidate = block.instructions.items[search_index]; + switch (air_tags[candidate]) { + .dbg_stmt => continue, + .alloc => { + if (Air.indexToRef(candidate) != alloc) break :ct; + break; + }, + else => break :ct, + } + } + + const store_op = air_datas[store_inst].bin_op; + const store_val = (try sema.resolveMaybeUndefVal(block, src, store_op.rhs)) orelse break :ct; + if (store_op.lhs != alloc) break :ct; + + // Remove all the unnecessary runtime instructions. + block.instructions.shrinkRetainingCapacity(search_index); + + var anon_decl = try block.startAnonDecl(src); + defer anon_decl.deinit(); + return sema.analyzeDeclRef(try anon_decl.finish( + try elem_ty.copy(anon_decl.arena()), + try store_val.copy(anon_decl.arena()), + ptr_info.@"align", + )); + } + + ptr_info.mutable = false; + const const_ptr_ty = try Type.ptr(sema.arena, sema.mod, ptr_info); + + // Detect if a comptime value simply needs to have its type changed. + if (try sema.resolveMaybeUndefVal(block, inst_data.src(), alloc)) |val| { + return sema.addConstant(const_ptr_ty, val); + } + + try sema.requireRuntimeBlock(block, src); + return block.addBitCast(const_ptr_ty, alloc); +} + +fn zirAllocInferredComptime( + sema: *Sema, + inst: Zir.Inst.Index, + inferred_alloc_ty: Type, +) CompileError!Air.Inst.Ref { const src_node = sema.code.instructions.items(.data)[inst].node; - const src: LazySrcLoc = .{ .node_offset = src_node }; + const src = LazySrcLoc.nodeOffset(src_node); sema.src = src; return sema.addConstant( - Type.initTag(.inferred_alloc_mut), - try Value.Tag.inferred_alloc_comptime.create(sema.arena, undefined), + inferred_alloc_ty, + try Value.Tag.inferred_alloc_comptime.create(sema.arena, .{ + .decl_index = undefined, + .alignment = 0, + }), ); } @@ -2389,12 +2978,13 @@ fn zirAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I if (block.is_comptime) { return sema.analyzeComptimeAlloc(block, var_ty, 0, ty_src); } - const ptr_type = try Type.ptr(sema.arena, .{ + const target = sema.mod.getTarget(); + const ptr_type = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = var_ty, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); try sema.requireRuntimeBlock(block, var_decl_src); - try sema.resolveTypeLayout(block, ty_src, var_ty); + try sema.queueFullTypeResolution(var_ty); return block.addTy(.alloc, ptr_type); } @@ -2410,12 +3000,13 @@ fn zirAllocMut(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai return sema.analyzeComptimeAlloc(block, var_ty, 0, ty_src); } try sema.validateVarType(block, ty_src, var_ty, false); - const ptr_type = try Type.ptr(sema.arena, .{ + const target = sema.mod.getTarget(); + const ptr_type = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = var_ty, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); try sema.requireRuntimeBlock(block, var_decl_src); - try sema.resolveTypeLayout(block, ty_src, var_ty); + try sema.queueFullTypeResolution(var_ty); return block.addTy(.alloc, ptr_type); } @@ -2429,13 +3020,16 @@ fn zirAllocInferred( defer tracy.end(); const src_node = sema.code.instructions.items(.data)[inst].node; - const src: LazySrcLoc = .{ .node_offset = src_node }; + const src = LazySrcLoc.nodeOffset(src_node); sema.src = src; if (block.is_comptime) { return sema.addConstant( inferred_alloc_ty, - try Value.Tag.inferred_alloc_comptime.create(sema.arena, undefined), + try Value.Tag.inferred_alloc_comptime.create(sema.arena, .{ + .decl_index = undefined, + .alignment = 0, + }), ); } @@ -2459,7 +3053,7 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const ty_src: LazySrcLoc = .{ .node_offset_var_decl_ty = inst_data.src_node }; - const ptr = sema.resolveInst(inst_data.operand); + const ptr = try sema.resolveInst(inst_data.operand); const ptr_inst = Air.refToIndex(ptr).?; assert(sema.air_instructions.items(.tag)[ptr_inst] == .constant); const value_index = sema.air_instructions.items(.data)[ptr_inst].ty_pl.payload; @@ -2474,12 +3068,14 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com switch (ptr_val.tag()) { .inferred_alloc_comptime => { const iac = ptr_val.castTag(.inferred_alloc_comptime).?; - const decl = iac.data.decl; - try sema.mod.declareDeclDependency(sema.owner_decl, decl); + const decl_index = iac.data.decl_index; + try sema.mod.declareDeclDependency(sema.owner_decl_index, decl_index); + const decl = sema.mod.declPtr(decl_index); const final_elem_ty = try decl.ty.copy(sema.arena); - const final_ptr_ty = try Type.ptr(sema.arena, .{ + const final_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = final_elem_ty, + .mutable = var_is_mut, .@"align" = iac.data.alignment, .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); @@ -2488,11 +3084,11 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com if (var_is_mut) { sema.air_values.items[value_index] = try Value.Tag.decl_ref_mut.create(sema.arena, .{ - .decl = decl, + .decl_index = decl_index, .runtime_index = block.runtime_index, }); } else { - sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, decl); + sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, decl_index); } }, .inferred_alloc => { @@ -2500,8 +3096,12 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com const peer_inst_list = inferred_alloc.data.stored_inst_list.items; const final_elem_ty = try sema.resolvePeerTypes(block, ty_src, peer_inst_list, .none); - try sema.requireRuntimeBlock(block, src); - try sema.resolveTypeLayout(block, ty_src, final_elem_ty); + const final_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = final_elem_ty, + .mutable = var_is_mut, + .@"align" = inferred_alloc.data.alignment, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + }); if (var_is_mut) { try sema.validateVarType(block, ty_src, final_elem_ty, false); @@ -2518,50 +3118,86 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com // instructions from the block, replacing the inst_map entry // corresponding to the ZIR alloc instruction with a constant // decl_ref pointing at our new Decl. + // dbg_stmt instructions may be interspersed into this pattern + // which must be ignored. if (block.instructions.items.len < 3) break :ct; - // zig fmt: off - const const_inst = block.instructions.items[block.instructions.items.len - 3]; - const bitcast_inst = block.instructions.items[block.instructions.items.len - 2]; - const store_inst = block.instructions.items[block.instructions.items.len - 1]; - const air_tags = sema.air_instructions.items(.tag); + var search_index: usize = block.instructions.items.len; + const air_tags = sema.air_instructions.items(.tag); const air_datas = sema.air_instructions.items(.data); - if (air_tags[const_inst] != .constant) break :ct; - if (air_tags[bitcast_inst] != .bitcast ) break :ct; - if (air_tags[store_inst] != .store ) break :ct; - // zig fmt: on + + const store_inst = while (true) { + if (search_index == 0) break :ct; + search_index -= 1; + + const candidate = block.instructions.items[search_index]; + switch (air_tags[candidate]) { + .dbg_stmt => continue, + .store => break candidate, + else => break :ct, + } + } else unreachable; // TODO shouldn't need this + + const bitcast_inst = while (true) { + if (search_index == 0) break :ct; + search_index -= 1; + + const candidate = block.instructions.items[search_index]; + switch (air_tags[candidate]) { + .dbg_stmt => continue, + .bitcast => break candidate, + else => break :ct, + } + } else unreachable; // TODO shouldn't need this + + const const_inst = while (true) { + if (search_index == 0) break :ct; + search_index -= 1; + + const candidate = block.instructions.items[search_index]; + switch (air_tags[candidate]) { + .dbg_stmt => continue, + .constant => break candidate, + else => break :ct, + } + } else unreachable; // TODO shouldn't need this + const store_op = air_datas[store_inst].bin_op; const store_val = (try sema.resolveMaybeUndefVal(block, src, store_op.rhs)) orelse break :ct; if (store_op.lhs != Air.indexToRef(bitcast_inst)) break :ct; if (air_datas[bitcast_inst].ty_op.operand != Air.indexToRef(const_inst)) break :ct; - const bitcast_ty_ref = air_datas[bitcast_inst].ty_op.ty; - - const new_decl = d: { + const new_decl_index = d: { var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); - const new_decl = try anon_decl.finish( + const new_decl_index = try anon_decl.finish( try final_elem_ty.copy(anon_decl.arena()), try store_val.copy(anon_decl.arena()), + inferred_alloc.data.alignment, ); - break :d new_decl; + break :d new_decl_index; }; - try sema.mod.declareDeclDependency(sema.owner_decl, new_decl); + try sema.mod.declareDeclDependency(sema.owner_decl_index, new_decl_index); // Even though we reuse the constant instruction, we still remove it from the // block so that codegen does not see it. - block.instructions.shrinkRetainingCapacity(block.instructions.items.len - 3); - sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, new_decl); - air_datas[ptr_inst].ty_pl.ty = bitcast_ty_ref; + block.instructions.shrinkRetainingCapacity(search_index); + sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, new_decl_index); + // if bitcast ty ref needs to be made const, make_ptr_const + // ZIR handles it later, so we can just use the ty ref here. + air_datas[ptr_inst].ty_pl.ty = air_datas[bitcast_inst].ty_op.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 sema.resolveTypeFully(block, ty_src, final_elem_ty); return; } + try sema.requireRuntimeBlock(block, src); + try sema.queueFullTypeResolution(final_elem_ty); + // Change it to a normal alloc. - const final_ptr_ty = try Type.ptr(sema.arena, .{ - .pointee_type = final_elem_ty, - .@"align" = inferred_alloc.data.alignment, - .@"addrspace" = target_util.defaultAddressSpace(target, .local), - }); sema.air_instructions.set(ptr_inst, .{ .tag = .alloc, .data = .{ .ty = final_ptr_ty }, @@ -2571,6 +3207,94 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com } } +fn zirArrayBasePtr( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, +) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + + const start_ptr = try sema.resolveInst(inst_data.operand); + var base_ptr = start_ptr; + while (true) switch (sema.typeOf(base_ptr).childType().zigTypeTag()) { + .ErrorUnion => 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, + }; + + const elem_ty = sema.typeOf(base_ptr).childType(); + switch (elem_ty.zigTypeTag()) { + .Array, .Vector => return base_ptr, + .Struct => if (elem_ty.isTuple()) { + // TODO validate element count + return base_ptr; + }, + else => {}, + } + return sema.failWithArrayInitNotSupported(block, src, sema.typeOf(start_ptr).childType()); +} + +fn zirFieldBasePtr( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, +) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + + const start_ptr = try sema.resolveInst(inst_data.operand); + var base_ptr = start_ptr; + while (true) switch (sema.typeOf(base_ptr).childType().zigTypeTag()) { + .ErrorUnion => 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, + }; + + const elem_ty = sema.typeOf(base_ptr).childType(); + switch (elem_ty.zigTypeTag()) { + .Struct, .Union => return base_ptr, + else => {}, + } + return sema.failWithStructInitNotSupported(block, src, sema.typeOf(start_ptr).childType()); +} + +fn validateArrayInitTy( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, +) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const ty = try sema.resolveType(block, src, inst_data.operand); + + switch (ty.zigTypeTag()) { + .Array, .Vector => return, + .Struct => if (ty.isTuple()) { + // TODO validate element count + return; + }, + else => {}, + } + return sema.failWithArrayInitNotSupported(block, src, ty); +} + +fn validateStructInitTy( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, +) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const ty = try sema.resolveType(block, src, inst_data.operand); + + switch (ty.zigTypeTag()) { + .Struct, .Union => return, + else => {}, + } + return sema.failWithStructInitNotSupported(block, src, ty); +} + fn zirValidateStructInit( sema: *Sema, block: *Block, @@ -2586,7 +3310,7 @@ fn zirValidateStructInit( const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len]; const field_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node; const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; - const object_ptr = sema.resolveInst(field_ptr_extra.lhs); + const object_ptr = try sema.resolveInst(field_ptr_extra.lhs); const agg_ty = sema.typeOf(object_ptr).childType(); switch (agg_ty.zigTypeTag()) { .Struct => return sema.validateStructInit( @@ -2598,10 +3322,11 @@ fn zirValidateStructInit( ), .Union => return sema.validateUnionInit( block, - agg_ty.cast(Type.Payload.Union).?.data, + agg_ty, init_src, instrs, object_ptr, + is_comptime, ), else => unreachable, } @@ -2610,15 +3335,40 @@ fn zirValidateStructInit( fn validateUnionInit( sema: *Sema, block: *Block, - union_obj: *Module.Union, + union_ty: Type, init_src: LazySrcLoc, instrs: []const Zir.Inst.Index, union_ptr: Air.Inst.Ref, + is_comptime: bool, ) CompileError!void { + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + if (instrs.len != 1) { - // TODO add note for other field - // TODO add note for union declared here - return sema.fail(block, init_src, "only one union field can be active at once", .{}); + const msg = msg: { + const msg = try sema.errMsg( + block, + 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)[inst].pl_node; + const inst_src: LazySrcLoc = .{ .node_offset_back2tok = inst_data.src_node }; + try sema.errNote(block, inst_src, msg, "additional initializer here", .{}); + } + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + + if ((is_comptime or block.is_comptime) and + (try sema.resolveDefinedValue(block, init_src, union_ptr)) != null) + { + // In this case, comptime machinery already did everything. No work to do here. + return; } const field_ptr = instrs[0]; @@ -2626,40 +3376,83 @@ fn validateUnionInit( const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_ptr_data.src_node }; const field_ptr_extra = sema.code.extraData(Zir.Inst.Field, field_ptr_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_ptr_extra.field_name_start); - const field_index_big = union_obj.fields.getIndex(field_name) orelse - return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); - const field_index = @intCast(u32, field_index_big); - - // Handle the possibility of the union value being comptime-known. - const union_ptr_inst = Air.refToIndex(sema.resolveInst(field_ptr_extra.lhs)).?; - switch (sema.air_instructions.items(.tag)[union_ptr_inst]) { - .constant => return, // In this case the tag has already been set. No validation to do. - .bitcast => { - // TODO here we need to go back and see if we need to convert the union - // to a comptime-known value. In such case, we must delete all the instructions - // added to the current block starting with the bitcast. - // If the bitcast result ptr is an alloc, the alloc should be replaced with - // a constant decl_ref. - // Otherwise, the bitcast should be preserved and a store instruction should be - // emitted to store the constant union value through the bitcast. - }, - .alloc => {}, - else => |t| { - if (std.debug.runtime_safety) { - std.debug.panic("unexpected AIR tag for union pointer: {s}", .{@tagName(t)}); - } else { - unreachable; + const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_src); + const air_tags = sema.air_instructions.items(.tag); + const air_datas = sema.air_instructions.items(.data); + const field_ptr_air_ref = sema.inst_map.get(field_ptr).?; + const field_ptr_air_inst = Air.refToIndex(field_ptr_air_ref).?; + + // Our task here is to determine if the union is comptime-known. In such case, + // we erase the runtime AIR instructions for initializing the union, and replace + // the mapping with the comptime value. Either way, we will need to populate the tag. + + // We expect to see something like this in the current block AIR: + // %a = alloc(*const U) + // %b = bitcast(*U, %a) + // %c = field_ptr(..., %b) + // %e!= store(%c!, %d!) + // If %d is a comptime operand, the union is comptime. + // If the union is comptime, we want `first_block_index` + // to point at %c so that the bitcast becomes the last instruction in the block. + // + // In the case of a comptime-known pointer to a union, the + // the field_ptr instruction is missing, so we have to pattern-match + // based only on the store instructions. + // `first_block_index` needs to point to the `field_ptr` if it exists; + // the `store` otherwise. + // + // It's also possible for there to be no store instruction, in the case + // of nested `coerce_result_ptr` instructions. If we see the `field_ptr` + // but we have not found a `store`, treat as a runtime-known field. + var first_block_index = block.instructions.items.len; + var block_index = block.instructions.items.len - 1; + var init_val: ?Value = null; + while (block_index > 0) : (block_index -= 1) { + const store_inst = block.instructions.items[block_index]; + if (store_inst == field_ptr_air_inst) break; + if (air_tags[store_inst] != .store) continue; + const bin_op = air_datas[store_inst].bin_op; + var lhs = bin_op.lhs; + if (Air.refToIndex(lhs)) |lhs_index| { + if (air_tags[lhs_index] == .bitcast) { + lhs = air_datas[lhs_index].ty_op.operand; + block_index -= 1; } - }, + } + if (lhs != field_ptr_air_ref) continue; + while (block_index > 0) : (block_index -= 1) { + const block_inst = block.instructions.items[block_index - 1]; + if (air_tags[block_inst] != .dbg_stmt) break; + } + if (block_index > 0 and + field_ptr_air_inst == block.instructions.items[block_index - 1]) + { + first_block_index = @minimum(first_block_index, block_index - 1); + } else { + first_block_index = @minimum(first_block_index, block_index); + } + init_val = try sema.resolveMaybeUndefValAllowVariables(block, init_src, bin_op.rhs); + break; } - // Otherwise, we set the new union tag now. - const new_tag = try sema.addConstant( - union_obj.tag_ty, - try Value.Tag.enum_field_index.create(sema.arena, field_index), - ); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + + if (init_val) |val| { + // Our task is to delete all the `field_ptr` and `store` instructions, and insert + // instead a single `store` to the result ptr with a comptime union value. + block.instructions.shrinkRetainingCapacity(first_block_index); + + const union_val = try Value.Tag.@"union".create(sema.arena, .{ + .tag = tag_val, + .val = val, + }); + const union_init = try sema.addConstant(union_ty, union_val); + try sema.storePtr2(block, init_src, union_ptr, init_src, union_init, init_src, .store); + return; + } try sema.requireRuntimeBlock(block, init_src); + const new_tag = try sema.addConstant(union_obj.tag_ty, tag_val); _ = try block.addBinOp(.set_union_tag, union_ptr, new_tag); } @@ -2698,7 +3491,7 @@ fn validateStructInit( try sema.errNote(block, other_field_src, msg, "other field here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } found_fields[field_index] = field_ptr; } @@ -2706,10 +3499,12 @@ fn validateStructInit( var root_msg: ?*Module.ErrorMsg = null; const fields = struct_obj.fields.values(); - const struct_ptr = sema.resolveInst(struct_ptr_zir_ref); + const struct_ptr = try sema.resolveInst(struct_ptr_zir_ref); const struct_ty = sema.typeOf(struct_ptr).childType(); - if (is_comptime or block.is_comptime) { + if ((is_comptime or block.is_comptime) and + (try sema.resolveDefinedValue(block, init_src, struct_ptr)) != null) + { // In this case the only thing we need to do is evaluate the implicit // store instructions for default field values, and report any missing fields. // Avoid the cost of the extra machinery for detecting a comptime struct init value. @@ -2737,22 +3532,23 @@ fn validateStructInit( } if (root_msg) |msg| { - const fqn = try struct_obj.getFullyQualifiedName(gpa); + const mod = sema.mod; + const fqn = try struct_obj.getFullyQualifiedName(mod); defer gpa.free(fqn); - try sema.mod.errNoteNonLazy( - struct_obj.srcLoc(), + try mod.errNoteNonLazy( + struct_obj.srcLoc(mod), msg, "struct '{s}' declared here", .{fqn}, ); - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } return; } var struct_is_comptime = true; - var first_block_index: usize = std.math.maxInt(u32); + var first_block_index = block.instructions.items.len; const air_tags = sema.air_instructions.items(.tag); const air_datas = sema.air_instructions.items(.data); @@ -2761,7 +3557,7 @@ fn validateStructInit( // ends up being comptime-known. const field_values = try sema.arena.alloc(Value, fields.len); - for (found_fields) |field_ptr, i| { + field: for (found_fields) |field_ptr, i| { const field = fields[i]; if (field_ptr != 0) { @@ -2776,40 +3572,69 @@ fn validateStructInit( const field_ptr_air_ref = sema.inst_map.get(field_ptr).?; const field_ptr_air_inst = Air.refToIndex(field_ptr_air_ref).?; - // Find the block index of the field_ptr so that we can look at the next - // instruction after it within the same block. + + //std.debug.print("validateStructInit (field_ptr_air_inst=%{d}):\n", .{ + // field_ptr_air_inst, + //}); + //for (block.instructions.items) |item| { + // std.debug.print(" %{d} = {s}\n", .{item, @tagName(air_tags[item])}); + //} + + // We expect to see something like this in the current block AIR: + // %a = field_ptr(...) + // store(%a, %b) + // If %b is a comptime operand, this field is comptime. + // + // However, in the case of a comptime-known pointer to a struct, the + // the field_ptr instruction is missing, so we have to pattern-match + // based only on the store instructions. + // `first_block_index` needs to point to the `field_ptr` if it exists; + // the `store` otherwise. + // + // It's also possible for there to be no store instruction, in the case + // of nested `coerce_result_ptr` instructions. If we see the `field_ptr` + // but we have not found a `store`, treat as a runtime-known field. + // Possible performance enhancement: save the `block_index` between iterations // of the for loop. - const next_air_inst = inst: { - var block_index = block.instructions.items.len - 1; - while (block.instructions.items[block_index] != field_ptr_air_inst) { - block_index -= 1; + var block_index = block.instructions.items.len - 1; + while (block_index > 0) : (block_index -= 1) { + const store_inst = block.instructions.items[block_index]; + if (store_inst == field_ptr_air_inst) { + struct_is_comptime = false; + continue :field; } - first_block_index = @minimum(first_block_index, block_index); - break :inst block.instructions.items[block_index + 1]; - }; - - // If the next instructon is a store with a comptime operand, this field - // is comptime. - switch (air_tags[next_air_inst]) { - .store => { - const bin_op = air_datas[next_air_inst].bin_op; - if (bin_op.lhs != field_ptr_air_ref) { - struct_is_comptime = false; - continue; - } - if (try sema.resolveMaybeUndefValAllowVariables(block, field_src, bin_op.rhs)) |val| { - field_values[i] = val; - } else { - struct_is_comptime = false; + if (air_tags[store_inst] != .store) continue; + const bin_op = air_datas[store_inst].bin_op; + var lhs = bin_op.lhs; + { + const lhs_index = Air.refToIndex(lhs) orelse continue; + if (air_tags[lhs_index] == .bitcast) { + lhs = air_datas[lhs_index].ty_op.operand; + block_index -= 1; } - continue; - }, - else => { + } + if (lhs != field_ptr_air_ref) continue; + while (block_index > 0) : (block_index -= 1) { + const block_inst = block.instructions.items[block_index - 1]; + if (air_tags[block_inst] != .dbg_stmt) break; + } + if (block_index > 0 and + field_ptr_air_inst == block.instructions.items[block_index - 1]) + { + first_block_index = @minimum(first_block_index, block_index - 1); + } else { + first_block_index = @minimum(first_block_index, block_index); + } + if (try sema.resolveMaybeUndefValAllowVariables(block, field_src, bin_op.rhs)) |val| { + field_values[i] = val; + } else { struct_is_comptime = false; - continue; - }, + } + continue :field; } + struct_is_comptime = false; + continue :field; } const field_name = struct_obj.fields.keys()[i]; @@ -2827,15 +3652,15 @@ fn validateStructInit( } if (root_msg) |msg| { - const fqn = try struct_obj.getFullyQualifiedName(gpa); + const fqn = try struct_obj.getFullyQualifiedName(sema.mod); defer gpa.free(fqn); try sema.mod.errNoteNonLazy( - struct_obj.srcLoc(), + struct_obj.srcLoc(sema.mod), msg, "struct '{s}' declared here", .{fqn}, ); - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } if (struct_is_comptime) { @@ -2852,7 +3677,7 @@ fn validateStructInit( field_values[i] = fields[i].default_val; } - const struct_val = try Value.Tag.@"struct".create(sema.arena, field_values); + const struct_val = try Value.Tag.aggregate.create(sema.arena, field_values); const struct_init = try sema.addConstant(struct_ty, struct_val); try sema.storePtr2(block, init_src, struct_ptr, init_src, struct_init, init_src, .store); return; @@ -2885,7 +3710,7 @@ fn zirValidateArrayInit( const instrs = sema.code.extra[validate_extra.end..][0..validate_extra.data.body_len]; const first_elem_ptr_data = sema.code.instructions.items(.data)[instrs[0]].pl_node; const elem_ptr_extra = sema.code.extraData(Zir.Inst.ElemPtrImm, first_elem_ptr_data.payload_index).data; - const array_ptr = sema.resolveInst(elem_ptr_extra.ptr); + const array_ptr = try sema.resolveInst(elem_ptr_extra.ptr); const array_ty = sema.typeOf(array_ptr).childType(); const array_len = array_ty.arrayLen(); @@ -2895,14 +3720,16 @@ fn zirValidateArrayInit( }); } - if (is_comptime or block.is_comptime) { + if ((is_comptime or block.is_comptime) and + (try sema.resolveDefinedValue(block, init_src, array_ptr)) != null) + { // In this case the comptime machinery will have evaluated the store instructions // at comptime so we have almost nothing to do here. However, in case of a // sentinel-terminated array, the sentinel will not have been populated by // any ZIR instructions at comptime; we need to do that here. if (array_ty.sentinel()) |sentinel_val| { const array_len_ref = try sema.addIntUnsigned(Type.usize, array_len); - const sentinel_ptr = try sema.elemPtrArray(block, init_src, array_ptr, array_len_ref, init_src); + const sentinel_ptr = try sema.elemPtrArray(block, init_src, array_ptr, init_src, array_len_ref, true); const sentinel = try sema.addConstant(array_ty.childType(), sentinel_val); try sema.storePtr2(block, init_src, sentinel_ptr, init_src, sentinel, init_src, .store); } @@ -2910,7 +3737,7 @@ fn zirValidateArrayInit( } var array_is_comptime = true; - var first_block_index: usize = std.math.maxInt(u32); + var first_block_index = block.instructions.items.len; // Collect the comptime element values in case the array literal ends up // being comptime-known. @@ -2920,38 +3747,74 @@ fn zirValidateArrayInit( const air_tags = sema.air_instructions.items(.tag); const air_datas = sema.air_instructions.items(.data); - for (instrs) |elem_ptr, i| { + outer: for (instrs) |elem_ptr, i| { const elem_ptr_data = sema.code.instructions.items(.data)[elem_ptr].pl_node; - const elem_src: LazySrcLoc = .{ .node_offset = elem_ptr_data.src_node }; + const elem_src = LazySrcLoc.nodeOffset(elem_ptr_data.src_node); // Determine whether the value stored to this pointer is comptime-known. - if (opt_opv) |opv| { - element_vals[i] = opv; - continue; - } - const elem_ptr_air_ref = sema.inst_map.get(elem_ptr).?; const elem_ptr_air_inst = Air.refToIndex(elem_ptr_air_ref).?; // Find the block index of the elem_ptr so that we can look at the next // instruction after it within the same block. // Possible performance enhancement: save the `block_index` between iterations // of the for loop. - const next_air_inst = inst: { - var block_index = block.instructions.items.len - 1; - while (block.instructions.items[block_index] != elem_ptr_air_inst) { - block_index -= 1; + var block_index = block.instructions.items.len - 1; + while (block.instructions.items[block_index] != elem_ptr_air_inst) { + if (block_index == 0) { + array_is_comptime = true; + continue :outer; } - first_block_index = @minimum(first_block_index, block_index); - break :inst block.instructions.items[block_index + 1]; - }; + block_index -= 1; + } + first_block_index = @minimum(first_block_index, block_index); + + // Array has one possible value, so value is always comptime-known + if (opt_opv) |opv| { + element_vals[i] = opv; + continue; + } // If the next instructon is a store with a comptime operand, this element // is comptime. + const next_air_inst = block.instructions.items[block_index + 1]; switch (air_tags[next_air_inst]) { .store => { const bin_op = air_datas[next_air_inst].bin_op; - if (bin_op.lhs != elem_ptr_air_ref) { + var lhs = bin_op.lhs; + if (Air.refToIndex(lhs)) |lhs_index| { + if (air_tags[lhs_index] == .bitcast) { + lhs = air_datas[lhs_index].ty_op.operand; + block_index -= 1; + } + } + if (lhs != elem_ptr_air_ref) { + array_is_comptime = false; + continue; + } + if (try sema.resolveMaybeUndefValAllowVariables(block, elem_src, bin_op.rhs)) |val| { + element_vals[i] = val; + } else { + array_is_comptime = false; + } + continue; + }, + .bitcast => { + // %a = bitcast(*arr_ty, %array_base) + // %b = ptr_elem_ptr(%a, %index) + // %c = bitcast(*elem_ty, %b) + // %d = store(%c, %val) + if (air_datas[next_air_inst].ty_op.operand != elem_ptr_air_ref) { + array_is_comptime = false; + continue; + } + const store_inst = block.instructions.items[block_index + 2]; + if (air_tags[store_inst] != .store) { + array_is_comptime = false; + continue; + } + const bin_op = air_datas[store_inst].bin_op; + if (bin_op.lhs != Air.indexToRef(next_air_inst)) { array_is_comptime = false; continue; } @@ -2970,6 +3833,13 @@ fn zirValidateArrayInit( } if (array_is_comptime) { + if (try sema.resolveDefinedValue(block, init_src, array_ptr)) |ptr_val| { + if (ptr_val.tag() == .comptime_field_ptr) { + // This store was validated by the individual elem ptrs. + return; + } + } + // Our task is to delete all the `elem_ptr` and `store` instructions, and insert // instead a single `store` to the array_ptr with a comptime struct value. // Also to populate the sentinel value, if any. @@ -2979,12 +3849,34 @@ fn zirValidateArrayInit( block.instructions.shrinkRetainingCapacity(first_block_index); - const array_val = try Value.Tag.array.create(sema.arena, element_vals); + const array_val = try Value.Tag.aggregate.create(sema.arena, element_vals); const array_init = try sema.addConstant(array_ty, array_val); try sema.storePtr2(block, init_src, array_ptr, init_src, array_init, init_src, .store); } } +fn zirValidateDeref(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { + const inst_data = sema.code.instructions.items(.data)[inst].un_tok; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .token_offset = inst_data.src_tok + 1 }; + const operand = try sema.resolveInst(inst_data.operand); + const operand_ty = sema.typeOf(operand); + + if (operand_ty.zigTypeTag() != .Pointer) { + return sema.fail(block, src, "cannot dereference non-pointer type '{}'", .{operand_ty.fmt(sema.mod)}); + } else switch (operand_ty.ptrSize()) { + .One, .C => {}, + .Many => return sema.fail(block, src, "index syntax required for unknown-length pointer type '{}'", .{operand_ty.fmt(sema.mod)}), + .Slice => return sema.fail(block, src, "index syntax required for slice type '{}'", .{operand_ty.fmt(sema.mod)}), + } + + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) { + return sema.fail(block, src, "cannot dereference undefined value", .{}); + } + } +} + fn failWithBadMemberAccess( sema: *Sema, block: *Block, @@ -3001,13 +3893,13 @@ fn failWithBadMemberAccess( }; const msg = msg: { const msg = try sema.errMsg(block, field_src, "{s} '{}' has no member named '{s}'", .{ - kw_name, agg_ty, field_name, + kw_name, agg_ty.fmt(sema.mod), field_name, }); errdefer msg.destroy(sema.gpa); try sema.addDeclaredHereNote(msg, agg_ty); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } fn failWithBadStructFieldAccess( @@ -3019,7 +3911,7 @@ fn failWithBadStructFieldAccess( ) CompileError { const gpa = sema.gpa; - const fqn = try struct_obj.getFullyQualifiedName(gpa); + const fqn = try struct_obj.getFullyQualifiedName(sema.mod); defer gpa.free(fqn); const msg = msg: { @@ -3030,10 +3922,10 @@ fn failWithBadStructFieldAccess( .{ field_name, fqn }, ); errdefer msg.destroy(gpa); - try sema.mod.errNoteNonLazy(struct_obj.srcLoc(), msg, "struct declared here", .{}); + try sema.mod.errNoteNonLazy(struct_obj.srcLoc(sema.mod), msg, "struct declared here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } fn failWithBadUnionFieldAccess( @@ -3045,7 +3937,7 @@ fn failWithBadUnionFieldAccess( ) CompileError { const gpa = sema.gpa; - const fqn = try union_obj.getFullyQualifiedName(gpa); + const fqn = try union_obj.getFullyQualifiedName(sema.mod); defer gpa.free(fqn); const msg = msg: { @@ -3056,14 +3948,14 @@ fn failWithBadUnionFieldAccess( .{ field_name, fqn }, ); errdefer msg.destroy(gpa); - try sema.mod.errNoteNonLazy(union_obj.srcLoc(), msg, "union declared here", .{}); + try sema.mod.errNoteNonLazy(union_obj.srcLoc(sema.mod), msg, "union declared here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } fn addDeclaredHereNote(sema: *Sema, parent: *Module.ErrorMsg, decl_ty: Type) !void { - const src_loc = decl_ty.declSrcLocOrNull() orelse return; + const src_loc = decl_ty.declSrcLocOrNull(sema.mod) orelse return; const category = switch (decl_ty.zigTypeTag()) { .Union => "union", .Struct => "struct", @@ -3080,24 +3972,31 @@ fn zirStoreToBlockPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE defer tracy.end(); const bin_inst = sema.code.instructions.items(.data)[inst].bin; - if (bin_inst.lhs == .none) { - // This is an elided instruction, but AstGen was not smart enough - // to omit it. + const ptr = sema.inst_map.get(Zir.refToIndex(bin_inst.lhs).?) orelse { + // This is an elided instruction, but AstGen was unable to omit it. return; + }; + const operand = try sema.resolveInst(bin_inst.rhs); + const src: LazySrcLoc = sema.src; + blk: { + const ptr_inst = Air.refToIndex(ptr) orelse break :blk; + if (sema.air_instructions.items(.tag)[ptr_inst] != .constant) break :blk; + const air_datas = sema.air_instructions.items(.data); + const ptr_val = sema.air_values.items[air_datas[ptr_inst].ty_pl.payload]; + switch (ptr_val.tag()) { + .inferred_alloc_comptime => { + const iac = ptr_val.castTag(.inferred_alloc_comptime).?; + return sema.storeToInferredAllocComptime(block, src, operand, iac); + }, + .inferred_alloc => { + const inferred_alloc = ptr_val.castTag(.inferred_alloc).?; + return sema.storeToInferredAlloc(block, src, ptr, operand, inferred_alloc); + }, + else => break :blk, + } } - const ptr = sema.resolveInst(bin_inst.lhs); - const value = sema.resolveInst(bin_inst.rhs); - const ptr_ty = try Type.ptr(sema.arena, .{ - .pointee_type = sema.typeOf(value), - // TODO figure out which address space is appropriate here - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - }); - // TODO detect when this store should be done at compile-time. For example, - // if expressions should force it when the condition is compile-time known. - const src: LazySrcLoc = .unneeded; - try sema.requireRuntimeBlock(block, src); - const bitcasted_ptr = try block.addBitCast(ptr_ty, ptr); - return sema.storePtr(block, src, bitcasted_ptr, value); + + return sema.storePtr(block, src, ptr, operand); } fn zirStoreToInferredPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -3106,56 +4005,81 @@ fn zirStoreToInferredPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compi const src: LazySrcLoc = sema.src; const bin_inst = sema.code.instructions.items(.data)[inst].bin; - const ptr = sema.resolveInst(bin_inst.lhs); - const operand = sema.resolveInst(bin_inst.rhs); - const operand_ty = sema.typeOf(operand); + const ptr = try sema.resolveInst(bin_inst.lhs); + const operand = try sema.resolveInst(bin_inst.rhs); const ptr_inst = Air.refToIndex(ptr).?; assert(sema.air_instructions.items(.tag)[ptr_inst] == .constant); const air_datas = sema.air_instructions.items(.data); const ptr_val = sema.air_values.items[air_datas[ptr_inst].ty_pl.payload]; - if (ptr_val.castTag(.inferred_alloc_comptime)) |iac| { - // There will be only one store_to_inferred_ptr because we are running at comptime. - // The alloc will turn into a Decl. - if (try sema.resolveMaybeUndefValAllowVariables(block, src, operand)) |operand_val| { - if (operand_val.tag() == .variable) { - return sema.failWithNeededComptime(block, src); - } - var anon_decl = try block.startAnonDecl(src); - defer anon_decl.deinit(); - iac.data.decl = try anon_decl.finish( - try operand_ty.copy(anon_decl.arena()), - try operand_val.copy(anon_decl.arena()), - ); - // TODO set the alignment on the decl - return; - } else { - return sema.failWithNeededComptime(block, src); - } + switch (ptr_val.tag()) { + .inferred_alloc_comptime => { + const iac = ptr_val.castTag(.inferred_alloc_comptime).?; + return sema.storeToInferredAllocComptime(block, src, operand, iac); + }, + .inferred_alloc => { + const inferred_alloc = ptr_val.castTag(.inferred_alloc).?; + return sema.storeToInferredAlloc(block, src, ptr, operand, inferred_alloc); + }, + else => unreachable, } +} - if (ptr_val.castTag(.inferred_alloc)) |inferred_alloc| { - // Add the stored instruction to the set we will use to resolve peer types - // for the inferred allocation. - try inferred_alloc.data.stored_inst_list.append(sema.arena, operand); - // Create a runtime bitcast instruction with exactly the type the pointer wants. - const ptr_ty = try Type.ptr(sema.arena, .{ - .pointee_type = operand_ty, - .@"align" = inferred_alloc.data.alignment, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - }); - const bitcasted_ptr = try block.addBitCast(ptr_ty, ptr); - return sema.storePtr(block, src, bitcasted_ptr, operand); +fn storeToInferredAlloc( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ptr: Air.Inst.Ref, + operand: Air.Inst.Ref, + inferred_alloc: *Value.Payload.InferredAlloc, +) CompileError!void { + const operand_ty = sema.typeOf(operand); + // Add the stored instruction to the set we will use to resolve peer types + // for the inferred allocation. + try inferred_alloc.data.stored_inst_list.append(sema.arena, operand); + // Create a runtime bitcast instruction with exactly the type the pointer wants. + const target = sema.mod.getTarget(); + const ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = operand_ty, + .@"align" = inferred_alloc.data.alignment, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + }); + const bitcasted_ptr = try block.addBitCast(ptr_ty, ptr); + return sema.storePtr(block, src, bitcasted_ptr, operand); +} + +fn storeToInferredAllocComptime( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + operand: Air.Inst.Ref, + iac: *Value.Payload.InferredAllocComptime, +) CompileError!void { + 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. + if (try sema.resolveMaybeUndefValAllowVariables(block, src, operand)) |operand_val| { + if (operand_val.tag() == .variable) { + return sema.failWithNeededComptime(block, src); + } + var anon_decl = try block.startAnonDecl(src); + defer anon_decl.deinit(); + iac.data.decl_index = try anon_decl.finish( + try operand_ty.copy(anon_decl.arena()), + try operand_val.copy(anon_decl.arena()), + iac.data.alignment, + ); + return; + } else { + return sema.failWithNeededComptime(block, src); } - unreachable; } fn zirSetEvalBranchQuota(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const quota = try sema.resolveAlreadyCoercedInt(block, src, inst_data.operand, u32); - if (sema.branch_quota < quota) - sema.branch_quota = quota; + const quota = @intCast(u32, try sema.resolveInt(block, src, inst_data.operand, Type.u32)); + sema.branch_quota = @maximum(sema.branch_quota, quota); } fn zirStore(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -3163,8 +4087,8 @@ fn zirStore(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void defer tracy.end(); const bin_inst = sema.code.instructions.items(.data)[inst].bin; - const ptr = sema.resolveInst(bin_inst.lhs); - const value = sema.resolveInst(bin_inst.rhs); + const ptr = try sema.resolveInst(bin_inst.lhs); + const value = try sema.resolveInst(bin_inst.rhs); return sema.storePtr(block, sema.src, ptr, value); } @@ -3172,12 +4096,68 @@ fn zirStoreNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!v const tracy = trace(@src()); defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const zir_tags = sema.code.instructions.items(.tag); + const zir_datas = sema.code.instructions.items(.data); + const inst_data = zir_datas[inst].pl_node; const src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const ptr = sema.resolveInst(extra.lhs); - const value = sema.resolveInst(extra.rhs); - return sema.storePtr(block, src, ptr, value); + const ptr = try sema.resolveInst(extra.lhs); + const operand = try sema.resolveInst(extra.rhs); + + // Check for the possibility of this pattern: + // %a = ret_ptr + // %b = store(%a, %c) + // Where %c is an error union or error set. In such case we need to add + // to the current function's inferred error set, if any. + if ((sema.typeOf(operand).zigTypeTag() == .ErrorUnion or + sema.typeOf(operand).zigTypeTag() == .ErrorSet) and + sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) + { + if (Zir.refToIndex(extra.lhs)) |ptr_index| { + if (zir_tags[ptr_index] == .ret_ptr) { + try sema.addToInferredErrorSet(operand); + } + } + } + + return sema.storePtr(block, src, ptr, operand); +} + +fn zirParamType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const callee_src = sema.src; + + const inst_data = sema.code.instructions.items(.data)[inst].param_type; + const callee = try sema.resolveInst(inst_data.callee); + const callee_ty = sema.typeOf(callee); + var param_index = inst_data.param_index; + + const fn_ty = if (callee_ty.tag() == .bound_fn) fn_ty: { + const bound_fn_val = try sema.resolveConstValue(block, callee_src, callee); + const bound_fn = bound_fn_val.castTag(.bound_fn).?.data; + const fn_ty = sema.typeOf(bound_fn.func_inst); + param_index += 1; + break :fn_ty fn_ty; + } else callee_ty; + + const fn_info = if (fn_ty.zigTypeTag() == .Pointer) + fn_ty.childType().fnInfo() + else + fn_ty.fnInfo(); + + if (param_index >= fn_info.param_types.len) { + if (fn_info.is_var_args) { + return sema.addType(Type.initTag(.var_args_param)); + } + // TODO implement begin_call/end_call Zir instructions and check + // argument count before casting arguments to parameter types. + return sema.fail(block, callee_src, "wrong number of arguments", .{}); + } + + if (fn_info.param_types[param_index].tag() == .generic_poison) { + return sema.addType(Type.initTag(.var_args_param)); + } + + return sema.addType(fn_info.param_types[param_index]); } fn zirStr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -3191,19 +4171,44 @@ fn zirStr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins fn addStrLit(sema: *Sema, block: *Block, zir_bytes: []const u8) CompileError!Air.Inst.Ref { // `zir_bytes` references memory inside the ZIR module, which can get deallocated // after semantic analysis is complete, for example in the case of the initialization - // expression of a variable declaration. We need the memory to be in the new - // anonymous Decl's arena. - var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded); - defer anon_decl.deinit(); + // expression of a variable declaration. + const mod = sema.mod; + const gpa = sema.gpa; + const string_bytes = &mod.string_literal_bytes; + const StringLiteralAdapter = Module.StringLiteralAdapter; + const StringLiteralContext = Module.StringLiteralContext; + try string_bytes.ensureUnusedCapacity(gpa, zir_bytes.len); + const gop = try mod.string_literal_table.getOrPutContextAdapted(gpa, zir_bytes, StringLiteralAdapter{ + .bytes = string_bytes, + }, StringLiteralContext{ + .bytes = string_bytes, + }); + if (!gop.found_existing) { + gop.key_ptr.* = .{ + .index = @intCast(u32, string_bytes.items.len), + .len = @intCast(u32, zir_bytes.len), + }; + string_bytes.appendSliceAssumeCapacity(zir_bytes); + gop.value_ptr.* = .none; + } + const decl_index = gop.value_ptr.unwrap() orelse di: { + var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded); + defer anon_decl.deinit(); - const bytes = try anon_decl.arena().dupeZ(u8, zir_bytes); + const decl_index = try anon_decl.finish( + try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), gop.key_ptr.len), + try Value.Tag.str_lit.create(anon_decl.arena(), gop.key_ptr.*), + 0, // default alignment + ); - const new_decl = try anon_decl.finish( - try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), - try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), - ); + // Needed so that `Decl.clearValues` will additionally set the corresponding + // string literal table value back to `Decl.OptionalIndex.none`. + mod.declPtr(decl_index).owns_tv = true; - return sema.analyzeDeclRef(new_decl); + gop.value_ptr.* = decl_index.toOptional(); + break :di decl_index; + }; + return sema.analyzeDeclRef(decl_index); } fn zirInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -3277,23 +4282,26 @@ fn zirCompileLog( const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand); const src_node = extra.data.src_node; - const src: LazySrcLoc = .{ .node_offset = src_node }; + const src = LazySrcLoc.nodeOffset(src_node); const args = sema.code.refSlice(extra.end, extended.small); for (args) |arg_ref, i| { if (i != 0) try writer.print(", ", .{}); - const arg = sema.resolveInst(arg_ref); + const arg = try sema.resolveInst(arg_ref); const arg_ty = sema.typeOf(arg); if (try sema.resolveMaybeUndefVal(block, src, arg)) |val| { - try writer.print("@as({}, {})", .{ arg_ty, val }); + try writer.print("@as({}, {})", .{ + arg_ty.fmt(sema.mod), val.fmtValue(arg_ty, sema.mod), + }); } else { - try writer.print("@as({}, [runtime value])", .{arg_ty}); + try writer.print("@as({}, [runtime value])", .{arg_ty.fmt(sema.mod)}); } } try writer.print("\n", .{}); - const gop = try sema.mod.compile_log_decls.getOrPut(sema.gpa, sema.owner_decl); + const decl_index = if (sema.func) |some| some.owner_decl else sema.owner_decl_index; + const gop = try sema.mod.compile_log_decls.getOrPut(sema.gpa, decl_index); if (!gop.found_existing) { gop.value_ptr.* = src_node; } @@ -3303,7 +4311,7 @@ fn zirCompileLog( fn zirPanic(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src: LazySrcLoc = inst_data.src(); - const msg_inst = sema.resolveInst(inst_data.operand); + const msg_inst = try sema.resolveInst(inst_data.operand); return sema.panicWithMsg(block, src, msg_inst); } @@ -3348,7 +4356,7 @@ fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError child_block.label = &label; child_block.runtime_cond = null; child_block.runtime_loop = src; - child_block.runtime_index += 1; + child_block.runtime_index.increment(); const merges = &child_block.label.?.merges; defer child_block.instructions.deinit(gpa); @@ -3358,7 +4366,7 @@ fn zirLoop(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError var loop_block = child_block.makeSubBlock(); defer loop_block.instructions.deinit(gpa); - _ = try sema.analyzeBody(&loop_block, body); + try sema.analyzeBody(&loop_block, body); try child_block.instructions.append(gpa, loop_inst); @@ -3400,9 +4408,11 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr }; defer child_block.instructions.deinit(sema.gpa); - _ = try sema.analyzeBody(&child_block, body); + // Ignore the result, all the relevant operations have written to c_import_buf already. + _ = try sema.analyzeBodyBreak(&child_block, body); - const c_import_res = sema.mod.comp.cImport(c_import_buf.items) catch |err| + const mod = sema.mod; + const c_import_res = mod.comp.cImport(c_import_buf.items) catch |err| return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); if (c_import_res.errors.len != 0) { @@ -3410,12 +4420,12 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr const msg = try sema.errMsg(&child_block, src, "C import failed", .{}); errdefer msg.destroy(sema.gpa); - if (!sema.mod.comp.bin_file.options.link_libc) + if (!mod.comp.bin_file.options.link_libc) try sema.errNote(&child_block, src, msg, "libc headers not available; compilation does not link against libc", .{}); for (c_import_res.errors) |_| { // TODO integrate with LazySrcLoc - // try sema.mod.errNoteNonLazy(.{}, msg, "{s}", .{clang_err.msg_ptr[0..clang_err.msg_len]}); + // try mod.errNoteNonLazy(.{}, msg, "{s}", .{clang_err.msg_ptr[0..clang_err.msg_len]}); // if (clang_err.filename_ptr) |p| p[0..clang_err.filename_len] else "(no file)", // clang_err.line + 1, // clang_err.column + 1, @@ -3423,7 +4433,7 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr @import("clang.zig").Stage2ErrorMsg.delete(c_import_res.errors.ptr, c_import_res.errors.len); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(parent_block, msg); } const c_import_pkg = Package.create( sema.gpa, @@ -3433,20 +4443,21 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr error.OutOfMemory => return error.OutOfMemory, else => unreachable, // we pass null for root_src_dir_path }; - const std_pkg = sema.mod.main_pkg.table.get("std").?; - const builtin_pkg = sema.mod.main_pkg.table.get("builtin").?; + const std_pkg = mod.main_pkg.table.get("std").?; + const builtin_pkg = mod.main_pkg.table.get("builtin").?; try c_import_pkg.add(sema.gpa, "builtin", builtin_pkg); try c_import_pkg.add(sema.gpa, "std", std_pkg); - const result = sema.mod.importPkg(c_import_pkg) catch |err| + const result = mod.importPkg(c_import_pkg) catch |err| return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); - sema.mod.astGenFile(result.file) catch |err| + mod.astGenFile(result.file) catch |err| return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); - try sema.mod.semaFile(result.file); - const file_root_decl = result.file.root_decl.?; - try sema.mod.declareDeclDependency(sema.owner_decl, file_root_decl); + try mod.semaFile(result.file); + const file_root_decl_index = result.file.root_decl.unwrap().?; + const file_root_decl = mod.declPtr(file_root_decl_index); + try mod.declareDeclDependency(sema.owner_decl_index, file_root_decl_index); return sema.addConstant(file_root_decl.ty, file_root_decl.val); } @@ -3456,11 +4467,7 @@ fn zirSuspendBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) Comp return sema.fail(parent_block, src, "TODO: implement Sema.zirSuspendBlock", .{}); } -fn zirBlock( - sema: *Sema, - parent_block: *Block, - inst: Zir.Inst.Index, -) CompileError!Air.Inst.Ref { +fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); @@ -3498,16 +4505,14 @@ fn zirBlock( .label = &label, .inlining = parent_block.inlining, .is_comptime = parent_block.is_comptime, + .want_safety = parent_block.want_safety, }; - const merges = &child_block.label.?.merges; defer child_block.instructions.deinit(gpa); - defer merges.results.deinit(gpa); - defer merges.br_list.deinit(gpa); + defer label.merges.results.deinit(gpa); + defer label.merges.br_list.deinit(gpa); - _ = try sema.analyzeBody(&child_block, body); - - return sema.analyzeBlockBody(parent_block, src, &child_block, merges); + return sema.resolveBlockBody(parent_block, src, &child_block, body, inst, &label.merges); } fn resolveBlockBody( @@ -3524,8 +4529,20 @@ fn resolveBlockBody( if (child_block.is_comptime) { return sema.resolveBody(child_block, body, body_inst); } else { - _ = try sema.analyzeBody(child_block, body); - return sema.analyzeBlockBody(parent_block, src, child_block, merges); + if (sema.analyzeBodyInner(child_block, body)) |_| { + return sema.analyzeBlockBody(parent_block, src, child_block, merges); + } else |err| switch (err) { + error.ComptimeBreak => { + const break_inst = sema.comptime_break_inst; + const break_data = sema.code.instructions.items(.data)[break_inst].@"break"; + if (break_data.block_inst == body_inst) { + return try sema.resolveInst(break_data.operand); + } else { + return error.ComptimeBreak; + } + }, + else => |e| return e, + } } } @@ -3540,6 +4557,7 @@ fn analyzeBlockBody( defer tracy.end(); const gpa = sema.gpa; + const mod = sema.mod; // Blocks must terminate with noreturn instruction. assert(child_block.instructions.items.len != 0); @@ -3571,6 +4589,25 @@ fn analyzeBlockBody( // 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, .none); + // TODO add note "missing else causes void value" + + const type_src = src; // TODO: better source location + const valid_rt = try sema.validateRunTimeType(child_block, type_src, resolved_ty, false); + if (!valid_rt) { + const msg = msg: { + const msg = try sema.errMsg(child_block, type_src, "value with comptime only type '{}' depends on runtime control flow", .{resolved_ty.fmt(mod)}); + errdefer msg.destroy(sema.gpa); + + const runtime_src = child_block.runtime_cond orelse child_block.runtime_loop.?; + try sema.errNote(child_block, runtime_src, msg, "runtime control flow here", .{}); + + const child_src_decl = mod.declPtr(child_block.src_decl); + try sema.explainWhyTypeIsComptime(child_block, type_src, msg, type_src.toSrcLoc(child_src_decl), resolved_ty); + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(child_block, msg); + } const ty_inst = try sema.addType(resolved_ty); try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len + child_block.instructions.items.len); @@ -3587,7 +4624,7 @@ fn analyzeBlockBody( const br_operand = sema.air_instructions.items(.data)[br].br.operand; const br_operand_src = src; const br_operand_ty = sema.typeOf(br_operand); - if (br_operand_ty.eql(resolved_ty)) { + if (br_operand_ty.eql(resolved_ty, mod)) { // No type coercion needed. continue; } @@ -3645,9 +4682,9 @@ fn zirExport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void if (extra.namespace != .none) { return sema.fail(block, src, "TODO: implement exporting with field access", .{}); } - const decl = try sema.lookupIdentifier(block, operand_src, decl_name); + const decl_index = try sema.lookupIdentifier(block, operand_src, decl_name); const options = try sema.resolveExportOptions(block, options_src, extra.options); - try sema.analyzeExport(block, src, options, decl); + try sema.analyzeExport(block, src, options, decl_index); } fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -3661,11 +4698,11 @@ fn zirExportValue(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const operand = try sema.resolveInstConst(block, operand_src, extra.operand); const options = try sema.resolveExportOptions(block, options_src, extra.options); - const decl = switch (operand.val.tag()) { + const decl_index = switch (operand.val.tag()) { .function => operand.val.castTag(.function).?.data.owner_decl, else => return sema.fail(block, operand_src, "TODO implement exporting arbitrary Value objects", .{}), // TODO put this Value into an anonymous Decl and then export it. }; - try sema.analyzeExport(block, src, options, decl); + try sema.analyzeExport(block, src, options, decl_index); } pub fn analyzeExport( @@ -3673,16 +4710,23 @@ pub fn analyzeExport( block: *Block, src: LazySrcLoc, borrowed_options: std.builtin.ExportOptions, - exported_decl: *Decl, + exported_decl_index: Decl.Index, ) !void { const Export = Module.Export; const mod = sema.mod; - try mod.ensureDeclAnalyzed(exported_decl); + if (borrowed_options.linkage == .Internal) { + return; + } + + try mod.ensureDeclAnalyzed(exported_decl_index); + const exported_decl = mod.declPtr(exported_decl_index); // TODO run the same checks as we do for C ABI struct fields switch (exported_decl.ty.zigTypeTag()) { - .Fn, .Int, .Struct, .Array, .Float => {}, - else => return sema.fail(block, src, "unable to export type '{}'", .{exported_decl.ty}), + .Fn, .Int, .Enum, .Struct, .Union, .Array, .Float, .Pointer, .Optional => {}, + else => return sema.fail(block, src, "unable to export type '{}'", .{ + exported_decl.ty.fmt(sema.mod), + }), } const gpa = mod.gpa; @@ -3699,18 +4743,12 @@ pub fn analyzeExport( const section: ?[]const u8 = if (borrowed_options.section) |s| try gpa.dupe(u8, s) else null; errdefer if (section) |s| gpa.free(s); - const src_decl = block.src_decl; - const owner_decl = sema.owner_decl; - - log.debug("exporting Decl '{s}' as symbol '{s}' from Decl '{s}'", .{ - exported_decl.name, symbol_name, owner_decl.name, - }); - new_export.* = .{ .options = .{ .name = symbol_name, .linkage = borrowed_options.linkage, .section = section, + .visibility = borrowed_options.visibility, }, .src = src, .link = switch (mod.comp.bin_file.tag) { @@ -3719,17 +4757,18 @@ pub fn analyzeExport( .macho => .{ .macho = .{} }, .plan9 => .{ .plan9 = null }, .c => .{ .c = {} }, - .wasm => .{ .wasm = {} }, + .wasm => .{ .wasm = .{} }, .spirv => .{ .spirv = {} }, + .nvptx => .{ .nvptx = {} }, }, - .owner_decl = owner_decl, - .src_decl = src_decl, - .exported_decl = exported_decl, + .owner_decl = sema.owner_decl_index, + .src_decl = block.src_decl, + .exported_decl = exported_decl_index, .status = .in_progress, }; // Add to export_owners table. - const eo_gop = mod.export_owners.getOrPutAssumeCapacity(owner_decl); + const eo_gop = mod.export_owners.getOrPutAssumeCapacity(sema.owner_decl_index); if (!eo_gop.found_existing) { eo_gop.value_ptr.* = &[0]*Export{}; } @@ -3738,7 +4777,7 @@ pub fn analyzeExport( errdefer eo_gop.value_ptr.* = gpa.shrink(eo_gop.value_ptr.*, eo_gop.value_ptr.len - 1); // Add to exported_decl table. - const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl); + const de_gop = mod.decl_exports.getOrPutAssumeCapacity(exported_decl_index); if (!de_gop.found_existing) { de_gop.value_ptr.* = &[0]*Export{}; } @@ -3747,11 +4786,11 @@ pub fn analyzeExport( errdefer de_gop.value_ptr.* = gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1); } -fn zirSetAlignStack(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const src: LazySrcLoc = inst_data.src(); - const alignment = try sema.resolveAlign(block, operand_src, inst_data.operand); +fn zirSetAlignStack(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { + const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const src = LazySrcLoc.nodeOffset(extra.node); + const alignment = try sema.resolveAlign(block, operand_src, extra.operand); if (alignment > 256) { return sema.fail(block, src, "attempt to @setAlignStack({d}); maximum is 256", .{ alignment, @@ -3760,7 +4799,8 @@ fn zirSetAlignStack(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const func = sema.owner_func orelse return sema.fail(block, src, "@setAlignStack outside function body", .{}); - switch (func.owner_decl.ty.fnCallingConvention()) { + const fn_owner_decl = sema.mod.declPtr(func.owner_decl); + switch (fn_owner_decl.ty.fnCallingConvention()) { .Naked => return sema.fail(block, src, "@setAlignStack in naked function", .{}), .Inline => return sema.fail(block, src, "@setAlignStack in inline function", .{}), else => {}, @@ -3771,10 +4811,10 @@ fn zirSetAlignStack(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const msg = msg: { const msg = try sema.errMsg(block, src, "multiple @setAlignStack in the same function body", .{}); errdefer msg.destroy(sema.gpa); - try sema.errNote(block, src, msg, "other instance here", .{}); + try sema.errNote(block, gop.value_ptr.src, msg, "other instance here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } gop.value_ptr.* = .{ .alignment = alignment, .src = src }; } @@ -3787,10 +4827,16 @@ fn zirSetCold(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!voi func.is_cold = is_cold; } -fn zirSetFloatMode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { - const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src: LazySrcLoc = inst_data.src(); - return sema.fail(block, src, "TODO: implement Sema.zirSetFloatMode", .{}); +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: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const float_mode = try sema.resolveBuiltinEnum(block, src, extra.operand, "FloatMode"); + switch (float_mode) { + .Strict => return, + .Optimized => { + // TODO implement optimized float mode + }, + } } fn zirSetRuntimeSafety(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { @@ -3799,12 +4845,12 @@ fn zirSetRuntimeSafety(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compile block.want_safety = try sema.resolveConstBool(block, operand_src, inst_data.operand); } -fn zirFence(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { +fn zirFence(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!void { if (block.is_comptime) return; - const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const order = try sema.resolveAtomicOrder(block, order_src, inst_data.operand); + const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; + const order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const order = try sema.resolveAtomicOrder(block, order_src, extra.operand); if (@enumToInt(order) < @enumToInt(std.builtin.AtomicOrder.Acquire)) { return sema.fail(block, order_src, "atomic ordering must be Acquire or stricter", .{}); @@ -3821,7 +4867,7 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].@"break"; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const zir_block = inst_data.block_inst; var block = start_block; @@ -3831,6 +4877,11 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError const br_ref = try start_block.addBr(label.merges.block_inst, operand); try label.merges.results.append(sema.gpa, operand); try label.merges.br_list.append(sema.gpa, Air.refToIndex(br_ref).?); + 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 inst; } } @@ -3839,14 +4890,11 @@ fn zirBreak(sema: *Sema, start_block: *Block, inst: Zir.Inst.Index) CompileError } fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void { - const tracy = trace(@src()); - defer tracy.end(); - // We do not set sema.src here because dbg_stmt instructions are only emitted for // ZIR code that possibly will need to generate runtime code. So error messages // and other source locations must not rely on sema.src being set from dbg_stmt // instructions. - if (block.is_comptime) return; + if (block.is_comptime or sema.mod.comp.bin_file.options.strip) return; const inst_data = sema.code.instructions.items(.data)[inst].dbg_stmt; _ = try block.addInst(.{ @@ -3858,12 +4906,82 @@ fn zirDbgStmt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!voi }); } +fn zirDbgBlockBegin(sema: *Sema, block: *Block) CompileError!void { + if (block.is_comptime or sema.mod.comp.bin_file.options.strip) return; + + _ = try block.addInst(.{ + .tag = .dbg_block_begin, + .data = undefined, + }); +} + +fn zirDbgBlockEnd(sema: *Sema, block: *Block) CompileError!void { + if (block.is_comptime or sema.mod.comp.bin_file.options.strip) return; + + _ = try block.addInst(.{ + .tag = .dbg_block_end, + .data = undefined, + }); +} + +fn zirDbgVar( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + air_tag: Air.Inst.Tag, +) CompileError!void { + if (block.is_comptime or sema.mod.comp.bin_file.options.strip) return; + + const str_op = sema.code.instructions.items(.data)[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 { + const operand_ty = sema.typeOf(operand); + switch (air_tag) { + .dbg_var_ptr => { + if (!(try sema.typeHasRuntimeBits(block, sema.src, operand_ty.childType()))) return; + }, + .dbg_var_val => { + if (!(try sema.typeHasRuntimeBits(block, sema.src, operand_ty))) return; + }, + else => unreachable, + } + + try sema.queueFullTypeResolution(operand_ty); + + // Add the name to the AIR. + const name_extra_index = @intCast(u32, sema.air_extra.items.len); + const elements_used = name.len / 4 + 1; + try sema.air_extra.ensureUnusedCapacity(sema.gpa, elements_used); + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, name); + buffer[name.len] = 0; + sema.air_extra.items.len += elements_used; + + _ = try block.addInst(.{ + .tag = air_tag, + .data = .{ .pl_op = .{ + .payload = name_extra_index, + .operand = operand, + } }, + }); +} + fn zirDeclRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].str_tok; const src = inst_data.src(); const decl_name = inst_data.get(sema.code); - const decl = try sema.lookupIdentifier(block, src, decl_name); - return sema.analyzeDeclRef(decl); + const decl_index = try sema.lookupIdentifier(block, src, decl_name); + return sema.analyzeDeclRef(decl_index); } fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -3874,11 +4992,11 @@ fn zirDeclVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air return sema.analyzeDeclVal(block, src, decl); } -fn lookupIdentifier(sema: *Sema, block: *Block, src: LazySrcLoc, name: []const u8) !*Decl { +fn lookupIdentifier(sema: *Sema, block: *Block, src: LazySrcLoc, name: []const u8) !Decl.Index { var namespace = block.namespace; while (true) { - if (try sema.lookupInNamespace(block, src, namespace, name, false)) |decl| { - return decl; + if (try sema.lookupInNamespace(block, src, namespace, name, false)) |decl_index| { + return decl_index; } namespace = namespace.parent orelse break; } @@ -3894,12 +5012,13 @@ fn lookupInNamespace( namespace: *Namespace, ident_name: []const u8, observe_usingnamespace: bool, -) CompileError!?*Decl { +) CompileError!?Decl.Index { const mod = sema.mod; - const namespace_decl = namespace.getDecl(); + const namespace_decl_index = namespace.getDeclIndex(); + const namespace_decl = sema.mod.declPtr(namespace_decl_index); if (namespace_decl.analysis == .file_failure) { - try mod.declareDeclDependency(sema.owner_decl, namespace_decl); + try mod.declareDeclDependency(sema.owner_decl_index, namespace_decl_index); return error.AnalysisFail; } @@ -3911,7 +5030,7 @@ fn lookupInNamespace( defer checked_namespaces.deinit(gpa); // Keep track of name conflicts for error notes. - var candidates: std.ArrayListUnmanaged(*Decl) = .{}; + var candidates: std.ArrayListUnmanaged(Decl.Index) = .{}; defer candidates.deinit(gpa); try checked_namespaces.put(gpa, namespace, {}); @@ -3919,23 +5038,27 @@ fn lookupInNamespace( while (check_i < checked_namespaces.count()) : (check_i += 1) { const check_ns = checked_namespaces.keys()[check_i]; - if (check_ns.decls.get(ident_name)) |decl| { + if (check_ns.decls.getKeyAdapted(ident_name, Module.DeclAdapter{ .mod = mod })) |decl_index| { // Skip decls which are not marked pub, which are in a different // file than the `a.b`/`@hasDecl` syntax. + const decl = mod.declPtr(decl_index); if (decl.is_pub or src_file == decl.getFileScope()) { - try candidates.append(gpa, decl); + try candidates.append(gpa, decl_index); } } var it = check_ns.usingnamespace_set.iterator(); while (it.next()) |entry| { - const sub_usingnamespace_decl = entry.key_ptr.*; + const sub_usingnamespace_decl_index = entry.key_ptr.*; + // Skip the decl we're currently analysing. + if (sub_usingnamespace_decl_index == sema.owner_decl_index) continue; + const sub_usingnamespace_decl = mod.declPtr(sub_usingnamespace_decl_index); const sub_is_pub = entry.value_ptr.*; if (!sub_is_pub and src_file != sub_usingnamespace_decl.getFileScope()) { // Skip usingnamespace decls which are not marked pub, which are in // a different file than the `a.b`/`@hasDecl` syntax. continue; } - try sema.ensureDeclAnalyzed(sub_usingnamespace_decl); + try sema.ensureDeclAnalyzed(sub_usingnamespace_decl_index); const ns_ty = sub_usingnamespace_decl.val.castTag(.ty).?.data; const sub_ns = ns_ty.getNamespace().?; try checked_namespaces.put(gpa, sub_ns, {}); @@ -3945,26 +5068,27 @@ fn lookupInNamespace( switch (candidates.items.len) { 0 => {}, 1 => { - const decl = candidates.items[0]; - try mod.declareDeclDependency(sema.owner_decl, decl); - return decl; + const decl_index = candidates.items[0]; + try mod.declareDeclDependency(sema.owner_decl_index, decl_index); + return decl_index; }, else => { const msg = msg: { const msg = try sema.errMsg(block, src, "ambiguous reference", .{}); errdefer msg.destroy(gpa); - for (candidates.items) |candidate| { + for (candidates.items) |candidate_index| { + const candidate = mod.declPtr(candidate_index); const src_loc = candidate.srcLoc(); try mod.errNoteNonLazy(src_loc, msg, "declared here", .{}); } break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }, } - } else if (namespace.decls.get(ident_name)) |decl| { - try mod.declareDeclDependency(sema.owner_decl, decl); - return decl; + } else if (namespace.decls.getKeyAdapted(ident_name, Module.DeclAdapter{ .mod = mod })) |decl_index| { + try mod.declareDeclDependency(sema.owner_decl_index, decl_index); + return decl_index; } log.debug("{*} ({s}) depends on non-existence of '{s}' in {*} ({s})", .{ @@ -3973,7 +5097,7 @@ fn lookupInNamespace( // TODO This dependency is too strong. Really, it should only be a dependency // on the non-existence of `ident_name` in the namespace. We can lessen the number of // outdated declarations by making this dependency more sophisticated. - try mod.declareDeclDependency(sema.owner_decl, namespace_decl); + try mod.declareDeclDependency(sema.owner_decl_index, namespace_decl_index); return null; } @@ -3994,7 +5118,7 @@ fn zirCall( const modifier = @intToEnum(std.builtin.CallOptions.Modifier, extra.data.flags.packed_modifier); const ensure_result_used = extra.data.flags.ensure_result_used; - var func = sema.resolveInst(extra.data.callee); + var func = try sema.resolveInst(extra.data.callee); var resolved_args: []Air.Inst.Ref = undefined; const func_type = sema.typeOf(func); @@ -4007,12 +5131,12 @@ fn zirCall( resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len + 1); resolved_args[0] = bound_data.arg0_inst; for (args) |zir_arg, i| { - resolved_args[i + 1] = sema.resolveInst(zir_arg); + resolved_args[i + 1] = try sema.resolveInst(zir_arg); } } else { resolved_args = try sema.arena.alloc(Air.Inst.Ref, args.len); for (args) |zir_arg, i| { - resolved_args[i] = sema.resolveInst(zir_arg); + resolved_args[i] = try sema.resolveInst(zir_arg); } } @@ -4023,31 +5147,44 @@ const GenericCallAdapter = struct { generic_fn: *Module.Fn, precomputed_hash: u64, func_ty_info: Type.Payload.Function.Data, + /// Unlike comptime_args, the Type here is not always present. + /// .generic_poison is used to communicate non-anytype parameters. comptime_tvs: []const TypedValue, + module: *Module, pub fn eql(ctx: @This(), adapted_key: void, other_key: *Module.Fn) bool { _ = adapted_key; // The generic function Decl is guaranteed to be the first dependency // of each of its instantiations. - const generic_owner_decl = other_key.owner_decl.dependencies.keys()[0]; + const other_owner_decl = ctx.module.declPtr(other_key.owner_decl); + const generic_owner_decl = other_owner_decl.dependencies.keys()[0]; if (ctx.generic_fn.owner_decl != generic_owner_decl) return false; const other_comptime_args = other_key.comptime_args.?; for (other_comptime_args[0..ctx.func_ty_info.param_types.len]) |other_arg, i| { - if (other_arg.ty.tag() != .generic_poison) { - // anytype parameter - if (!other_arg.ty.eql(ctx.comptime_tvs[i].ty)) { + const this_arg = ctx.comptime_tvs[i]; + const this_is_comptime = this_arg.val.tag() != .generic_poison; + const other_is_comptime = other_arg.val.tag() != .generic_poison; + const this_is_anytype = this_arg.ty.tag() != .generic_poison; + const other_is_anytype = other_key.anytype_args[i]; + + if (other_is_anytype != this_is_anytype) return false; + if (other_is_comptime != this_is_comptime) return false; + + if (this_is_anytype) { + // Both are anytype parameters. + if (!this_arg.ty.eql(other_arg.ty, ctx.module)) { return false; } - } - if (other_arg.val.tag() != .generic_poison) { - // comptime parameter - if (ctx.comptime_tvs[i].val.tag() == .generic_poison) { - // No match because the instantiation has a comptime parameter - // but the callsite does not. - return false; + if (this_is_comptime) { + // Both are comptime and anytype parameters with matching types. + if (!this_arg.val.eql(other_arg.val, other_arg.ty, ctx.module)) { + return false; + } } - if (!other_arg.val.eql(ctx.comptime_tvs[i].val, other_arg.ty)) { + } else if (this_is_comptime) { + // Both are comptime parameters but not anytype parameters. + if (!this_arg.val.eql(other_arg.val, other_arg.ty, ctx.module)) { return false; } } @@ -4063,22 +5200,6 @@ const GenericCallAdapter = struct { } }; -const GenericRemoveAdapter = struct { - precomputed_hash: u64, - - pub fn eql(ctx: @This(), adapted_key: *Module.Fn, other_key: *Module.Fn) bool { - _ = ctx; - return adapted_key == other_key; - } - - /// The implementation of the hash is in semantic analysis of function calls, so - /// that any errors when computing the hash can be properly reported. - pub fn hash(ctx: @This(), adapted_key: *Module.Fn) u64 { - _ = adapted_key; - return ctx.precomputed_hash; - } -}; - fn analyzeCall( sema: *Sema, block: *Block, @@ -4103,7 +5224,7 @@ fn analyzeCall( }, else => {}, } - return sema.fail(block, func_src, "type '{}' not a function", .{callee_ty}); + return sema.fail(block, func_src, "type '{}' not a function", .{callee_ty.fmt(sema.mod)}); }; const func_ty_info = func_ty.fnInfo(); @@ -4139,32 +5260,71 @@ fn analyzeCall( ); } - switch (modifier) { + const call_tag: Air.Inst.Tag = switch (modifier) { .auto, .always_inline, .compile_time, - => {}, - - .async_kw, - .never_tail, - .never_inline, .no_async, - .always_tail, - => return sema.fail(block, call_src, "TODO implement call with modifier {}", .{ - modifier, - }), + => Air.Inst.Tag.call, + + .never_tail => Air.Inst.Tag.call_never_tail, + .never_inline => Air.Inst.Tag.call_never_inline, + .always_tail => Air.Inst.Tag.call_always_tail, + + .async_kw => return sema.fail(block, call_src, "TODO implement async call", .{}), + }; + + if (modifier == .never_inline and func_ty_info.cc == .Inline) { + return sema.fail(block, call_src, "no-inline call of inline function", .{}); } const gpa = sema.gpa; - const is_comptime_call = block.is_comptime or modifier == .compile_time or - try sema.typeRequiresComptime(block, func_src, func_ty_info.return_type); - const is_inline_call = is_comptime_call or modifier == .always_inline or + var is_generic_call = func_ty_info.is_generic; + var is_comptime_call = block.is_comptime or modifier == .compile_time; + if (!is_comptime_call) { + if (sema.typeRequiresComptime(block, func_src, func_ty_info.return_type)) |ct| { + is_comptime_call = ct; + } else |err| switch (err) { + error.GenericPoison => is_generic_call = true, + else => |e| return e, + } + } + var is_inline_call = is_comptime_call or modifier == .always_inline or func_ty_info.cc == .Inline; + + if (!is_inline_call and is_generic_call) { + if (sema.instantiateGenericCall( + block, + func, + func_src, + call_src, + func_ty_info, + ensure_result_used, + uncasted_args, + call_tag, + )) |some| { + return some; + } else |err| switch (err) { + error.GenericPoison => { + is_inline_call = true; + }, + error.ComptimeReturn => { + is_inline_call = true; + is_comptime_call = true; + }, + else => |e| return e, + } + } + + if (is_comptime_call and modifier == .never_inline) { + return sema.fail(block, call_src, "unable to perform 'never_inline' call at compile-time", .{}); + } + const result: Air.Inst.Ref = if (is_inline_call) res: { const func_val = try sema.resolveConstValue(block, func_src, func); const module_fn = switch (func_val.tag()) { - .decl_ref => func_val.castTag(.decl_ref).?.data.val.castTag(.function).?.data, + .decl_ref => mod.declPtr(func_val.castTag(.decl_ref).?.data).val.castTag(.function).?.data, .function => func_val.castTag(.function).?.data, .extern_fn => return sema.fail(block, call_src, "{s} call of extern function", .{ @as([]const u8, if (is_comptime_call) "comptime" else "inline"), @@ -4195,7 +5355,8 @@ fn analyzeCall( // In order to save a bit of stack space, directly modify Sema rather // than create a child one. const parent_zir = sema.code; - sema.code = module_fn.owner_decl.getFileScope().zir; + const fn_owner_decl = mod.declPtr(module_fn.owner_decl); + sema.code = fn_owner_decl.getFileScope().zir; defer sema.code = parent_zir; const parent_inst_map = sema.inst_map; @@ -4209,14 +5370,14 @@ fn analyzeCall( sema.func = module_fn; defer sema.func = parent_func; - var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, module_fn.owner_decl.src_scope); + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, fn_owner_decl.src_scope); defer wip_captures.deinit(); var child_block: Block = .{ .parent = null, .sema = sema, .src_decl = module_fn.owner_decl, - .namespace = module_fn.owner_decl.src_namespace, + .namespace = fn_owner_decl.src_namespace, .wip_capture_scope = wip_captures.scope, .instructions = .{}, .label = null, @@ -4243,7 +5404,15 @@ fn analyzeCall( delete_memoized_call_key = true; } - try sema.emitBackwardBranch(&child_block, call_src); + try sema.emitBackwardBranch(block, call_src); + + // Whether this call should be memoized, set to false if the call can mutate + // comptime state. + var should_memoize = true; + + var new_fn_info = fn_owner_decl.ty.fnInfo(); + new_fn_info.param_types = try sema.arena.alloc(Type, new_fn_info.param_types.len); + new_fn_info.comptime_params = (try sema.arena.alloc(bool, new_fn_info.param_types.len)).ptr; // This will have return instructions analyzed as break instructions to // the block_inst above. Here we are performing "comptime/inline semantic analysis" @@ -4264,6 +5433,7 @@ fn analyzeCall( const param_body = sema.code.extra[extra.end..][0..extra.data.body_len]; const param_ty_inst = try sema.resolveBody(&child_block, param_body, inst); const param_ty = try sema.analyzeAsType(&child_block, param_src, param_ty_inst); + new_fn_info.param_types[arg_i] = param_ty; const arg_src = call_src; // TODO: better source location const casted_arg = try sema.coerce(&child_block, param_ty, uncasted_args[arg_i], arg_src); try sema.inst_map.putNoClobber(gpa, inst, casted_arg); @@ -4276,8 +5446,14 @@ fn analyzeCall( // parameter or return type. return error.GenericPoison; }, - else => {}, + else => { + // Needed so that lazy values do not trigger + // assertion due to type not being resolved + // when the hash function is called. + try sema.resolveLazyValue(&child_block, arg_src, arg_val); + }, } + should_memoize = should_memoize and !arg_val.canMutateComptimeVarState(); memoized_call_key.args[arg_i] = .{ .ty = param_ty, .val = arg_val, @@ -4290,6 +5466,7 @@ fn analyzeCall( .param_anytype, .param_anytype_comptime => { // No coercion needed. const uncasted_arg = uncasted_args[arg_i]; + new_fn_info.param_types[arg_i] = sema.typeOf(uncasted_arg); try sema.inst_map.putNoClobber(gpa, inst, uncasted_arg); if (is_comptime_call) { @@ -4301,8 +5478,14 @@ fn analyzeCall( // parameter or return type. return error.GenericPoison; }, - else => {}, + else => { + // Needed so that lazy values do not trigger + // assertion due to type not being resolved + // when the hash function is called. + try sema.resolveLazyValue(&child_block, arg_src, arg_val); + }, } + should_memoize = should_memoize and !arg_val.canMutateComptimeVarState(); memoized_call_key.args[arg_i] = .{ .ty = sema.typeOf(uncasted_arg), .val = arg_val, @@ -4319,26 +5502,30 @@ fn analyzeCall( // on parameters, we must now do the same for the return type as we just did with // each of the parameters, resolving the return type and providing it to the child // `Sema` so that it can be used for the `ret_ptr` instruction. - const ret_ty_inst = try sema.resolveBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst); + const ret_ty_inst = if (fn_info.ret_ty_body.len != 0) + try sema.resolveBody(&child_block, fn_info.ret_ty_body, module_fn.zir_body_inst) + else + try sema.resolveInst(fn_info.ret_ty_ref); const ret_ty_src = func_src; // TODO better source location const bare_return_type = try sema.analyzeAsType(&child_block, ret_ty_src, ret_ty_inst); // Create a fresh inferred error set type for inline/comptime calls. const fn_ret_ty = blk: { - if (func_ty_info.return_type.castTag(.error_union)) |payload| { - if (payload.data.error_set.tag() == .error_set_inferred) { - const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode); - node.data = .{ .func = module_fn }; - parent_func.?.inferred_error_sets.prepend(node); - - const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, &node.data); - break :blk try Type.Tag.error_union.create(sema.arena, .{ - .error_set = error_set_ty, - .payload = bare_return_type, - }); + if (module_fn.hasInferredErrorSet(mod)) { + const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode); + node.data = .{ .func = module_fn }; + if (parent_func) |some| { + some.inferred_error_sets.prepend(node); } + + const error_set_ty = try Type.Tag.error_set_inferred.create(sema.arena, &node.data); + break :blk try Type.Tag.error_union.create(sema.arena, .{ + .error_set = error_set_ty, + .payload = bare_return_type, + }); } break :blk bare_return_type; }; + new_fn_info.return_type = fn_ret_ty; const parent_fn_ret_ty = sema.fn_ret_ty; sema.fn_ret_ty = fn_ret_ty; defer sema.fn_ret_ty = parent_fn_ret_ty; @@ -4346,8 +5533,8 @@ fn analyzeCall( // This `res2` is here instead of directly breaking from `res` due to a stage1 // bug generating invalid LLVM IR. const res2: Air.Inst.Ref = res2: { - if (is_comptime_call) { - if (mod.memoized_calls.get(memoized_call_key)) |result| { + if (should_memoize and is_comptime_call) { + if (mod.memoized_calls.getContext(memoized_call_key, .{ .module = mod })) |result| { const ty_inst = try sema.addType(fn_ret_ty); try sema.air_values.append(gpa, result.val); sema.air_instructions.set(block_inst, .{ @@ -4361,16 +5548,55 @@ fn analyzeCall( } } + const new_func_resolved_ty = try Type.Tag.function.create(sema.arena, new_fn_info); + if (!is_comptime_call) { + try sema.emitDbgInline(block, parent_func.?, module_fn, new_func_resolved_ty, .dbg_inline_begin); + + for (fn_info.param_body) |param| switch (zir_tags[param]) { + .param, .param_comptime => { + const inst_data = sema.code.instructions.items(.data)[param].pl_tok; + const extra = sema.code.extraData(Zir.Inst.Param, inst_data.payload_index); + const param_name = sema.code.nullTerminatedString(extra.data.name); + const inst = sema.inst_map.get(param).?; + + try sema.addDbgVar(&child_block, inst, .dbg_var_val, param_name); + }, + .param_anytype, .param_anytype_comptime => { + const inst_data = sema.code.instructions.items(.data)[param].str_tok; + const param_name = inst_data.get(sema.code); + const inst = sema.inst_map.get(param).?; + + try sema.addDbgVar(&child_block, inst, .dbg_var_val, param_name); + }, + else => continue, + }; + } + const result = result: { - _ = sema.analyzeBody(&child_block, fn_info.body) catch |err| switch (err) { + sema.analyzeBody(&child_block, fn_info.body) catch |err| switch (err) { error.ComptimeReturn => break :result inlining.comptime_result, - error.ComptimeBreak => unreachable, // Can't break through a fn call. + error.AnalysisFail => { + const err_msg = inlining.err orelse return err; + try sema.errNote(block, call_src, err_msg, "called from here", .{}); + if (block.inlining) |some| some.err = err_msg; + return err; + }, else => |e| return e, }; break :result try sema.analyzeBlockBody(block, call_src, &child_block, merges); }; - if (is_comptime_call) { + if (!is_comptime_call) { + try sema.emitDbgInline( + block, + module_fn, + parent_func.?, + mod.declPtr(parent_func.?.owner_decl).ty, + .dbg_inline_end, + ); + } + + if (should_memoize and is_comptime_call) { const result_val = try sema.resolveConstMaybeUndefVal(block, call_src, result); // TODO: check whether any external comptime memory was mutated by the @@ -4386,10 +5612,10 @@ fn analyzeCall( arg.* = try arg.*.copy(arena); } - try mod.memoized_calls.put(gpa, memoized_call_key, .{ + try mod.memoized_calls.putContext(gpa, memoized_call_key, .{ .val = try result_val.copy(arena), .arena = arena_allocator.state, - }); + }, .{ .module = mod }); delete_memoized_call_key = false; } } @@ -4400,278 +5626,8 @@ fn analyzeCall( try wip_captures.finalize(); break :res res2; - } else if (func_ty_info.is_generic) res: { - const func_val = try sema.resolveConstValue(block, func_src, func); - const module_fn = switch (func_val.tag()) { - .function => func_val.castTag(.function).?.data, - .decl_ref => func_val.castTag(.decl_ref).?.data.val.castTag(.function).?.data, - else => unreachable, - }; - // Check the Module's generic function map with an adapted context, so that we - // can match against `uncasted_args` rather than doing the work below to create a - // generic Scope only to junk it if it matches an existing instantiation. - const namespace = module_fn.owner_decl.src_namespace; - const fn_zir = namespace.file_scope.zir; - const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); - const zir_tags = fn_zir.instructions.items(.tag); - - // This hash must match `Module.MonomorphedFuncsContext.hash`. - // For parameters explicitly marked comptime and simple parameter type expressions, - // we know whether a parameter is elided from a monomorphed function, and can - // use it in the hash here. However, for parameter type expressions that are not - // explicitly marked comptime and rely on previous parameter comptime values, we - // don't find out until after generating a monomorphed function whether the parameter - // type ended up being a "must-be-comptime-known" type. - var hasher = std.hash.Wyhash.init(0); - std.hash.autoHash(&hasher, @ptrToInt(module_fn)); - - const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len); - - for (func_ty_info.param_types) |param_ty, i| { - const is_comptime = func_ty_info.paramIsComptime(i); - if (is_comptime) { - const arg_src = call_src; // TODO better source location - const casted_arg = try sema.coerce(block, param_ty, uncasted_args[i], arg_src); - if (try sema.resolveMaybeUndefVal(block, arg_src, casted_arg)) |arg_val| { - if (param_ty.tag() != .generic_poison) { - arg_val.hash(param_ty, &hasher); - } - comptime_tvs[i] = .{ - // This will be different than `param_ty` in the case of `generic_poison`. - .ty = sema.typeOf(casted_arg), - .val = arg_val, - }; - } else { - return sema.failWithNeededComptime(block, arg_src); - } - } else { - comptime_tvs[i] = .{ - .ty = sema.typeOf(uncasted_args[i]), - .val = Value.initTag(.generic_poison), - }; - } - } - - const precomputed_hash = hasher.final(); - - const adapter: GenericCallAdapter = .{ - .generic_fn = module_fn, - .precomputed_hash = precomputed_hash, - .func_ty_info = func_ty_info, - .comptime_tvs = comptime_tvs, - }; - const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); - if (gop.found_existing) { - const callee_func = gop.key_ptr.*; - break :res try sema.finishGenericCall( - block, - call_src, - callee_func, - func_src, - uncasted_args, - fn_info, - zir_tags, - ); - } - const new_module_func = try gpa.create(Module.Fn); - gop.key_ptr.* = new_module_func; - { - errdefer gpa.destroy(new_module_func); - const remove_adapter: GenericRemoveAdapter = .{ - .precomputed_hash = precomputed_hash, - }; - errdefer assert(mod.monomorphed_funcs.removeAdapted(new_module_func, remove_adapter)); - - try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); - - // Create a Decl for the new function. - const src_decl = namespace.getDecl(); - // TODO better names for generic function instantiations - const name_index = mod.getNextAnonNameIndex(); - const decl_name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ - module_fn.owner_decl.name, name_index, - }); - const new_decl = try mod.allocateNewDecl(decl_name, namespace, module_fn.owner_decl.src_node, src_decl.src_scope); - new_decl.src_line = module_fn.owner_decl.src_line; - new_decl.is_pub = module_fn.owner_decl.is_pub; - new_decl.is_exported = module_fn.owner_decl.is_exported; - new_decl.has_align = module_fn.owner_decl.has_align; - new_decl.has_linksection_or_addrspace = module_fn.owner_decl.has_linksection_or_addrspace; - new_decl.@"addrspace" = module_fn.owner_decl.@"addrspace"; - new_decl.zir_decl_index = module_fn.owner_decl.zir_decl_index; - new_decl.alive = true; // This Decl is called at runtime. - new_decl.has_tv = true; - new_decl.owns_tv = true; - new_decl.analysis = .in_progress; - new_decl.generation = mod.generation; - - namespace.anon_decls.putAssumeCapacityNoClobber(new_decl, {}); - - // The generic function Decl is guaranteed to be the first dependency - // of each of its instantiations. - assert(new_decl.dependencies.keys().len == 0); - try mod.declareDeclDependency(new_decl, module_fn.owner_decl); - - var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); - errdefer new_decl_arena.deinit(); - const new_decl_arena_allocator = new_decl_arena.allocator(); - - // Re-run the block that creates the function, with the comptime parameters - // pre-populated inside `inst_map`. This causes `param_comptime` and - // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a - // new, monomorphized function, with the comptime parameters elided. - var child_sema: Sema = .{ - .mod = mod, - .gpa = gpa, - .arena = sema.arena, - .perm_arena = new_decl_arena_allocator, - .code = fn_zir, - .owner_decl = new_decl, - .func = null, - .fn_ret_ty = Type.void, - .owner_func = null, - .comptime_args = try new_decl_arena_allocator.alloc(TypedValue, uncasted_args.len), - .comptime_args_fn_inst = module_fn.zir_body_inst, - .preallocated_new_func = new_module_func, - }; - defer child_sema.deinit(); - - var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope); - defer wip_captures.deinit(); - - var child_block: Block = .{ - .parent = null, - .sema = &child_sema, - .src_decl = new_decl, - .namespace = namespace, - .wip_capture_scope = wip_captures.scope, - .instructions = .{}, - .inlining = null, - .is_comptime = true, - }; - defer { - child_block.instructions.deinit(gpa); - child_block.params.deinit(gpa); - } - - try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); - var arg_i: usize = 0; - for (fn_info.param_body) |inst| { - var is_comptime = false; - var is_anytype = false; - switch (zir_tags[inst]) { - .param => { - is_comptime = func_ty_info.paramIsComptime(arg_i); - }, - .param_comptime => { - is_comptime = true; - }, - .param_anytype => { - is_anytype = true; - is_comptime = func_ty_info.paramIsComptime(arg_i); - }, - .param_anytype_comptime => { - is_anytype = true; - is_comptime = true; - }, - else => continue, - } - const arg_src = call_src; // TODO: better source location - const arg = uncasted_args[arg_i]; - if (is_comptime) { - if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { - const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else { - return sema.failWithNeededComptime(block, arg_src); - } - } else if (is_anytype) { - const arg_ty = sema.typeOf(arg); - if (try sema.typeRequiresComptime(block, arg_src, arg_ty)) { - const arg_val = try sema.resolveConstValue(block, arg_src, arg); - const child_arg = try child_sema.addConstant(arg_ty, arg_val); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } else { - // We insert into the map an instruction which is runtime-known - // but has the type of the argument. - const child_arg = try child_block.addArg(arg_ty, 0); - child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); - } - } - arg_i += 1; - } - const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| { - // TODO look up the compile error that happened here and attach a note to it - // pointing here, at the generic instantiation callsite. - if (sema.owner_func) |owner_func| { - owner_func.state = .dependency_failure; - } else { - sema.owner_decl.analysis = .dependency_failure; - } - return err; - }; - const new_func_val = child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst) catch unreachable; - const new_func = new_func_val.castTag(.function).?.data; - assert(new_func == new_module_func); - - arg_i = 0; - for (fn_info.param_body) |inst| { - switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, - else => continue, - } - const arg = child_sema.inst_map.get(inst).?; - const copied_arg_ty = try child_sema.typeOf(arg).copy(new_decl_arena_allocator); - if (child_sema.resolveMaybeUndefValAllowVariables( - &child_block, - .unneeded, - arg, - ) catch unreachable) |arg_val| { - child_sema.comptime_args[arg_i] = .{ - .ty = copied_arg_ty, - .val = try arg_val.copy(new_decl_arena_allocator), - }; - } else { - child_sema.comptime_args[arg_i] = .{ - .ty = copied_arg_ty, - .val = Value.initTag(.generic_poison), - }; - } - - arg_i += 1; - } - - try wip_captures.finalize(); - - // Populate the Decl ty/val with the function and its type. - new_decl.ty = try child_sema.typeOf(new_func_inst).copy(new_decl_arena_allocator); - new_decl.val = try Value.Tag.function.create(new_decl_arena_allocator, new_func); - new_decl.analysis = .complete; - - log.debug("generic function '{s}' instantiated with type {}", .{ - new_decl.name, new_decl.ty, - }); - assert(!new_decl.ty.fnInfo().is_generic); - - // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field - // will be populated, ensuring it will have `analyzeBody` called with the ZIR - // parameters mapped appropriately. - try mod.comp.bin_file.allocateDeclIndexes(new_decl); - try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); - - try new_decl.finalizeNewArena(&new_decl_arena); - } - - break :res try sema.finishGenericCall( - block, - call_src, - new_module_func, - func_src, - uncasted_args, - fn_info, - zir_tags, - ); } else res: { + assert(!func_ty_info.is_generic); try sema.requireRuntimeBlock(block, call_src); const args = try sema.arena.alloc(Air.Inst.Ref, uncasted_args.len); @@ -4686,12 +5642,15 @@ fn analyzeCall( } } - try sema.resolveTypeFully(block, call_src, func_ty_info.return_type); + try sema.queueFullTypeResolution(func_ty_info.return_type); + if (sema.owner_func != null and func_ty_info.return_type.isError()) { + sema.owner_func.?.calls_or_awaits_errorable_fn = true; + } try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Call).Struct.fields.len + args.len); const func_inst = try block.addInst(.{ - .tag = .call, + .tag = call_tag, .data = .{ .pl_op = .{ .operand = func, .payload = sema.addExtraAssumeCapacity(Air.Call{ @@ -4709,41 +5668,372 @@ fn analyzeCall( return result; } -fn finishGenericCall( +fn instantiateGenericCall( sema: *Sema, block: *Block, - call_src: LazySrcLoc, - callee: *Module.Fn, + func: Air.Inst.Ref, func_src: LazySrcLoc, + call_src: LazySrcLoc, + func_ty_info: Type.Payload.Function.Data, + ensure_result_used: bool, uncasted_args: []const Air.Inst.Ref, - fn_info: Zir.FnInfo, - zir_tags: []const Zir.Inst.Tag, + call_tag: Air.Inst.Tag, ) CompileError!Air.Inst.Ref { - const callee_inst = try sema.analyzeDeclVal(block, func_src, callee.owner_decl); + const mod = sema.mod; + const gpa = sema.gpa; - // Make a runtime call to the new function, making sure to omit the comptime args. - try sema.requireRuntimeBlock(block, call_src); + const func_val = try sema.resolveConstValue(block, func_src, func); + const module_fn = switch (func_val.tag()) { + .function => func_val.castTag(.function).?.data, + .decl_ref => mod.declPtr(func_val.castTag(.decl_ref).?.data).val.castTag(.function).?.data, + else => unreachable, + }; + // Check the Module's generic function map with an adapted context, so that we + // can match against `uncasted_args` rather than doing the work below to create a + // generic Scope only to junk it if it matches an existing instantiation. + const fn_owner_decl = mod.declPtr(module_fn.owner_decl); + const namespace = fn_owner_decl.src_namespace; + const fn_zir = namespace.file_scope.zir; + const fn_info = fn_zir.getFnInfo(module_fn.zir_body_inst); + const zir_tags = fn_zir.instructions.items(.tag); + + // This hash must match `Module.MonomorphedFuncsContext.hash`. + // For parameters explicitly marked comptime and simple parameter type expressions, + // we know whether a parameter is elided from a monomorphed function, and can + // use it in the hash here. However, for parameter type expressions that are not + // explicitly marked comptime and rely on previous parameter comptime values, we + // don't find out until after generating a monomorphed function whether the parameter + // type ended up being a "must-be-comptime-known" type. + var hasher = std.hash.Wyhash.init(0); + std.hash.autoHash(&hasher, @ptrToInt(module_fn)); + + const comptime_tvs = try sema.arena.alloc(TypedValue, func_ty_info.param_types.len); - const comptime_args = callee.comptime_args.?; - const runtime_args_len = count: { - var count: u32 = 0; - var arg_i: usize = 0; + { + var i: usize = 0; for (fn_info.param_body) |inst| { + var is_comptime = false; + var is_anytype = false; switch (zir_tags[inst]) { - .param_comptime, .param_anytype_comptime, .param, .param_anytype => { - if (comptime_args[arg_i].val.tag() == .generic_poison) { - count += 1; - } - arg_i += 1; + .param => { + is_comptime = func_ty_info.paramIsComptime(i); + }, + .param_comptime => { + is_comptime = true; + }, + .param_anytype => { + is_anytype = true; + is_comptime = func_ty_info.paramIsComptime(i); + }, + .param_anytype_comptime => { + is_anytype = true; + is_comptime = true; }, else => continue, } + + if (is_comptime) { + const arg_src = call_src; // TODO better source location + const arg_ty = sema.typeOf(uncasted_args[i]); + const arg_val = try sema.resolveValue(block, arg_src, uncasted_args[i]); + arg_val.hash(arg_ty, &hasher, mod); + if (is_anytype) { + arg_ty.hashWithHasher(&hasher, mod); + comptime_tvs[i] = .{ + .ty = arg_ty, + .val = arg_val, + }; + } else { + comptime_tvs[i] = .{ + .ty = Type.initTag(.generic_poison), + .val = arg_val, + }; + } + } else if (is_anytype) { + const arg_ty = sema.typeOf(uncasted_args[i]); + arg_ty.hashWithHasher(&hasher, mod); + comptime_tvs[i] = .{ + .ty = arg_ty, + .val = Value.initTag(.generic_poison), + }; + } else { + comptime_tvs[i] = .{ + .ty = Type.initTag(.generic_poison), + .val = Value.initTag(.generic_poison), + }; + } + + i += 1; } - break :count count; + } + + const precomputed_hash = hasher.final(); + + const adapter: GenericCallAdapter = .{ + .generic_fn = module_fn, + .precomputed_hash = precomputed_hash, + .func_ty_info = func_ty_info, + .comptime_tvs = comptime_tvs, + .module = mod, }; + const gop = try mod.monomorphed_funcs.getOrPutAdapted(gpa, {}, adapter); + const callee = if (!gop.found_existing) callee: { + const new_module_func = try gpa.create(Module.Fn); + // This ensures that we can operate on the hash map before the Module.Fn + // struct is fully initialized. + new_module_func.hash = precomputed_hash; + gop.key_ptr.* = new_module_func; + errdefer gpa.destroy(new_module_func); + errdefer assert(mod.monomorphed_funcs.remove(new_module_func)); + + try namespace.anon_decls.ensureUnusedCapacity(gpa, 1); + + // Create a Decl for the new function. + const src_decl_index = namespace.getDeclIndex(); + const src_decl = mod.declPtr(src_decl_index); + const new_decl_index = try mod.allocateNewDecl(namespace, fn_owner_decl.src_node, src_decl.src_scope); + errdefer mod.destroyDecl(new_decl_index); + const new_decl = mod.declPtr(new_decl_index); + // TODO better names for generic function instantiations + const decl_name = try std.fmt.allocPrintZ(gpa, "{s}__anon_{d}", .{ + fn_owner_decl.name, @enumToInt(new_decl_index), + }); + new_decl.name = decl_name; + new_decl.src_line = fn_owner_decl.src_line; + new_decl.is_pub = fn_owner_decl.is_pub; + new_decl.is_exported = fn_owner_decl.is_exported; + new_decl.has_align = fn_owner_decl.has_align; + new_decl.has_linksection_or_addrspace = fn_owner_decl.has_linksection_or_addrspace; + new_decl.@"addrspace" = fn_owner_decl.@"addrspace"; + new_decl.zir_decl_index = fn_owner_decl.zir_decl_index; + new_decl.alive = true; // This Decl is called at runtime. + new_decl.analysis = .in_progress; + new_decl.generation = mod.generation; + + namespace.anon_decls.putAssumeCapacityNoClobber(new_decl_index, {}); + errdefer assert(namespace.anon_decls.orderedRemove(new_decl_index)); + + // The generic function Decl is guaranteed to be the first dependency + // of each of its instantiations. + assert(new_decl.dependencies.keys().len == 0); + try mod.declareDeclDependency(new_decl_index, module_fn.owner_decl); + // Resolving the new function type below will possibly declare more decl dependencies + // and so we remove them all here in case of error. + errdefer { + for (new_decl.dependencies.keys()) |dep_index| { + const dep = mod.declPtr(dep_index); + dep.removeDependant(new_decl_index); + } + } + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + // Re-run the block that creates the function, with the comptime parameters + // pre-populated inside `inst_map`. This causes `param_comptime` and + // `param_anytype_comptime` ZIR instructions to be ignored, resulting in a + // new, monomorphized function, with the comptime parameters elided. + var child_sema: Sema = .{ + .mod = mod, + .gpa = gpa, + .arena = sema.arena, + .perm_arena = new_decl_arena_allocator, + .code = fn_zir, + .owner_decl = new_decl, + .owner_decl_index = new_decl_index, + .func = null, + .fn_ret_ty = Type.void, + .owner_func = null, + .comptime_args = try new_decl_arena_allocator.alloc(TypedValue, uncasted_args.len), + .comptime_args_fn_inst = module_fn.zir_body_inst, + .preallocated_new_func = new_module_func, + }; + defer child_sema.deinit(); + + var wip_captures = try WipCaptureScope.init(gpa, sema.perm_arena, new_decl.src_scope); + defer wip_captures.deinit(); + + var child_block: Block = .{ + .parent = null, + .sema = &child_sema, + .src_decl = new_decl_index, + .namespace = namespace, + .wip_capture_scope = wip_captures.scope, + .instructions = .{}, + .inlining = null, + .is_comptime = true, + }; + defer { + child_block.instructions.deinit(gpa); + child_block.params.deinit(gpa); + } + + try child_sema.inst_map.ensureUnusedCapacity(gpa, @intCast(u32, uncasted_args.len)); + var arg_i: usize = 0; + for (fn_info.param_body) |inst| { + var is_comptime = false; + var is_anytype = false; + switch (zir_tags[inst]) { + .param => { + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_comptime => { + is_comptime = true; + }, + .param_anytype => { + is_anytype = true; + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_anytype_comptime => { + is_anytype = true; + is_comptime = true; + }, + else => continue, + } + const arg_src = call_src; // TODO: better source location + const arg = uncasted_args[arg_i]; + if (is_comptime) { + if (try sema.resolveMaybeUndefVal(block, arg_src, arg)) |arg_val| { + const child_arg = try child_sema.addConstant(sema.typeOf(arg), arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else { + return sema.failWithNeededComptime(block, arg_src); + } + } else if (is_anytype) { + const arg_ty = sema.typeOf(arg); + if (try sema.typeRequiresComptime(block, arg_src, arg_ty)) { + const arg_val = try sema.resolveConstValue(block, arg_src, arg); + const child_arg = try child_sema.addConstant(arg_ty, arg_val); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } else { + // We insert into the map an instruction which is runtime-known + // but has the type of the argument. + const child_arg = try child_block.addArg(arg_ty); + child_sema.inst_map.putAssumeCapacityNoClobber(inst, child_arg); + } + } + arg_i += 1; + } + const new_func_inst = child_sema.resolveBody(&child_block, fn_info.param_body, fn_info.param_body_inst) catch |err| { + // TODO look up the compile error that happened here and attach a note to it + // pointing here, at the generic instantiation callsite. + if (sema.owner_func) |owner_func| { + owner_func.state = .dependency_failure; + } else { + sema.owner_decl.analysis = .dependency_failure; + } + return err; + }; + const new_func_val = child_sema.resolveConstValue(&child_block, .unneeded, new_func_inst) catch unreachable; + const new_func = new_func_val.castTag(.function).?.data; + errdefer new_func.deinit(gpa); + assert(new_func == new_module_func); + + const anytype_args = try new_decl_arena_allocator.alloc(bool, func_ty_info.param_types.len); + new_func.anytype_args = anytype_args.ptr; + arg_i = 0; + for (fn_info.param_body) |inst| { + var is_comptime = false; + var is_anytype = false; + switch (zir_tags[inst]) { + .param => { + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_comptime => { + is_comptime = true; + }, + .param_anytype => { + is_anytype = true; + is_comptime = func_ty_info.paramIsComptime(arg_i); + }, + .param_anytype_comptime => { + is_anytype = true; + is_comptime = true; + }, + else => continue, + } + + // We populate the Type here regardless because it is needed by + // `GenericCallAdapter.eql` as well as function body analysis. + // Whether it is anytype is communicated by `anytype_args`. + const arg = child_sema.inst_map.get(inst).?; + const copied_arg_ty = try child_sema.typeOf(arg).copy(new_decl_arena_allocator); + anytype_args[arg_i] = is_anytype; + + const arg_src = call_src; // TODO: better source location + if (try sema.typeRequiresComptime(block, arg_src, copied_arg_ty)) { + is_comptime = true; + } + + if (is_comptime) { + const arg_val = (child_sema.resolveMaybeUndefValAllowVariables( + &child_block, + .unneeded, + arg, + ) catch unreachable).?; + child_sema.comptime_args[arg_i] = .{ + .ty = copied_arg_ty, + .val = try arg_val.copy(new_decl_arena_allocator), + }; + } else { + child_sema.comptime_args[arg_i] = .{ + .ty = copied_arg_ty, + .val = Value.initTag(.generic_poison), + }; + } + + arg_i += 1; + } + + try wip_captures.finalize(); + + // Populate the Decl ty/val with the function and its type. + new_decl.ty = try child_sema.typeOf(new_func_inst).copy(new_decl_arena_allocator); + // If the call evaluated to a return type that requires comptime, never mind + // our generic instantiation. Instead we need to perform a comptime call. + const new_fn_info = new_decl.ty.fnInfo(); + if (try sema.typeRequiresComptime(block, call_src, new_fn_info.return_type)) { + return error.ComptimeReturn; + } + // Similarly, if the call evaluated to a generic type we need to instead + // call it inline. + if (new_fn_info.is_generic or new_fn_info.cc == .Inline) { + return error.GenericPoison; + } + + new_decl.val = try Value.Tag.function.create(new_decl_arena_allocator, new_func); + new_decl.@"align" = 0; + new_decl.has_tv = true; + new_decl.owns_tv = true; + new_decl.analysis = .complete; + + log.debug("generic function '{s}' instantiated with type {}", .{ + new_decl.name, new_decl.ty.fmtDebug(), + }); + + // Queue up a `codegen_func` work item for the new Fn. The `comptime_args` field + // will be populated, ensuring it will have `analyzeBody` called with the ZIR + // parameters mapped appropriately. + try mod.comp.bin_file.allocateDeclIndexes(new_decl_index); + try mod.comp.work_queue.writeItem(.{ .codegen_func = new_func }); + + try new_decl.finalizeNewArena(&new_decl_arena); + break :callee new_func; + } else gop.key_ptr.*; + + callee.branch_quota = @maximum(callee.branch_quota, sema.branch_quota); + + const callee_inst = try sema.analyzeDeclVal(block, func_src, callee.owner_decl); + + // Make a runtime call to the new function, making sure to omit the comptime args. + try sema.requireRuntimeBlock(block, call_src); + + const comptime_args = callee.comptime_args.?; + const new_fn_info = mod.declPtr(callee.owner_decl).ty.fnInfo(); + const runtime_args_len = @intCast(u32, new_fn_info.param_types.len); const runtime_args = try sema.arena.alloc(Air.Inst.Ref, runtime_args_len); { - const new_fn_ty = callee.owner_decl.ty; var runtime_i: u32 = 0; var total_i: u32 = 0; for (fn_info.param_body) |inst| { @@ -4751,25 +6041,32 @@ fn finishGenericCall( .param_comptime, .param_anytype_comptime, .param, .param_anytype => {}, else => continue, } - const is_runtime = comptime_args[total_i].val.tag() == .generic_poison; + const is_runtime = comptime_args[total_i].val.tag() == .generic_poison and + comptime_args[total_i].ty.hasRuntimeBits() and + !comptime_args[total_i].ty.comptimeOnly(); if (is_runtime) { - const param_ty = new_fn_ty.fnParamType(runtime_i); + const param_ty = new_fn_info.param_types[runtime_i]; const arg_src = call_src; // TODO: better source location const uncasted_arg = uncasted_args[total_i]; - try sema.resolveTypeFully(block, arg_src, param_ty); const casted_arg = try sema.coerce(block, param_ty, uncasted_arg, arg_src); + try sema.queueFullTypeResolution(param_ty); runtime_args[runtime_i] = casted_arg; runtime_i += 1; } total_i += 1; } - try sema.resolveTypeFully(block, call_src, new_fn_ty.fnReturnType()); + try sema.queueFullTypeResolution(new_fn_info.return_type); + } + + if (sema.owner_func != null and new_fn_info.return_type.isError()) { + sema.owner_func.?.calls_or_awaits_errorable_fn = true; } + try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Call).Struct.fields.len + runtime_args_len); const func_inst = try block.addInst(.{ - .tag = .call, + .tag = call_tag, .data = .{ .pl_op = .{ .operand = callee_inst, .payload = sema.addExtraAssumeCapacity(Air.Call{ @@ -4778,9 +6075,36 @@ fn finishGenericCall( } }, }); sema.appendRefsAssumeCapacity(runtime_args); + + if (ensure_result_used) { + try sema.ensureResultUsed(block, func_inst, call_src); + } return func_inst; } +fn emitDbgInline( + sema: *Sema, + block: *Block, + old_func: *Module.Fn, + new_func: *Module.Fn, + new_func_ty: Type, + tag: Air.Inst.Tag, +) CompileError!void { + if (sema.mod.comp.bin_file.options.strip) return; + + // Recursive inline call; no dbg_inline needed. + if (old_func == new_func) return; + + try sema.air_values.append(sema.gpa, try Value.Tag.function.create(sema.arena, new_func)); + _ = try block.addInst(.{ + .tag = tag, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(new_func_ty), + .payload = @intCast(u32, sema.air_values.items.len - 1), + } }, + }); +} + fn zirIntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { _ = block; const tracy = trace(@src()); @@ -4804,12 +6128,17 @@ fn zirOptionalType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro return sema.addType(opt_type); } -fn zirElemType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - const array_type = try sema.resolveType(block, src, inst_data.operand); - const elem_type = array_type.elemType(); - return sema.addType(elem_type); +fn zirElemTypeIndex(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const bin = sema.code.instructions.items(.data)[inst].bin; + const indexable_ty = try sema.resolveType(block, .unneeded, bin.lhs); + assert(indexable_ty.isIndexable()); // validated by a previous instruction + if (indexable_ty.zigTypeTag() == .Struct) { + const elem_type = indexable_ty.tupleFields().types[@enumToInt(bin.rhs)]; + return sema.addType(elem_type); + } else { + const elem_type = indexable_ty.elemType2(); + return sema.addType(elem_type); + } } fn zirVectorType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -4817,11 +6146,11 @@ fn zirVectorType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const elem_type_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const len = try sema.resolveAlreadyCoercedInt(block, len_src, extra.lhs, u32); + const len = try sema.resolveInt(block, len_src, extra.lhs, Type.u32); 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 Type.Tag.vector.create(sema.arena, .{ - .len = len, + .len = @intCast(u32, len), .elem_type = elem_type, }); return sema.addType(vector_type); @@ -4832,9 +6161,11 @@ fn zirArrayType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A defer tracy.end(); const bin_inst = sema.code.instructions.items(.data)[inst].bin; - const len = try sema.resolveInt(block, .unneeded, bin_inst.lhs, Type.usize); - const elem_type = try sema.resolveType(block, .unneeded, bin_inst.rhs); - const array_ty = try Type.array(sema.arena, len, null, elem_type); + const len_src = sema.src; // TODO better source location + const elem_src = sema.src; // TODO better source location + const len = try sema.resolveInt(block, len_src, bin_inst.lhs, Type.usize); + const elem_type = try sema.resolveType(block, elem_src, bin_inst.rhs); + const array_ty = try Type.array(sema.arena, len, null, elem_type, sema.mod); return sema.addType(array_ty); } @@ -4850,10 +6181,10 @@ fn zirArrayTypeSentinel(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Compil const elem_src: LazySrcLoc = .{ .node_offset_array_type_elem = inst_data.src_node }; const len = try sema.resolveInt(block, len_src, extra.len, Type.usize); const elem_type = try sema.resolveType(block, elem_src, extra.elem_type); - const uncasted_sentinel = sema.resolveInst(extra.sentinel); + 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.resolveConstValue(block, sentinel_src, sentinel); - const array_ty = try Type.array(sema.arena, len, sentinel_val, elem_type); + const array_ty = try Type.array(sema.arena, len, sentinel_val, elem_type, sema.mod); return sema.addType(array_ty); } @@ -4878,15 +6209,15 @@ fn zirErrorUnionType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; - const error_union = try sema.resolveType(block, lhs_src, extra.lhs); + const error_set = try sema.resolveType(block, lhs_src, extra.lhs); const payload = try sema.resolveType(block, rhs_src, extra.rhs); - if (error_union.zigTypeTag() != .ErrorSet) { - return sema.fail(block, lhs_src, "expected error set type, found {}", .{ - error_union.elemType(), + if (error_set.zigTypeTag() != .ErrorSet) { + return sema.fail(block, lhs_src, "expected error set type, found '{}'", .{ + error_set.fmt(sema.mod), }); } - const err_union_ty = try Module.errorUnionType(sema.arena, error_union, payload); + const err_union_ty = try Type.errorUnion(sema.arena, error_set, payload, sema.mod); return sema.addType(err_union_ty); } @@ -4915,20 +6246,41 @@ fn zirErrorToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const op = sema.resolveInst(inst_data.operand); + const op = try sema.resolveInst(inst_data.operand); const op_coerced = try sema.coerce(block, Type.anyerror, op, operand_src); - const result_ty = Type.initTag(.u16); + const result_ty = Type.u16; if (try sema.resolveMaybeUndefVal(block, src, op_coerced)) |val| { if (val.isUndef()) { return sema.addConstUndef(result_ty); } - const payload = try sema.arena.create(Value.Payload.U64); - payload.* = .{ - .base = .{ .tag = .int_u64 }, - .data = (try sema.mod.getErrorValue(val.castTag(.@"error").?.data.name)).value, - }; - return sema.addConstant(result_ty, Value.initPayload(&payload.base)); + switch (val.tag()) { + .@"error" => { + const payload = try sema.arena.create(Value.Payload.U64); + payload.* = .{ + .base = .{ .tag = .int_u64 }, + .data = (try sema.mod.getErrorValue(val.castTag(.@"error").?.data.name)).value, + }; + return sema.addConstant(result_ty, Value.initPayload(&payload.base)); + }, + + // This is not a valid combination with the type `anyerror`. + .the_only_possible_value => unreachable, + + // Assume it's already encoded as an integer. + else => return sema.addConstant(result_ty, val), + } + } + + const op_ty = sema.typeOf(op); + try sema.resolveInferredErrorSetTy(block, src, op_ty); + if (!op_ty.isAnyError()) { + const names = op_ty.errorSetNames(); + switch (names.len) { + 0 => return sema.addConstant(result_ty, Value.zero), + 1 => return sema.addIntUnsigned(result_ty, sema.mod.global_error_set.get(names[0]).?), + else => {}, + } } try sema.requireRuntimeBlock(block, src); @@ -4942,31 +6294,31 @@ fn zirIntToError(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const uncasted_operand = try sema.resolveInst(inst_data.operand); + const operand = try sema.coerce(block, Type.u16, uncasted_operand, operand_src); + const target = sema.mod.getTarget(); - const op = sema.resolveInst(inst_data.operand); - - if (try sema.resolveDefinedValue(block, operand_src, op)) |value| { - const int = value.toUnsignedInt(); + if (try sema.resolveDefinedValue(block, operand_src, operand)) |value| { + const int = try sema.usizeCast(block, operand_src, value.toUnsignedInt(target)); if (int > sema.mod.global_error_set.count() or int == 0) - return sema.fail(block, operand_src, "integer value {d} represents no error", .{int}); + return sema.fail(block, operand_src, "integer value '{d}' represents no error", .{int}); const payload = try sema.arena.create(Value.Payload.Error); payload.* = .{ .base = .{ .tag = .@"error" }, - .data = .{ .name = sema.mod.error_name_list.items[@intCast(usize, int)] }, + .data = .{ .name = sema.mod.error_name_list.items[int] }, }; return sema.addConstant(Type.anyerror, Value.initPayload(&payload.base)); } try sema.requireRuntimeBlock(block, src); if (block.wantSafety()) { - return sema.fail(block, src, "TODO: get max errors in compilation", .{}); - // const is_gt_max = @panic("TODO get max errors in compilation"); - // try sema.addSafetyCheck(block, is_gt_max, .invalid_error_code); + const is_lt_len = try block.addUnOp(.cmp_lt_errors_len, operand); + try sema.addSafetyCheck(block, is_lt_len, .invalid_error_code); } return block.addInst(.{ .tag = .bitcast, .data = .{ .ty_op = .{ .ty = Air.Inst.Ref.anyerror_type, - .operand = op, + .operand = operand, } }, }); } @@ -4980,8 +6332,8 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr const src: LazySrcLoc = .{ .node_offset_bin_op = inst_data.src_node }; const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); if (sema.typeOf(lhs).zigTypeTag() == .Bool and sema.typeOf(rhs).zigTypeTag() == .Bool) { const msg = msg: { const msg = try sema.errMsg(block, lhs_src, "expected error set type, found 'bool'", .{}); @@ -4989,44 +6341,36 @@ fn zirMergeErrorSets(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileEr try sema.errNote(block, src, msg, "'||' merges error sets; 'or' performs boolean OR", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(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() != .ErrorSet) - return sema.fail(block, lhs_src, "expected error set type, found {}", .{lhs_ty}); + return sema.fail(block, lhs_src, "expected error set type, found '{}'", .{lhs_ty.fmt(sema.mod)}); if (rhs_ty.zigTypeTag() != .ErrorSet) - return sema.fail(block, rhs_src, "expected error set type, found {}", .{rhs_ty}); + return sema.fail(block, rhs_src, "expected error set type, found '{}'", .{rhs_ty.fmt(sema.mod)}); // Anything merged with anyerror is anyerror. if (lhs_ty.tag() == .anyerror or rhs_ty.tag() == .anyerror) { return Air.Inst.Ref.anyerror_type; } - // Resolve both error sets now. - const lhs_names = lhs_ty.errorSetNames(); - const rhs_names = rhs_ty.errorSetNames(); - - // TODO do we really want to create a Decl for this? - // The reason we do it right now is for memory management. - var anon_decl = try block.startAnonDecl(src); - defer anon_decl.deinit(); - var names = Module.ErrorSet.NameMap{}; - // TODO: Guess is an upper bound, but maybe this needs to be reduced by computing the exact size first. - try names.ensureUnusedCapacity(anon_decl.arena(), @intCast(u32, lhs_names.len + rhs_names.len)); - for (lhs_names) |name| { - names.putAssumeCapacityNoClobber(name, {}); + if (lhs_ty.castTag(.error_set_inferred)) |payload| { + try sema.resolveInferredErrorSet(block, src, payload.data); + // isAnyError might have changed from a false negative to a true positive after resolution. + if (lhs_ty.isAnyError()) { + return Air.Inst.Ref.anyerror_type; + } } - for (rhs_names) |name| { - names.putAssumeCapacity(name, {}); + if (rhs_ty.castTag(.error_set_inferred)) |payload| { + try sema.resolveInferredErrorSet(block, src, payload.data); + // isAnyError might have changed from a false negative to a true positive after resolution. + if (rhs_ty.isAnyError()) { + return Air.Inst.Ref.anyerror_type; + } } - const err_set_ty = try Type.Tag.error_set_merged.create(anon_decl.arena(), names); - const err_set_decl = try anon_decl.finish( - Type.type, - try Value.Tag.ty.create(anon_decl.arena(), err_set_ty), - ); - try sema.mod.declareDeclDependency(sema.owner_decl, err_set_decl); + const err_set_ty = try lhs_ty.errorSetMerge(sema.arena, rhs_ty); return sema.addType(err_set_ty); } @@ -5048,25 +6392,25 @@ fn zirEnumToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); const enum_tag: Air.Inst.Ref = switch (operand_ty.zigTypeTag()) { .Enum => operand, - .Union => { - //if (!operand_ty.unionHasTag()) { - // return sema.fail( - // block, - // operand_src, - // "untagged union '{}' cannot be converted to integer", - // .{dest_ty_src}, - // ); - //} - return sema.fail(block, operand_src, "TODO zirEnumToInt for tagged unions", .{}); + .Union => blk: { + const tag_ty = operand_ty.unionTagType() orelse { + return sema.fail( + block, + operand_src, + "untagged union '{}' cannot be converted to integer", + .{src}, + ); + }; + break :blk try sema.unionToTag(block, tag_ty, operand, operand_src); }, else => { - return sema.fail(block, operand_src, "expected enum or tagged union, found {}", .{ - operand_ty, + return sema.fail(block, operand_src, "expected enum or tagged union, found '{}'", .{ + operand_ty.fmt(sema.mod), }); }, }; @@ -5090,18 +6434,18 @@ fn zirEnumToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const target = sema.mod.getTarget(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const src = inst_data.src(); const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const operand = try sema.resolveInst(extra.rhs); if (dest_ty.zigTypeTag() != .Enum) { - return sema.fail(block, dest_ty_src, "expected enum, found {}", .{dest_ty}); + return sema.fail(block, dest_ty_src, "expected enum, found '{}'", .{dest_ty.fmt(sema.mod)}); } + _ = try sema.checkIntType(block, operand_src, sema.typeOf(operand)); if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |int_val| { if (dest_ty.isNonexhaustiveEnum()) { @@ -5110,24 +6454,24 @@ fn zirIntToEnum(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A if (int_val.isUndef()) { return sema.failWithUseOfUndef(block, operand_src); } - if (!dest_ty.enumHasInt(int_val, target)) { + if (!(try sema.enumHasInt(block, src, dest_ty, int_val))) { const msg = msg: { const msg = try sema.errMsg( block, src, - "enum '{}' has no tag with value {}", - .{ dest_ty, int_val }, + "enum '{}' has no tag with value '{}'", + .{ dest_ty.fmt(sema.mod), int_val.fmtValue(sema.typeOf(operand), sema.mod) }, ); errdefer msg.destroy(sema.gpa); try sema.mod.errNoteNonLazy( - dest_ty.declSrcLoc(), + dest_ty.declSrcLoc(sema.mod), msg, "enum declared here", .{}, ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } return sema.addConstant(dest_ty, int_val); } @@ -5148,32 +6492,63 @@ fn zirOptionalPayloadPtr( defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const optional_ptr = sema.resolveInst(inst_data.operand); + const optional_ptr = try sema.resolveInst(inst_data.operand); + const src = inst_data.src(); + + 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 optional_ptr_ty = sema.typeOf(optional_ptr); assert(optional_ptr_ty.zigTypeTag() == .Pointer); - const src = inst_data.src(); const opt_type = optional_ptr_ty.elemType(); if (opt_type.zigTypeTag() != .Optional) { - return sema.fail(block, src, "expected optional type, found {}", .{opt_type}); + return sema.fail(block, src, "expected optional type, found '{}'", .{opt_type.fmt(sema.mod)}); } const child_type = try opt_type.optionalChildAlloc(sema.arena); - const child_pointer = try Type.ptr(sema.arena, .{ + const child_pointer = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = child_type, .mutable = !optional_ptr_ty.isConstPtr(), .@"addrspace" = optional_ptr_ty.ptrAddressSpace(), }); - if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { - if (try sema.pointerDeref(block, src, pointer_val, optional_ptr_ty)) |val| { + if (try sema.resolveDefinedValue(block, src, optional_ptr)) |ptr_val| { + if (initializing) { + if (!ptr_val.isComptimeMutablePtr()) { + // If the pointer resulting from this function was stored at comptime, + // the optional non-null bit would be set that way. But in this case, + // we need to emit a runtime instruction to do it. + try sema.requireRuntimeBlock(block, src); + _ = try block.addTyOp(.optional_payload_ptr_set, child_pointer, optional_ptr); + } + return sema.addConstant( + child_pointer, + try Value.Tag.opt_payload_ptr.create(sema.arena, .{ + .container_ptr = ptr_val, + .container_ty = optional_ptr_ty.childType(), + }), + ); + } + if (try sema.pointerDeref(block, src, ptr_val, optional_ptr_ty)) |val| { if (val.isNull()) { return sema.fail(block, src, "unable to unwrap null", .{}); } // The same Value represents the pointer to the optional and the payload. return sema.addConstant( child_pointer, - try Value.Tag.opt_payload_ptr.create(sema.arena, pointer_val), + try Value.Tag.opt_payload_ptr.create(sema.arena, .{ + .container_ptr = ptr_val, + .container_ty = optional_ptr_ty.childType(), + }), ); } } @@ -5183,7 +6558,11 @@ fn zirOptionalPayloadPtr( const is_non_null = try block.addUnOp(.is_non_null_ptr, optional_ptr); try sema.addSafetyCheck(block, is_non_null, .unwrap_null); } - return block.addTyOp(.optional_payload_ptr, child_pointer, optional_ptr); + const air_tag: Air.Inst.Tag = if (initializing) + .optional_payload_ptr_set + else + .optional_payload_ptr; + return block.addTyOp(air_tag, child_pointer, optional_ptr); } /// Value in, value out. @@ -5198,7 +6577,7 @@ fn zirOptionalPayload( const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); const result_ty = switch (operand_ty.zigTypeTag()) { .Optional => try operand_ty.optionalChildAlloc(sema.arena), @@ -5207,7 +6586,7 @@ fn zirOptionalPayload( return sema.failWithExpectedOptionalType(block, src, operand_ty); } const ptr_info = operand_ty.ptrInfo().data; - break :t try Type.ptr(sema.arena, .{ + break :t try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = try ptr_info.pointee_type.copy(sema.arena), .@"align" = ptr_info.@"align", .@"addrspace" = ptr_info.@"addrspace", @@ -5250,27 +6629,45 @@ fn zirErrUnionPayload( const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_src = src; - const operand_ty = sema.typeOf(operand); - if (operand_ty.zigTypeTag() != .ErrorUnion) - return sema.fail(block, operand_src, "expected error union type, found '{}'", .{operand_ty}); + const err_union_ty = sema.typeOf(operand); + if (err_union_ty.zigTypeTag() != .ErrorUnion) { + return sema.fail(block, operand_src, "expected error union type, found '{}'", .{ + err_union_ty.fmt(sema.mod), + }); + } + return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, safety_check); +} - if (try sema.resolveDefinedValue(block, src, operand)) |val| { +fn analyzeErrUnionPayload( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + err_union_ty: Type, + operand: Zir.Inst.Ref, + operand_src: LazySrcLoc, + safety_check: bool, +) CompileError!Air.Inst.Ref { + const payload_ty = err_union_ty.errorUnionPayload(); + if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| { if (val.getError()) |name| { return sema.fail(block, src, "caught unexpected error '{s}'", .{name}); } const data = val.castTag(.eu_payload).?.data; - const result_ty = operand_ty.errorUnionPayload(); - return sema.addConstant(result_ty, data); + return sema.addConstant(payload_ty, data); } + try sema.requireRuntimeBlock(block, src); - if (safety_check and block.wantSafety()) { - const is_non_err = try block.addUnOp(.is_err, operand); - try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion); + + // If the error set has no fields then no safety check is needed. + if (safety_check and block.wantSafety() and + !err_union_ty.errorUnionSet().errorSetIsEmpty()) + { + try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err); } - const result_ty = operand_ty.errorUnionPayload(); - return block.addTyOp(.unwrap_errunion_payload, result_ty, operand); + + return block.addTyOp(.unwrap_errunion_payload, payload_ty, operand); } /// Pointer in, pointer out. @@ -5284,39 +6681,83 @@ fn zirErrUnionPayloadPtr( defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const operand = try sema.resolveInst(inst_data.operand); const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + + return sema.analyzeErrUnionPayloadPtr(block, src, operand, safety_check, false); +} + +fn analyzeErrUnionPayloadPtr( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + operand: Air.Inst.Ref, + safety_check: bool, + initializing: bool, +) CompileError!Air.Inst.Ref { const operand_ty = sema.typeOf(operand); assert(operand_ty.zigTypeTag() == .Pointer); - if (operand_ty.elemType().zigTypeTag() != .ErrorUnion) - return sema.fail(block, src, "expected error union type, found {}", .{operand_ty.elemType()}); + if (operand_ty.elemType().zigTypeTag() != .ErrorUnion) { + return sema.fail(block, src, "expected error union type, found '{}'", .{ + operand_ty.elemType().fmt(sema.mod), + }); + } - const payload_ty = operand_ty.elemType().errorUnionPayload(); - const operand_pointer_ty = try Type.ptr(sema.arena, .{ + const err_union_ty = operand_ty.elemType(); + const payload_ty = err_union_ty.errorUnionPayload(); + const operand_pointer_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = payload_ty, .mutable = !operand_ty.isConstPtr(), .@"addrspace" = operand_ty.ptrAddressSpace(), }); - if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { - if (try sema.pointerDeref(block, src, pointer_val, operand_ty)) |val| { + if (try sema.resolveDefinedValue(block, src, operand)) |ptr_val| { + if (initializing) { + if (!ptr_val.isComptimeMutablePtr()) { + // If the pointer resulting from this function was stored at comptime, + // the error union error code would be set that way. But in this case, + // we need to emit a runtime instruction to do it. + try sema.requireRuntimeBlock(block, src); + _ = try block.addTyOp(.errunion_payload_ptr_set, operand_pointer_ty, operand); + } + return sema.addConstant( + operand_pointer_ty, + try Value.Tag.eu_payload_ptr.create(sema.arena, .{ + .container_ptr = ptr_val, + .container_ty = operand_ty.elemType(), + }), + ); + } + if (try sema.pointerDeref(block, src, ptr_val, operand_ty)) |val| { if (val.getError()) |name| { return sema.fail(block, src, "caught unexpected error '{s}'", .{name}); } + return sema.addConstant( operand_pointer_ty, - try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val), + try Value.Tag.eu_payload_ptr.create(sema.arena, .{ + .container_ptr = ptr_val, + .container_ty = operand_ty.elemType(), + }), ); } } try sema.requireRuntimeBlock(block, src); - if (safety_check and block.wantSafety()) { - const is_non_err = try block.addUnOp(.is_err, operand); - try sema.addSafetyCheck(block, is_non_err, .unwrap_errunion); + + // If the error set has no fields then no safety check is needed. + if (safety_check and block.wantSafety() and + !err_union_ty.errorUnionSet().errorSetIsEmpty()) + { + try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err_ptr, .is_non_err_ptr); } - return block.addTyOp(.unwrap_errunion_payload_ptr, operand_pointer_ty, operand); + + const air_tag: Air.Inst.Tag = if (initializing) + .errunion_payload_ptr_set + else + .unwrap_errunion_payload_ptr; + return block.addTyOp(air_tag, operand_pointer_ty, operand); } /// Value in, value out @@ -5326,10 +6767,13 @@ fn zirErrUnionCode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErro const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); - if (operand_ty.zigTypeTag() != .ErrorUnion) - return sema.fail(block, src, "expected error union type, found '{}'", .{operand_ty}); + if (operand_ty.zigTypeTag() != .ErrorUnion) { + return sema.fail(block, src, "expected error union type, found '{}'", .{ + operand_ty.fmt(sema.mod), + }); + } const result_ty = operand_ty.errorUnionSet(); @@ -5349,12 +6793,15 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); assert(operand_ty.zigTypeTag() == .Pointer); - if (operand_ty.elemType().zigTypeTag() != .ErrorUnion) - return sema.fail(block, src, "expected error union type, found {}", .{operand_ty.elemType()}); + if (operand_ty.elemType().zigTypeTag() != .ErrorUnion) { + return sema.fail(block, src, "expected error union type, found '{}'", .{ + operand_ty.elemType().fmt(sema.mod), + }); + } const result_ty = operand_ty.elemType().errorUnionSet(); @@ -5375,10 +6822,13 @@ fn zirEnsureErrPayloadVoid(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com const inst_data = sema.code.instructions.items(.data)[inst].un_tok; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); - if (operand_ty.zigTypeTag() != .ErrorUnion) - return sema.fail(block, src, "expected error union type, found '{}'", .{operand_ty}); + if (operand_ty.zigTypeTag() != .ErrorUnion) { + return sema.fail(block, src, "expected error union type, found '{}'", .{ + operand_ty.fmt(sema.mod), + }); + } if (operand_ty.errorUnionPayload().zigTypeTag() != .Void) { return sema.fail(block, src, "expression value is ignored", .{}); } @@ -5395,9 +6845,34 @@ fn zirFunc( const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Func, inst_data.payload_index); + const target = sema.mod.getTarget(); + const ret_ty_src = inst_data.src(); // TODO better source location + var extra_index = extra.end; - const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; - extra_index += ret_ty_body.len; + + const ret_ty: Type = switch (extra.data.ret_body_len) { + 0 => Type.void, + 1 => blk: { + const ret_ty_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + if (sema.resolveType(block, ret_ty_src, ret_ty_ref)) |ret_ty| { + break :blk ret_ty; + } else |err| switch (err) { + error.GenericPoison => { + break :blk Type.initTag(.generic_poison); + }, + else => |e| return e, + } + }, + else => blk: { + const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; + extra_index += ret_ty_body.len; + + const ret_ty_val = try sema.resolveGenericBody(block, ret_ty_src, ret_ty_body, inst, Type.type); + var buffer: Value.ToTypeBuffer = undefined; + break :blk try ret_ty_val.toType(&buffer).copy(sema.arena); + }, + }; var src_locs: Zir.Inst.Func.SrcLocs = undefined; const has_body = extra.data.body_len != 0; @@ -5406,7 +6881,11 @@ fn zirFunc( src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } - const cc: std.builtin.CallingConvention = if (sema.owner_decl.is_exported) + // If this instruction has a body it means it's the type of the `owner_decl` + // otherwise it's a function type without a `callconv` attribute and should + // never be `.C`. + // NOTE: revisit when doing #1717 + const cc: std.builtin.CallingConvention = if (sema.owner_decl.is_exported and has_body) .C else .Unspecified; @@ -5415,67 +6894,160 @@ fn zirFunc( block, inst_data.src_node, inst, - ret_ty_body, + 0, + target_util.defaultAddressSpace(target, .function), + FuncLinkSection.default, cc, - Value.@"null", + ret_ty, false, inferred_error_set, false, has_body, src_locs, null, + 0, ); } +fn resolveGenericBody( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + body: []const Zir.Inst.Index, + func_inst: Zir.Inst.Index, + dest_ty: Type, +) !Value { + assert(body.len != 0); + + const err = err: { + // Make sure any nested param instructions don't clobber our work. + const prev_params = block.params; + block.params = .{}; + defer { + block.params.deinit(sema.gpa); + block.params = prev_params; + } + const uncasted = sema.resolveBody(block, body, func_inst) catch |err| break :err err; + const result = sema.coerce(block, dest_ty, uncasted, src) catch |err| break :err err; + const val = sema.resolveConstValue(block, src, result) catch |err| break :err err; + return val; + }; + switch (err) { + error.GenericPoison => { + if (dest_ty.tag() == .type) { + return Value.initTag(.generic_poison_type); + } else { + return Value.initTag(.generic_poison); + } + }, + else => |e| return e, + } +} + +/// Given a library name, examines if the library name should end up in +/// `link.File.Options.system_libs` table (for example, libc is always +/// specified via dedicated flag `link.File.Options.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 `Module`. +/// To deallocate, call `deinit` on the respective `Decl` (`ExternFn` or `Var`). +fn handleExternLibName( + sema: *Sema, + block: *Block, + src_loc: LazySrcLoc, + lib_name: []const u8, +) CompileError![:0]u8 { + blk: { + const mod = sema.mod; + const target = mod.getTarget(); + log.debug("extern fn symbol expected in lib '{s}'", .{lib_name}); + if (target_util.is_libc_lib_name(target, lib_name)) { + if (!mod.comp.bin_file.options.link_libc) { + return sema.fail( + block, + src_loc, + "dependency on libc must be explicitly specified in the build command", + .{}, + ); + } + mod.comp.bin_file.options.link_libc = true; + break :blk; + } + if (target_util.is_libcpp_lib_name(target, lib_name)) { + if (!mod.comp.bin_file.options.link_libcpp) { + return sema.fail( + block, + src_loc, + "dependency on libc++ must be explicitly specified in the build command", + .{}, + ); + } + mod.comp.bin_file.options.link_libcpp = true; + break :blk; + } + if (mem.eql(u8, lib_name, "unwind")) { + mod.comp.bin_file.options.link_libunwind = true; + break :blk; + } + if (!target.isWasm() and !mod.comp.bin_file.options.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 }, + ); + } + mod.comp.stage1AddLinkLib(lib_name) catch |err| { + return sema.fail(block, src_loc, "unable to add link lib '{s}': {s}", .{ + lib_name, @errorName(err), + }); + }; + } + return sema.gpa.dupeZ(u8, lib_name); +} + +const FuncLinkSection = union(enum) { + generic, + default, + explicit: [*:0]const u8, +}; + fn funcCommon( sema: *Sema, block: *Block, src_node_offset: i32, func_inst: Zir.Inst.Index, - ret_ty_body: []const Zir.Inst.Index, - cc: std.builtin.CallingConvention, - align_val: Value, + /// null means generic poison + alignment: ?u32, + /// null means generic poison + address_space: ?std.builtin.AddressSpace, + /// outer null means generic poison; inner null means default link section + section: FuncLinkSection, + /// null means generic poison + cc: ?std.builtin.CallingConvention, + /// this might be Type.generic_poison + bare_return_type: Type, var_args: bool, inferred_error_set: bool, is_extern: bool, has_body: bool, src_locs: Zir.Inst.Func.SrcLocs, opt_lib_name: ?[]const u8, + noalias_bits: u32, ) CompileError!Air.Inst.Ref { const ret_ty_src: LazySrcLoc = .{ .node_offset_fn_type_ret_ty = src_node_offset }; + const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = src_node_offset }; - // The return type body might be a type expression that depends on generic parameters. - // In such case we need to use a generic_poison value for the return type and mark - // the function as generic. - var is_generic = false; - const bare_return_type: Type = ret_ty: { - if (ret_ty_body.len == 0) break :ret_ty Type.void; - - const err = err: { - // Make sure any nested param instructions don't clobber our work. - const prev_params = block.params; - block.params = .{}; - defer { - block.params.deinit(sema.gpa); - block.params = prev_params; - } - if (sema.resolveBody(block, ret_ty_body, func_inst)) |ret_ty_inst| { - if (sema.analyzeAsType(block, ret_ty_src, ret_ty_inst)) |ret_ty| { - break :ret_ty ret_ty; - } else |err| break :err err; - } else |err| break :err err; - }; - switch (err) { - error.GenericPoison => { - // The type is not available until the generic instantiation. - is_generic = true; - break :ret_ty Type.initTag(.generic_poison); - }, - else => |e| return e, - } - }; - - const mod = sema.mod; + var is_generic = bare_return_type.tag() == .generic_poison or + alignment == null or + address_space == null or + section == .generic or + cc == null; + // Check for generic params. + for (block.params.items) |param| { + if (param.ty.tag() == .generic_poison) is_generic = true; + } const new_func: *Module.Fn = new_func: { if (!has_body) break :new_func undefined; @@ -5492,36 +7064,28 @@ fn funcCommon( errdefer if (maybe_inferred_error_set_node) |node| sema.gpa.destroy(node); // Note: no need to errdefer since this will still be in its default state at the end of the function. - const target = mod.getTarget(); - + const target = sema.mod.getTarget(); const fn_ty: Type = fn_ty: { - const alignment: u32 = if (align_val.tag() == .null_value) 0 else a: { - const alignment = @intCast(u32, align_val.toUnsignedInt()); - if (alignment == target_util.defaultFunctionAlignment(target)) { - break :a 0; - } else { - break :a alignment; - } - }; - // Hot path for some common function types. // TODO can we eliminate some of these Type tag values? seems unnecessarily complicated. - if (!is_generic and block.params.items.len == 0 and !var_args and - alignment == 0 and !inferred_error_set) + if (!is_generic and block.params.items.len == 0 and !var_args and !inferred_error_set and + alignment.? == 0 and + address_space.? == target_util.defaultAddressSpace(target, .function) and + section == .default) { - if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Unspecified) { + if (bare_return_type.zigTypeTag() == .NoReturn and cc.? == .Unspecified) { break :fn_ty Type.initTag(.fn_noreturn_no_args); } - if (bare_return_type.zigTypeTag() == .Void and cc == .Unspecified) { + if (bare_return_type.zigTypeTag() == .Void and cc.? == .Unspecified) { break :fn_ty Type.initTag(.fn_void_no_args); } - if (bare_return_type.zigTypeTag() == .NoReturn and cc == .Naked) { + if (bare_return_type.zigTypeTag() == .NoReturn and cc.? == .Naked) { break :fn_ty Type.initTag(.fn_naked_noreturn_no_args); } - if (bare_return_type.zigTypeTag() == .Void and cc == .C) { + if (bare_return_type.zigTypeTag() == .Void and cc.? == .C) { break :fn_ty Type.initTag(.fn_ccc_void_no_args); } } @@ -5529,17 +7093,31 @@ fn funcCommon( const param_types = try sema.arena.alloc(Type, block.params.items.len); const comptime_params = try sema.arena.alloc(bool, block.params.items.len); for (block.params.items) |param, i| { - const param_src: LazySrcLoc = .{ .node_offset = src_node_offset }; // TODO better src + const param_src = LazySrcLoc.nodeOffset(src_node_offset); // TODO better soruce location param_types[i] = param.ty; comptime_params[i] = param.is_comptime or try sema.typeRequiresComptime(block, param_src, param.ty); is_generic = is_generic or comptime_params[i] or param.ty.tag() == .generic_poison; + if (is_extern and is_generic) { + // TODO add note: function is generic because of this parameter + return sema.fail(block, param_src, "extern function cannot be generic", .{}); + } } - is_generic = is_generic or - try sema.typeRequiresComptime(block, ret_ty_src, bare_return_type); + const ret_poison = if (!is_generic) rp: { + if (sema.typeRequiresComptime(block, ret_ty_src, bare_return_type)) |ret_comptime| { + is_generic = ret_comptime; + break :rp bare_return_type.tag() == .generic_poison; + } else |err| switch (err) { + error.GenericPoison => { + is_generic = true; + break :rp true; + }, + else => |e| return e, + } + } else bare_return_type.tag() == .generic_poison; - const return_type = if (!inferred_error_set or bare_return_type.tag() == .generic_poison) + const return_type = if (!inferred_error_set or ret_poison) bare_return_type else blk: { const node = try sema.gpa.create(Module.Fn.InferredErrorSetListNode); @@ -5553,68 +7131,101 @@ fn funcCommon( }); }; + // These locals are pulled out from the init expression below to work around + // a stage1 compiler bug. + // In the case of generic calling convention, or generic alignment, we use + // default values which are only meaningful for the generic function, *not* + // the instantiation, which can depend on comptime parameters. + // Related proposal: https://github.com/ziglang/zig/issues/11834 + const cc_workaround = cc orelse .Unspecified; + const align_workaround = alignment orelse 0; + + const arch = sema.mod.getTarget().cpu.arch; + if (switch (cc_workaround) { + .Unspecified, .C, .Naked, .Async, .Inline => null, + .Interrupt => switch (arch) { + .i386, .x86_64, .avr, .msp430 => null, + else => @as([]const u8, "i386, x86_64, AVR, and MSP430"), + }, + .Signal => switch (arch) { + .avr => null, + else => @as([]const u8, "AVR"), + }, + .Stdcall, .Fastcall, .Thiscall => switch (arch) { + .i386 => null, + else => @as([]const u8, "i386"), + }, + .Vectorcall => switch (arch) { + .i386, .aarch64, .aarch64_be, .aarch64_32 => null, + else => @as([]const u8, "i386 and AArch64"), + }, + .APCS, .AAPCS, .AAPCSVFP => switch (arch) { + .arm, .armeb, .aarch64, .aarch64_be, .aarch64_32 => null, + else => @as([]const u8, "ARM"), + }, + .SysV, .Win64 => switch (arch) { + .x86_64 => null, + else => @as([]const u8, "x86_64"), + }, + .PtxKernel => switch (arch) { + .nvptx, .nvptx64 => null, + else => @as([]const u8, "nvptx and nvptx64"), + }, + }) |allowed_platform| { + return sema.fail(block, cc_src, "callconv '{s}' is only available on {s}, not {s}", .{ + @tagName(cc_workaround), + allowed_platform, + @tagName(arch), + }); + } + break :fn_ty try Type.Tag.function.create(sema.arena, .{ .param_types = param_types, .comptime_params = comptime_params.ptr, .return_type = return_type, - .cc = cc, - .alignment = alignment, + .cc = cc_workaround, + .cc_is_generic = cc == null, + .alignment = align_workaround, + .align_is_generic = alignment == null, + .section_is_generic = section == .generic, + .addrspace_is_generic = address_space == null, .is_var_args = var_args, .is_generic = is_generic, + .noalias_bits = noalias_bits, }); }; - if (opt_lib_name) |lib_name| blk: { - const lib_name_src: LazySrcLoc = .{ .node_offset_lib_name = src_node_offset }; - log.debug("extern fn symbol expected in lib '{s}'", .{lib_name}); - if (target_util.is_libc_lib_name(target, lib_name)) { - if (!mod.comp.bin_file.options.link_libc) { - return sema.fail( - block, - lib_name_src, - "dependency on libc must be explicitly specified in the build command", - .{}, - ); - } - mod.comp.bin_file.options.link_libc = true; - break :blk; - } - if (target_util.is_libcpp_lib_name(target, lib_name)) { - if (!mod.comp.bin_file.options.link_libcpp) { - return sema.fail( - block, - lib_name_src, - "dependency on libc++ must be explicitly specified in the build command", - .{}, - ); - } - mod.comp.bin_file.options.link_libcpp = true; - break :blk; - } - if (mem.eql(u8, lib_name, "unwind")) { - mod.comp.bin_file.options.link_libunwind = true; - break :blk; - } - if (!target.isWasm() and !mod.comp.bin_file.options.pic) { - return sema.fail( - block, - lib_name_src, - "dependency on dynamic library '{s}' requires enabling Position Independent Code. Fixed by `-l{s}` or `-fPIC`.", - .{ lib_name, lib_name }, - ); + if (sema.owner_decl.owns_tv) { + switch (section) { + .generic => sema.owner_decl.@"linksection" = undefined, + .default => sema.owner_decl.@"linksection" = null, + .explicit => |s| sema.owner_decl.@"linksection" = s, } - mod.comp.stage1AddLinkLib(lib_name) catch |err| { - return sema.fail(block, lib_name_src, "unable to add link lib '{s}': {s}", .{ - lib_name, @errorName(err), - }); - }; + if (alignment) |a| sema.owner_decl.@"align" = a; + if (address_space) |a| sema.owner_decl.@"addrspace" = a; } if (is_extern) { - return sema.addConstant( - fn_ty, - try Value.Tag.extern_fn.create(sema.arena, sema.owner_decl), - ); + const new_extern_fn = try sema.gpa.create(Module.ExternFn); + errdefer sema.gpa.destroy(new_extern_fn); + + new_extern_fn.* = Module.ExternFn{ + .owner_decl = sema.owner_decl_index, + .lib_name = null, + }; + + if (opt_lib_name) |lib_name| { + new_extern_fn.lib_name = try sema.handleExternLibName(block, .{ + .node_offset_lib_name = src_node_offset, + }, lib_name); + } + + const extern_fn_payload = try sema.arena.create(Value.Payload.ExternFn); + extern_fn_payload.* = .{ + .base = .{ .tag = .extern_fn }, + .data = new_extern_fn, + }; + return sema.addConstant(fn_ty, Value.initPayload(&extern_fn_payload.base)); } if (!has_body) { @@ -5628,16 +7239,26 @@ fn funcCommon( break :blk if (sema.comptime_args.len == 0) null else sema.comptime_args.ptr; } else null; + const param_names = try sema.gpa.alloc([:0]const u8, block.params.items.len); + for (param_names) |*param_name, i| { + param_name.* = try sema.gpa.dupeZ(u8, block.params.items[i].name); + } + + const hash = new_func.hash; const fn_payload = try sema.arena.create(Value.Payload.Function); new_func.* = .{ .state = anal_state, .zir_body_inst = func_inst, - .owner_decl = sema.owner_decl, + .owner_decl = sema.owner_decl_index, .comptime_args = comptime_args, + .anytype_args = undefined, + .hash = hash, .lbrace_line = src_locs.lbrace_line, .rbrace_line = src_locs.rbrace_line, .lbrace_column = @truncate(u16, src_locs.columns), .rbrace_column = @truncate(u16, src_locs.columns >> 16), + .param_names = param_names, + .branch_quota = default_branch_quota, }; if (maybe_inferred_error_set_node) |node| { new_func.inferred_error_sets.prepend(node); @@ -5662,24 +7283,32 @@ fn zirParam( const param_name = sema.code.nullTerminatedString(extra.data.name); const body = sema.code.extra[extra.end..][0..extra.data.body_len]; - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - _ = param_name; - // We could be in a generic function instantiation, or we could be evaluating a generic // function without any comptime args provided. const param_ty = param_ty: { const err = err: { // Make sure any nested param instructions don't clobber our work. const prev_params = block.params; + const prev_preallocated_new_func = sema.preallocated_new_func; block.params = .{}; + sema.preallocated_new_func = null; defer { block.params.deinit(sema.gpa); block.params = prev_params; + sema.preallocated_new_func = prev_preallocated_new_func; } if (sema.resolveBody(block, body, inst)) |param_ty_inst| { if (sema.analyzeAsType(block, src, param_ty_inst)) |param_ty| { + if (param_ty.zigTypeTag() == .Fn and param_ty.fnInfo().is_generic) { + // zirFunc will not emit error.GenericPoison to build a + // partial type for generic functions but we still need to + // detect if a function parameter is a generic function + // to force the parent function to also be generic. + if (!sema.inst_map.contains(inst)) { + break :err error.GenericPoison; + } + } break :param_ty param_ty; } else |err| break :err err; } else |err| break :err err; @@ -5692,6 +7321,7 @@ fn zirParam( try block.params.append(sema.gpa, .{ .ty = Type.initTag(.generic_poison), .is_comptime = comptime_syntax, + .name = param_name, }); try sema.inst_map.putNoClobber(sema.gpa, inst, .generic_poison); return; @@ -5714,9 +7344,21 @@ fn zirParam( assert(sema.inst_map.remove(inst)); } + if (sema.preallocated_new_func != null) { + if (try sema.typeHasOnePossibleValue(block, src, param_ty)) |opv| { + // In this case we are instantiating a generic function call with a non-comptime + // non-anytype parameter that ended up being a one-possible-type. + // We don't want the parameter to be part of the instantiated function type. + const result = try sema.addConstant(param_ty, opv); + try sema.inst_map.put(sema.gpa, inst, result); + return; + } + } + try block.params.append(sema.gpa, .{ .ty = param_ty, .is_comptime = is_comptime, + .name = param_name, }); const result = try sema.addConstant(param_ty, Value.initTag(.generic_poison)); try sema.inst_map.putNoClobber(sema.gpa, inst, result); @@ -5732,10 +7374,6 @@ fn zirParamAnytype( const src = inst_data.src(); const param_name = inst_data.get(sema.code); - // TODO check if param_name shadows a Decl. This only needs to be done if - // usingnamespace is implemented. - _ = param_name; - if (sema.inst_map.get(inst)) |air_ref| { const param_ty = sema.typeOf(air_ref); if (comptime_syntax or try sema.typeRequiresComptime(block, src, param_ty)) { @@ -5743,10 +7381,14 @@ fn zirParamAnytype( // function type of the function instruction in this block. return; } + if (null != try sema.typeHasOnePossibleValue(block, src, param_ty)) { + return; + } // The map is already populated but we do need to add a runtime parameter. try block.params.append(sema.gpa, .{ .ty = param_ty, .is_comptime = false, + .name = param_name, }); return; } @@ -5756,6 +7398,7 @@ fn zirParamAnytype( try block.params.append(sema.gpa, .{ .ty = Type.initTag(.generic_poison), .is_comptime = comptime_syntax, + .name = param_name, }); try sema.inst_map.put(sema.gpa, inst, .generic_poison); } @@ -5765,7 +7408,7 @@ fn zirAs(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst defer tracy.end(); const bin_inst = sema.code.instructions.items(.data)[inst].bin; - return sema.analyzeAs(block, .unneeded, bin_inst.lhs, bin_inst.rhs); + return sema.analyzeAs(block, sema.src, bin_inst.lhs, bin_inst.rhs); } fn zirAsNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -5786,7 +7429,8 @@ fn analyzeAs( zir_operand: Zir.Inst.Ref, ) CompileError!Air.Inst.Ref { const dest_ty = try sema.resolveType(block, src, zir_dest_type); - const operand = sema.resolveInst(zir_operand); + const operand = try sema.resolveInst(zir_operand); + if (dest_ty.tag() == .var_args_param) return operand; return sema.coerce(block, dest_ty, operand, src); } @@ -5796,10 +7440,10 @@ fn zirPtrToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const inst_data = sema.code.instructions.items(.data)[inst].un_node; const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const ptr = sema.resolveInst(inst_data.operand); + const ptr = try sema.resolveInst(inst_data.operand); const ptr_ty = sema.typeOf(ptr); if (!ptr_ty.isPtrAtRuntime()) { - return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ty}); + return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ty.fmt(sema.mod)}); } if (try sema.resolveMaybeUndefVal(block, ptr_src, ptr)) |ptr_val| { return sema.addConstant(Type.usize, ptr_val); @@ -5817,7 +7461,7 @@ fn zirFieldVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; const field_name = sema.code.nullTerminatedString(extra.field_name_start); - const object = sema.resolveInst(extra.lhs); + const object = try sema.resolveInst(extra.lhs); return sema.fieldVal(block, src, object, field_name, field_name_src); } @@ -5830,7 +7474,7 @@ fn zirFieldPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; const field_name = sema.code.nullTerminatedString(extra.field_name_start); - const object_ptr = sema.resolveInst(extra.lhs); + const object_ptr = try sema.resolveInst(extra.lhs); return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); } @@ -5843,7 +7487,7 @@ fn zirFieldCallBind(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Field, inst_data.payload_index).data; const field_name = sema.code.nullTerminatedString(extra.field_name_start); - const object_ptr = sema.resolveInst(extra.lhs); + const object_ptr = try sema.resolveInst(extra.lhs); return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src); } @@ -5855,7 +7499,7 @@ fn zirFieldValNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const src = inst_data.src(); const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; - const object = sema.resolveInst(extra.lhs); + const object = try sema.resolveInst(extra.lhs); const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name); return sema.fieldVal(block, src, object, field_name, field_name_src); } @@ -5868,20 +7512,19 @@ fn zirFieldPtrNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const src = inst_data.src(); const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; - const object_ptr = sema.resolveInst(extra.lhs); + const object_ptr = try sema.resolveInst(extra.lhs); const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name); return sema.fieldPtr(block, src, object_ptr, field_name, field_name_src); } -fn zirFieldCallBindNamed(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirFieldCallBindNamed(sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; - const extra = sema.code.extraData(Zir.Inst.FieldNamed, inst_data.payload_index).data; - const object_ptr = sema.resolveInst(extra.lhs); + const extra = sema.code.extraData(Zir.Inst.FieldNamedNode, extended.operand).data; + const src = LazySrcLoc.nodeOffset(extra.node); + const field_name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; + const object_ptr = try sema.resolveInst(extra.lhs); const field_name = try sema.resolveConstString(block, field_name_src, extra.field_name); return sema.fieldCallBind(block, src, object_ptr, field_name, field_name_src); } @@ -5891,25 +7534,146 @@ fn zirIntCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const operand = try sema.resolveInst(extra.rhs); - const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_ty); - _ = try sema.checkIntType(block, operand_src, sema.typeOf(operand)); + return sema.intCast(block, dest_ty, dest_ty_src, operand, operand_src, true); +} + +fn intCast( + sema: *Sema, + block: *Block, + dest_ty: Type, + dest_ty_src: LazySrcLoc, + operand: Air.Inst.Ref, + operand_src: LazySrcLoc, + runtime_safety: bool, +) CompileError!Air.Inst.Ref { + 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(block, operand_src, operand)) { return sema.coerce(block, dest_ty, operand, operand_src); - } else if (dest_is_comptime_int) { - return sema.fail(block, src, "unable to cast runtime value to 'comptime_int'", .{}); + } else if (dest_scalar_ty.zigTypeTag() == .ComptimeInt) { + 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() == .Vector; + + if ((try sema.typeHasOnePossibleValue(block, dest_ty_src, dest_ty))) |opv| { + // requirement: intCast(u0, input) iff input == 0 + if (runtime_safety and block.wantSafety()) { + try sema.requireRuntimeBlock(block, operand_src); + const target = sema.mod.getTarget(); + const wanted_info = dest_scalar_ty.intInfo(target); + const wanted_bits = wanted_info.bits; + + if (wanted_bits == 0) { + const zero_inst = try sema.addConstant(sema.typeOf(operand), Value.zero); + const is_in_range = try block.addBinOp(.cmp_eq, operand, zero_inst); + try sema.addSafetyCheck(block, is_in_range, .cast_truncated_data); + } + } + + return sema.addConstant(dest_ty, opv); } try sema.requireRuntimeBlock(block, operand_src); - // TODO insert safety check to make sure the value fits in the dest type + if (runtime_safety and block.wantSafety()) { + const target = sema.mod.getTarget(); + const actual_info = operand_scalar_ty.intInfo(target); + const wanted_info = dest_scalar_ty.intInfo(target); + const actual_bits = actual_info.bits; + const wanted_bits = wanted_info.bits; + const actual_value_bits = actual_bits - @boolToInt(actual_info.signedness == .signed); + const wanted_value_bits = wanted_bits - @boolToInt(wanted_info.signedness == .signed); + + // range shrinkage + // requirement: int value fits into target type + if (wanted_value_bits < actual_value_bits) { + const dest_max_val_scalar = try dest_scalar_ty.maxInt(sema.arena, target); + const dest_max_val = if (is_vector) + try Value.Tag.repeated.create(sema.arena, dest_max_val_scalar) + else + dest_max_val_scalar; + const dest_max = try sema.addConstant(operand_ty, dest_max_val); + const diff = try block.addBinOp(.subwrap, dest_max, operand); + + if (actual_info.signedness == .signed) { + // Reinterpret the sign-bit as part of the value. This will make + // negative differences (`operand` > `dest_max`) appear too big. + const unsigned_operand_ty = try Type.Tag.int_unsigned.create(sema.arena, actual_bits); + const diff_unsigned = try block.addBitCast(unsigned_operand_ty, diff); + + // If the destination type is signed, then we need to double its + // range to account for negative values. + const dest_range_val = if (wanted_info.signedness == .signed) range_val: { + const range_minus_one = try dest_max_val.shl(Value.one, unsigned_operand_ty, sema.arena, target); + break :range_val try sema.intAdd(block, operand_src, range_minus_one, Value.one, unsigned_operand_ty); + } else dest_max_val; + const dest_range = try sema.addConstant(unsigned_operand_ty, dest_range_val); + + const ok = if (is_vector) ok: { + const is_in_range = try block.addCmpVector(diff_unsigned, dest_range, .lte, try sema.addType(operand_ty)); + const all_in_range = try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = is_in_range, + .operation = .And, + } }, + }); + break :ok all_in_range; + } else ok: { + const is_in_range = try block.addBinOp(.cmp_lte, diff_unsigned, dest_range); + break :ok is_in_range; + }; + try sema.addSafetyCheck(block, ok, .cast_truncated_data); + } else { + const ok = if (is_vector) ok: { + const is_in_range = try block.addCmpVector(diff, dest_max, .lte, try sema.addType(operand_ty)); + const all_in_range = try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = is_in_range, + .operation = .And, + } }, + }); + break :ok all_in_range; + } else ok: { + const is_in_range = try block.addBinOp(.cmp_lte, diff, dest_max); + break :ok is_in_range; + }; + try sema.addSafetyCheck(block, ok, .cast_truncated_data); + } + } else if (actual_info.signedness == .signed and wanted_info.signedness == .unsigned) { + // no shrinkage, yes sign loss + // requirement: signed to unsigned >= 0 + const ok = if (is_vector) ok: { + const zero_val = try Value.Tag.repeated.create(sema.arena, Value.zero); + const zero_inst = try sema.addConstant(operand_ty, zero_val); + const is_in_range = try block.addCmpVector(operand, zero_inst, .lte, try sema.addType(operand_ty)); + const all_in_range = try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = is_in_range, + .operation = .And, + } }, + }); + break :ok all_in_range; + } else ok: { + const zero_inst = try sema.addConstant(operand_ty, Value.zero); + const is_in_range = try block.addBinOp(.cmp_gte, operand, zero_inst); + break :ok is_in_range; + }; + try sema.addSafetyCheck(block, ok, .cast_truncated_data); + } + } return block.addTyOp(.intcast, dest_ty, operand); } @@ -5923,7 +7687,61 @@ fn zirBitcast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const operand = try sema.resolveInst(extra.rhs); + switch (dest_ty.zigTypeTag()) { + .AnyFrame, + .ComptimeFloat, + .ComptimeInt, + .EnumLiteral, + .ErrorSet, + .ErrorUnion, + .Fn, + .Frame, + .NoReturn, + .Null, + .Opaque, + .Optional, + .Type, + .Undefined, + .Void, + => return sema.fail(block, dest_ty_src, "cannot @bitCast to '{}'", .{dest_ty.fmt(sema.mod)}), + + .Enum => { + const msg = msg: { + const msg = try sema.errMsg(block, dest_ty_src, "cannot @bitCast to '{}'", .{dest_ty.fmt(sema.mod)}); + errdefer msg.destroy(sema.gpa); + switch (sema.typeOf(operand).zigTypeTag()) { + .Int, .ComptimeInt => try sema.errNote(block, dest_ty_src, msg, "use @intToEnum for type coercion", .{}), + else => {}, + } + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + }, + + .Pointer => return sema.fail(block, dest_ty_src, "cannot @bitCast to '{}', use @ptrCast to cast to a pointer", .{ + dest_ty.fmt(sema.mod), + }), + .Struct, .Union => if (dest_ty.containerLayout() == .Auto) { + const container = switch (dest_ty.zigTypeTag()) { + .Struct => "struct", + .Union => "union", + else => unreachable, + }; + return sema.fail(block, dest_ty_src, "cannot @bitCast to '{}', {s} does not have a guaranteed in-memory layout", .{ + dest_ty.fmt(sema.mod), container, + }); + }, + .BoundFn => @panic("TODO remove this type from the language and compiler"), + + .Array, + .Bool, + .Float, + .Int, + .Vector, + => {}, + } return sema.bitCast(block, dest_ty, operand, operand_src); } @@ -5932,14 +7750,14 @@ fn zirFloatCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const operand = try sema.resolveInst(extra.rhs); + const target = sema.mod.getTarget(); const dest_is_comptime_float = switch (dest_ty.zigTypeTag()) { .ComptimeFloat => true, .Float => false, @@ -5947,7 +7765,7 @@ fn zirFloatCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A block, dest_ty_src, "expected float type, found '{}'", - .{dest_ty}, + .{dest_ty.fmt(sema.mod)}, ), }; @@ -5958,17 +7776,16 @@ fn zirFloatCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A block, operand_src, "expected float type, found '{}'", - .{operand_ty}, + .{operand_ty.fmt(sema.mod)}, ), } - if (try sema.isComptimeKnown(block, operand_src, operand)) { - return sema.coerce(block, dest_ty, operand, operand_src); + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |operand_val| { + return sema.addConstant(dest_ty, try operand_val.floatCast(sema.arena, dest_ty, target)); } if (dest_is_comptime_float) { - return sema.fail(block, src, "unable to cast runtime value to 'comptime_float'", .{}); + return sema.fail(block, operand_src, "unable to cast runtime value to 'comptime_float'", .{}); } - const target = sema.mod.getTarget(); const src_bits = operand_ty.floatBits(target); const dst_bits = dest_ty.floatBits(target); if (dst_bits >= src_bits) { @@ -5982,10 +7799,12 @@ fn zirElemVal(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const tracy = trace(@src()); defer tracy.end(); - const bin_inst = sema.code.instructions.items(.data)[inst].bin; - const array = sema.resolveInst(bin_inst.lhs); - const elem_index = sema.resolveInst(bin_inst.rhs); - return sema.elemVal(block, sema.src, array, elem_index, sema.src); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + 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); } fn zirElemValNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -5996,8 +7815,8 @@ fn zirElemValNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const src = inst_data.src(); const elem_index_src: LazySrcLoc = .{ .node_offset_array_access_index = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const array = sema.resolveInst(extra.lhs); - const elem_index = sema.resolveInst(extra.rhs); + const array = try sema.resolveInst(extra.lhs); + const elem_index = try sema.resolveInst(extra.rhs); return sema.elemVal(block, src, array, elem_index, elem_index_src); } @@ -6005,10 +7824,12 @@ fn zirElemPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const tracy = trace(@src()); defer tracy.end(); - const bin_inst = sema.code.instructions.items(.data)[inst].bin; - const array_ptr = sema.resolveInst(bin_inst.lhs); - const elem_index = sema.resolveInst(bin_inst.rhs); - return sema.elemPtr(block, sema.src, array_ptr, elem_index, sema.src); + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + 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); + return sema.elemPtr(block, src, array_ptr, elem_index, src, false); } fn zirElemPtrNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6019,9 +7840,9 @@ fn zirElemPtrNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const src = inst_data.src(); const elem_index_src: LazySrcLoc = .{ .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 = sema.resolveInst(extra.lhs); - const elem_index = sema.resolveInst(extra.rhs); - return sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src); + const array_ptr = try sema.resolveInst(extra.lhs); + const elem_index = try sema.resolveInst(extra.rhs); + return sema.elemPtr(block, src, array_ptr, elem_index, elem_index_src, false); } fn zirElemPtrImm(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6031,9 +7852,9 @@ fn zirElemPtrImm(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.ElemPtrImm, inst_data.payload_index).data; - const array_ptr = sema.resolveInst(extra.ptr); + const array_ptr = try sema.resolveInst(extra.ptr); const elem_index = try sema.addIntUnsigned(Type.usize, extra.index); - return sema.elemPtr(block, src, array_ptr, elem_index, src); + return sema.elemPtr(block, src, array_ptr, elem_index, src, true); } fn zirSliceStart(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -6043,8 +7864,8 @@ fn zirSliceStart(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.SliceStart, inst_data.payload_index).data; - const array_ptr = sema.resolveInst(extra.lhs); - const start = sema.resolveInst(extra.start); + const array_ptr = try sema.resolveInst(extra.lhs); + const start = try sema.resolveInst(extra.start); return sema.analyzeSlice(block, src, array_ptr, start, .none, .none, .unneeded); } @@ -6056,9 +7877,9 @@ fn zirSliceEnd(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); const extra = sema.code.extraData(Zir.Inst.SliceEnd, inst_data.payload_index).data; - const array_ptr = sema.resolveInst(extra.lhs); - const start = sema.resolveInst(extra.start); - const end = sema.resolveInst(extra.end); + const array_ptr = try sema.resolveInst(extra.lhs); + const start = try sema.resolveInst(extra.start); + const end = try sema.resolveInst(extra.end); return sema.analyzeSlice(block, src, array_ptr, start, end, .none, .unneeded); } @@ -6071,10 +7892,10 @@ fn zirSliceSentinel(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const src = inst_data.src(); const sentinel_src: LazySrcLoc = .{ .node_offset_slice_sentinel = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.SliceSentinel, inst_data.payload_index).data; - const array_ptr = sema.resolveInst(extra.lhs); - const start = sema.resolveInst(extra.start); - const end = sema.resolveInst(extra.end); - const sentinel = sema.resolveInst(extra.sentinel); + const array_ptr = try sema.resolveInst(extra.lhs); + const start = try sema.resolveInst(extra.start); + const end = try sema.resolveInst(extra.end); + const sentinel = try sema.resolveInst(extra.sentinel); return sema.analyzeSlice(block, src, array_ptr, start, end, sentinel, sentinel_src); } @@ -6098,55 +7919,78 @@ fn zirSwitchCapture( const operand_is_ref = switch_extra.data.bits.is_ref; const cond_inst = Zir.refToIndex(switch_extra.data.operand).?; const cond_info = sema.code.instructions.items(.data)[cond_inst].un_node; - const operand_ptr = sema.resolveInst(cond_info.operand); + const operand_ptr = try sema.resolveInst(cond_info.operand); const operand_ptr_ty = sema.typeOf(operand_ptr); const operand_ty = if (operand_is_ref) operand_ptr_ty.childType() else operand_ptr_ty; + const operand = if (operand_is_ref) + try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src) + else + operand_ptr; + if (capture_info.prong_index == std.math.maxInt(@TypeOf(capture_info.prong_index))) { // It is the else/`_` prong. - switch (operand_ty.zigTypeTag()) { - .ErrorSet => { - return sema.fail(block, operand_src, "TODO implement Sema for zirSwitchCaptureElse for error sets", .{}); - }, - else => {}, - } if (is_ref) { assert(operand_is_ref); return operand_ptr; } - const operand = if (operand_is_ref) - try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src) - else - operand_ptr; - - return operand; + switch (operand_ty.zigTypeTag()) { + .ErrorSet => if (block.switch_else_err_ty) |some| { + return sema.bitCast(block, some, operand, operand_src); + } else { + try block.addUnreachable(operand_src, false); + return Air.Inst.Ref.unreachable_value; + }, + else => return operand, + } } - if (is_multi) { - return sema.fail(block, switch_src, "TODO implement Sema for switch capture multi", .{}); - } - const scalar_prong = switch_extra.data.getScalarProng(sema.code, switch_extra.end, capture_info.prong_index); - const item = sema.resolveInst(scalar_prong.item); - // Previous switch validation ensured this will succeed - const item_val = sema.resolveConstValue(block, .unneeded, item) catch unreachable; + const items = if (is_multi) + switch_extra.data.getMultiProng(sema.code, switch_extra.end, capture_info.prong_index).items + else + &[_]Zir.Inst.Ref{ + switch_extra.data.getScalarProng(sema.code, switch_extra.end, capture_info.prong_index).item, + }; switch (operand_ty.zigTypeTag()) { .Union => { const union_obj = operand_ty.cast(Type.Payload.Union).?.data; const enum_ty = union_obj.tag_ty; - const field_index_usize = enum_ty.enumTagFieldIndex(item_val).?; - const field_index = @intCast(u32, field_index_usize); - const field = union_obj.fields.values()[field_index]; + const first_item = try sema.resolveInst(items[0]); + // Previous switch validation ensured this will succeed + const first_item_val = sema.resolveConstValue(block, .unneeded, first_item) catch unreachable; + + const first_field_index = @intCast(u32, enum_ty.enumTagFieldIndex(first_item_val, sema.mod).?); + const first_field = union_obj.fields.values()[first_field_index]; + + for (items[1..]) |item| { + const item_ref = try sema.resolveInst(item); + // Previous switch validation ensured this will succeed + const item_val = sema.resolveConstValue(block, .unneeded, item_ref) catch unreachable; - // TODO handle multiple union tags which have compatible types + const field_index = enum_ty.enumTagFieldIndex(item_val, sema.mod).?; + const field = union_obj.fields.values()[field_index]; + if (!field.ty.eql(first_field.ty, sema.mod)) { + const first_item_src = switch_src; // TODO better source location + const item_src = switch_src; + const msg = msg: { + const msg = try sema.errMsg(block, switch_src, "capture group with incompatible types", .{}); + errdefer msg.destroy(sema.gpa); + try sema.errNote(block, first_item_src, msg, "type '{}' here", .{first_field.ty.fmt(sema.mod)}); + try sema.errNote(block, item_src, msg, "type '{}' here", .{field.ty.fmt(sema.mod)}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + } if (is_ref) { assert(operand_is_ref); - const field_ty_ptr = try Type.ptr(sema.arena, .{ - .pointee_type = field.ty, + const field_ty_ptr = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = first_field.ty, .@"addrspace" = .generic, .mutable = operand_ptr_ty.ptrIsMutable(), }); @@ -6156,35 +8000,60 @@ fn zirSwitchCapture( field_ty_ptr, try Value.Tag.field_ptr.create(sema.arena, .{ .container_ptr = op_ptr_val, - .field_index = field_index, + .container_ty = operand_ty, + .field_index = first_field_index, }), ); } try sema.requireRuntimeBlock(block, operand_src); - return block.addStructFieldPtr(operand_ptr, field_index, field_ty_ptr); + return block.addStructFieldPtr(operand_ptr, first_field_index, field_ty_ptr); } - const operand = if (operand_is_ref) - try sema.analyzeLoad(block, operand_src, operand_ptr, operand_src) - else - operand_ptr; - if (try sema.resolveDefinedValue(block, operand_src, operand)) |operand_val| { return sema.addConstant( - field.ty, + first_field.ty, operand_val.castTag(.@"union").?.data.val, ); } try sema.requireRuntimeBlock(block, operand_src); - return block.addStructFieldVal(operand, field_index, field.ty); + return block.addStructFieldVal(operand, first_field_index, first_field.ty); }, .ErrorSet => { - return sema.fail(block, operand_src, "TODO implement Sema for zirSwitchCapture for error sets", .{}); + if (is_multi) { + var names: Module.ErrorSet.NameMap = .{}; + try names.ensureUnusedCapacity(sema.arena, items.len); + for (items) |item| { + const item_ref = try sema.resolveInst(item); + // Previous switch validation ensured this will succeed + const item_val = sema.resolveConstValue(block, .unneeded, item_ref) catch unreachable; + names.putAssumeCapacityNoClobber( + item_val.getError().?, + {}, + ); + } + // names must be sorted + Module.ErrorSet.sortNames(&names); + const else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names); + + return sema.bitCast(block, else_error_ty, operand, operand_src); + } else { + const item_ref = try sema.resolveInst(items[0]); + // Previous switch validation ensured this will succeed + const item_val = sema.resolveConstValue(block, .unneeded, item_ref) catch unreachable; + + const item_ty = try Type.Tag.error_set_single.create(sema.arena, item_val.getError().?); + return sema.bitCast(block, item_ty, operand, operand_src); + } }, else => { - return sema.fail(block, operand_src, "switch on type '{}' provides no capture value", .{ - operand_ty, - }); + // In this case the capture value is just the passed-through value of the + // switch condition. + if (is_ref) { + assert(operand_is_ref); + return operand_ptr; + } else { + return operand; + } }, } } @@ -6198,7 +8067,7 @@ fn zirSwitchCond( const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src = src; // TODO make this point at the switch operand - const operand_ptr = sema.resolveInst(inst_data.operand); + const operand_ptr = try sema.resolveInst(inst_data.operand); const operand = if (is_ref) try sema.analyzeLoad(block, src, operand_ptr, operand_src) else @@ -6234,7 +8103,7 @@ fn zirSwitchCond( try sema.addDeclaredHereNote(msg, union_ty); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }; return sema.unionToTag(block, enum_ty, operand, src); }, @@ -6251,7 +8120,7 @@ fn zirSwitchCond( .Vector, .Frame, .AnyFrame, - => return sema.fail(block, src, "switch on type '{}'", .{operand_ty}), + => return sema.fail(block, src, "switch on type '{}'", .{operand_ty.fmt(sema.mod)}), } } @@ -6269,7 +8138,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const special_prong_src: LazySrcLoc = .{ .node_offset_switch_special_prong = src_node_offset }; const extra = sema.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index); - const operand = sema.resolveInst(extra.data.operand); + const operand = try sema.resolveInst(extra.data.operand); var header_extra_index: usize = extra.end; @@ -6295,6 +8164,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const operand_ty = sema.typeOf(operand); + var else_error_ty: ?Type = null; + // Validate usage of '_' prongs. if (special_prong == .under and !operand_ty.isNonexhaustiveEnum()) { const msg = msg: { @@ -6314,9 +8185,11 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } + const target = sema.mod.getTarget(); + // Validate for duplicate items, missing else prong, and invalid range. switch (operand_ty.zigTypeTag()) { .Enum => { @@ -6399,14 +8272,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError ); } try sema.mod.errNoteNonLazy( - operand_ty.declSrcLoc(), + operand_ty.declSrcLoc(sema.mod), msg, "enum '{}' declared here", - .{operand_ty}, + .{operand_ty.fmt(sema.mod)}, ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } }, .under => { @@ -6476,6 +8349,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } } + try sema.resolveInferredErrorSetTy(block, src, operand_ty); + if (operand_ty.isAnyError()) { if (special_prong != .@"else") { return sema.fail( @@ -6485,7 +8360,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .{}, ); } - } else { + else_error_ty = Type.@"anyerror"; + } else else_validation: { var maybe_msg: ?*Module.ErrorMsg = null; errdefer if (maybe_msg) |msg| msg.destroy(sema.gpa); @@ -6505,23 +8381,40 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError block, src, msg, - "unhandled error value: error.{s}", + "unhandled error value: 'error.{s}'", .{error_name}, ); } } if (maybe_msg) |msg| { - try sema.mod.errNoteNonLazy( - operand_ty.declSrcLoc(), - msg, - "error set '{}' declared here", - .{operand_ty}, - ); - return sema.failWithOwnedErrorMsg(msg); + maybe_msg = null; + try sema.addDeclaredHereNote(msg, operand_ty); + return sema.failWithOwnedErrorMsg(block, msg); } - if (special_prong == .@"else") { + if (special_prong == .@"else" and seen_errors.count() == operand_ty.errorSetNames().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); + for (special.body) |else_inst| switch (tags[else_inst]) { + .dbg_block_begin, + .dbg_block_end, + .dbg_stmt, + .dbg_var_val, + .switch_capture, + .ret_type, + .as_node, + .ret_node, + .@"unreachable", + => {}, + else => break, + } else break :else_validation; + return sema.fail( block, special_prong_src, @@ -6529,11 +8422,24 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .{}, ); } + + const error_names = operand_ty.errorSetNames(); + var names: Module.ErrorSet.NameMap = .{}; + try names.ensureUnusedCapacity(sema.arena, error_names.len); + for (error_names) |error_name| { + if (seen_errors.contains(error_name)) continue; + + names.putAssumeCapacityNoClobber(error_name, {}); + } + + // names must be sorted + Module.ErrorSet.sortNames(&names); + else_error_ty = try Type.Tag.error_set_merged.create(sema.arena, names); } }, .Union => return sema.fail(block, src, "TODO validate switch .Union", .{}), .Int, .ComptimeInt => { - var range_set = RangeSet.init(gpa); + var range_set = RangeSet.init(gpa, sema.mod); defer range_set.deinit(); var extra_index: usize = special.end; @@ -6606,7 +8512,6 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError var arena = std.heap.ArenaAllocator.init(gpa); defer arena.deinit(); - const target = sema.mod.getTarget(); const min_int = try operand_ty.minInt(arena.allocator(), target); const max_int = try operand_ty.maxInt(arena.allocator(), target); if (try range_set.spans(min_int, max_int, operand_ty)) { @@ -6710,11 +8615,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError block, src, "else prong required when switching on type '{}'", - .{operand_ty}, + .{operand_ty.fmt(sema.mod)}, ); } - var seen_values = ValueSrcMap.initContext(gpa, .{ .ty = operand_ty }); + var seen_values = ValueSrcMap.initContext(gpa, .{ + .ty = operand_ty, + .mod = sema.mod, + }); defer seen_values.deinit(); var extra_index: usize = special.end; @@ -6778,7 +8686,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .ComptimeFloat, .Float, => return sema.fail(block, operand_src, "invalid switch operand type '{}'", .{ - operand_ty, + operand_ty.fmt(sema.mod), }), } @@ -6806,6 +8714,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError .label = &label, .inlining = block.inlining, .is_comptime = block.is_comptime, + .switch_else_err_ty = else_error_ty, }; const merges = &child_block.label.?.merges; defer child_block.instructions.deinit(gpa); @@ -6824,10 +8733,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; - const item = sema.resolveInst(item_ref); + const item = try sema.resolveInst(item_ref); // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item) catch unreachable; - if (operand_val.eql(item_val, operand_ty)) { + if (operand_val.eql(item_val, operand_ty, sema.mod)) { return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); } } @@ -6846,10 +8755,10 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const body = sema.code.extra[extra_index + 2 * ranges_len ..][0..body_len]; for (items) |item_ref| { - const item = sema.resolveInst(item_ref); + const item = try sema.resolveInst(item_ref); // Validation above ensured these will succeed. const item_val = sema.resolveConstValue(&child_block, .unneeded, item) catch unreachable; - if (operand_val.eql(item_val, operand_ty)) { + if (operand_val.eql(item_val, operand_ty, sema.mod)) { return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); } } @@ -6864,8 +8773,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError // Validation above ensured these will succeed. const first_tv = sema.resolveInstConst(&child_block, .unneeded, item_first) catch unreachable; const last_tv = sema.resolveInstConst(&child_block, .unneeded, item_last) catch unreachable; - if (Value.compare(operand_val, .gte, first_tv.val, operand_ty) and - Value.compare(operand_val, .lte, last_tv.val, operand_ty)) + if ((try sema.compare(block, src, operand_val, .gte, first_tv.val, operand_ty)) and + (try sema.compare(block, src, operand_val, .lte, last_tv.val, operand_ty))) { return sema.resolveBlockBody(block, src, &child_block, body, inst, merges); } @@ -6878,6 +8787,9 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError } if (scalar_cases_len + multi_cases_len == 0) { + if (special_prong == .none) { + return sema.fail(block, src, "switch must handle all possibilities", .{}); + } return sema.resolveBlockBody(block, src, &child_block, special.body, inst, merges); } @@ -6891,7 +8803,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError var case_block = child_block.makeSubBlock(); case_block.runtime_loop = null; case_block.runtime_cond = operand_src; - case_block.runtime_index += 1; + case_block.runtime_index.increment(); defer case_block.instructions.deinit(gpa); var extra_index: usize = special.end; @@ -6911,10 +8823,21 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError case_block.instructions.shrinkRetainingCapacity(0); case_block.wip_capture_scope = wip_captures.scope; - const item = sema.resolveInst(item_ref); + const item = try sema.resolveInst(item_ref); // `item` is already guaranteed to be constant known. - _ = try sema.analyzeBody(&case_block, body); + _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&case_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; try wip_captures.finalize(); @@ -6957,7 +8880,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; - _ = try sema.analyzeBody(&case_block, body); + _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&case_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len + case_block.instructions.items.len); @@ -6966,14 +8900,14 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError cases_extra.appendAssumeCapacity(@intCast(u32, case_block.instructions.items.len)); for (items) |item_ref| { - const item = sema.resolveInst(item_ref); + const item = try sema.resolveInst(item_ref); cases_extra.appendAssumeCapacity(@enumToInt(item)); } cases_extra.appendSliceAssumeCapacity(case_block.instructions.items); } else { for (items) |item_ref| { - const item = sema.resolveInst(item_ref); + const item = try sema.resolveInst(item_ref); const cmp_ok = try case_block.addBinOp(.cmp_eq, operand, item); if (any_ok != .none) { any_ok = try case_block.addBinOp(.bool_or, any_ok, cmp_ok); @@ -6989,8 +8923,8 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const last_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const item_first = sema.resolveInst(first_ref); - const item_last = sema.resolveInst(last_ref); + const item_first = try sema.resolveInst(first_ref); + const item_last = try sema.resolveInst(last_ref); // operand >= first and operand <= last const range_first_ok = try case_block.addBinOp( @@ -7032,7 +8966,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const body = sema.code.extra[extra_index..][0..body_len]; extra_index += body_len; - _ = try sema.analyzeBody(&case_block, body); + _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&case_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; try wip_captures.finalize(); @@ -7054,6 +8999,7 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError sema.air_extra.appendSliceAssumeCapacity(prev_then_body); sema.air_extra.appendSliceAssumeCapacity(cond_body); } + gpa.free(prev_then_body); prev_then_body = case_block.instructions.toOwnedSlice(gpa); prev_cond_br = new_cond_br; } @@ -7068,7 +9014,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError case_block.wip_capture_scope = wip_captures.scope; if (special.body.len != 0) { - _ = try sema.analyzeBody(&case_block, special.body); + _ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&case_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; } else { // We still need a terminator in this block, but we have proven // that it is unreachable. @@ -7120,7 +9077,7 @@ fn resolveSwitchItemVal( switch_prong_src: Module.SwitchProngSrc, range_expand: Module.SwitchProngSrc.RangeExpand, ) CompileError!TypedValue { - const item = sema.resolveInst(item_ref); + const item = try sema.resolveInst(item_ref); const item_ty = sema.typeOf(item); // 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 @@ -7129,7 +9086,7 @@ fn resolveSwitchItemVal( return TypedValue{ .ty = item_ty, .val = val }; } else |err| switch (err) { error.NeededSourceLocation => { - const src = switch_prong_src.resolve(sema.gpa, block.src_decl, switch_node_offset, range_expand); + const src = switch_prong_src.resolve(sema.gpa, sema.mod.declPtr(block.src_decl), switch_node_offset, range_expand); return TypedValue{ .ty = item_ty, .val = try sema.resolveConstValue(block, src, item), @@ -7151,6 +9108,10 @@ fn validateSwitchRange( ) CompileError!void { const first_val = (try sema.resolveSwitchItemVal(block, first_ref, src_node_offset, switch_prong_src, .first)).val; const last_val = (try sema.resolveSwitchItemVal(block, last_ref, src_node_offset, switch_prong_src, .last)).val; + if (first_val.compare(.gt, last_val, operand_ty, sema.mod)) { + const src = switch_prong_src.resolve(sema.gpa, sema.mod.declPtr(block.src_decl), src_node_offset, .first); + return sema.fail(block, src, "range start value is greater than the end value", .{}); + } const maybe_prev_src = try range_set.add(first_val, last_val, operand_ty, switch_prong_src); return sema.validateSwitchDupe(block, maybe_prev_src, switch_prong_src, src_node_offset); } @@ -7178,25 +9139,25 @@ fn validateSwitchItemEnum( switch_prong_src: Module.SwitchProngSrc, ) CompileError!void { const item_tv = try sema.resolveSwitchItemVal(block, item_ref, src_node_offset, switch_prong_src, .none); - const field_index = item_tv.ty.enumTagFieldIndex(item_tv.val) orelse { + const field_index = item_tv.ty.enumTagFieldIndex(item_tv.val, sema.mod) orelse { const msg = msg: { - const src = switch_prong_src.resolve(sema.gpa, block.src_decl, src_node_offset, .none); + const src = switch_prong_src.resolve(sema.gpa, sema.mod.declPtr(block.src_decl), src_node_offset, .none); const msg = try sema.errMsg( block, src, "enum '{}' has no tag with value '{}'", - .{ item_tv.ty, item_tv.val }, + .{ item_tv.ty.fmt(sema.mod), item_tv.val.fmtValue(item_tv.ty, sema.mod) }, ); errdefer msg.destroy(sema.gpa); try sema.mod.errNoteNonLazy( - item_tv.ty.declSrcLoc(), + item_tv.ty.declSrcLoc(sema.mod), msg, "enum declared here", .{}, ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }; const maybe_prev_src = seen_fields[field_index]; seen_fields[field_index] = switch_prong_src; @@ -7230,8 +9191,9 @@ fn validateSwitchDupe( ) CompileError!void { const prev_prong_src = maybe_prev_src orelse return; const gpa = sema.gpa; - const src = switch_prong_src.resolve(gpa, block.src_decl, src_node_offset, .none); - const prev_src = prev_prong_src.resolve(gpa, block.src_decl, src_node_offset, .none); + const block_src_decl = sema.mod.declPtr(block.src_decl); + const src = switch_prong_src.resolve(gpa, block_src_decl, src_node_offset, .none); + const prev_src = prev_prong_src.resolve(gpa, block_src_decl, src_node_offset, .none); const msg = msg: { const msg = try sema.errMsg( block, @@ -7249,7 +9211,7 @@ fn validateSwitchDupe( ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } fn validateSwitchItemBool( @@ -7268,7 +9230,8 @@ fn validateSwitchItemBool( false_count.* += 1; } if (true_count.* + false_count.* > 2) { - const src = switch_prong_src.resolve(sema.gpa, block.src_decl, src_node_offset, .none); + const block_src_decl = sema.mod.declPtr(block.src_decl); + const src = switch_prong_src.resolve(sema.gpa, block_src_decl, src_node_offset, .none); return sema.fail(block, src, "duplicate switch value", .{}); } } @@ -7306,7 +9269,7 @@ fn validateSwitchNoRange( block, operand_src, "ranges not allowed when switching on type '{}'", - .{operand_ty}, + .{operand_ty.fmt(sema.mod)}, ); errdefer msg.destroy(sema.gpa); try sema.errNote( @@ -7318,7 +9281,7 @@ fn validateSwitchNoRange( ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -7336,13 +9299,22 @@ fn zirHasField(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai if (mem.eql(u8, field_name, "len")) break :hf true; break :hf false; } + if (ty.castTag(.anon_struct)) |pl| { + break :hf for (pl.data.names) |name| { + if (mem.eql(u8, name, field_name)) break true; + } else false; + } + if (ty.isTuple()) { + const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch break :hf false; + break :hf field_index < ty.structFieldCount(); + } break :hf switch (ty.zigTypeTag()) { .Struct => ty.structFields().contains(field_name), .Union => ty.unionFields().contains(field_name), .Enum => ty.enumFields().contains(field_name), .Array => mem.eql(u8, field_name, "len"), else => return sema.fail(block, ty_src, "type '{}' does not support '@hasField'", .{ - ty, + ty.fmt(sema.mod), }), }; }; @@ -7362,13 +9334,11 @@ fn zirHasDecl(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const container_type = try sema.resolveType(block, lhs_src, extra.lhs); const decl_name = try sema.resolveConstString(block, rhs_src, extra.rhs); - const namespace = container_type.getNamespace() orelse return sema.fail( - block, - lhs_src, - "expected struct, enum, union, or opaque, found '{}'", - .{container_type}, - ); - if (try sema.lookupInNamespace(block, src, namespace, decl_name, true)) |decl| { + try checkNamespaceType(sema, block, lhs_src, container_type); + + const namespace = container_type.getNamespace() orelse return Air.Inst.Ref.bool_false; + if (try sema.lookupInNamespace(block, src, namespace, decl_name, true)) |decl_index| { + const decl = sema.mod.declPtr(decl_index); if (decl.is_pub or decl.getFileScope() == block.getFileScope()) { return Air.Inst.Ref.bool_true; } @@ -7396,8 +9366,9 @@ fn zirImport(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. }, }; try mod.semaFile(result.file); - const file_root_decl = result.file.root_decl.?; - try mod.declareDeclDependency(sema.owner_decl, file_root_decl); + const file_root_decl_index = result.file.root_decl.unwrap().?; + const file_root_decl = mod.declPtr(file_root_decl_index); + try mod.declareDeclDependency(sema.owner_decl_index, file_root_decl_index); return sema.addConstant(file_root_decl.ty, file_root_decl.val); } @@ -7435,15 +9406,23 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A embed_file.owner_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), embed_file.bytes.len), try Value.Tag.bytes.create(anon_decl.arena(), bytes_including_null), + 0, // default alignment ); return sema.analyzeDeclRef(embed_file.owner_decl); } -fn zirRetErrValueCode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - _ = block; - _ = inst; - return sema.fail(block, sema.src, "TODO implement zirRetErrValueCode", .{}); +fn zirRetErrValueCode(sema: *Sema, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].str_tok; + const err_name = inst_data.get(sema.code); + + // Return the error code from the function. + const kv = try sema.mod.getErrorValue(err_name); + const result_inst = try sema.addConstant( + try Type.Tag.error_set_single.create(sema.arena, kv.key), + try Value.Tag.@"error".create(sema.arena, .{ .name = kv.key }), + ); + return result_inst; } fn zirShl( @@ -7456,50 +9435,85 @@ fn zirShl( defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + 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 target = sema.mod.getTarget(); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + + const scalar_ty = lhs_ty.scalarType(); + const scalar_rhs_ty = rhs_ty.scalarType(); // TODO coerce rhs if air_tag is not shl_sat + const rhs_is_comptime_int = try sema.checkIntType(block, rhs_src, scalar_rhs_ty); const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); - const runtime_src = if (maybe_lhs_val) |lhs_val| rs: { - const lhs_ty = sema.typeOf(lhs); + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.addConstUndef(sema.typeOf(lhs)); + } + // If rhs is 0, return lhs without doing any calculations. + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return lhs; + } + if (scalar_ty.zigTypeTag() != .ComptimeInt and air_tag != .shl_sat) { + var bits_payload = Value.Payload.U64{ + .base = .{ .tag = .int_u64 }, + .data = scalar_ty.intInfo(target).bits, + }; + const bit_value = Value.initPayload(&bits_payload.base); + if (rhs_ty.zigTypeTag() == .Vector) { + var i: usize = 0; + while (i < rhs_ty.vectorLen()) : (i += 1) { + if (rhs_val.indexVectorlike(i).compareHetero(.gte, bit_value, target)) { + return sema.fail(block, rhs_src, "shift amount '{}' at index '{d}' is too large for operand type '{}'", .{ + rhs_val.indexVectorlike(i).fmtValue(scalar_ty, sema.mod), + i, + scalar_ty.fmt(sema.mod), + }); + } + } + } else if (rhs_val.compareHetero(.gte, bit_value, target)) { + return sema.fail(block, rhs_src, "shift amount '{}' is too large for operand type '{}'", .{ + rhs_val.fmtValue(scalar_ty, sema.mod), + scalar_ty.fmt(sema.mod), + }); + } + } + } + const runtime_src = if (maybe_lhs_val) |lhs_val| rs: { if (lhs_val.isUndef()) return sema.addConstUndef(lhs_ty); const rhs_val = maybe_rhs_val orelse break :rs rhs_src; - if (rhs_val.isUndef()) return sema.addConstUndef(lhs_ty); - // If rhs is 0, return lhs without doing any calculations. - if (rhs_val.compareWithZero(.eq)) { - return sema.addConstant(lhs_ty, lhs_val); - } - const target = sema.mod.getTarget(); const val = switch (air_tag) { .shl_exact => val: { - const shifted = try lhs_val.shl(rhs_val, sema.arena); - if (lhs_ty.zigTypeTag() == .ComptimeInt) { + const shifted = try lhs_val.shl(rhs_val, lhs_ty, sema.arena, target); + if (scalar_ty.zigTypeTag() == .ComptimeInt) { break :val shifted; } - const int_info = lhs_ty.intInfo(target); - const truncated = try shifted.intTrunc(sema.arena, int_info.signedness, int_info.bits); - if (truncated.compareHetero(.eq, shifted)) { + const int_info = scalar_ty.intInfo(target); + const truncated = try shifted.intTrunc(lhs_ty, sema.arena, int_info.signedness, int_info.bits, target); + if (try sema.compare(block, src, truncated, .eq, shifted, lhs_ty)) { break :val shifted; } return sema.addConstUndef(lhs_ty); }, - .shl_sat => if (lhs_ty.zigTypeTag() == .ComptimeInt) - try lhs_val.shl(rhs_val, sema.arena) + .shl_sat => if (scalar_ty.zigTypeTag() == .ComptimeInt) + try lhs_val.shl(rhs_val, lhs_ty, sema.arena, target) else try lhs_val.shlSat(rhs_val, lhs_ty, sema.arena, target), - .shl => if (lhs_ty.zigTypeTag() == .ComptimeInt) - try lhs_val.shl(rhs_val, sema.arena) + .shl => if (scalar_ty.zigTypeTag() == .ComptimeInt) + try lhs_val.shl(rhs_val, lhs_ty, sema.arena, target) else try lhs_val.shlTrunc(rhs_val, lhs_ty, sema.arena, target), @@ -7507,17 +9521,61 @@ fn zirShl( }; return sema.addConstant(lhs_ty, val); - } else rs: { - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) return sema.addConstUndef(sema.typeOf(lhs)); - } - break :rs lhs_src; - }; + } else lhs_src; - // TODO: insert runtime safety check for shl_exact + const new_rhs = if (air_tag == .shl_sat) rhs: { + // Limit the RHS type for saturating shl to be an integer as small as the LHS. + if (rhs_is_comptime_int or + scalar_rhs_ty.intInfo(target).bits > scalar_ty.intInfo(target).bits) + { + const max_int = try sema.addConstant( + lhs_ty, + try lhs_ty.maxInt(sema.arena, target), + ); + const rhs_limited = try sema.analyzeMinMax(block, rhs_src, rhs, max_int, .min, rhs_src, rhs_src); + break :rhs try sema.intCast(block, lhs_ty, rhs_src, rhs_limited, rhs_src, false); + } else { + break :rhs rhs; + } + } else rhs; try sema.requireRuntimeBlock(block, runtime_src); - return block.addBinOp(air_tag, lhs, rhs); + if (block.wantSafety()) { + const maybe_op_ov: ?Air.Inst.Tag = switch (air_tag) { + .shl_exact => .shl_with_overflow, + else => null, + }; + if (maybe_op_ov) |op_ov_tag| { + const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(lhs_ty); + const op_ov = try block.addInst(.{ + .tag = op_ov_tag, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(op_ov_tuple_ty), + .payload = try sema.addExtra(Air.Bin{ + .lhs = lhs, + .rhs = rhs, + }), + } }, + }); + const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); + const any_ov_bit = if (lhs_ty.zigTypeTag() == .Vector) + try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = ov_bit, + .operation = .Or, + } }, + }) + else + ov_bit; + const zero_ov = try sema.addConstant(Type.@"u1", Value.zero); + const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov); + + try sema.addSafetyCheck(block, no_ov, .shl_overflow); + return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); + } + } + return block.addBinOp(air_tag, lhs, new_rhs); } fn zirShr( @@ -7530,39 +9588,64 @@ fn zirShr( defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + 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 target = sema.mod.getTarget(); + const scalar_ty = lhs_ty.scalarType(); const runtime_src = if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| rs: { + if (rhs_val.isUndef()) { + return sema.addConstUndef(lhs_ty); + } + // If rhs is 0, return lhs without doing any calculations. + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return lhs; + } + if (scalar_ty.zigTypeTag() != .ComptimeInt) { + var bits_payload = Value.Payload.U64{ + .base = .{ .tag = .int_u64 }, + .data = scalar_ty.intInfo(target).bits, + }; + const bit_value = Value.initPayload(&bits_payload.base); + if (rhs_ty.zigTypeTag() == .Vector) { + var i: usize = 0; + while (i < rhs_ty.vectorLen()) : (i += 1) { + if (rhs_val.indexVectorlike(i).compareHetero(.gte, bit_value, target)) { + return sema.fail(block, rhs_src, "shift amount '{}' at index '{d}' is too large for operand type '{}'", .{ + rhs_val.indexVectorlike(i).fmtValue(scalar_ty, sema.mod), + i, + scalar_ty.fmt(sema.mod), + }); + } + } + } else if (rhs_val.compareHetero(.gte, bit_value, target)) { + return sema.fail(block, rhs_src, "shift amount '{}' is too large for operand type '{}'", .{ + rhs_val.fmtValue(scalar_ty, sema.mod), + scalar_ty.fmt(sema.mod), + }); + } + } if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { - const lhs_ty = sema.typeOf(lhs); - if (lhs_val.isUndef() or rhs_val.isUndef()) { + if (lhs_val.isUndef()) { return sema.addConstUndef(lhs_ty); } - // If rhs is 0, return lhs without doing any calculations. - if (rhs_val.compareWithZero(.eq)) { - return sema.addConstant(lhs_ty, lhs_val); - } if (air_tag == .shr_exact) { // Detect if any ones would be shifted out. - const bits = @intCast(u16, rhs_val.toUnsignedInt()); - const truncated = try lhs_val.intTrunc(sema.arena, .unsigned, bits); - if (!truncated.compareWithZero(.eq)) { + const truncated = try lhs_val.intTruncBitsAsValue(lhs_ty, sema.arena, .unsigned, rhs_val, target); + if (!(try truncated.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { return sema.addConstUndef(lhs_ty); } } - const val = try lhs_val.shr(rhs_val, sema.arena); + const val = try lhs_val.shr(rhs_val, lhs_ty, sema.arena, target); return sema.addConstant(lhs_ty, val); } else { - // Even if lhs is not comptime known, we can still deduce certain things based - // on rhs. - // If rhs is 0, return lhs without doing any calculations. - if (rhs_val.compareWithZero(.eq)) { - return lhs; - } break :rs lhs_src; } } else rhs_src; @@ -7585,57 +9668,48 @@ fn zirBitwise( const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + 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 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 = if (resolved_type.zigTypeTag() == .Vector) - resolved_type.elemType() - else - resolved_type; - + const scalar_type = resolved_type.scalarType(); const scalar_tag = scalar_type.zigTypeTag(); - if (lhs_ty.zigTypeTag() == .Vector and rhs_ty.zigTypeTag() == .Vector) { - if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) { - return sema.fail(block, src, "vector length mismatch: {d} and {d}", .{ - lhs_ty.arrayLen(), - rhs_ty.arrayLen(), - }); - } - return sema.fail(block, src, "TODO implement support for vectors in zirBitwise", .{}); - } else if (lhs_ty.zigTypeTag() == .Vector or rhs_ty.zigTypeTag() == .Vector) { - return sema.fail(block, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{ - lhs_ty, - rhs_ty, - }); - } + 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 = scalar_tag == .Int or scalar_tag == .ComptimeInt; + const target = sema.mod.getTarget(); if (!is_int) { return sema.fail(block, src, "invalid operands to binary bitwise expression: '{s}' and '{s}'", .{ @tagName(lhs_ty.zigTypeTag()), @tagName(rhs_ty.zigTypeTag()) }); } - if (try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs)) |lhs_val| { - if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { - const result_val = switch (air_tag) { - .bit_and => try lhs_val.bitwiseAnd(rhs_val, sema.arena), - .bit_or => try lhs_val.bitwiseOr(rhs_val, sema.arena), - .xor => try lhs_val.bitwiseXor(rhs_val, sema.arena), - else => unreachable, - }; - return sema.addConstant(scalar_type, result_val); + 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.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs)) |rhs_val| { + const result_val = switch (air_tag) { + .bit_and => try lhs_val.bitwiseAnd(rhs_val, resolved_type, sema.arena, target), + .bit_or => try lhs_val.bitwiseOr(rhs_val, resolved_type, sema.arena, target), + .xor => try lhs_val.bitwiseXor(rhs_val, resolved_type, sema.arena, target), + else => unreachable, + }; + return sema.addConstant(resolved_type, result_val); + } else { + break :runtime rhs_src; + } + } else { + break :runtime lhs_src; } - } + }; - try sema.requireRuntimeBlock(block, src); + try sema.requireRuntimeBlock(block, runtime_src); return block.addBinOp(air_tag, casted_lhs, casted_rhs); } @@ -7647,33 +9721,35 @@ fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const src = inst_data.src(); const operand_src = src; // TODO put this on the operand, not the '~' - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_type = sema.typeOf(operand); const scalar_type = operand_type.scalarType(); + const target = sema.mod.getTarget(); if (scalar_type.zigTypeTag() != .Int) { - return sema.fail(block, src, "unable to perform binary not operation on type '{}'", .{operand_type}); + return sema.fail(block, src, "unable to perform binary not operation on type '{}'", .{ + operand_type.fmt(sema.mod), + }); } if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { - const target = sema.mod.getTarget(); if (val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(operand_type); } else if (operand_type.zigTypeTag() == .Vector) { - const vec_len = try sema.usizeCast(block, operand_src, operand_type.arrayLen()); + const vec_len = try sema.usizeCast(block, operand_src, operand_type.vectorLen()); var elem_val_buf: Value.ElemValueBuffer = undefined; const elems = try sema.arena.alloc(Value, vec_len); for (elems) |*elem, i| { - const elem_val = val.elemValueBuffer(i, &elem_val_buf); + const elem_val = val.elemValueBuffer(sema.mod, i, &elem_val_buf); elem.* = try elem_val.bitwiseNot(scalar_type, sema.arena, target); } return sema.addConstant( operand_type, - try Value.Tag.array.create(sema.arena, elems), + try Value.Tag.aggregate.create(sema.arena, elems), ); } else { - const result_val = try val.bitwiseNot(scalar_type, sema.arena, target); - return sema.addConstant(scalar_type, result_val); + const result_val = try val.bitwiseNot(operand_type, sema.arena, target); + return sema.addConstant(operand_type, result_val); } } @@ -7681,100 +9757,347 @@ fn zirBitNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. return block.addTyOp(.not, operand_type, operand); } +fn analyzeTupleCat( + sema: *Sema, + block: *Block, + src_node: i32, + lhs: Air.Inst.Ref, + rhs: Air.Inst.Ref, +) CompileError!Air.Inst.Ref { + const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node }; + + const lhs_tuple = lhs_ty.tupleFields(); + const rhs_tuple = rhs_ty.tupleFields(); + const dest_fields = lhs_tuple.types.len + rhs_tuple.types.len; + + if (dest_fields == 0) { + return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value)); + } + const final_len = try sema.usizeCast(block, rhs_src, dest_fields); + + const types = try sema.arena.alloc(Type, final_len); + const values = try sema.arena.alloc(Value, final_len); + + const opt_runtime_src = rs: { + var runtime_src: ?LazySrcLoc = null; + for (lhs_tuple.types) |ty, i| { + types[i] = ty; + values[i] = lhs_tuple.values[i]; + const operand_src = lhs_src; // TODO better source location + if (values[i].tag() == .unreachable_value) { + runtime_src = operand_src; + } + } + const offset = lhs_tuple.types.len; + for (rhs_tuple.types) |ty, i| { + types[i + offset] = ty; + values[i + offset] = rhs_tuple.values[i]; + const operand_src = rhs_src; // TODO better source location + if (rhs_tuple.values[i].tag() == .unreachable_value) { + runtime_src = operand_src; + } + } + break :rs runtime_src; + }; + + const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ + .types = types, + .values = values, + }); + + const runtime_src = opt_runtime_src orelse { + const tuple_val = try Value.Tag.aggregate.create(sema.arena, values); + return sema.addConstant(tuple_ty, tuple_val); + }; + + try sema.requireRuntimeBlock(block, runtime_src); + + const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len); + for (lhs_tuple.types) |_, i| { + const operand_src = lhs_src; // TODO better source location + element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, lhs, @intCast(u32, i), lhs_ty); + } + const offset = lhs_tuple.types.len; + for (rhs_tuple.types) |_, i| { + const operand_src = rhs_src; // TODO better source location + element_refs[i + offset] = + try sema.tupleFieldValByIndex(block, operand_src, rhs, @intCast(u32, 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 inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + 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 = inst_data.src(); + + if (lhs_ty.isTuple() and rhs_ty.isTuple()) { + return sema.analyzeTupleCat(block, inst_data.src_node, lhs, rhs); + } + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; - const lhs_info = getArrayCatInfo(lhs_ty) orelse - return sema.fail(block, lhs_src, "expected array, found '{}'", .{lhs_ty}); - const rhs_info = getArrayCatInfo(rhs_ty) orelse - return sema.fail(block, rhs_src, "expected array, found '{}'", .{rhs_ty}); - if (!lhs_info.elem_type.eql(rhs_info.elem_type)) { - return sema.fail(block, rhs_src, "expected array of type '{}', found '{}'", .{ lhs_info.elem_type, rhs_ty }); - } + const lhs_info = try sema.getArrayCatInfo(block, lhs_src, lhs); + const rhs_info = try sema.getArrayCatInfo(block, rhs_src, rhs); + + const resolved_elem_ty = t: { + var trash_block = block.makeSubBlock(); + trash_block.is_comptime = false; + 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. The type system - // will catch this if it is a problem. - var res_sent: ?Value = null; - if (rhs_info.sentinel != null and lhs_info.sentinel != null) { - if (rhs_info.sentinel.?.eql(lhs_info.sentinel.?, lhs_info.elem_type)) { - res_sent = lhs_info.sentinel.?; + // 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 = try sema.addConstant(lhs_info.elem_type, lhs_sent_val); + if (rhs_info.sentinel) |rhs_sent_val| { + const rhs_sent = try sema.addConstant(rhs_info.elem_type, rhs_sent_val); + 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.resolveConstValue(block, lhs_src, lhs_sent_casted); + const rhs_sent_casted_val = try sema.resolveConstValue(block, rhs_src, rhs_sent_casted); + if (try sema.valuesEqual(block, src, 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.resolveConstValue(block, lhs_src, lhs_sent_casted); + break :s lhs_sent_casted_val; + } + } else { + if (rhs_info.sentinel) |rhs_sent_val| { + const rhs_sent = try sema.addConstant(rhs_info.elem_type, rhs_sent_val); + const rhs_sent_casted = try sema.coerce(block, resolved_elem_ty, rhs_sent, rhs_src); + const rhs_sent_casted_val = try sema.resolveConstValue(block, rhs_src, rhs_sent_casted); + break :s rhs_sent_casted_val; + } else { + break :s null; + } } - } + }; - if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { + const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len); + const rhs_len = try sema.usizeCast(block, lhs_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 Type.array(sema.arena, result_len, res_sent_val, resolved_elem_ty, sema.mod); + const ptr_addrspace = p: { + if (lhs_ty.zigTypeTag() == .Pointer) break :p lhs_ty.ptrAddressSpace(); + if (rhs_ty.zigTypeTag() == .Pointer) break :p rhs_ty.ptrAddressSpace(); + break :p null; + }; + + const runtime_src = if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| rs: { if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| { - const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len); - const rhs_len = try sema.usizeCast(block, lhs_src, rhs_info.len); - const final_len = lhs_len + rhs_len; - const final_len_including_sent = final_len + @boolToInt(res_sent != null); - const is_pointer = lhs_ty.zigTypeTag() == .Pointer; - const lhs_sub_val = if (is_pointer) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val; - const rhs_sub_val = if (is_pointer) (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).? else rhs_val; - var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded); - defer anon_decl.deinit(); + const lhs_sub_val = if (lhs_ty.isSinglePointer()) + (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? + else + lhs_val; - const buf = try anon_decl.arena().alloc(Value, final_len_including_sent); - { - var i: usize = 0; - while (i < lhs_len) : (i += 1) { - const val = try lhs_sub_val.elemValue(sema.arena, i); - buf[i] = try val.copy(anon_decl.arena()); - } + const rhs_sub_val = if (rhs_ty.isSinglePointer()) + (try sema.pointerDeref(block, rhs_src, rhs_val, rhs_ty)).? + else + rhs_val; + + const final_len_including_sent = result_len + @boolToInt(res_sent_val != null); + const element_vals = try sema.arena.alloc(Value, final_len_including_sent); + var elem_i: usize = 0; + while (elem_i < lhs_len) : (elem_i += 1) { + element_vals[elem_i] = try lhs_sub_val.elemValue(sema.mod, sema.arena, elem_i); } - { - var i: usize = 0; - while (i < rhs_len) : (i += 1) { - const val = try rhs_sub_val.elemValue(sema.arena, i); - buf[lhs_len + i] = try val.copy(anon_decl.arena()); - } + while (elem_i < result_len) : (elem_i += 1) { + element_vals[elem_i] = try rhs_sub_val.elemValue(sema.mod, sema.arena, elem_i - lhs_len); } - const ty = if (res_sent) |rs| ty: { - buf[final_len] = try rs.copy(anon_decl.arena()); - break :ty try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ - .len = final_len, - .elem_type = try lhs_info.elem_type.copy(anon_decl.arena()), - .sentinel = try rs.copy(anon_decl.arena()), - }); - } else try Type.Tag.array.create(anon_decl.arena(), .{ - .len = final_len, - .elem_type = try lhs_info.elem_type.copy(anon_decl.arena()), - }); - const val = try Value.Tag.array.create(anon_decl.arena(), buf); - const decl = try anon_decl.finish(ty, val); - if (is_pointer) { - return sema.analyzeDeclRef(decl); - } else { - return sema.analyzeDeclVal(block, .unneeded, decl); + if (res_sent_val) |sent_val| { + element_vals[result_len] = sent_val; } - } else { - return sema.fail(block, lhs_src, "TODO runtime array_cat", .{}); + const val = try Value.Tag.aggregate.create(sema.arena, element_vals); + return sema.addConstantMaybeRef(block, src, result_ty, val, ptr_addrspace != null); + } else break :rs rhs_src; + } else lhs_src; + + try sema.requireRuntimeBlock(block, runtime_src); + + if (ptr_addrspace) |ptr_as| { + const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = result_ty, + .@"addrspace" = ptr_as, + }); + const alloc = try block.addTy(.alloc, alloc_ty); + const elem_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = resolved_elem_ty, + .@"addrspace" = ptr_as, + }); + + var elem_i: usize = 0; + while (elem_i < lhs_len) : (elem_i += 1) { + const elem_index = try sema.addIntUnsigned(Type.usize, elem_i); + const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty); + const init = try sema.elemVal(block, lhs_src, lhs, elem_index, src); + try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store); + } + while (elem_i < result_len) : (elem_i += 1) { + const elem_index = try sema.addIntUnsigned(Type.usize, elem_i); + const rhs_index = try sema.addIntUnsigned(Type.usize, elem_i - lhs_len); + const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty); + const init = try sema.elemVal(block, rhs_src, rhs, rhs_index, src); + try sema.storePtr2(block, src, elem_ptr, src, init, rhs_src, .store); + } + if (res_sent_val) |sent_val| { + const elem_index = try sema.addIntUnsigned(Type.usize, result_len); + const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty); + const init = try sema.addConstant(lhs_info.elem_type, sent_val); + 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); + { + var elem_i: usize = 0; + while (elem_i < lhs_len) : (elem_i += 1) { + const index = try sema.addIntUnsigned(Type.usize, elem_i); + const init = try sema.elemVal(block, lhs_src, lhs, index, src); + element_refs[elem_i] = try sema.coerce(block, resolved_elem_ty, init, lhs_src); + } + while (elem_i < result_len) : (elem_i += 1) { + const index = try sema.addIntUnsigned(Type.usize, elem_i - lhs_len); + const init = try sema.elemVal(block, rhs_src, rhs, index, src); + element_refs[elem_i] = try sema.coerce(block, resolved_elem_ty, init, rhs_src); } - } else { - return sema.fail(block, lhs_src, "TODO runtime array_cat", .{}); } + + return block.addAggregateInit(result_ty, element_refs); } -fn getArrayCatInfo(t: Type) ?Type.ArrayInfo { - return switch (t.zigTypeTag()) { - .Array => t.arrayInfo(), - .Pointer => blk: { - const ptrinfo = t.ptrInfo().data; - if (ptrinfo.pointee_type.zigTypeTag() != .Array) return null; - if (ptrinfo.size != .One) return null; - break :blk ptrinfo.pointee_type.arrayInfo(); +fn getArrayCatInfo(sema: *Sema, block: *Block, src: LazySrcLoc, operand: Air.Inst.Ref) !Type.ArrayInfo { + const operand_ty = sema.typeOf(operand); + switch (operand_ty.zigTypeTag()) { + .Array => return operand_ty.arrayInfo(), + .Pointer => { + const ptr_info = operand_ty.ptrInfo().data; + switch (ptr_info.size) { + // TODO: in the Many case here this should only work if the type + // has a sentinel, and this code should compute the length based + // on the sentinel value. + .Slice, .Many => { + const val = try sema.resolveConstValue(block, src, operand); + return Type.ArrayInfo{ + .elem_type = ptr_info.pointee_type, + .sentinel = ptr_info.sentinel, + .len = val.sliceLen(sema.mod), + }; + }, + .One => { + if (ptr_info.pointee_type.zigTypeTag() == .Array) { + return ptr_info.pointee_type.arrayInfo(); + } + }, + .C => {}, + } }, - else => null, + else => {}, + } + return sema.fail(block, src, "expected indexable; found '{}'", .{operand_ty.fmt(sema.mod)}); +} + +fn analyzeTupleMul( + sema: *Sema, + block: *Block, + src_node: i32, + operand: Air.Inst.Ref, + factor: u64, +) CompileError!Air.Inst.Ref { + const operand_ty = sema.typeOf(operand); + const operand_tuple = operand_ty.tupleFields(); + const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = src_node }; + const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = src_node }; + + const tuple_len = operand_tuple.types.len; + const final_len_u64 = std.math.mul(u64, tuple_len, factor) catch + return sema.fail(block, rhs_src, "operation results in overflow", .{}); + + if (final_len_u64 == 0) { + return sema.addConstant(Type.initTag(.empty_struct_literal), Value.initTag(.empty_struct_value)); + } + const final_len = try sema.usizeCast(block, rhs_src, final_len_u64); + + const types = try sema.arena.alloc(Type, final_len); + const values = try sema.arena.alloc(Value, final_len); + + const opt_runtime_src = rs: { + var runtime_src: ?LazySrcLoc = null; + for (operand_tuple.types) |ty, i| { + types[i] = ty; + values[i] = operand_tuple.values[i]; + const operand_src = lhs_src; // TODO better source location + if (values[i].tag() == .unreachable_value) { + runtime_src = operand_src; + } + } + var i: usize = 1; + while (i < factor) : (i += 1) { + mem.copy(Type, types[tuple_len * i ..], operand_tuple.types); + mem.copy(Value, values[tuple_len * i ..], operand_tuple.values); + } + break :rs runtime_src; + }; + + const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ + .types = types, + .values = values, + }); + + const runtime_src = opt_runtime_src orelse { + const tuple_val = try Value.Tag.aggregate.create(sema.arena, values); + return sema.addConstant(tuple_ty, tuple_val); }; + + try sema.requireRuntimeBlock(block, runtime_src); + + const element_refs = try sema.arena.alloc(Air.Inst.Ref, final_len); + for (operand_tuple.types) |_, i| { + const operand_src = lhs_src; // TODO better source location + element_refs[i] = try sema.tupleFieldValByIndex(block, operand_src, operand, @intCast(u32, i), operand_ty); + } + var i: usize = 1; + while (i < factor) : (i += 1) { + mem.copy(Air.Inst.Ref, element_refs[tuple_len * i ..], 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 { @@ -7783,91 +10106,171 @@ fn zirArrayMul(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const lhs = sema.resolveInst(extra.lhs); + const lhs = try sema.resolveInst(extra.lhs); const lhs_ty = sema.typeOf(lhs); const src: LazySrcLoc = inst_data.src(); const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; - // In `**` rhs has to be comptime-known, but lhs can be runtime-known + // In `**` rhs must be comptime-known, but lhs can be runtime-known const factor = try sema.resolveInt(block, rhs_src, extra.rhs, Type.usize); - const mulinfo = getArrayCatInfo(lhs_ty) orelse - return sema.fail(block, lhs_src, "expected array, found '{}'", .{lhs_ty}); - const final_len_u64 = std.math.mul(u64, mulinfo.len, factor) catch + if (lhs_ty.isTuple()) { + return sema.analyzeTupleMul(block, inst_data.src_node, lhs, factor); + } + + const lhs_info = try sema.getArrayCatInfo(block, lhs_src, lhs); + + 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); - if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { - const final_len = try sema.usizeCast(block, src, final_len_u64); - const final_len_including_sent = final_len + @boolToInt(mulinfo.sentinel != null); - const lhs_len = try sema.usizeCast(block, lhs_src, mulinfo.len); + const result_ty = try Type.array(sema.arena, result_len, lhs_info.sentinel, lhs_info.elem_type, sema.mod); - const lhs_sub_val = if (lhs_ty.zigTypeTag() == .Pointer) (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else lhs_val; + const ptr_addrspace = if (lhs_ty.zigTypeTag() == .Pointer) lhs_ty.ptrAddressSpace() else null; + const lhs_len = try sema.usizeCast(block, lhs_src, lhs_info.len); - var anon_decl = try block.startAnonDecl(src); - defer anon_decl.deinit(); + if (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| { + const final_len_including_sent = result_len + @boolToInt(lhs_info.sentinel != null); - const final_ty = if (mulinfo.sentinel) |sent| - try Type.Tag.array_sentinel.create(anon_decl.arena(), .{ - .len = final_len, - .elem_type = try mulinfo.elem_type.copy(anon_decl.arena()), - .sentinel = try sent.copy(anon_decl.arena()), - }) + const lhs_sub_val = if (lhs_ty.isSinglePointer()) + (try sema.pointerDeref(block, lhs_src, lhs_val, lhs_ty)).? else - try Type.Tag.array.create(anon_decl.arena(), .{ - .len = final_len, - .elem_type = try mulinfo.elem_type.copy(anon_decl.arena()), - }); - const buf = try anon_decl.arena().alloc(Value, final_len_including_sent); - - // Optimization for the common pattern of a single element repeated N times, such - // as zero-filling a byte array. - const val = if (lhs_len == 1) blk: { - const elem_val = try lhs_sub_val.elemValue(sema.arena, 0); - const copied_val = try elem_val.copy(anon_decl.arena()); - break :blk try Value.Tag.repeated.create(anon_decl.arena(), copied_val); - } else blk: { - // the actual loop - var i: usize = 0; - while (i < factor) : (i += 1) { - var j: usize = 0; - while (j < lhs_len) : (j += 1) { - const val = try lhs_sub_val.elemValue(sema.arena, j); - buf[lhs_len * i + j] = try val.copy(anon_decl.arena()); + 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) { + const elem_val = try lhs_sub_val.elemValue(sema.mod, sema.arena, 0); + break :v try Value.Tag.repeated.create(sema.arena, elem_val); + } + + const element_vals = try sema.arena.alloc(Value, final_len_including_sent); + 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(sema.mod, sema.arena, lhs_i); + element_vals[elem_i] = elem_val; + elem_i += 1; } } - if (mulinfo.sentinel) |sent| { - buf[final_len] = try sent.copy(anon_decl.arena()); + if (lhs_info.sentinel) |sent_val| { + element_vals[result_len] = sent_val; } - break :blk try Value.Tag.array.create(anon_decl.arena(), buf); + break :v try Value.Tag.aggregate.create(sema.arena, element_vals); }; - const decl = try anon_decl.finish(final_ty, val); - if (lhs_ty.zigTypeTag() == .Pointer) { - return sema.analyzeDeclRef(decl); - } else { - return sema.analyzeDeclVal(block, .unneeded, decl); + return sema.addConstantMaybeRef(block, src, result_ty, val, ptr_addrspace != null); + } + + try sema.requireRuntimeBlock(block, lhs_src); + + if (ptr_addrspace) |ptr_as| { + const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = result_ty, + .@"addrspace" = ptr_as, + }); + const alloc = try block.addTy(.alloc, alloc_ty); + const elem_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = lhs_info.elem_type, + .@"addrspace" = ptr_as, + }); + + 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_index = try sema.addIntUnsigned(Type.usize, elem_i); + elem_i += 1; + const lhs_index = try sema.addIntUnsigned(Type.usize, lhs_i); + const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty); + const init = try sema.elemVal(block, lhs_src, lhs, lhs_index, src); + try sema.storePtr2(block, src, elem_ptr, src, init, lhs_src, .store); + } + } + if (lhs_info.sentinel) |sent_val| { + const elem_index = try sema.addIntUnsigned(Type.usize, result_len); + const elem_ptr = try block.addPtrElemPtr(alloc, elem_index, elem_ptr_ty); + const init = try sema.addConstant(lhs_info.elem_type, sent_val); + 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); + var elem_i: usize = 0; + while (elem_i < result_len) { + var lhs_i: usize = 0; + while (lhs_i < lhs_len) : (lhs_i += 1) { + const lhs_index = try sema.addIntUnsigned(Type.usize, lhs_i); + const init = try sema.elemVal(block, lhs_src, lhs, lhs_index, src); + element_refs[elem_i] = init; + elem_i += 1; } } - return sema.fail(block, lhs_src, "TODO runtime array_mul", .{}); + + return block.addAggregateInit(result_ty, element_refs); } -fn zirNegate( - sema: *Sema, - block: *Block, - inst: Zir.Inst.Index, - tag_override: Zir.Inst.Tag, -) CompileError!Air.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); +fn zirNegate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); + const lhs_src = src; + const rhs_src = src; // TODO better source location + + const rhs = try sema.resolveInst(inst_data.operand); + const rhs_ty = sema.typeOf(rhs); + const rhs_scalar_ty = rhs_ty.scalarType(); + + if (rhs_scalar_ty.isUnsignedInt() or switch (rhs_scalar_ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => false, + else => true, + }) { + return sema.fail(block, src, "negation of type '{}'", .{rhs_ty.fmt(sema.mod)}); + } + + if (rhs_scalar_ty.isAnyFloat()) { + // We handle float negation here to ensure negative zero is represented in the bits. + if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { + if (rhs_val.isUndef()) return sema.addConstUndef(rhs_ty); + const target = sema.mod.getTarget(); + return sema.addConstant(rhs_ty, try rhs_val.floatNeg(rhs_ty, sema.arena, target)); + } + try sema.requireRuntimeBlock(block, rhs_src); + return block.addUnOp(.neg, rhs); + } + const lhs = if (rhs_ty.zigTypeTag() == .Vector) + try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, Value.zero)) + else + try sema.resolveInst(.zero); + + return sema.analyzeArithmetic(block, .sub, lhs, rhs, src, lhs_src, rhs_src); +} + +fn zirNegateWrap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const lhs_src = src; const rhs_src = src; // TODO better source location - const lhs = sema.resolveInst(.zero); - const rhs = sema.resolveInst(inst_data.operand); - return sema.analyzeArithmetic(block, tag_override, lhs, rhs, src, lhs_src, rhs_src); + const rhs = try sema.resolveInst(inst_data.operand); + const rhs_ty = sema.typeOf(rhs); + const rhs_scalar_ty = rhs_ty.scalarType(); + + switch (rhs_scalar_ty.zigTypeTag()) { + .Int, .ComptimeInt, .Float, .ComptimeFloat => {}, + else => return sema.fail(block, src, "negation of type '{}'", .{rhs_ty.fmt(sema.mod)}), + } + + const lhs = if (rhs_ty.zigTypeTag() == .Vector) + try sema.addConstant(rhs_ty, try Value.Tag.repeated.create(sema.arena, Value.zero)) + else + try sema.resolveInst(.zero); + + return sema.analyzeArithmetic(block, .subwrap, lhs, rhs, src, lhs_src, rhs_src); } fn zirArithmetic( @@ -7884,8 +10287,8 @@ fn zirArithmetic( const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); return sema.analyzeArithmetic(block, zir_tag, lhs, rhs, sema.src, lhs_src, rhs_src); } @@ -7900,31 +10303,40 @@ fn zirOverflowArithmetic( defer tracy.end(); const extra = sema.code.extraData(Zir.Inst.OverflowArithmetic, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; + const src = LazySrcLoc.nodeOffset(extra.node); const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = extra.node }; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); - const ptr = sema.resolveInst(extra.ptr); + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); + const ptr = try sema.resolveInst(extra.ptr); const lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + const mod = sema.mod; + const target = mod.getTarget(); // Note, the types of lhs/rhs (also for shifting)/ptr are already correct as ensured by astgen. + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); const dest_ty = lhs_ty; - if (dest_ty.zigTypeTag() != .Int) { - return sema.fail(block, src, "expected integer type, found '{}'", .{dest_ty}); + if (dest_ty.scalarType().zigTypeTag() != .Int) { + return sema.fail(block, src, "expected vector of integers or integer type, found '{}'", .{dest_ty.fmt(mod)}); } - const target = sema.mod.getTarget(); - const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, lhs); const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs); + const tuple_ty = try sema.overflowArithmeticTupleType(dest_ty); + const ov_ty = tuple_ty.tupleFields().types[1]; + // TODO: Remove and use `ov_ty` instead. + // This is a temporary type used until overflow arithmetic properly returns `u1` instead of `bool`. + const overflowed_ty = if (dest_ty.zigTypeTag() == .Vector) try Type.vector(sema.arena, dest_ty.vectorLen(), Type.@"bool") else Type.@"bool"; + const result: struct { - overflowed: enum { yes, no, undef }, + /// TODO: Rename to `overflow_bit` and make of type `u1`. + overflowed: Air.Inst.Ref, wrapped: Air.Inst.Ref, } = result: { switch (zir_tag) { @@ -7933,24 +10345,25 @@ fn zirOverflowArithmetic( // 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() and lhs_val.compareWithZero(.eq)) { - break :result .{ .overflowed = .no, .wrapped = rhs }; + if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; } } if (maybe_rhs_val) |rhs_val| { - if (!rhs_val.isUndef() and rhs_val.compareWithZero(.eq)) { - break :result .{ .overflowed = .no, .wrapped = lhs }; + if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } if (maybe_lhs_val) |lhs_val| { if (maybe_rhs_val) |rhs_val| { if (lhs_val.isUndef() or rhs_val.isUndef()) { - break :result .{ .overflowed = .undef, .wrapped = try sema.addConstUndef(dest_ty) }; + break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; } - const result = try lhs_val.intAddWithOverflow(rhs_val, dest_ty, sema.arena, target); - const inst = try sema.addConstant(dest_ty, result.wrapped_result); - break :result .{ .overflowed = if (result.overflowed) .yes else .no, .wrapped = inst }; + const result = try sema.intAddWithOverflow(block, src, lhs_val, rhs_val, dest_ty); + const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); + const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); + break :result .{ .overflowed = overflowed, .wrapped = wrapped }; } } }, @@ -7959,17 +10372,18 @@ fn zirOverflowArithmetic( // Otherwise, if either result is undefined, both results are undefined. if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { - break :result .{ .overflowed = .undef, .wrapped = try sema.addConstUndef(dest_ty) }; - } else if (rhs_val.compareWithZero(.eq)) { - break :result .{ .overflowed = .no, .wrapped = lhs }; + break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; + } else if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } else if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - break :result .{ .overflowed = .undef, .wrapped = try sema.addConstUndef(dest_ty) }; + break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; } - const result = try lhs_val.intSubWithOverflow(rhs_val, dest_ty, sema.arena, target); - const inst = try sema.addConstant(dest_ty, result.wrapped_result); - break :result .{ .overflowed = if (result.overflowed) .yes else .no, .wrapped = inst }; + const result = try sema.intSubWithOverflow(block, src, lhs_val, rhs_val, dest_ty); + const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); + const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); + break :result .{ .overflowed = overflowed, .wrapped = wrapped }; } } }, @@ -7979,20 +10393,20 @@ fn zirOverflowArithmetic( // Otherwise, if either of the arguments is undefined, both results are undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - break :result .{ .overflowed = .no, .wrapped = lhs }; - } else if (lhs_val.compare(.eq, Value.one, dest_ty)) { - break :result .{ .overflowed = .no, .wrapped = rhs }; + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; + } else if (try sema.compare(block, src, lhs_val, .eq, Value.one, dest_ty)) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; } } } if (maybe_rhs_val) |rhs_val| { if (!rhs_val.isUndef()) { - if (rhs_val.compareWithZero(.eq)) { - break :result .{ .overflowed = .no, .wrapped = rhs }; - } else if (rhs_val.compare(.eq, Value.one, dest_ty)) { - break :result .{ .overflowed = .no, .wrapped = lhs }; + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = rhs }; + } else if (try sema.compare(block, src, rhs_val, .eq, Value.one, dest_ty)) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } } @@ -8000,12 +10414,13 @@ fn zirOverflowArithmetic( if (maybe_lhs_val) |lhs_val| { if (maybe_rhs_val) |rhs_val| { if (lhs_val.isUndef() or rhs_val.isUndef()) { - break :result .{ .overflowed = .undef, .wrapped = try sema.addConstUndef(dest_ty) }; + break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; } const result = try lhs_val.intMulWithOverflow(rhs_val, dest_ty, sema.arena, target); - const inst = try sema.addConstant(dest_ty, result.wrapped_result); - break :result .{ .overflowed = if (result.overflowed) .yes else .no, .wrapped = inst }; + const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); + const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); + break :result .{ .overflowed = overflowed, .wrapped = wrapped }; } } }, @@ -8014,24 +10429,25 @@ fn zirOverflowArithmetic( // If rhs is zero, the result is lhs (even if undefined) and no overflow occurred. // Oterhwise if either of the arguments is undefined, both results are undefined. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { - break :result .{ .overflowed = .no, .wrapped = lhs }; + if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } if (maybe_rhs_val) |rhs_val| { - if (!rhs_val.isUndef() and rhs_val.compareWithZero(.eq)) { - break :result .{ .overflowed = .no, .wrapped = lhs }; + if (!rhs_val.isUndef() and (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { + break :result .{ .overflowed = try sema.addBool(overflowed_ty, false), .wrapped = lhs }; } } if (maybe_lhs_val) |lhs_val| { if (maybe_rhs_val) |rhs_val| { if (lhs_val.isUndef() or rhs_val.isUndef()) { - break :result .{ .overflowed = .undef, .wrapped = try sema.addConstUndef(dest_ty) }; + break :result .{ .overflowed = try sema.addConstUndef(overflowed_ty), .wrapped = try sema.addConstUndef(dest_ty) }; } const result = try lhs_val.shlWithOverflow(rhs_val, dest_ty, sema.arena, target); - const inst = try sema.addConstant(dest_ty, result.wrapped_result); - break :result .{ .overflowed = if (result.overflowed) .yes else .no, .wrapped = inst }; + const overflowed = try sema.addConstant(overflowed_ty, result.overflowed); + const wrapped = try sema.addConstant(dest_ty, result.wrapped_result); + break :result .{ .overflowed = overflowed, .wrapped = wrapped }; } } }, @@ -8047,25 +10463,52 @@ fn zirOverflowArithmetic( }; try sema.requireRuntimeBlock(block, src); - return block.addInst(.{ + + const tuple = try block.addInst(.{ .tag = air_tag, - .data = .{ .pl_op = .{ - .operand = ptr, - .payload = try sema.addExtra(Air.Bin{ + .data = .{ .ty_pl = .{ + .ty = try block.sema.addType(tuple_ty), + .payload = try block.sema.addExtra(Air.Bin{ .lhs = lhs, .rhs = rhs, }), } }, }); + + const wrapped = try sema.tupleFieldValByIndex(block, src, tuple, 0, tuple_ty); + try sema.storePtr2(block, src, ptr, ptr_src, wrapped, src, .store); + + const overflow_bit = try sema.tupleFieldValByIndex(block, src, tuple, 1, tuple_ty); + const zero_ov_val = if (dest_ty.zigTypeTag() == .Vector) try Value.Tag.repeated.create(sema.arena, Value.zero) else Value.zero; + const zero_ov = try sema.addConstant(ov_ty, zero_ov_val); + + const overflowed_inst = if (dest_ty.zigTypeTag() == .Vector) + block.addCmpVector(overflow_bit, .zero, .neq, try sema.addType(ov_ty)) + else + block.addBinOp(.cmp_neq, overflow_bit, zero_ov); + return overflowed_inst; }; try sema.storePtr2(block, src, ptr, ptr_src, result.wrapped, src, .store); + return result.overflowed; +} - return switch (result.overflowed) { - .yes => Air.Inst.Ref.bool_true, - .no => Air.Inst.Ref.bool_false, - .undef => try sema.addConstUndef(Type.bool), - }; +fn overflowArithmeticTupleType(sema: *Sema, ty: Type) !Type { + const ov_ty = if (ty.zigTypeTag() == .Vector) try Type.vector(sema.arena, ty.vectorLen(), Type.@"u1") else Type.@"u1"; + + const types = try sema.arena.alloc(Type, 2); + const values = try sema.arena.alloc(Value, 2); + const tuple_ty = try Type.Tag.tuple.create(sema.arena, .{ + .types = types, + .values = values, + }); + + types[0] = ty; + types[1] = ov_ty; + values[0] = Value.initTag(.unreachable_value); + values[1] = Value.initTag(.unreachable_value); + + return tuple_ty; } fn analyzeArithmetic( @@ -8083,18 +10526,8 @@ fn analyzeArithmetic( const rhs_ty = sema.typeOf(rhs); const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); - if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) { - if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) { - return sema.fail(block, src, "vector length mismatch: {d} and {d}", .{ - lhs_ty.arrayLen(), rhs_ty.arrayLen(), - }); - } - return sema.fail(block, src, "TODO implement support for vectors in Sema.analyzeArithmetic", .{}); - } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) { - return sema.fail(block, src, "mixed scalar and vector operands to binary expression: '{}' and '{}'", .{ - lhs_ty, rhs_ty, - }); - } + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + if (lhs_zig_ty_tag == .Pointer) switch (lhs_ty.ptrSize()) { .One, .Slice => {}, .Many, .C => { @@ -8117,15 +10550,13 @@ fn analyzeArithmetic( const resolved_type = try sema.resolvePeerTypes(block, src, instructions, .{ .override = &[_]LazySrcLoc{ 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 = if (resolved_type.zigTypeTag() == .Vector) - resolved_type.elemType() - else - resolved_type; - - const scalar_tag = scalar_type.zigTypeTag(); + const lhs_scalar_ty = lhs_ty.scalarType(); + const rhs_scalar_ty = rhs_ty.scalarType(); + const scalar_tag = resolved_type.scalarType().zigTypeTag(); const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; @@ -8136,9 +10567,10 @@ fn analyzeArithmetic( }); } - const target = sema.mod.getTarget(); - const maybe_lhs_val = try sema.resolveMaybeUndefVal(block, lhs_src, casted_lhs); - const maybe_rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs); + const mod = sema.mod; + const target = mod.getTarget(); + const maybe_lhs_val = try sema.resolveMaybeUndefValIntable(block, lhs_src, casted_lhs); + const maybe_rhs_val = try sema.resolveMaybeUndefValIntable(block, rhs_src, casted_rhs); const rs: struct { src: LazySrcLoc, air_tag: Air.Inst.Tag } = rs: { switch (zir_tag) { .add => { @@ -8150,7 +10582,7 @@ fn analyzeArithmetic( // overflow (max_int), causing illegal behavior. // For floats: either operand being undef makes the result undef. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { return casted_rhs; } } @@ -8159,10 +10591,10 @@ fn analyzeArithmetic( if (is_int) { return sema.failWithUseOfUndef(block, rhs_src); } else { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } @@ -8171,19 +10603,21 @@ fn analyzeArithmetic( if (is_int) { return sema.failWithUseOfUndef(block, lhs_src); } else { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } } if (maybe_rhs_val) |rhs_val| { if (is_int) { - return sema.addConstant( - scalar_type, - try lhs_val.intAdd(rhs_val, sema.arena), - ); + const sum = try sema.intAdd(block, src, lhs_val, rhs_val, resolved_type); + var vector_index: usize = undefined; + if (!(try sema.intFitsInType(block, src, sum, resolved_type, &vector_index))) { + return sema.failWithIntegerOverflow(block, src, resolved_type, sum, vector_index); + } + return sema.addConstant(resolved_type, sum); } else { return sema.addConstant( - scalar_type, - try lhs_val.floatAdd(rhs_val, scalar_type, sema.arena), + resolved_type, + try sema.floatAdd(lhs_val, rhs_val, resolved_type), ); } } else break :rs .{ .src = rhs_src, .air_tag = .add }; @@ -8194,21 +10628,21 @@ fn analyzeArithmetic( // If either of the operands are zero, the other operand is returned. // If either of the operands are undefined, the result is undefined. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { return casted_rhs; } } if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { return sema.addConstant( - scalar_type, - try lhs_val.numberAddWrap(rhs_val, scalar_type, sema.arena, target), + resolved_type, + try sema.numberAddWrap(block, src, lhs_val, rhs_val, resolved_type), ); } else break :rs .{ .src = lhs_src, .air_tag = .addwrap }; } else break :rs .{ .src = rhs_src, .air_tag = .addwrap }; @@ -8218,24 +10652,24 @@ fn analyzeArithmetic( // If either of the operands are zero, then the other operand is returned. // If either of the operands are undefined, the result is undefined. if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef() and lhs_val.compareWithZero(.eq)) { + if (!lhs_val.isUndef() and (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { return casted_rhs; } } if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { const val = if (scalar_tag == .ComptimeInt) - try lhs_val.intAdd(rhs_val, sema.arena) + try sema.intAdd(block, src, lhs_val, rhs_val, resolved_type) else - try lhs_val.intAddSat(rhs_val, scalar_type, sema.arena, target); + try lhs_val.intAddSat(rhs_val, resolved_type, sema.arena, target); - return sema.addConstant(scalar_type, val); + return sema.addConstant(resolved_type, val); } else break :rs .{ .src = lhs_src, .air_tag = .add_sat }; } else break :rs .{ .src = rhs_src, .air_tag = .add_sat }; }, @@ -8252,10 +10686,10 @@ fn analyzeArithmetic( if (is_int) { return sema.failWithUseOfUndef(block, rhs_src); } else { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } @@ -8264,19 +10698,21 @@ fn analyzeArithmetic( if (is_int) { return sema.failWithUseOfUndef(block, lhs_src); } else { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } } if (maybe_rhs_val) |rhs_val| { if (is_int) { - return sema.addConstant( - scalar_type, - try lhs_val.intSub(rhs_val, sema.arena), - ); + const diff = try sema.intSub(block, src, lhs_val, rhs_val, resolved_type); + var vector_index: usize = undefined; + if (!(try sema.intFitsInType(block, src, diff, resolved_type, &vector_index))) { + return sema.failWithIntegerOverflow(block, src, resolved_type, diff, vector_index); + } + return sema.addConstant(resolved_type, diff); } else { return sema.addConstant( - scalar_type, - try lhs_val.floatSub(rhs_val, scalar_type, sema.arena), + resolved_type, + try sema.floatSub(lhs_val, rhs_val, resolved_type), ); } } else break :rs .{ .src = rhs_src, .air_tag = .sub }; @@ -8288,20 +10724,20 @@ fn analyzeArithmetic( // If either of the operands are undefined, the result is undefined. if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } if (maybe_rhs_val) |rhs_val| { return sema.addConstant( - scalar_type, - try lhs_val.numberSubWrap(rhs_val, scalar_type, sema.arena, target), + resolved_type, + try sema.numberSubWrap(block, src, lhs_val, rhs_val, resolved_type), ); } else break :rs .{ .src = rhs_src, .air_tag = .subwrap }; } else break :rs .{ .src = lhs_src, .air_tag = .subwrap }; @@ -8312,23 +10748,23 @@ fn analyzeArithmetic( // If either of the operands are undefined, result is undefined. if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return casted_lhs; } } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } if (maybe_rhs_val) |rhs_val| { const val = if (scalar_tag == .ComptimeInt) - try lhs_val.intSub(rhs_val, sema.arena) + try sema.intSub(block, src, lhs_val, rhs_val, resolved_type) else - try lhs_val.intSubSat(rhs_val, scalar_type, sema.arena, target); + try lhs_val.intSubSat(rhs_val, resolved_type, sema.arena, target); - return sema.addConstant(scalar_type, val); + return sema.addConstant(resolved_type, val); } else break :rs .{ .src = rhs_src, .air_tag = .sub_sat }; } else break :rs .{ .src = lhs_src, .air_tag = .sub_sat }; }, @@ -8351,48 +10787,60 @@ fn analyzeArithmetic( // TODO: emit runtime safety for division by zero // // For floats: - // If the rhs is zero, compile error for division by zero. - // If the rhs is undefined, compile error because there is a possible - // value (zero) for which the division would be illegal behavior. + // If the rhs is zero: + // * comptime_float: compile error for division by zero. + // * other float type: + // * if the lhs is zero: QNaN + // * otherwise: +Inf or -Inf depending on lhs sign + // If the rhs is undefined: + // * comptime_float: compile error because there is a possible + // value (zero) for which the division would be illegal behavior. + // * other float type: result is undefined // If the lhs is undefined, result is undefined. - if (maybe_lhs_val) |lhs_val| { - if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + switch (scalar_tag) { + .Int, .ComptimeInt, .ComptimeFloat => { + if (maybe_lhs_val) |lhs_val| { + if (!lhs_val.isUndef()) { + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); + } + } } - } - } - if (maybe_rhs_val) |rhs_val| { - if (rhs_val.isUndef()) { - return sema.failWithUseOfUndef(block, rhs_src); - } - if (rhs_val.compareWithZero(.eq)) { - return sema.failWithDivideByZero(block, rhs_src); - } + if (maybe_rhs_val) |rhs_val| { + if (rhs_val.isUndef()) { + return sema.failWithUseOfUndef(block, rhs_src); + } + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.failWithDivideByZero(block, rhs_src); + } + } + }, + else => {}, } + if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - if (lhs_ty.isSignedInt() and rhs_ty.isSignedInt()) { + if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { if (maybe_rhs_val) |rhs_val| { - if (rhs_val.compare(.neq, Value.negative_one, scalar_type)) { - return sema.addConstUndef(scalar_type); + if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + return sema.addConstUndef(resolved_type); } } return sema.failWithUseOfUndef(block, rhs_src); } - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } if (maybe_rhs_val) |rhs_val| { if (is_int) { return sema.addConstant( - scalar_type, - try lhs_val.intDiv(rhs_val, sema.arena), + resolved_type, + try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), ); } else { return sema.addConstant( - scalar_type, - try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena), + resolved_type, + try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), ); } } else { @@ -8432,8 +10880,8 @@ fn analyzeArithmetic( // If the lhs is undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } } } @@ -8441,33 +10889,33 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - if (lhs_ty.isSignedInt() and rhs_ty.isSignedInt()) { + if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { if (maybe_rhs_val) |rhs_val| { - if (rhs_val.compare(.neq, Value.negative_one, scalar_type)) { - return sema.addConstUndef(scalar_type); + if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + return sema.addConstUndef(resolved_type); } } return sema.failWithUseOfUndef(block, rhs_src); } - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } if (maybe_rhs_val) |rhs_val| { if (is_int) { return sema.addConstant( - scalar_type, - try lhs_val.intDiv(rhs_val, sema.arena), + resolved_type, + try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), ); } else { return sema.addConstant( - scalar_type, - try lhs_val.floatDivTrunc(rhs_val, scalar_type, sema.arena), + resolved_type, + try lhs_val.floatDivTrunc(rhs_val, resolved_type, sema.arena, target), ); } } else break :rs .{ .src = rhs_src, .air_tag = .div_trunc }; @@ -8495,8 +10943,8 @@ fn analyzeArithmetic( // If the lhs is undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } } } @@ -8504,33 +10952,33 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - if (lhs_ty.isSignedInt() and rhs_ty.isSignedInt()) { + if (lhs_scalar_ty.isSignedInt() and rhs_scalar_ty.isSignedInt()) { if (maybe_rhs_val) |rhs_val| { - if (rhs_val.compare(.neq, Value.negative_one, scalar_type)) { - return sema.addConstUndef(scalar_type); + if (try sema.compare(block, src, rhs_val, .neq, Value.negative_one, resolved_type)) { + return sema.addConstUndef(resolved_type); } } return sema.failWithUseOfUndef(block, rhs_src); } - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } if (maybe_rhs_val) |rhs_val| { if (is_int) { return sema.addConstant( - scalar_type, - try lhs_val.intDivFloor(rhs_val, sema.arena), + resolved_type, + try lhs_val.intDivFloor(rhs_val, resolved_type, sema.arena, target), ); } else { return sema.addConstant( - scalar_type, - try lhs_val.floatDivFloor(rhs_val, scalar_type, sema.arena), + resolved_type, + try lhs_val.floatDivFloor(rhs_val, resolved_type, sema.arena, target), ); } } else break :rs .{ .src = rhs_src, .air_tag = .div_floor }; @@ -8557,8 +11005,8 @@ fn analyzeArithmetic( if (lhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } else { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } } } @@ -8566,7 +11014,7 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } } @@ -8575,14 +11023,14 @@ fn analyzeArithmetic( if (is_int) { // TODO: emit compile error if there is a remainder return sema.addConstant( - scalar_type, - try lhs_val.intDiv(rhs_val, sema.arena), + resolved_type, + try lhs_val.intDiv(rhs_val, resolved_type, sema.arena, target), ); } else { // TODO: emit compile error if there is a remainder return sema.addConstant( - scalar_type, - try lhs_val.floatDiv(rhs_val, scalar_type, sema.arena), + resolved_type, + try lhs_val.floatDiv(rhs_val, resolved_type, sema.arena, target), ); } } else break :rs .{ .src = rhs_src, .air_tag = .div_exact }; @@ -8599,10 +11047,10 @@ fn analyzeArithmetic( // For floats: either operand being undef makes the result undef. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } - if (lhs_val.compare(.eq, Value.one, scalar_type)) { + if (try sema.compare(block, src, lhs_val, .eq, Value.one, resolved_type)) { return casted_rhs; } } @@ -8612,13 +11060,13 @@ fn analyzeArithmetic( if (is_int) { return sema.failWithUseOfUndef(block, rhs_src); } else { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } } - if (rhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } - if (rhs_val.compare(.eq, Value.one, scalar_type)) { + if (try sema.compare(block, src, rhs_val, .eq, Value.one, resolved_type)) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { @@ -8626,18 +11074,20 @@ fn analyzeArithmetic( if (is_int) { return sema.failWithUseOfUndef(block, lhs_src); } else { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } } if (is_int) { - return sema.addConstant( - scalar_type, - try lhs_val.intMul(rhs_val, sema.arena), - ); + const product = try lhs_val.intMul(rhs_val, resolved_type, sema.arena, target); + var vector_index: usize = undefined; + if (!(try sema.intFitsInType(block, src, product, resolved_type, &vector_index))) { + return sema.failWithIntegerOverflow(block, src, resolved_type, product, vector_index); + } + return sema.addConstant(resolved_type, product); } else { return sema.addConstant( - scalar_type, - try lhs_val.floatMul(rhs_val, scalar_type, sema.arena), + resolved_type, + try lhs_val.floatMul(rhs_val, resolved_type, sema.arena, target), ); } } else break :rs .{ .src = lhs_src, .air_tag = .mul }; @@ -8650,31 +11100,31 @@ fn analyzeArithmetic( // If either of the operands are undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } - if (lhs_val.compare(.eq, Value.one, scalar_type)) { + if (try sema.compare(block, src, lhs_val, .eq, Value.one, resolved_type)) { return casted_rhs; } } } if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } - if (rhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } - if (rhs_val.compare(.eq, Value.one, scalar_type)) { + if (try sema.compare(block, src, rhs_val, .eq, Value.one, resolved_type)) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } return sema.addConstant( - scalar_type, - try lhs_val.numberMulWrap(rhs_val, scalar_type, sema.arena, target), + resolved_type, + try lhs_val.numberMulWrap(rhs_val, resolved_type, sema.arena, target), ); } else break :rs .{ .src = lhs_src, .air_tag = .mulwrap }; } else break :rs .{ .src = rhs_src, .air_tag = .mulwrap }; @@ -8686,35 +11136,35 @@ fn analyzeArithmetic( // If either of the operands are undefined, result is undefined. if (maybe_lhs_val) |lhs_val| { if (!lhs_val.isUndef()) { - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } - if (lhs_val.compare(.eq, Value.one, scalar_type)) { + if (try sema.compare(block, src, lhs_val, .eq, Value.one, resolved_type)) { return casted_rhs; } } } if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } - if (rhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } - if (rhs_val.compare(.eq, Value.one, scalar_type)) { + if (try sema.compare(block, src, rhs_val, .eq, Value.one, resolved_type)) { return casted_lhs; } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } const val = if (scalar_tag == .ComptimeInt) - try lhs_val.intMul(rhs_val, sema.arena) + try lhs_val.intMul(rhs_val, resolved_type, sema.arena, target) else - try lhs_val.intMulSat(rhs_val, scalar_type, sema.arena, target); + try lhs_val.intMulSat(rhs_val, resolved_type, sema.arena, target); - return sema.addConstant(scalar_type, val); + return sema.addConstant(resolved_type, val); } else break :rs .{ .src = lhs_src, .air_tag = .mul_sat }; } else break :rs .{ .src = rhs_src, .air_tag = .mul_sat }; }, @@ -8737,40 +11187,40 @@ fn analyzeArithmetic( if (lhs_val.isUndef()) { return sema.failWithUseOfUndef(block, lhs_src); } - if (lhs_val.compareWithZero(.eq)) { - return sema.addConstant(scalar_type, Value.zero); + if (try lhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { + return sema.addConstant(resolved_type, Value.zero); } - } else if (lhs_ty.isSignedInt()) { + } else if (lhs_scalar_ty.isSignedInt()) { return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); } if (maybe_rhs_val) |rhs_val| { if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } if (maybe_lhs_val) |lhs_val| { - const rem_result = try lhs_val.intRem(rhs_val, sema.arena); + const rem_result = try lhs_val.intRem(rhs_val, resolved_type, sema.arena, target); // If this answer could possibly be different by doing `intMod`, // we must emit a compile error. Otherwise, it's OK. - if (rhs_val.compareWithZero(.lt) != lhs_val.compareWithZero(.lt) and - !rem_result.compareWithZero(.eq)) + if ((try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) != (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) and + !(try rem_result.compareWithZeroAdvanced(.eq, sema.kit(block, src)))) { - const bad_src = if (lhs_val.compareWithZero(.lt)) + const bad_src = if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) lhs_src else rhs_src; return sema.failWithModRemNegative(block, bad_src, lhs_ty, rhs_ty); } - if (lhs_val.compareWithZero(.lt)) { + if (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { // Negative - return sema.addConstant(scalar_type, Value.zero); + return sema.addConstant(resolved_type, Value.zero); } - return sema.addConstant(scalar_type, rem_result); + return sema.addConstant(resolved_type, rem_result); } break :rs .{ .src = lhs_src, .air_tag = .rem }; - } else if (rhs_ty.isSignedInt()) { + } else if (rhs_scalar_ty.isSignedInt()) { return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); } else { break :rs .{ .src = rhs_src, .air_tag = .rem }; @@ -8781,19 +11231,19 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } - if (rhs_val.compareWithZero(.lt)) { + if (try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) { return sema.failWithModRemNegative(block, rhs_src, lhs_ty, rhs_ty); } if (maybe_lhs_val) |lhs_val| { - if (lhs_val.isUndef() or lhs_val.compareWithZero(.lt)) { + if (lhs_val.isUndef() or (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src)))) { return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); } return sema.addConstant( - scalar_type, - try lhs_val.floatRem(rhs_val, sema.arena), + resolved_type, + try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target), ); } else { return sema.failWithModRemNegative(block, lhs_src, lhs_ty, rhs_ty); @@ -8824,13 +11274,13 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } if (maybe_lhs_val) |lhs_val| { return sema.addConstant( - scalar_type, - try lhs_val.intRem(rhs_val, sema.arena), + resolved_type, + try lhs_val.intRem(rhs_val, resolved_type, sema.arena, target), ); } break :rs .{ .src = lhs_src, .air_tag = .rem }; @@ -8843,18 +11293,18 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } if (maybe_rhs_val) |rhs_val| { return sema.addConstant( - scalar_type, - try lhs_val.floatRem(rhs_val, sema.arena), + resolved_type, + try lhs_val.floatRem(rhs_val, resolved_type, sema.arena, target), ); } else break :rs .{ .src = rhs_src, .air_tag = .rem }; } else break :rs .{ .src = lhs_src, .air_tag = .rem }; @@ -8881,13 +11331,13 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } if (maybe_lhs_val) |lhs_val| { return sema.addConstant( - scalar_type, - try lhs_val.intMod(rhs_val, sema.arena), + resolved_type, + try lhs_val.intMod(rhs_val, resolved_type, sema.arena, target), ); } break :rs .{ .src = lhs_src, .air_tag = .mod }; @@ -8900,18 +11350,18 @@ fn analyzeArithmetic( if (rhs_val.isUndef()) { return sema.failWithUseOfUndef(block, rhs_src); } - if (rhs_val.compareWithZero(.eq)) { + if (try rhs_val.compareWithZeroAdvanced(.eq, sema.kit(block, src))) { return sema.failWithDivideByZero(block, rhs_src); } } if (maybe_lhs_val) |lhs_val| { if (lhs_val.isUndef()) { - return sema.addConstUndef(scalar_type); + return sema.addConstUndef(resolved_type); } if (maybe_rhs_val) |rhs_val| { return sema.addConstant( - scalar_type, - try lhs_val.floatMod(rhs_val, sema.arena), + resolved_type, + try lhs_val.floatMod(rhs_val, resolved_type, sema.arena, target), ); } else break :rs .{ .src = rhs_src, .air_tag = .mod }; } else break :rs .{ .src = lhs_src, .air_tag = .mod }; @@ -8921,6 +11371,45 @@ fn analyzeArithmetic( }; try sema.requireRuntimeBlock(block, rs.src); + if (block.wantSafety()) { + if (scalar_tag == .Int) { + const maybe_op_ov: ?Air.Inst.Tag = switch (rs.air_tag) { + .add => .add_with_overflow, + .sub => .sub_with_overflow, + .mul => .mul_with_overflow, + else => null, + }; + if (maybe_op_ov) |op_ov_tag| { + const op_ov_tuple_ty = try sema.overflowArithmeticTupleType(resolved_type); + const op_ov = try block.addInst(.{ + .tag = op_ov_tag, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(op_ov_tuple_ty), + .payload = try sema.addExtra(Air.Bin{ + .lhs = casted_lhs, + .rhs = casted_rhs, + }), + } }, + }); + const ov_bit = try sema.tupleFieldValByIndex(block, src, op_ov, 1, op_ov_tuple_ty); + const any_ov_bit = if (resolved_type.zigTypeTag() == .Vector) + try block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = ov_bit, + .operation = .Or, + } }, + }) + else + ov_bit; + const zero_ov = try sema.addConstant(Type.@"u1", Value.zero); + const no_ov = try block.addBinOp(.cmp_eq, any_ov_bit, zero_ov); + + try sema.addSafetyCheck(block, no_ov, .integer_overflow); + return sema.tupleFieldValByIndex(block, src, op_ov, 0, op_ov_tuple_ty); + } + } + } return block.addBinOp(rs.air_tag, casted_lhs, casted_rhs); } @@ -8937,26 +11426,56 @@ fn analyzePtrArithmetic( // 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, Type.usize, uncasted_offset, offset_src); - // TODO adjust the return type according to alignment and other factors - const runtime_src = rs: { - if (try sema.resolveMaybeUndefVal(block, ptr_src, ptr)) |ptr_val| { - if (try sema.resolveMaybeUndefVal(block, offset_src, offset)) |offset_val| { - const ptr_ty = sema.typeOf(ptr); - const new_ptr_ty = ptr_ty; // TODO modify alignment - - if (ptr_val.isUndef() or offset_val.isUndef()) { - return sema.addConstUndef(new_ptr_ty); - } + const target = sema.mod.getTarget(); + const opt_ptr_val = try sema.resolveMaybeUndefVal(block, ptr_src, ptr); + const opt_off_val = try sema.resolveDefinedValue(block, offset_src, offset); + const ptr_ty = sema.typeOf(ptr); + const ptr_info = ptr_ty.ptrInfo().data; + const elem_ty = if (ptr_info.size == .One and ptr_info.pointee_type.zigTypeTag() == .Array) + ptr_info.pointee_type.childType() + else + ptr_info.pointee_type; + + const new_ptr_ty = t: { + // Calculate the new pointer alignment. + // This code is duplicated in `elemPtrType`. + if (ptr_info.@"align" == 0) { + // 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 = elem_ty.abiSize(target); + const addend = if (opt_off_val) |off_val| a: { + const off_int = try sema.usizeCast(block, offset_src, off_val.toUnsignedInt(target)); + 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 = @as(u32, 1) << @intCast(u5, @ctz(u64, addend | ptr_info.@"align")); + + break :t try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = ptr_info.pointee_type, + .sentinel = ptr_info.sentinel, + .@"align" = new_align, + .@"addrspace" = ptr_info.@"addrspace", + .mutable = ptr_info.mutable, + .@"allowzero" = ptr_info.@"allowzero", + .@"volatile" = ptr_info.@"volatile", + .size = ptr_info.size, + }); + }; - const offset_int = try sema.usizeCast(block, offset_src, offset_val.toUnsignedInt()); - if (ptr_val.getUnsignedInt()) |addr| { - const target = sema.mod.getTarget(); - const ptr_child_ty = ptr_ty.childType(); - const elem_ty = if (ptr_ty.isSinglePointer() and ptr_child_ty.zigTypeTag() == .Array) - ptr_child_ty.childType() - else - ptr_child_ty; + const runtime_src = rs: { + if (opt_ptr_val) |ptr_val| { + if (opt_off_val) |offset_val| { + if (ptr_val.isUndef()) return sema.addConstUndef(new_ptr_ty); + const offset_int = try sema.usizeCast(block, offset_src, offset_val.toUnsignedInt(target)); + if (offset_int == 0) return ptr; + if (try ptr_val.getUnsignedIntAdvanced(target, sema.kit(block, ptr_src))) |addr| { const elem_size = elem_ty.abiSize(target); const new_addr = switch (air_tag) { .ptr_add => addr + elem_size * offset_int, @@ -8969,14 +11488,23 @@ fn analyzePtrArithmetic( if (air_tag == .ptr_sub) { return sema.fail(block, op_src, "TODO implement Sema comptime pointer subtraction", .{}); } - const new_ptr_val = try ptr_val.elemPtr(sema.arena, offset_int); + const new_ptr_val = try ptr_val.elemPtr(ptr_ty, sema.arena, offset_int, sema.mod); return sema.addConstant(new_ptr_ty, new_ptr_val); } else break :rs offset_src; } else break :rs ptr_src; }; try sema.requireRuntimeBlock(block, runtime_src); - return block.addBinOp(air_tag, ptr, offset); + return block.addInst(.{ + .tag = air_tag, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(new_ptr_ty), + .payload = try sema.addExtra(Air.Bin{ + .lhs = ptr, + .rhs = offset, + }), + } }, + }); } fn zirLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -8986,7 +11514,7 @@ fn zirLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.In const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const ptr_src: LazySrcLoc = .{ .node_offset_deref_ptr = inst_data.src_node }; - const ptr = sema.resolveInst(inst_data.operand); + const ptr = try sema.resolveInst(inst_data.operand); return sema.analyzeLoad(block, src, ptr, ptr_src); } @@ -8994,17 +11522,18 @@ fn zirAsm( 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.Asm, extended.operand); - const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + const src = LazySrcLoc.nodeOffset(extra.data.src_node); const ret_ty_src: LazySrcLoc = .{ .node_offset_asm_ret_ty = extra.data.src_node }; const outputs_len = @truncate(u5, extended.small); const inputs_len = @truncate(u5, extended.small >> 5); const clobbers_len = @truncate(u5, extended.small >> 10); + const is_volatile = @truncate(u1, extended.small >> 15) != 0; + const is_global_assembly = sema.func == null; if (extra.data.asm_source == 0) { // This can move to become an AstGen error after inline assembly improvements land @@ -9012,65 +11541,139 @@ fn zirAsm( return sema.fail(block, src, "assembly code must use string literal syntax", .{}); } - if (outputs_len > 1) { - return sema.fail(block, src, "TODO implement Sema for asm with more than 1 output", .{}); + const asm_source = sema.code.nullTerminatedString(extra.data.asm_source); + + if (is_global_assembly) { + if (outputs_len != 0) { + return sema.fail(block, src, "module-level assembly does not support outputs", .{}); + } + if (inputs_len != 0) { + return sema.fail(block, src, "module-level assembly does not support inputs", .{}); + } + if (clobbers_len != 0) { + return sema.fail(block, src, "module-level assembly does not support clobbers", .{}); + } + if (is_volatile) { + return sema.fail(block, src, "volatile keyword is redundant on module-level assembly", .{}); + } + try sema.mod.addGlobalAssembly(sema.owner_decl_index, asm_source); + return Air.Inst.Ref.void_value; + } + + if (block.is_comptime) { + try sema.requireRuntimeBlock(block, src); } 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; - const Output = struct { constraint: []const u8, ty: Type }; - const output: ?Output = if (outputs_len == 0) null else blk: { + for (out_args) |*arg, out_i| { const output = sema.code.extraData(Zir.Inst.Asm.Output, extra_i); extra_i = output.end; const is_type = @truncate(u1, output_type_bits) != 0; output_type_bits >>= 1; - if (!is_type) { - return sema.fail(block, src, "TODO implement Sema for asm with non `->` output", .{}); + 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); + try sema.queueFullTypeResolution(out_ty); + expr_ty = try sema.addType(out_ty); + } else { + arg.* = try sema.resolveInst(output.data.operand); } const constraint = sema.code.nullTerminatedString(output.data.constraint); - break :blk Output{ - .constraint = constraint, - .ty = try sema.resolveType(block, ret_ty_src, output.data.operand), - }; - }; + const name = sema.code.nullTerminatedString(output.data.name); + needed_capacity += (constraint.len + name.len + (2 + 3)) / 4; + + outputs[out_i] = .{ .c = constraint, .n = name }; + } const args = try sema.arena.alloc(Air.Inst.Ref, inputs_len); - const inputs = try sema.arena.alloc([]const u8, inputs_len); + const inputs = try sema.arena.alloc(ConstraintName, inputs_len); for (args) |*arg, arg_i| { const input = sema.code.extraData(Zir.Inst.Asm.Input, extra_i); extra_i = input.end; - const name = sema.code.nullTerminatedString(input.data.name); - _ = name; // TODO: use the name + const uncasted_arg = try sema.resolveInst(input.data.operand); + const uncasted_arg_ty = sema.typeOf(uncasted_arg); + switch (uncasted_arg_ty.zigTypeTag()) { + .ComptimeInt => arg.* = try sema.coerce(block, Type.initTag(.usize), uncasted_arg, src), + .ComptimeFloat => arg.* = try sema.coerce(block, Type.initTag(.f64), uncasted_arg, src), + else => { + arg.* = uncasted_arg; + try sema.queueFullTypeResolution(uncasted_arg_ty); + }, + } - arg.* = sema.resolveInst(input.data.operand); - inputs[arg_i] = sema.code.nullTerminatedString(input.data.constraint); + 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 = try sema.arena.alloc([]const u8, clobbers_len); for (clobbers) |*name| { name.* = sema.code.nullTerminatedString(sema.code.extra[extra_i]); extra_i += 1; + + needed_capacity += name.*.len / 4 + 1; } - try sema.requireRuntimeBlock(block, src); + needed_capacity += (asm_source.len + 3) / 4; + const gpa = sema.gpa; - try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Asm).Struct.fields.len + args.len); + try sema.air_extra.ensureUnusedCapacity(gpa, needed_capacity); const asm_air = try block.addInst(.{ .tag = .assembly, .data = .{ .ty_pl = .{ - .ty = if (output) |o| try sema.addType(o.ty) else Air.Inst.Ref.void_type, + .ty = expr_ty, .payload = sema.addExtraAssumeCapacity(Air.Asm{ - .zir_index = inst, + .source_len = @intCast(u32, asm_source.len), + .outputs_len = outputs_len, + .inputs_len = @intCast(u32, args.len), + .flags = (@as(u32, @boolToInt(is_volatile)) << 31) | @intCast(u32, clobbers.len), }), } }, }); + sema.appendRefsAssumeCapacity(out_args); sema.appendRefsAssumeCapacity(args); + for (outputs) |o| { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, o.c); + buffer[o.c.len] = 0; + mem.copy(u8, buffer[o.c.len + 1 ..], 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()); + mem.copy(u8, buffer, input.c); + buffer[input.c.len] = 0; + mem.copy(u8, buffer[input.c.len + 1 ..], 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; + } + for (clobbers) |clobber| { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, clobber); + buffer[clobber.len] = 0; + sema.air_extra.items.len += clobber.len / 4 + 1; + } + { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + mem.copy(u8, buffer, asm_source); + sema.air_extra.items.len += (asm_source.len + 3) / 4; + } return asm_air; } @@ -9090,8 +11693,8 @@ fn zirCmpEq( const src: LazySrcLoc = inst_data.src(); const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + 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); @@ -9116,7 +11719,7 @@ fn zirCmpEq( 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 '{}' with null", .{non_null_type}); + return sema.fail(block, src, "comparison of '{}' with null", .{non_null_type.fmt(sema.mod)}); } if (lhs_ty_tag == .Union and (rhs_ty_tag == .EnumLiteral or rhs_ty_tag == .Enum)) { @@ -9156,7 +11759,7 @@ fn zirCmpEq( 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); - if (lhs_as_type.eql(rhs_as_type) == (op == .eq)) { + if (lhs_as_type.eql(rhs_as_type, sema.mod) == (op == .eq)) { return Air.Inst.Ref.bool_true; } else { return Air.Inst.Ref.bool_false; @@ -9176,8 +11779,13 @@ fn analyzeCmpUnionTag( ) CompileError!Air.Inst.Ref { const union_ty = try sema.resolveTypeFields(block, un_src, sema.typeOf(un)); const union_tag_ty = union_ty.unionTagType() orelse { - // TODO note at declaration site that says "union foo is not tagged" - return sema.fail(block, un_src, "comparison of union and enum literal is only valid for tagged union types", .{}); + const msg = msg: { + const msg = try sema.errMsg(block, un_src, "comparison of union and enum literal is only valid for tagged union types", .{}); + errdefer msg.destroy(sema.gpa); + try sema.mod.errNoteNonLazy(union_ty.declSrcLoc(sema.mod), msg, "union '{}' is not a tagged union", .{union_ty.fmt(sema.mod)}); + 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. @@ -9202,8 +11810,8 @@ fn zirCmp( const src: LazySrcLoc = inst_data.src(); const lhs_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + 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); } @@ -9220,6 +11828,11 @@ fn analyzeCmp( ) CompileError!Air.Inst.Ref { 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); + + if (lhs_ty.zigTypeTag() == .Vector and rhs_ty.zigTypeTag() == .Vector) { + return sema.cmpVector(block, src, lhs, rhs, op, lhs_src, rhs_src); + } if (lhs_ty.isNumeric() and rhs_ty.isNumeric()) { // 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 @@ -9229,8 +11842,8 @@ fn analyzeCmp( 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(is_equality_cmp)) { - return sema.fail(block, src, "{s} operator not allowed for type '{}'", .{ - @tagName(op), resolved_type, + return sema.fail(block, src, "operator {s} not allowed for type '{}'", .{ + compareOperatorName(op), resolved_type.fmt(sema.mod), }); } const casted_lhs = try sema.coerce(block, resolved_type, lhs, lhs_src); @@ -9238,6 +11851,17 @@ fn analyzeCmp( return sema.cmpSelf(block, 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, @@ -9254,7 +11878,13 @@ fn cmpSelf( if (try sema.resolveMaybeUndefVal(block, rhs_src, casted_rhs)) |rhs_val| { if (rhs_val.isUndef()) return sema.addConstUndef(Type.bool); - if (lhs_val.compare(op, rhs_val, resolved_type)) { + if (resolved_type.zigTypeTag() == .Vector) { + const result_ty = try Type.vector(sema.arena, resolved_type.vectorLen(), Type.@"bool"); + const cmp_val = try sema.compareVector(block, lhs_src, lhs_val, op, rhs_val, resolved_type); + return sema.addConstant(result_ty, cmp_val); + } + + if (try sema.compare(block, lhs_src, lhs_val, op, rhs_val, resolved_type)) { return Air.Inst.Ref.bool_true; } else { return Air.Inst.Ref.bool_false; @@ -9279,16 +11909,12 @@ fn cmpSelf( } }; try sema.requireRuntimeBlock(block, runtime_src); - - const tag: Air.Inst.Tag = switch (op) { - .lt => .cmp_lt, - .lte => .cmp_lte, - .eq => .cmp_eq, - .gte => .cmp_gte, - .gt => .cmp_gt, - .neq => .cmp_neq, - }; - // TODO handle vectors + if (resolved_type.zigTypeTag() == .Vector) { + const result_ty = try Type.vector(sema.arena, resolved_type.vectorLen(), Type.@"bool"); + const result_ty_ref = try sema.addType(result_ty); + return block.addCmpVector(casted_lhs, casted_rhs, op, result_ty_ref); + } + const tag = Air.Inst.Tag.fromCmpOp(op); return block.addBinOp(tag, casted_lhs, casted_rhs); } @@ -9316,23 +11942,22 @@ fn zirSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const operand_ty = try sema.resolveType(block, operand_src, inst_data.operand); - try sema.resolveTypeLayout(block, src, operand_ty); - const target = sema.mod.getTarget(); - const abi_size = switch (operand_ty.zigTypeTag()) { - .Fn => unreachable, + const ty = try sema.resolveType(block, operand_src, inst_data.operand); + switch (ty.zigTypeTag()) { + .Fn, .NoReturn, .Undefined, .Null, .BoundFn, .Opaque, - => return sema.fail(block, src, "no size available for type '{}'", .{operand_ty}), + => return sema.fail(block, src, "no size available for type '{}'", .{ty.fmt(sema.mod)}), + .Type, .EnumLiteral, .ComptimeFloat, .ComptimeInt, .Void, - => 0, + => return sema.addIntUnsigned(Type.comptime_int, 0), .Bool, .Int, @@ -9348,18 +11973,24 @@ fn zirSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air. .Vector, .Frame, .AnyFrame, - => operand_ty.abiSize(target), - }; - return sema.addIntUnsigned(Type.comptime_int, abi_size); + => {}, + } + const target = sema.mod.getTarget(); + const val = try ty.lazyAbiSize(target, sema.arena); + if (val.tag() == .lazy_size) { + try sema.queueFullTypeResolution(ty); + } + return sema.addConstant(Type.comptime_int, val); } fn zirBitSizeOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; + const src = inst_data.src(); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_ty = try sema.resolveType(block, operand_src, inst_data.operand); const target = sema.mod.getTarget(); - const bit_size = operand_ty.bitSize(target); - return sema.addIntUnsigned(Type.initTag(.comptime_int), bit_size); + const bit_size = try operand_ty.bitSizeAdvanced(target, sema.kit(block, src)); + return sema.addIntUnsigned(Type.comptime_int, bit_size); } fn zirThis( @@ -9367,9 +11998,9 @@ fn zirThis( block: *Block, extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { - const this_decl = block.namespace.getDecl(); - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; - return sema.analyzeDeclVal(block, src, this_decl); + const this_decl_index = block.namespace.getDeclIndex(); + const src = LazySrcLoc.nodeOffset(@bitCast(i32, extended.operand)); + return sema.analyzeDeclVal(block, src, this_decl_index); } fn zirClosureCapture( @@ -9379,10 +12010,19 @@ fn zirClosureCapture( ) CompileError!void { // TODO: Compile error when closed over values are modified const inst_data = sema.code.instructions.items(.data)[inst].un_tok; - const tv = try sema.resolveInstConst(block, inst_data.src(), inst_data.operand); + const src = inst_data.src(); + // Closures are not necessarily constant values. For example, the + // code might do something like this: + // fn foo(x: anytype) void { const S = struct {field: @TypeOf(x)}; } + // ...in which case the closure_capture instruction has access to a runtime + // value only. In such case we preserve the type and use a dummy runtime value. + const operand = try sema.resolveInst(inst_data.operand); + const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, operand)) orelse + Value.initTag(.generic_poison); + try block.wip_capture_scope.captures.putNoClobber(sema.gpa, inst, .{ - .ty = try tv.ty.copy(sema.perm_arena), - .val = try tv.val.copy(sema.perm_arena), + .ty = try sema.typeOf(operand).copy(sema.perm_arena), + .val = try val.copy(sema.perm_arena), }); } @@ -9393,7 +12033,7 @@ fn zirClosureGet( ) CompileError!Air.Inst.Ref { // TODO CLOSURE: Test this with inline functions const inst_data = sema.code.instructions.items(.data)[inst].inst_node; - var scope: *CaptureScope = block.src_decl.src_scope.?; + var scope: *CaptureScope = sema.mod.declPtr(block.src_decl).src_scope.?; // Note: The target closure must be in this scope list. // If it's not here, the zir is invalid, or the list is broken. const tv = while (true) { @@ -9413,14 +12053,21 @@ fn zirRetAddr( block: *Block, extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { - const tracy = trace(@src()); - defer tracy.end(); - - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + const src = LazySrcLoc.nodeOffset(@bitCast(i32, extended.operand)); try sema.requireRuntimeBlock(block, src); return try block.addNoOp(.ret_addr); } +fn zirFrameAddress( + sema: *Sema, + block: *Block, + extended: Zir.Inst.Extended.InstData, +) CompileError!Air.Inst.Ref { + const src = LazySrcLoc.nodeOffset(@bitCast(i32, extended.operand)); + try sema.requireRuntimeBlock(block, src); + return try block.addNoOp(.frame_addr); +} + fn zirBuiltinSrc( sema: *Sema, block: *Block, @@ -9429,18 +12076,20 @@ fn zirBuiltinSrc( const tracy = trace(@src()); defer tracy.end(); - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; + const src = LazySrcLoc.nodeOffset(@bitCast(i32, extended.operand)); const extra = sema.code.extraData(Zir.Inst.LineColumn, extended.operand).data; const func = sema.func orelse return sema.fail(block, src, "@src outside function", .{}); + const fn_owner_decl = sema.mod.declPtr(func.owner_decl); const func_name_val = blk: { var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); - const name = std.mem.span(func.owner_decl.name); + const name = std.mem.span(fn_owner_decl.name); const bytes = try anon_decl.arena().dupe(u8, name[0 .. name.len + 1]); const new_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len - 1), try Value.Tag.bytes.create(anon_decl.arena(), bytes), + 0, // default alignment ); break :blk try Value.Tag.decl_ref.create(sema.arena, new_decl); }; @@ -9448,10 +12097,15 @@ fn zirBuiltinSrc( const file_name_val = blk: { var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); - const name = try func.owner_decl.getFileScope().fullPathZ(anon_decl.arena()); + const relative_path = try fn_owner_decl.getFileScope().fullPath(sema.arena); + const absolute_path = std.fs.realpathAlloc(sema.arena, relative_path) catch |err| { + return sema.fail(block, src, "failed to get absolute path of file '{s}': {s}", .{ relative_path, @errorName(err) }); + }; + const aboslute_duped = try anon_decl.arena().dupeZ(u8, absolute_path); const new_decl = try anon_decl.finish( - try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), name.len), - try Value.Tag.bytes.create(anon_decl.arena(), name[0 .. name.len + 1]), + try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), aboslute_duped.len), + try Value.Tag.bytes.create(anon_decl.arena(), aboslute_duped[0 .. aboslute_duped.len + 1]), + 0, // default alignment ); break :blk try Value.Tag.decl_ref.create(sema.arena, new_decl); }; @@ -9461,6 +12115,7 @@ fn zirBuiltinSrc( field_values[0] = file_name_val; // fn_name: [:0]const u8, field_values[1] = func_name_val; + // TODO these should be runtime only! // line: u32 field_values[2] = try Value.Tag.int_u64.create(sema.arena, extra.line + 1); // column: u32, @@ -9468,7 +12123,7 @@ fn zirBuiltinSrc( return sema.addConstant( try sema.getBuiltinType(block, src, "SourceLocation"), - try Value.Tag.@"struct".create(sema.arena, field_values), + try Value.Tag.aggregate.create(sema.arena, field_values), ); } @@ -9476,7 +12131,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const ty = try sema.resolveType(block, src, inst_data.operand); - const type_info_ty = try sema.getBuiltinType(block, src, "TypeInfo"); + const type_info_ty = try sema.getBuiltinType(block, src, "Type"); const target = sema.mod.getTarget(); switch (ty.zigTypeTag()) { @@ -9484,86 +12139,165 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Type)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .Void => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Void)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .Bool => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Bool)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .NoReturn => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.NoReturn)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .ComptimeFloat => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.ComptimeFloat)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .ComptimeInt => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.ComptimeInt)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .Undefined => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Undefined)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .Null => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Null)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .EnumLiteral => return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.EnumLiteral)), - .val = Value.initTag(.unreachable_value), + .val = Value.@"void", }), ), .Fn => { + // TODO: look into memoizing this result. const info = ty.fnInfo(); - const field_values = try sema.arena.alloc(Value, 6); - // calling_convention: CallingConvention, - field_values[0] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.cc)); - // alignment: comptime_int, - field_values[1] = try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target)); - // is_generic: bool, - field_values[2] = if (info.is_generic) Value.@"true" else Value.@"false"; - // is_var_args: bool, - field_values[3] = if (info.is_var_args) Value.@"true" else Value.@"false"; - // return_type: ?type, - field_values[4] = try Value.Tag.ty.create(sema.arena, ty.fnReturnType()); - // args: []const FnArg, - field_values[5] = Value.@"null"; // TODO + + var params_anon_decl = try block.startAnonDecl(src); + defer params_anon_decl.deinit(); + + const param_vals = try params_anon_decl.arena().alloc(Value, info.param_types.len); + for (param_vals) |*param_val, i| { + const param_ty = info.param_types[i]; + const is_generic = param_ty.tag() == .generic_poison; + const param_ty_val = if (is_generic) + Value.@"null" + else + try Value.Tag.opt_payload.create( + params_anon_decl.arena(), + try Value.Tag.ty.create(params_anon_decl.arena(), param_ty), + ); + + const param_fields = try params_anon_decl.arena().create([3]Value); + param_fields.* = .{ + // is_generic: bool, + Value.makeBool(is_generic), + // is_noalias: bool, + Value.@"false", // TODO + // arg_type: ?type, + param_ty_val, + }; + param_val.* = try Value.Tag.aggregate.create(params_anon_decl.arena(), param_fields); + } + + const args_val = v: { + const fn_info_decl_index = (try sema.namespaceLookup( + block, + src, + type_info_ty.getNamespace().?, + "Fn", + )).?; + try sema.mod.declareDeclDependency(sema.owner_decl_index, fn_info_decl_index); + try sema.ensureDeclAnalyzed(fn_info_decl_index); + const fn_info_decl = sema.mod.declPtr(fn_info_decl_index); + var fn_ty_buffer: Value.ToTypeBuffer = undefined; + const fn_ty = fn_info_decl.val.toType(&fn_ty_buffer); + const param_info_decl_index = (try sema.namespaceLookup( + block, + src, + fn_ty.getNamespace().?, + "Param", + )).?; + try sema.mod.declareDeclDependency(sema.owner_decl_index, param_info_decl_index); + try sema.ensureDeclAnalyzed(param_info_decl_index); + const param_info_decl = sema.mod.declPtr(param_info_decl_index); + var param_buffer: Value.ToTypeBuffer = undefined; + const param_ty = param_info_decl.val.toType(¶m_buffer); + const new_decl = try params_anon_decl.finish( + try Type.Tag.array.create(params_anon_decl.arena(), .{ + .len = param_vals.len, + .elem_type = try param_ty.copy(params_anon_decl.arena()), + }), + try Value.Tag.aggregate.create( + params_anon_decl.arena(), + param_vals, + ), + 0, // default alignment + ); + break :v try Value.Tag.slice.create(sema.arena, .{ + .ptr = try Value.Tag.decl_ref.create(sema.arena, new_decl), + .len = try Value.Tag.int_u64.create(sema.arena, param_vals.len), + }); + }; + + const ret_ty_opt = if (info.return_type.tag() != .generic_poison) + try Value.Tag.opt_payload.create( + sema.arena, + try Value.Tag.ty.create(sema.arena, info.return_type), + ) + else + Value.@"null"; + + const field_values = try sema.arena.create([6]Value); + field_values.* = .{ + // calling_convention: CallingConvention, + try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.cc)), + // alignment: comptime_int, + try Value.Tag.int_u64.create(sema.arena, ty.abiAlignment(target)), + // is_generic: bool, + Value.makeBool(info.is_generic), + // is_var_args: bool, + Value.makeBool(info.is_var_args), + // return_type: ?type, + ret_ty_opt, + // args: []const Fn.Param, + args_val, + }; return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Fn)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, @@ -9582,7 +12316,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Int)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, @@ -9595,35 +12329,42 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Float)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, .Pointer => { const info = ty.ptrInfo().data; - const field_values = try sema.arena.alloc(Value, 8); - // size: Size, - field_values[0] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.size)); - // is_const: bool, - field_values[1] = if (!info.mutable) Value.@"true" else Value.@"false"; - // is_volatile: bool, - field_values[2] = if (info.@"volatile") Value.@"true" else Value.@"false"; - // alignment: comptime_int, - field_values[3] = try Value.Tag.int_u64.create(sema.arena, info.@"align"); - // address_space: AddressSpace - field_values[4] = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.@"addrspace")); - // child: type, - field_values[5] = try Value.Tag.ty.create(sema.arena, info.pointee_type); - // is_allowzero: bool, - field_values[6] = if (info.@"allowzero") Value.@"true" else Value.@"false"; - // sentinel: anytype, - field_values[7] = if (info.sentinel) |some| try Value.Tag.opt_payload.create(sema.arena, some) else Value.@"null"; + const alignment = if (info.@"align" != 0) + try Value.Tag.int_u64.create(sema.arena, info.@"align") + else + try info.pointee_type.lazyAbiAlignment(target, sema.arena); + + const field_values = try sema.arena.create([8]Value); + field_values.* = .{ + // size: Size, + try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.size)), + // is_const: bool, + Value.makeBool(!info.mutable), + // is_volatile: bool, + Value.makeBool(info.@"volatile"), + // alignment: comptime_int, + alignment, + // address_space: AddressSpace + try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(info.@"addrspace")), + // child: type, + try Value.Tag.ty.create(sema.arena, info.pointee_type), + // is_allowzero: bool, + Value.makeBool(info.@"allowzero"), + // sentinel: ?*const anyopaque, + try sema.optRefValue(block, src, info.pointee_type, info.sentinel), + }; return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Pointer)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, @@ -9634,14 +12375,14 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai field_values[0] = try Value.Tag.int_u64.create(sema.arena, info.len); // child: type, field_values[1] = try Value.Tag.ty.create(sema.arena, info.elem_type); - // sentinel: anytype, - field_values[2] = if (info.sentinel) |some| try Value.Tag.opt_payload.create(sema.arena, some) else Value.@"null"; + // sentinel: ?*const anyopaque, + field_values[2] = try sema.optRefValue(block, src, info.elem_type, info.sentinel); return sema.addConstant( type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Array)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, @@ -9657,7 +12398,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Vector)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, @@ -9670,7 +12411,97 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Optional)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), + }), + ); + }, + .ErrorSet => { + var fields_anon_decl = try block.startAnonDecl(src); + defer fields_anon_decl.deinit(); + + // Get the Error type + const error_field_ty = t: { + const set_field_ty_decl_index = (try sema.namespaceLookup( + block, + src, + type_info_ty.getNamespace().?, + "Error", + )).?; + try sema.mod.declareDeclDependency(sema.owner_decl_index, set_field_ty_decl_index); + try sema.ensureDeclAnalyzed(set_field_ty_decl_index); + const set_field_ty_decl = sema.mod.declPtr(set_field_ty_decl_index); + var buffer: Value.ToTypeBuffer = undefined; + break :t try set_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena()); + }; + + try sema.queueFullTypeResolution(try error_field_ty.copy(sema.arena)); + + // If the error set is inferred it must be resolved at this point + try sema.resolveInferredErrorSetTy(block, src, ty); + + // Build our list of Error values + // Optional value is only null if anyerror + // Value can be zero-length slice otherwise + const error_field_vals: ?[]Value = if (ty.isAnyError()) null else blk: { + const names = ty.errorSetNames(); + const vals = try fields_anon_decl.arena().alloc(Value, names.len); + for (vals) |*field_val, i| { + const name = names[i]; + const name_val = v: { + var anon_decl = try block.startAnonDecl(src); + defer anon_decl.deinit(); + const bytes = try anon_decl.arena().dupeZ(u8, name); + const new_decl = try anon_decl.finish( + try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), + try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), + 0, // default alignment + ); + break :v try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl); + }; + + const error_field_fields = try fields_anon_decl.arena().create([1]Value); + error_field_fields.* = .{ + // name: []const u8, + name_val, + }; + + field_val.* = try Value.Tag.aggregate.create( + fields_anon_decl.arena(), + error_field_fields, + ); + } + + break :blk vals; + }; + + // Build our ?[]const Error value + const errors_val = if (error_field_vals) |vals| v: { + const new_decl = try fields_anon_decl.finish( + try Type.Tag.array.create(fields_anon_decl.arena(), .{ + .len = vals.len, + .elem_type = error_field_ty, + }), + try Value.Tag.aggregate.create( + fields_anon_decl.arena(), + vals, + ), + 0, // default alignment + ); + + const new_decl_val = try Value.Tag.decl_ref.create(sema.arena, new_decl); + const slice_val = try Value.Tag.slice.create(sema.arena, .{ + .ptr = new_decl_val, + .len = try Value.Tag.int_u64.create(sema.arena, vals.len), + }); + break :v try Value.Tag.opt_payload.create(sema.arena, slice_val); + } else Value.@"null"; + + // Construct Type{ .ErrorSet = errors_val } + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.ErrorSet)), + .val = errors_val, }), ); }, @@ -9685,7 +12516,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.ErrorUnion)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, @@ -9694,20 +12525,21 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai var int_tag_type_buffer: Type.Payload.Bits = undefined; const int_tag_ty = try ty.intTagType(&int_tag_type_buffer).copy(sema.arena); - const is_exhaustive = if (ty.isNonexhaustiveEnum()) Value.@"false" else Value.@"true"; + const is_exhaustive = Value.makeBool(!ty.isNonexhaustiveEnum()); var fields_anon_decl = try block.startAnonDecl(src); defer fields_anon_decl.deinit(); const enum_field_ty = t: { - const enum_field_ty_decl = (try sema.namespaceLookup( + const enum_field_ty_decl_index = (try sema.namespaceLookup( block, src, type_info_ty.getNamespace().?, "EnumField", )).?; - try sema.mod.declareDeclDependency(sema.owner_decl, enum_field_ty_decl); - try sema.ensureDeclAnalyzed(enum_field_ty_decl); + try sema.mod.declareDeclDependency(sema.owner_decl_index, enum_field_ty_decl_index); + try sema.ensureDeclAnalyzed(enum_field_ty_decl_index); + const enum_field_ty_decl = sema.mod.declPtr(enum_field_ty_decl_index); var buffer: Value.ToTypeBuffer = undefined; break :t try enum_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena()); }; @@ -9733,6 +12565,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const new_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), + 0, // default alignment ); break :v try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl); }; @@ -9744,7 +12577,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai // value: comptime_int, int_val, }; - field_val.* = try Value.Tag.@"struct".create(fields_anon_decl.arena(), enum_field_fields); + field_val.* = try Value.Tag.aggregate.create(fields_anon_decl.arena(), enum_field_fields); } const fields_val = v: { @@ -9753,10 +12586,11 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .len = enum_field_vals.len, .elem_type = enum_field_ty, }), - try Value.Tag.array.create( + try Value.Tag.aggregate.create( fields_anon_decl.arena(), - try fields_anon_decl.arena().dupe(Value, enum_field_vals), + enum_field_vals, ), + 0, // default alignment ); break :v try Value.Tag.decl_ref.create(sema.arena, new_decl); }; @@ -9768,7 +12602,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai // layout: ContainerLayout, try Value.Tag.enum_field_index.create( sema.arena, - @enumToInt(std.builtin.TypeInfo.ContainerLayout.Auto), + @enumToInt(std.builtin.Type.ContainerLayout.Auto), ), // tag_type: type, @@ -9785,7 +12619,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Enum)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, @@ -9796,19 +12630,23 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai defer fields_anon_decl.deinit(); const union_field_ty = t: { - const union_field_ty_decl = (try sema.namespaceLookup( + const union_field_ty_decl_index = (try sema.namespaceLookup( block, src, type_info_ty.getNamespace().?, "UnionField", )).?; - try sema.mod.declareDeclDependency(sema.owner_decl, union_field_ty_decl); - try sema.ensureDeclAnalyzed(union_field_ty_decl); + try sema.mod.declareDeclDependency(sema.owner_decl_index, union_field_ty_decl_index); + try sema.ensureDeclAnalyzed(union_field_ty_decl_index); + const union_field_ty_decl = sema.mod.declPtr(union_field_ty_decl_index); var buffer: Value.ToTypeBuffer = undefined; break :t try union_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena()); }; const union_ty = try sema.resolveTypeFields(block, src, ty); + try sema.resolveTypeLayout(block, src, ty); // Getting alignment requires type layout + const layout = union_ty.containerLayout(); + const union_fields = union_ty.unionFields(); const union_field_vals = try fields_anon_decl.arena().alloc(Value, union_fields.count()); @@ -9822,20 +12660,26 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const new_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), + 0, // default alignment ); break :v try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl); }; const union_field_fields = try fields_anon_decl.arena().create([3]Value); + const alignment = switch (layout) { + .Auto, .Extern => try sema.unionFieldAlignment(block, src, field), + .Packed => 0, + }; + union_field_fields.* = .{ // name: []const u8, name_val, // field_type: type, try Value.Tag.ty.create(fields_anon_decl.arena(), field.ty), // alignment: comptime_int, - try field.abi_align.copy(fields_anon_decl.arena()), + try Value.Tag.int_u64.create(fields_anon_decl.arena(), alignment), }; - field_val.* = try Value.Tag.@"struct".create(fields_anon_decl.arena(), union_field_fields); + field_val.* = try Value.Tag.aggregate.create(fields_anon_decl.arena(), union_field_fields); } const fields_val = v: { @@ -9844,12 +12688,16 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai .len = union_field_vals.len, .elem_type = union_field_ty, }), - try Value.Tag.array.create( + try Value.Tag.aggregate.create( fields_anon_decl.arena(), try fields_anon_decl.arena().dupe(Value, union_field_vals), ), + 0, // default alignment ); - break :v try Value.Tag.decl_ref.create(sema.arena, new_decl); + break :v try Value.Tag.slice.create(sema.arena, .{ + .ptr = try Value.Tag.decl_ref.create(sema.arena, new_decl), + .len = try Value.Tag.int_u64.create(sema.arena, union_field_vals.len), + }); }; const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, union_ty.getNamespace()); @@ -9864,7 +12712,7 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai // layout: ContainerLayout, try Value.Tag.enum_field_index.create( sema.arena, - @enumToInt(std.builtin.TypeInfo.ContainerLayout.Auto), + @enumToInt(layout), ), // tag_type: ?type, @@ -9879,17 +12727,174 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Union)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, - .Struct => return sema.fail(block, src, "TODO: implement zirTypeInfo for Struct", .{}), - .Opaque => { + .Struct => { // TODO: look into memoizing this result. var fields_anon_decl = try block.startAnonDecl(src); defer fields_anon_decl.deinit(); + const struct_field_ty = t: { + const struct_field_ty_decl_index = (try sema.namespaceLookup( + block, + src, + type_info_ty.getNamespace().?, + "StructField", + )).?; + try sema.mod.declareDeclDependency(sema.owner_decl_index, struct_field_ty_decl_index); + try sema.ensureDeclAnalyzed(struct_field_ty_decl_index); + const struct_field_ty_decl = sema.mod.declPtr(struct_field_ty_decl_index); + var buffer: Value.ToTypeBuffer = undefined; + break :t try struct_field_ty_decl.val.toType(&buffer).copy(fields_anon_decl.arena()); + }; + const struct_ty = try sema.resolveTypeFields(block, src, ty); + try sema.resolveTypeLayout(block, src, ty); // Getting alignment requires type layout + const layout = struct_ty.containerLayout(); + + const struct_field_vals = fv: { + if (struct_ty.isTupleOrAnonStruct()) { + const tuple = struct_ty.tupleFields(); + const field_types = tuple.types; + const struct_field_vals = try fields_anon_decl.arena().alloc(Value, field_types.len); + for (struct_field_vals) |*struct_field_val, i| { + const field_ty = field_types[i]; + const name_val = v: { + var anon_decl = try block.startAnonDecl(src); + defer anon_decl.deinit(); + const bytes = if (struct_ty.castTag(.anon_struct)) |payload| + try anon_decl.arena().dupeZ(u8, payload.data.names[i]) + else + try std.fmt.allocPrintZ(anon_decl.arena(), "{d}", .{i}); + const new_decl = try anon_decl.finish( + try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), + try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), + 0, // default alignment + ); + break :v try Value.Tag.slice.create(fields_anon_decl.arena(), .{ + .ptr = try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl), + .len = try Value.Tag.int_u64.create(fields_anon_decl.arena(), bytes.len), + }); + }; + + const struct_field_fields = try fields_anon_decl.arena().create([5]Value); + const field_val = tuple.values[i]; + const is_comptime = field_val.tag() != .unreachable_value; + const opt_default_val = if (is_comptime) field_val else null; + const default_val_ptr = try sema.optRefValue(block, src, field_ty, opt_default_val); + struct_field_fields.* = .{ + // name: []const u8, + name_val, + // field_type: type, + try Value.Tag.ty.create(fields_anon_decl.arena(), field_ty), + // default_value: ?*const anyopaque, + try default_val_ptr.copy(fields_anon_decl.arena()), + // is_comptime: bool, + Value.makeBool(is_comptime), + // alignment: comptime_int, + try field_ty.lazyAbiAlignment(target, fields_anon_decl.arena()), + }; + struct_field_val.* = try Value.Tag.aggregate.create(fields_anon_decl.arena(), struct_field_fields); + } + break :fv struct_field_vals; + } + const struct_fields = struct_ty.structFields(); + const struct_field_vals = try fields_anon_decl.arena().alloc(Value, struct_fields.count()); + + for (struct_field_vals) |*field_val, i| { + const field = struct_fields.values()[i]; + const name = struct_fields.keys()[i]; + const name_val = v: { + var anon_decl = try block.startAnonDecl(src); + defer anon_decl.deinit(); + const bytes = try anon_decl.arena().dupeZ(u8, name); + const new_decl = try anon_decl.finish( + try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), + try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), + 0, // default alignment + ); + break :v try Value.Tag.slice.create(fields_anon_decl.arena(), .{ + .ptr = try Value.Tag.decl_ref.create(fields_anon_decl.arena(), new_decl), + .len = try Value.Tag.int_u64.create(fields_anon_decl.arena(), bytes.len), + }); + }; + + const struct_field_fields = try fields_anon_decl.arena().create([5]Value); + const opt_default_val = if (field.default_val.tag() == .unreachable_value) + null + else + field.default_val; + const default_val_ptr = try sema.optRefValue(block, src, field.ty, opt_default_val); + const alignment = switch (layout) { + .Auto, .Extern => field.normalAlignment(target), + .Packed => 0, + }; + + struct_field_fields.* = .{ + // name: []const u8, + name_val, + // field_type: type, + try Value.Tag.ty.create(fields_anon_decl.arena(), field.ty), + // default_value: ?*const anyopaque, + try default_val_ptr.copy(fields_anon_decl.arena()), + // is_comptime: bool, + Value.makeBool(field.is_comptime), + // alignment: comptime_int, + try Value.Tag.int_u64.create(fields_anon_decl.arena(), alignment), + }; + field_val.* = try Value.Tag.aggregate.create(fields_anon_decl.arena(), struct_field_fields); + } + break :fv struct_field_vals; + }; + + const fields_val = v: { + const new_decl = try fields_anon_decl.finish( + try Type.Tag.array.create(fields_anon_decl.arena(), .{ + .len = struct_field_vals.len, + .elem_type = struct_field_ty, + }), + try Value.Tag.aggregate.create( + fields_anon_decl.arena(), + try fields_anon_decl.arena().dupe(Value, struct_field_vals), + ), + 0, // default alignment + ); + break :v try Value.Tag.slice.create(sema.arena, .{ + .ptr = try Value.Tag.decl_ref.create(sema.arena, new_decl), + .len = try Value.Tag.int_u64.create(sema.arena, struct_field_vals.len), + }); + }; + + const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, struct_ty.getNamespace()); + + const field_values = try sema.arena.create([4]Value); + field_values.* = .{ + // layout: ContainerLayout, + try Value.Tag.enum_field_index.create( + sema.arena, + @enumToInt(layout), + ), + // fields: []const StructField, + fields_val, + // decls: []const Declaration, + decls_val, + // is_tuple: bool, + Value.makeBool(struct_ty.isTuple()), + }; + + return sema.addConstant( + type_info_ty, + try Value.Tag.@"union".create(sema.arena, .{ + .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Struct)), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), + }), + ); + }, + .Opaque => { + // TODO: look into memoizing this result. + const opaque_ty = try sema.resolveTypeFields(block, src, ty); const decls_val = try sema.typeInfoDecls(block, src, type_info_ty, opaque_ty.getNamespace()); @@ -9903,11 +12908,10 @@ fn zirTypeInfo(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai type_info_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = try Value.Tag.enum_field_index.create(sema.arena, @enumToInt(std.builtin.TypeId.Opaque)), - .val = try Value.Tag.@"struct".create(sema.arena, field_values), + .val = try Value.Tag.aggregate.create(sema.arena, field_values), }), ); }, - .ErrorSet => return sema.fail(block, src, "TODO: implement zirTypeInfo for ErrorSet", .{}), .BoundFn => @panic("TODO remove this type from the language and compiler"), .Frame => return sema.fail(block, src, "TODO: implement zirTypeInfo for Frame", .{}), .AnyFrame => return sema.fail(block, src, "TODO: implement zirTypeInfo for AnyFrame", .{}), @@ -9921,51 +12925,52 @@ fn typeInfoDecls( type_info_ty: Type, opt_namespace: ?*Module.Namespace, ) CompileError!Value { - const namespace = opt_namespace orelse return Value.initTag(.empty_array); - const decls_len = namespace.decls.count(); - if (decls_len == 0) return Value.initTag(.empty_array); - var decls_anon_decl = try block.startAnonDecl(src); defer decls_anon_decl.deinit(); const declaration_ty = t: { - const declaration_ty_decl = (try sema.namespaceLookup( + const declaration_ty_decl_index = (try sema.namespaceLookup( block, src, type_info_ty.getNamespace().?, - "EnumField", + "Declaration", )).?; - try sema.mod.declareDeclDependency(sema.owner_decl, declaration_ty_decl); - try sema.ensureDeclAnalyzed(declaration_ty_decl); + try sema.mod.declareDeclDependency(sema.owner_decl_index, declaration_ty_decl_index); + try sema.ensureDeclAnalyzed(declaration_ty_decl_index); + const declaration_ty_decl = sema.mod.declPtr(declaration_ty_decl_index); var buffer: Value.ToTypeBuffer = undefined; break :t try declaration_ty_decl.val.toType(&buffer).copy(decls_anon_decl.arena()); }; + try sema.queueFullTypeResolution(try declaration_ty.copy(sema.arena)); + const decls_len = if (opt_namespace) |ns| ns.decls.count() else 0; const decls_vals = try decls_anon_decl.arena().alloc(Value, decls_len); for (decls_vals) |*decls_val, i| { - const decl = namespace.decls.values()[i]; - const name = namespace.decls.keys()[i]; + const decl_index = opt_namespace.?.decls.keys()[i]; + const decl = sema.mod.declPtr(decl_index); const name_val = v: { var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); - const bytes = try anon_decl.arena().dupeZ(u8, name); + const bytes = try anon_decl.arena().dupeZ(u8, mem.sliceTo(decl.name, 0)); const new_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), + 0, // default alignment ); - break :v try Value.Tag.decl_ref.create(decls_anon_decl.arena(), new_decl); + break :v try Value.Tag.slice.create(decls_anon_decl.arena(), .{ + .ptr = try Value.Tag.decl_ref.create(decls_anon_decl.arena(), new_decl), + .len = try Value.Tag.int_u64.create(decls_anon_decl.arena(), bytes.len), + }); }; - const is_pub = if (decl.is_pub) Value.@"true" else Value.@"false"; - const fields = try decls_anon_decl.arena().create([2]Value); fields.* = .{ //name: []const u8, name_val, //is_pub: bool, - is_pub, + Value.makeBool(decl.is_pub), }; - decls_val.* = try Value.Tag.@"struct".create(decls_anon_decl.arena(), fields); + decls_val.* = try Value.Tag.aggregate.create(decls_anon_decl.arena(), fields); } const new_decl = try decls_anon_decl.finish( @@ -9973,41 +12978,72 @@ fn typeInfoDecls( .len = decls_vals.len, .elem_type = declaration_ty, }), - try Value.Tag.array.create( + try Value.Tag.aggregate.create( decls_anon_decl.arena(), try decls_anon_decl.arena().dupe(Value, decls_vals), ), + 0, // default alignment ); - return try Value.Tag.decl_ref.create(sema.arena, new_decl); + return try Value.Tag.slice.create(sema.arena, .{ + .ptr = try Value.Tag.decl_ref.create(sema.arena, new_decl), + .len = try Value.Tag.int_u64.create(sema.arena, decls_vals.len), + }); } 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[inst].un_node; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); + const operand_ty = sema.typeOf(operand); + return sema.addType(operand_ty); +} + +fn zirTypeofBuiltin(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const pl_node = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index); + const body = sema.code.extra[extra.end..][0..extra.data.body_len]; + + var child_block: Block = .{ + .parent = block, + .sema = sema, + .src_decl = block.src_decl, + .namespace = block.namespace, + .wip_capture_scope = block.wip_capture_scope, + .instructions = .{}, + .inlining = block.inlining, + .is_comptime = false, + .is_typeof = true, + .want_safety = false, + }; + defer child_block.instructions.deinit(sema.gpa); + + const operand = try sema.resolveBody(&child_block, body, inst); const operand_ty = sema.typeOf(operand); + if (operand_ty.tag() == .generic_poison) return error.GenericPoison; return sema.addType(operand_ty); } fn zirTypeofLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); - return sema.log2IntType(block, operand_ty, src); + const res_ty = try sema.log2IntType(block, operand_ty, src); + return sema.addType(res_ty); } fn zirLog2IntType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand = try sema.resolveType(block, src, inst_data.operand); - return sema.log2IntType(block, operand, src); + const res_ty = try sema.log2IntType(block, operand, src); + return sema.addType(res_ty); } -fn log2IntType(sema: *Sema, block: *Block, operand: Type, src: LazySrcLoc) CompileError!Air.Inst.Ref { +fn log2IntType(sema: *Sema, block: *Block, operand: Type, src: LazySrcLoc) CompileError!Type { switch (operand.zigTypeTag()) { - .ComptimeInt => return Air.Inst.Ref.comptime_int_type, + .ComptimeInt => return Type.@"comptime_int", .Int => { const bits = operand.bitSize(sema.mod.getTarget()); const count = if (bits == 0) @@ -10020,16 +13056,24 @@ fn log2IntType(sema: *Sema, block: *Block, operand: Type, src: LazySrcLoc) Compi } break :blk count; }; - const res = try Module.makeIntType(sema.arena, .unsigned, count); - return sema.addType(res); + return Module.makeIntType(sema.arena, .unsigned, count); }, - else => return sema.fail( - block, - src, - "bit shifting operation expected integer type, found '{}'", - .{operand}, - ), + .Vector => { + const elem_ty = operand.elemType2(); + const log2_elem_ty = try sema.log2IntType(block, elem_ty, src); + return Type.Tag.vector.create(sema.arena, .{ + .len = operand.vectorLen(), + .elem_type = log2_elem_ty, + }); + }, + else => {}, } + return sema.fail( + block, + src, + "bit shifting operation expected integer type, found '{}'", + .{operand.fmt(sema.mod)}, + ); } fn zirTypeofPeer( @@ -10040,15 +13084,32 @@ fn zirTypeofPeer( const tracy = trace(@src()); defer tracy.end(); - const extra = sema.code.extraData(Zir.Inst.NodeMultiOp, extended.operand); - const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; + const extra = sema.code.extraData(Zir.Inst.TypeOfPeer, extended.operand); + const src = LazySrcLoc.nodeOffset(extra.data.src_node); + const body = sema.code.extra[extra.data.body_index..][0..extra.data.body_len]; + + var child_block: Block = .{ + .parent = block, + .sema = sema, + .src_decl = block.src_decl, + .namespace = block.namespace, + .wip_capture_scope = block.wip_capture_scope, + .instructions = .{}, + .inlining = block.inlining, + .is_comptime = false, + .is_typeof = true, + }; + defer child_block.instructions.deinit(sema.gpa); + // Ignore the result, we only care about the instructions in `args`. + _ = try sema.analyzeBodyBreak(&child_block, body); + 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) |arg_ref, i| { - inst_list[i] = sema.resolveInst(arg_ref); + 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 }); @@ -10062,7 +13123,7 @@ fn zirBoolNot(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); const operand_src = src; // TODO put this on the operand, not the `!` - const uncasted_operand = sema.resolveInst(inst_data.operand); + const uncasted_operand = try sema.resolveInst(inst_data.operand); const operand = try sema.coerce(block, Type.bool, uncasted_operand, operand_src); if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { @@ -10088,7 +13149,7 @@ fn zirBoolBr( const datas = sema.code.instructions.items(.data); const inst_data = datas[inst].bool_br; - const lhs = sema.resolveInst(inst_data.lhs); + const lhs = try sema.resolveInst(inst_data.lhs); const lhs_src = sema.src; const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index); const body = sema.code.extra[extra.end..][0..extra.data.body_len]; @@ -10120,7 +13181,7 @@ fn zirBoolBr( var child_block = parent_block.makeSubBlock(); child_block.runtime_loop = null; child_block.runtime_cond = lhs_src; - child_block.runtime_index += 1; + child_block.runtime_index.increment(); defer child_block.instructions.deinit(gpa); var then_block = child_block.makeSubBlock(); @@ -10173,7 +13234,7 @@ fn zirIsNonNull( const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); return sema.analyzeIsNull(block, src, operand, true); } @@ -10187,7 +13248,7 @@ fn zirIsNonNullPtr( const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const ptr = sema.resolveInst(inst_data.operand); + const ptr = try sema.resolveInst(inst_data.operand); if ((try sema.resolveMaybeUndefVal(block, src, ptr)) == null) { return block.addUnOp(.is_non_null_ptr, ptr); } @@ -10200,7 +13261,7 @@ fn zirIsNonErr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); return sema.analyzeIsNonErr(block, inst_data.src(), operand); } @@ -10210,7 +13271,7 @@ fn zirIsNonErrPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const ptr = sema.resolveInst(inst_data.operand); + const ptr = try sema.resolveInst(inst_data.operand); const loaded = try sema.analyzeLoad(block, src, ptr, src); return sema.analyzeIsNonErr(block, src, loaded); } @@ -10224,20 +13285,20 @@ fn zirCondbr( defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); const cond_src: LazySrcLoc = .{ .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.extra[extra.end..][0..extra.data.then_body_len]; const else_body = sema.code.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; - const uncasted_cond = sema.resolveInst(extra.data.condition); + const uncasted_cond = try sema.resolveInst(extra.data.condition); const cond = try sema.coerce(parent_block, Type.bool, uncasted_cond, cond_src); - if (try sema.resolveDefinedValue(parent_block, src, cond)) |cond_val| { + if (try sema.resolveDefinedValue(parent_block, cond_src, cond)) |cond_val| { const body = if (cond_val.toBool()) then_body else else_body; - _ = try sema.analyzeBody(parent_block, body); - return always_noreturn; + // We use `analyzeBodyInner` since we want to propagate any possible + // `error.ComptimeBreak` to the caller. + return sema.analyzeBodyInner(parent_block, body); } const gpa = sema.gpa; @@ -10247,14 +13308,36 @@ fn zirCondbr( var sub_block = parent_block.makeSubBlock(); sub_block.runtime_loop = null; sub_block.runtime_cond = cond_src; - sub_block.runtime_index += 1; + sub_block.runtime_index.increment(); defer sub_block.instructions.deinit(gpa); - _ = try sema.analyzeBody(&sub_block, then_body); + _ = sema.analyzeBodyInner(&sub_block, then_body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&sub_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; const true_instructions = sub_block.instructions.toOwnedSlice(gpa); defer gpa.free(true_instructions); - _ = try sema.analyzeBody(&sub_block, else_body); + _ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) { + error.ComptimeBreak => { + const zir_datas = sema.code.instructions.items(.data); + const break_data = zir_datas[sema.comptime_break_inst].@"break"; + try sema.addRuntimeBreak(&sub_block, .{ + .block_inst = break_data.block_inst, + .operand = break_data.operand, + .inst = sema.comptime_break_inst, + }); + }, + else => |e| return e, + }; try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len + true_instructions.len + sub_block.instructions.items.len); _ = try parent_block.addInst(.{ @@ -10272,15 +13355,173 @@ fn zirCondbr( return always_noreturn; } -fn zirUnreachable(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index { - const tracy = trace(@src()); - defer tracy.end(); +fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const body = sema.code.extra[extra.end..][0..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() != .ErrorUnion) { + return sema.fail(parent_block, operand_src, "expected error union type, found '{}'", .{ + err_union_ty.fmt(sema.mod), + }); + } + const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union); + if (is_non_err != .none) { + 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. + return sema.resolveBody(parent_block, body, inst); + } + + var sub_block = parent_block.makeSubBlock(); + defer sub_block.instructions.deinit(sema.gpa); + + // This body is guaranteed to end with noreturn and has no breaks. + _ = try sema.analyzeBodyInner(&sub_block, body); + + 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 = .@"try", + .data = .{ .pl_op = .{ + .operand = err_union, + .payload = sema.addExtraAssumeCapacity(Air.Try{ + .body_len = @intCast(u32, sub_block.instructions.items.len), + }), + } }, + }); + sema.air_extra.appendSliceAssumeCapacity(sub_block.instructions.items); + return try_inst; +} + +fn zirTryPtr(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Ref { + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; + const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index); + const body = sema.code.extra[extra.end..][0..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); + if (err_union_ty.zigTypeTag() != .ErrorUnion) { + return sema.fail(parent_block, operand_src, "expected error union type, found '{}'", .{ + err_union_ty.fmt(sema.mod), + }); + } + const is_non_err = try sema.analyzeIsNonErrComptimeOnly(parent_block, operand_src, err_union); + if (is_non_err != .none) { + 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. + return sema.resolveBody(parent_block, body, inst); + } + + var sub_block = parent_block.makeSubBlock(); + defer sub_block.instructions.deinit(sema.gpa); + // This body is guaranteed to end with noreturn and has no breaks. + _ = try sema.analyzeBodyInner(&sub_block, body); + + const operand_ty = sema.typeOf(operand); + const ptr_info = operand_ty.ptrInfo().data; + const res_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = err_union_ty.errorUnionPayload(), + .@"addrspace" = ptr_info.@"addrspace", + .mutable = ptr_info.mutable, + .@"allowzero" = ptr_info.@"allowzero", + .@"volatile" = ptr_info.@"volatile", + }); + const res_ty_ref = try sema.addType(res_ty); + 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 = .try_ptr, + .data = .{ .ty_pl = .{ + .ty = res_ty_ref, + .payload = sema.addExtraAssumeCapacity(Air.TryPtr{ + .ptr = operand, + .body_len = @intCast(u32, sub_block.instructions.items.len), + }), + } }, + }); + sema.air_extra.appendSliceAssumeCapacity(sub_block.instructions.items); + return try_inst; +} + +// 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, break_data: BreakData) !void { + const gop = try sema.inst_map.getOrPut(sema.gpa, break_data.block_inst); + const labeled_block = if (!gop.found_existing) blk: { + try sema.post_hoc_blocks.ensureUnusedCapacity(sema.gpa, 1); + + const new_block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len); + gop.value_ptr.* = Air.indexToRef(new_block_inst); + try sema.air_instructions.append(sema.gpa, .{ + .tag = .block, + .data = undefined, + }); + const labeled_block = try sema.gpa.create(LabeledBlock); + labeled_block.* = .{ + .label = .{ + .zir_block = break_data.block_inst, + .merges = .{ + .results = .{}, + .br_list = .{}, + .block_inst = new_block_inst, + }, + }, + .block = .{ + .parent = child_block, + .sema = sema, + .src_decl = child_block.src_decl, + .namespace = child_block.namespace, + .wip_capture_scope = child_block.wip_capture_scope, + .instructions = .{}, + .label = &labeled_block.label, + .inlining = child_block.inlining, + .is_comptime = child_block.is_comptime, + }, + }; + sema.post_hoc_blocks.putAssumeCapacityNoClobber(new_block_inst, labeled_block); + break :blk labeled_block; + } else blk: { + const new_block_inst = Air.refToIndex(gop.value_ptr.*).?; + const labeled_block = sema.post_hoc_blocks.get(new_block_inst).?; + break :blk labeled_block; + }; + + const operand = try sema.resolveInst(break_data.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, Air.refToIndex(br_ref).?); + 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!Zir.Inst.Index { const inst_data = sema.code.instructions.items(.data)[inst].@"unreachable"; const src = inst_data.src(); + + if (block.is_comptime or inst_data.force_comptime) { + return sema.fail(block, src, "reached unreachable code", .{}); + } try sema.requireRuntimeBlock(block, src); // TODO Add compile error for @optimizeFor occurring too late in a scope. - try block.addUnreachable(src, inst_data.safety); + try block.addUnreachable(src, true); return always_noreturn; } @@ -10302,7 +13543,7 @@ fn zirRetErrValue( return sema.analyzeRet(block, result_inst, src); } -fn zirRetCoerce( +fn zirRetTok( sema: *Sema, block: *Block, inst: Zir.Inst.Index, @@ -10311,7 +13552,7 @@ fn zirRetCoerce( defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_tok; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const src = inst_data.src(); return sema.analyzeRet(block, operand, src); @@ -10322,7 +13563,7 @@ fn zirRetNode(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir defer tracy.end(); const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const src = inst_data.src(); return sema.analyzeRet(block, operand, src); @@ -10334,7 +13575,7 @@ fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const ret_ptr = sema.resolveInst(inst_data.operand); + const ret_ptr = try sema.resolveInst(inst_data.operand); if (block.is_comptime or block.inlining != null) { const operand = try sema.analyzeLoad(block, src, ret_ptr, src); @@ -10345,6 +13586,23 @@ fn zirRetLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir return always_noreturn; } +fn addToInferredErrorSet(sema: *Sema, uncasted_operand: Air.Inst.Ref) !void { + assert(sema.fn_ret_ty.zigTypeTag() == .ErrorUnion); + + if (sema.fn_ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| { + const op_ty = sema.typeOf(uncasted_operand); + switch (op_ty.zigTypeTag()) { + .ErrorSet => { + try payload.data.addErrorSet(sema.gpa, op_ty); + }, + .ErrorUnion => { + try payload.data.addErrorSet(sema.gpa, op_ty.errorUnionSet()); + }, + else => {}, + } + } +} + fn analyzeRet( sema: *Sema, block: *Block, @@ -10355,18 +13613,7 @@ fn analyzeRet( // add the error tag to the inferred error set of the in-scope function, so // that the coercion below works correctly. if (sema.fn_ret_ty.zigTypeTag() == .ErrorUnion) { - if (sema.fn_ret_ty.errorUnionSet().castTag(.error_set_inferred)) |payload| { - const op_ty = sema.typeOf(uncasted_operand); - switch (op_ty.zigTypeTag()) { - .ErrorSet => { - try payload.data.addErrorSet(sema.gpa, op_ty); - }, - .ErrorUnion => { - try payload.data.addErrorSet(sema.gpa, op_ty.errorUnionSet()); - }, - else => {}, - } - } + try sema.addToInferredErrorSet(uncasted_operand); } const operand = try sema.coerce(block, sema.fn_ret_ty, uncasted_operand, src); @@ -10381,6 +13628,26 @@ fn analyzeRet( return always_noreturn; } + // TODO implement this feature in all the backends and then delete this check. + const backend_supports_error_return_tracing = + sema.mod.comp.bin_file.options.use_llvm; + + if (sema.fn_ret_ty.isError() and + sema.mod.comp.bin_file.options.error_return_tracing and + backend_supports_error_return_tracing) + ret_err: { + if (try sema.resolveMaybeUndefVal(block, src, operand)) |ret_val| { + if (ret_val.tag() != .@"error") break :ret_err; + } + const return_err_fn = try sema.getBuiltin(block, src, "returnError"); + const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace"); + const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); + const ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty); + const err_return_trace = try block.addTy(.err_return_trace, ptr_stack_trace_ty); + const args: [1]Air.Inst.Ref = .{err_return_trace}; + _ = try sema.analyzeCall(block, return_err_fn, src, src, .never_inline, false, &args); + } + try sema.resolveTypeLayout(block, src, sema.fn_ret_ty); _ = try block.addUnOp(.ret, operand); return always_noreturn; @@ -10400,7 +13667,7 @@ fn zirPtrTypeSimple(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileErr const inst_data = sema.code.instructions.items(.data)[inst].ptr_type_simple; const elem_type = try sema.resolveType(block, .unneeded, inst_data.elem_type); - const ty = try Type.ptr(sema.arena, .{ + const ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = elem_type, .@"addrspace" = .generic, .mutable = inst_data.is_mutable, @@ -10415,9 +13682,13 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const tracy = trace(@src()); defer tracy.end(); - const src: LazySrcLoc = .unneeded; + // TODO better source location + const src: LazySrcLoc = sema.src; + const elem_ty_src: LazySrcLoc = .unneeded; const inst_data = sema.code.instructions.items(.data)[inst].ptr_type; const extra = sema.code.extraData(Zir.Inst.PtrType, inst_data.payload_index); + const unresolved_elem_ty = try sema.resolveType(block, elem_ty_src, extra.data.elem_type); + const target = sema.mod.getTarget(); var extra_i = extra.end; @@ -10427,10 +13698,20 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air break :blk (try sema.resolveInstConst(block, .unneeded, ref)).val; } else null; - const abi_align = if (inst_data.flags.has_align) blk: { + const abi_align: u32 = if (inst_data.flags.has_align) blk: { const ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_i]); extra_i += 1; - break :blk try sema.resolveAlreadyCoercedInt(block, .unneeded, ref, u32); + const coerced = try sema.coerce(block, Type.u32, try sema.resolveInst(ref), src); + const val = try sema.resolveConstValue(block, src, coerced); + // Check if this happens to be the lazy alignment of our element type, in + // which case we can make this 0 without resolving it. + if (val.castTag(.lazy_align)) |payload| { + if (payload.data.eql(unresolved_elem_ty, sema.mod)) { + break :blk 0; + } + } + const abi_align = (try val.getUnsignedIntAdvanced(target, sema.kit(block, src))).?; + break :blk @intCast(u32, abi_align); } else 0; const address_space = if (inst_data.flags.has_addrspace) blk: { @@ -10439,32 +13720,40 @@ fn zirPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air break :blk try sema.analyzeAddrspace(block, .unneeded, ref, .pointer); } else .generic; - const bit_start = if (inst_data.flags.has_bit_range) blk: { + const bit_offset = if (inst_data.flags.has_bit_range) blk: { const ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_i]); extra_i += 1; - break :blk try sema.resolveAlreadyCoercedInt(block, .unneeded, ref, u16); + const bit_offset = try sema.resolveInt(block, .unneeded, ref, Type.u16); + break :blk @intCast(u16, bit_offset); } else 0; - const bit_end = if (inst_data.flags.has_bit_range) blk: { + const host_size: u16 = if (inst_data.flags.has_bit_range) blk: { const ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_i]); extra_i += 1; - break :blk try sema.resolveAlreadyCoercedInt(block, .unneeded, ref, u16); + const host_size = try sema.resolveInt(block, .unneeded, ref, Type.u16); + break :blk @intCast(u16, host_size); } else 0; - if (bit_end != 0 and bit_start >= bit_end * 8) + if (host_size != 0 and bit_offset >= host_size * 8) { return sema.fail(block, src, "bit offset starts after end of host integer", .{}); + } - const elem_type = try sema.resolveType(block, .unneeded, extra.data.elem_type); - - const ty = try Type.ptr(sema.arena, .{ - .pointee_type = elem_type, + const elem_ty = if (abi_align == 0) + unresolved_elem_ty + else t: { + const elem_ty = try sema.resolveTypeFields(block, elem_ty_src, unresolved_elem_ty); + try sema.resolveTypeLayout(block, elem_ty_src, elem_ty); + break :t elem_ty; + }; + const ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = elem_ty, .sentinel = sentinel, .@"align" = abi_align, .@"addrspace" = address_space, - .bit_offset = bit_start, - .host_size = bit_end, + .bit_offset = bit_offset, + .host_size = host_size, .mutable = inst_data.flags.is_mutable, - .@"allowzero" = inst_data.flags.is_allowzero or inst_data.size == .C, + .@"allowzero" = inst_data.flags.is_allowzero, .@"volatile" = inst_data.flags.is_volatile, .size = inst_data.size, }); @@ -10483,7 +13772,7 @@ fn zirStructInitEmpty(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileE .Struct => return structInitEmpty(sema, block, obj_ty, src, src), .Array => return arrayInitEmpty(sema, obj_ty), .Void => return sema.addConstant(obj_ty, Value.void), - else => unreachable, + else => return sema.failWithArrayInitNotSupported(block, src, obj_ty), } } @@ -10531,10 +13820,44 @@ fn arrayInitEmpty(sema: *Sema, obj_ty: Type) CompileError!Air.Inst.Ref { } } -fn zirUnionInitPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirUnionInit(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirUnionInitPtr", .{}); + const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const field_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const init_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + 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); + const field_name = try sema.resolveConstString(block, field_src, extra.field_name); + 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: []const u8, + field_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { + const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_src); + const field = union_ty.unionFields().values()[field_index]; + const init = try sema.coerce(block, field.ty, uncasted_init, init_src); + + if (try sema.resolveMaybeUndefVal(block, init_src, init)) |init_val| { + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); + return sema.addConstant(union_ty, try Value.Tag.@"union".create(sema.arena, .{ + .tag = tag_val, + .val = init_val, + })); + } + + try sema.requireRuntimeBlock(block, init_src); + _ = union_ty_src; + try sema.queueFullTypeResolution(union_ty); + return block.addUnionInit(union_ty, field_index, init); } fn zirStructInit( @@ -10592,10 +13915,10 @@ fn zirStructInit( try sema.errNote(block, other_field_src, msg, "other field here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } found_fields[field_index] = item.data.field_type; - field_inits[field_index] = sema.resolveInst(item.data.init); + field_inits[field_index] = try sema.resolveInst(item.data.init); } var root_msg: ?*Module.ErrorMsg = null; @@ -10619,9 +13942,7 @@ fn zirStructInit( } } return sema.finishStructInit(block, src, field_inits, root_msg, struct_obj, resolved_ty, is_ref); - } else if (resolved_ty.cast(Type.Payload.Union)) |union_payload| { - const union_obj = union_payload.data; - + } else if (resolved_ty.zigTypeTag() == .Union) { if (extra.data.fields_len != 1) { return sema.fail(block, src, "union initialization expects exactly one field", .{}); } @@ -10632,23 +13953,39 @@ fn zirStructInit( const field_src: LazySrcLoc = .{ .node_offset_back2tok = field_type_data.src_node }; const field_type_extra = sema.code.extraData(Zir.Inst.FieldType, field_type_data.payload_index).data; const field_name = sema.code.nullTerminatedString(field_type_extra.name_start); - const field_index_usize = union_obj.fields.getIndex(field_name) orelse - return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); - const field_index = @intCast(u32, field_index_usize); + const field_index = try sema.unionFieldIndex(block, resolved_ty, field_name, field_src); + const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); - if (is_ref) { - return sema.fail(block, src, "TODO: Sema.zirStructInit is_ref=true union", .{}); - } - - const init_inst = sema.resolveInst(item.data.init); + const init_inst = try sema.resolveInst(item.data.init); if (try sema.resolveMaybeUndefVal(block, field_src, init_inst)) |val| { - const tag_val = try Value.Tag.enum_field_index.create(sema.arena, field_index); - return sema.addConstant( + return sema.addConstantMaybeRef( + block, + src, resolved_ty, try Value.Tag.@"union".create(sema.arena, .{ .tag = tag_val, .val = val }), + is_ref, ); } - return sema.fail(block, src, "TODO: Sema.zirStructInit for runtime-known union values", .{}); + + if (is_ref) { + const target = sema.mod.getTarget(); + const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = resolved_ty, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + }); + const alloc = try block.addTy(.alloc, alloc_ty); + const field_ptr = try sema.unionFieldPtr(block, field_src, alloc, field_name, field_src, resolved_ty); + try sema.storePtr(block, src, field_ptr, init_inst); + const new_tag = try sema.addConstant(resolved_ty.unionTagTypeHypothetical(), tag_val); + _ = try block.addBinOp(.set_union_tag, alloc, new_tag); + return alloc; + } + + try sema.requireRuntimeBlock(block, src); + try sema.queueFullTypeResolution(resolved_ty); + return block.addUnionInit(resolved_ty, field_index, init_inst); + } else if (resolved_ty.isAnonStruct()) { + return sema.fail(block, src, "TODO anon struct init validation", .{}); } unreachable; } @@ -10666,15 +14003,15 @@ fn finishStructInit( const gpa = sema.gpa; if (root_msg) |msg| { - const fqn = try struct_obj.getFullyQualifiedName(gpa); + const fqn = try struct_obj.getFullyQualifiedName(sema.mod); defer gpa.free(fqn); try sema.mod.errNoteNonLazy( - struct_obj.srcLoc(), + struct_obj.srcLoc(sema.mod), msg, "struct '{s}' declared here", .{fqn}, ); - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } const is_comptime = for (field_inits) |field_init| { @@ -10688,12 +14025,17 @@ fn finishStructInit( for (field_inits) |field_init, i| { values[i] = (sema.resolveMaybeUndefVal(block, src, field_init) catch unreachable).?; } - const struct_val = try Value.Tag.@"struct".create(sema.arena, values); + const struct_val = try Value.Tag.aggregate.create(sema.arena, values); return sema.addConstantMaybeRef(block, src, struct_ty, struct_val, is_ref); } if (is_ref) { - const alloc = try block.addTy(.alloc, struct_ty); + const target = sema.mod.getTarget(); + const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = struct_ty, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + }); + const alloc = try block.addTy(.alloc, alloc_ty); for (field_inits) |field_init, i_usize| { const i = @intCast(u32, i_usize); const field_src = src; @@ -10705,15 +14047,94 @@ fn finishStructInit( } try sema.requireRuntimeBlock(block, src); - return block.addVectorInit(struct_ty, field_inits); + try sema.queueFullTypeResolution(struct_ty); + return block.addAggregateInit(struct_ty, field_inits); } -fn zirStructInitAnon(sema: *Sema, block: *Block, inst: Zir.Inst.Index, is_ref: bool) CompileError!Air.Inst.Ref { +fn zirStructInitAnon( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + is_ref: bool, +) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.StructInitAnon, inst_data.payload_index); + const types = try sema.arena.alloc(Type, extra.data.fields_len); + const values = try sema.arena.alloc(Value, types.len); + const names = try sema.arena.alloc([]const u8, types.len); + + const opt_runtime_src = rs: { + var runtime_src: ?LazySrcLoc = null; + var extra_index = extra.end; + for (types) |*field_ty, i| { + const item = sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index); + extra_index = item.end; + + names[i] = sema.code.nullTerminatedString(item.data.field_name); + const init = try sema.resolveInst(item.data.init); + field_ty.* = sema.typeOf(init); + const init_src = src; // TODO better source location + if (try sema.resolveMaybeUndefVal(block, init_src, init)) |init_val| { + values[i] = init_val; + } else { + values[i] = Value.initTag(.unreachable_value); + runtime_src = init_src; + } + } + break :rs runtime_src; + }; + + const tuple_ty = try Type.Tag.anon_struct.create(sema.arena, .{ + .names = names, + .types = types, + .values = values, + }); + + const runtime_src = opt_runtime_src orelse { + const tuple_val = try Value.Tag.aggregate.create(sema.arena, values); + return sema.addConstantMaybeRef(block, src, tuple_ty, tuple_val, is_ref); + }; + + try sema.requireRuntimeBlock(block, runtime_src); - _ = is_ref; - return sema.fail(block, src, "TODO: Sema.zirStructInitAnon", .{}); + if (is_ref) { + const target = sema.mod.getTarget(); + const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = tuple_ty, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + }); + const alloc = try block.addTy(.alloc, alloc_ty); + var extra_index = extra.end; + for (types) |field_ty, i_usize| { + const i = @intCast(u32, i_usize); + const item = sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index); + extra_index = item.end; + + const field_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .mutable = true, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + .pointee_type = field_ty, + }); + if (values[i].tag() == .unreachable_value) { + 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 alloc; + } + + const element_refs = try sema.arena.alloc(Air.Inst.Ref, types.len); + var extra_index = extra.end; + for (types) |_, i| { + const item = sema.code.extraData(Zir.Inst.StructInitAnon.Item, extra_index); + extra_index = item.end; + element_refs[i] = try sema.resolveInst(item.data.init); + } + + return block.addAggregateInit(tuple_ty, element_refs); } fn zirArrayInit( @@ -10728,19 +14149,26 @@ fn zirArrayInit( 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 != 0); + assert(args.len >= 2); // array_ty + at least one element - const resolved_args = try gpa.alloc(Air.Inst.Ref, args.len); - defer gpa.free(resolved_args); - - for (args) |arg, i| resolved_args[i] = sema.resolveInst(arg); + const array_ty = try sema.resolveType(block, src, args[0]); + const sentinel_val = array_ty.sentinel(); - const elem_ty = sema.typeOf(resolved_args[0]); + const resolved_args = try gpa.alloc(Air.Inst.Ref, args.len - 1 + @boolToInt(sentinel_val != null)); + defer gpa.free(resolved_args); + for (args[1..]) |arg, i| { + const resolved_arg = try sema.resolveInst(arg); + const arg_src = src; // TODO better source location + const elem_ty = if (array_ty.zigTypeTag() == .Struct) + array_ty.tupleFields().types[i] + else + array_ty.elemType2(); + resolved_args[i] = try sema.coerce(block, elem_ty, resolved_arg, arg_src); + } - const array_ty = try Type.Tag.array.create(sema.arena, .{ - .len = resolved_args.len, - .elem_type = elem_ty, - }); + if (sentinel_val) |some| { + resolved_args[resolved_args.len - 1] = try sema.addConstant(array_ty.elemType2(), some); + } const opt_runtime_src: ?LazySrcLoc = for (resolved_args) |arg| { const arg_src = src; // TODO better source location @@ -10756,24 +14184,42 @@ fn zirArrayInit( elem_vals[i] = (sema.resolveMaybeUndefVal(block, src, arg) catch unreachable).?; } - const array_val = try Value.Tag.array.create(sema.arena, elem_vals); + const array_val = try Value.Tag.aggregate.create(sema.arena, elem_vals); return sema.addConstantMaybeRef(block, src, array_ty, array_val, is_ref); }; try sema.requireRuntimeBlock(block, runtime_src); - try sema.resolveTypeLayout(block, src, elem_ty); + try sema.queueFullTypeResolution(array_ty); if (is_ref) { - const alloc_ty = try Type.ptr(sema.arena, .{ + const target = sema.mod.getTarget(); + const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = array_ty, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + .@"addrspace" = target_util.defaultAddressSpace(target, .local), }); const alloc = try block.addTy(.alloc, alloc_ty); - const elem_ptr_ty = try Type.ptr(sema.arena, .{ + if (array_ty.isTuple()) { + const types = array_ty.tupleFields().types; + for (resolved_args) |arg, i| { + const elem_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ + .mutable = true, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + .pointee_type = types[i], + }); + const elem_ptr_ty_ref = try sema.addType(elem_ptr_ty); + + const index = try sema.addIntUnsigned(Type.usize, i); + const elem_ptr = try block.addPtrElemPtrTypeRef(alloc, index, elem_ptr_ty_ref); + _ = try block.addBinOp(.store, elem_ptr, arg); + } + return alloc; + } + + const elem_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ .mutable = true, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), - .pointee_type = elem_ty, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + .pointee_type = array_ty.elemType2(), }); const elem_ptr_ty_ref = try sema.addType(elem_ptr_ty); @@ -10785,7 +14231,7 @@ fn zirArrayInit( return alloc; } - return block.addVectorInit(array_ty, resolved_args); + return block.addAggregateInit(array_ty, resolved_args); } fn zirArrayInitAnon( @@ -10805,7 +14251,7 @@ fn zirArrayInitAnon( const opt_runtime_src = rs: { var runtime_src: ?LazySrcLoc = null; for (operands) |operand, i| { - const elem = sema.resolveInst(operand); + const elem = try sema.resolveInst(operand); types[i] = sema.typeOf(elem); const operand_src = src; // TODO better source location if (try sema.resolveMaybeUndefVal(block, operand_src, elem)) |val| { @@ -10824,23 +14270,30 @@ fn zirArrayInitAnon( }); const runtime_src = opt_runtime_src orelse { - const tuple_val = try Value.Tag.@"struct".create(sema.arena, values); + const tuple_val = try Value.Tag.aggregate.create(sema.arena, values); return sema.addConstantMaybeRef(block, src, tuple_ty, tuple_val, is_ref); }; try sema.requireRuntimeBlock(block, runtime_src); if (is_ref) { - const alloc = try block.addTy(.alloc, tuple_ty); + const target = sema.mod.getTarget(); + const alloc_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = tuple_ty, + .@"addrspace" = target_util.defaultAddressSpace(target, .local), + }); + const alloc = try block.addTy(.alloc, alloc_ty); for (operands) |operand, i_usize| { const i = @intCast(u32, i_usize); - const field_ptr_ty = try Type.ptr(sema.arena, .{ + const field_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ .mutable = true, - .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .local), + .@"addrspace" = target_util.defaultAddressSpace(target, .local), .pointee_type = types[i], }); - const field_ptr = try block.addStructFieldPtr(alloc, i, field_ptr_ty); - _ = try block.addBinOp(.store, field_ptr, sema.resolveInst(operand)); + if (values[i].tag() == .unreachable_value) { + const field_ptr = try block.addStructFieldPtr(alloc, i, field_ptr_ty); + _ = try block.addBinOp(.store, field_ptr, try sema.resolveInst(operand)); + } } return alloc; @@ -10848,10 +14301,10 @@ fn zirArrayInitAnon( const element_refs = try sema.arena.alloc(Air.Inst.Ref, operands.len); for (operands) |operand, i| { - element_refs[i] = sema.resolveInst(operand); + element_refs[i] = try sema.resolveInst(operand); } - return block.addVectorInit(tuple_ty, element_refs); + return block.addAggregateInit(tuple_ty, element_refs); } fn addConstantMaybeRef( @@ -10869,84 +14322,133 @@ fn addConstantMaybeRef( const decl = try anon_decl.finish( try ty.copy(anon_decl.arena()), try val.copy(anon_decl.arena()), + 0, // default alignment ); return sema.analyzeDeclRef(decl); } fn zirFieldTypeRef(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirFieldTypeRef", .{}); + const extra = sema.code.extraData(Zir.Inst.FieldTypeRef, inst_data.payload_index).data; + const ty_src = inst_data.src(); + const field_src = inst_data.src(); + const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type); + const field_name = try sema.resolveConstString(block, field_src, extra.field_name); + return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src); } fn zirFieldType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.FieldType, inst_data.payload_index).data; - const src = inst_data.src(); + const ty_src = inst_data.src(); + const field_src = inst_data.src(); + const aggregate_ty = try sema.resolveType(block, ty_src, extra.container_type); + if (aggregate_ty.tag() == .var_args_param) return sema.addType(aggregate_ty); const field_name = sema.code.nullTerminatedString(extra.name_start); - const unresolved_ty = try sema.resolveType(block, src, extra.container_type); - const resolved_ty = try sema.resolveTypeFields(block, src, unresolved_ty); - switch (resolved_ty.zigTypeTag()) { - .Struct => { - const struct_obj = resolved_ty.castTag(.@"struct").?.data; - const field = struct_obj.fields.get(field_name) orelse - return sema.failWithBadStructFieldAccess(block, struct_obj, src, field_name); - return sema.addType(field.ty); - }, - .Union => { - const union_obj = resolved_ty.cast(Type.Payload.Union).?.data; - const field = union_obj.fields.get(field_name) orelse - return sema.failWithBadUnionFieldAccess(block, union_obj, src, field_name); - return sema.addType(field.ty); - }, - else => return sema.fail(block, src, "expected struct or union; found '{}'", .{ - resolved_ty, - }), - } + return sema.fieldType(block, aggregate_ty, field_name, field_src, ty_src); } -fn zirErrorReturnTrace( +fn fieldType( sema: *Sema, block: *Block, - extended: Zir.Inst.Extended.InstData, + aggregate_ty: Type, + field_name: []const u8, + field_src: LazySrcLoc, + ty_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; - return sema.fail(block, src, "TODO: Sema.zirErrorReturnTrace", .{}); + const resolved_ty = try sema.resolveTypeFields(block, ty_src, aggregate_ty); + var cur_ty = resolved_ty; + while (true) { + switch (cur_ty.zigTypeTag()) { + .Struct => { + if (cur_ty.isAnonStruct()) { + const field_index = try sema.anonStructFieldIndex(block, cur_ty, field_name, field_src); + return sema.addType(cur_ty.tupleFields().types[field_index]); + } + const struct_obj = cur_ty.castTag(.@"struct").?.data; + const field = struct_obj.fields.get(field_name) orelse + return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name); + return sema.addType(field.ty); + }, + .Union => { + const union_obj = cur_ty.cast(Type.Payload.Union).?.data; + const field = union_obj.fields.get(field_name) orelse + return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); + return sema.addType(field.ty); + }, + .Optional => { + if (cur_ty.castTag(.optional)) |some| { + // 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 = some.data; + continue; + } + }, + .ErrorUnion => { + cur_ty = cur_ty.errorUnionPayload(); + continue; + }, + else => {}, + } + return sema.fail(block, ty_src, "expected struct or union; found '{}'", .{ + resolved_ty.fmt(sema.mod), + }); + } } -fn zirFrame( +fn zirErrorReturnTrace( sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; - return sema.fail(block, src, "TODO: Sema.zirFrame", .{}); + const src = LazySrcLoc.nodeOffset(@bitCast(i32, extended.operand)); + return sema.getErrorReturnTrace(block, src); } -fn zirFrameAddress( +fn getErrorReturnTrace(sema: *Sema, block: *Block, src: LazySrcLoc) CompileError!Air.Inst.Ref { + const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace"); + const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); + const opt_ptr_stack_trace_ty = try Type.Tag.optional_single_mut_pointer.create(sema.arena, stack_trace_ty); + + // TODO implement this feature in all the backends and then delete this check. + const backend_supports_error_return_tracing = + sema.mod.comp.bin_file.options.use_llvm; + + if (sema.owner_func != null and + sema.owner_func.?.calls_or_awaits_errorable_fn and + sema.mod.comp.bin_file.options.error_return_tracing and + backend_supports_error_return_tracing) + { + return block.addTy(.err_return_trace, opt_ptr_stack_trace_ty); + } + return sema.addConstant(opt_ptr_stack_trace_ty, Value.@"null"); +} + +fn zirFrame( sema: *Sema, block: *Block, extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { - const src: LazySrcLoc = .{ .node_offset = @bitCast(i32, extended.operand) }; - return sema.fail(block, src, "TODO: Sema.zirFrameAddress", .{}); + const src = LazySrcLoc.nodeOffset(@bitCast(i32, extended.operand)); + return sema.fail(block, src, "TODO: Sema.zirFrame", .{}); } fn zirAlignOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const ty = try sema.resolveType(block, operand_src, inst_data.operand); - const resolved_ty = try sema.resolveTypeFields(block, operand_src, ty); - try sema.resolveTypeLayout(block, operand_src, resolved_ty); const target = sema.mod.getTarget(); - const abi_align = resolved_ty.abiAlignment(target); - return sema.addIntUnsigned(Type.comptime_int, abi_align); + const val = try ty.lazyAbiAlignment(target, sema.arena); + if (val.tag() == .lazy_align) { + try sema.queueFullTypeResolution(ty); + } + return sema.addConstant(Type.comptime_int, val); } fn zirBoolToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { if (val.isUndef()) return sema.addConstUndef(Type.initTag(.u1)); const bool_ints = [2]Air.Inst.Ref{ .zero, .one }; @@ -10959,7 +14461,7 @@ fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); _ = src; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| { @@ -10972,19 +14474,82 @@ fn zirErrorName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A return block.addUnOp(.error_name, operand); } -fn zirUnaryMath(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirUnaryMath( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + air_tag: Air.Inst.Tag, + eval: fn (Value, Type, Allocator, std.Target) Allocator.Error!Value, +) CompileError!Air.Inst.Ref { + const tracy = trace(@src()); + defer tracy.end(); + const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirUnaryMath", .{}); + const operand = try sema.resolveInst(inst_data.operand); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_ty = sema.typeOf(operand); + const target = sema.mod.getTarget(); + + switch (operand_ty.zigTypeTag()) { + .ComptimeFloat, .Float => {}, + .Vector => { + const scalar_ty = operand_ty.scalarType(); + switch (scalar_ty.zigTypeTag()) { + .ComptimeFloat, .Float => {}, + else => return sema.fail(block, operand_src, "expected vector of floats or float type, found '{}'", .{scalar_ty.fmt(sema.mod)}), + } + }, + else => return sema.fail(block, operand_src, "expected vector of floats or float type, found '{}'", .{operand_ty.fmt(sema.mod)}), + } + + switch (operand_ty.zigTypeTag()) { + .Vector => { + const scalar_ty = operand_ty.scalarType(); + const vec_len = operand_ty.vectorLen(); + const result_ty = try Type.vector(sema.arena, vec_len, scalar_ty); + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) + return sema.addConstUndef(result_ty); + + var elem_buf: Value.ElemValueBuffer = undefined; + const elems = try sema.arena.alloc(Value, vec_len); + for (elems) |*elem, i| { + const elem_val = val.elemValueBuffer(sema.mod, i, &elem_buf); + elem.* = try eval(elem_val, scalar_ty, sema.arena, target); + } + return sema.addConstant( + result_ty, + try Value.Tag.aggregate.create(sema.arena, elems), + ); + } + + try sema.requireRuntimeBlock(block, operand_src); + return block.addUnOp(air_tag, operand); + }, + .ComptimeFloat, .Float => { + if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |operand_val| { + if (operand_val.isUndef()) + return sema.addConstUndef(operand_ty); + const result_val = try eval(operand_val, operand_ty, sema.arena, target); + return sema.addConstant(operand_ty, result_val); + } + + try sema.requireRuntimeBlock(block, operand_src); + return block.addUnOp(air_tag, operand); + }, + else => unreachable, + } } fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const src = inst_data.src(); - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); + const mod = sema.mod; + try sema.resolveTypeLayout(block, operand_src, operand_ty); const enum_ty = switch (operand_ty.zigTypeTag()) { .EnumLiteral => { const val = try sema.resolveConstValue(block, operand_src, operand); @@ -10993,34 +14558,36 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air }, .Enum => operand_ty, .Union => operand_ty.unionTagType() orelse { - const decl = operand_ty.getOwnerDecl(); + const decl_index = operand_ty.getOwnerDecl(); + const decl = mod.declPtr(decl_index); const msg = msg: { const msg = try sema.errMsg(block, src, "union '{s}' is untagged", .{ decl.name, }); errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy(decl.srcLoc(), msg, "declared here", .{}); + try mod.errNoteNonLazy(decl.srcLoc(), msg, "declared here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }, - else => return sema.fail(block, operand_src, "expected enum or union; found {}", .{ - operand_ty, + else => return sema.fail(block, operand_src, "expected enum or union; found '{}'", .{ + operand_ty.fmt(mod), }), }; - const enum_decl = enum_ty.getOwnerDecl(); + const enum_decl_index = enum_ty.getOwnerDecl(); 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) orelse { + const field_index = enum_ty.enumTagFieldIndex(val, mod) orelse { + const enum_decl = mod.declPtr(enum_decl_index); const msg = msg: { - const msg = try sema.errMsg(block, src, "no field with value {} in enum '{s}'", .{ - casted_operand, enum_decl.name, + const msg = try sema.errMsg(block, src, "no field with value '{}' in enum '{s}'", .{ + val.fmtValue(enum_ty, sema.mod), enum_decl.name, }); errdefer msg.destroy(sema.gpa); - try sema.mod.errNoteNonLazy(enum_decl.srcLoc(), msg, "declared here", .{}); + try mod.errNoteNonLazy(enum_decl.srcLoc(), msg, "declared here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }; const field_name = enum_ty.enumFieldName(field_index); return sema.addStrLit(block, field_name); @@ -11032,16 +14599,18 @@ fn zirTagName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air } fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { + const mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - const type_info_ty = try sema.resolveBuiltinTypeFields(block, src, "TypeInfo"); - const uncasted_operand = sema.resolveInst(inst_data.operand); + const type_info_ty = try sema.resolveBuiltinTypeFields(block, src, "Type"); + const uncasted_operand = try sema.resolveInst(inst_data.operand); const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const type_info = try sema.coerce(block, type_info_ty, uncasted_operand, operand_src); const val = try sema.resolveConstValue(block, operand_src, type_info); const union_val = val.cast(Value.Payload.Union).?.data; const tag_ty = type_info_ty.unionTagType().?; - const tag_index = tag_ty.enumTagFieldIndex(union_val.tag).?; + const target = mod.getTarget(); + const tag_index = tag_ty.enumTagFieldIndex(union_val.tag, mod).?; switch (@intToEnum(std.builtin.TypeId, tag_index)) { .Type => return Air.Inst.Ref.type_type, .Void => return Air.Inst.Ref.void_type, @@ -11054,13 +14623,13 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I .AnyFrame => return Air.Inst.Ref.anyframe_type, .EnumLiteral => return Air.Inst.Ref.enum_literal_type, .Int => { - const struct_val = union_val.val.castTag(.@"struct").?.data; + const struct_val = union_val.val.castTag(.aggregate).?.data; // TODO use reflection instead of magic numbers here const signedness_val = struct_val[0]; const bits_val = struct_val[1]; const signedness = signedness_val.toEnum(std.builtin.Signedness); - const bits = @intCast(u16, bits_val.toUnsignedInt()); + const bits = @intCast(u16, bits_val.toUnsignedInt(target)); const ty = switch (signedness) { .signed => try Type.Tag.int_signed.create(sema.arena, bits), .unsigned => try Type.Tag.int_unsigned.create(sema.arena, bits), @@ -11068,21 +14637,39 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I return sema.addType(ty); }, .Vector => { - const struct_val = union_val.val.castTag(.@"struct").?.data; + const struct_val = union_val.val.castTag(.aggregate).?.data; // TODO use reflection instead of magic numbers here const len_val = struct_val[0]; const child_val = struct_val[1]; - const len = len_val.toUnsignedInt(); + const len = len_val.toUnsignedInt(target); var buffer: Value.ToTypeBuffer = undefined; const child_ty = child_val.toType(&buffer); + try sema.checkVectorElemType(block, src, child_ty); + const ty = try Type.vector(sema.arena, len, try child_ty.copy(sema.arena)); return sema.addType(ty); }, - .Float => return sema.fail(block, src, "TODO: Sema.zirReify for Float", .{}), + .Float => { + const struct_val = union_val.val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // bits: comptime_int, + const bits_val = struct_val[0]; + + const bits = @intCast(u16, bits_val.toUnsignedInt(target)); + const ty = switch (bits) { + 16 => Type.@"f16", + 32 => Type.@"f32", + 64 => Type.@"f64", + 80 => Type.@"f80", + 128 => Type.@"f128", + else => return sema.fail(block, src, "{}-bit float unsupported", .{bits}), + }; + return sema.addType(ty); + }, .Pointer => { - const struct_val = union_val.val.castTag(.@"struct").?.data; + const struct_val = union_val.val.castTag(.aggregate).?.data; // TODO use reflection instead of magic numbers here const size_val = struct_val[0]; const is_const_val = struct_val[1]; @@ -11096,36 +14683,566 @@ fn zirReify(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I var buffer: Value.ToTypeBuffer = undefined; const child_ty = child_val.toType(&buffer); + const ptr_size = size_val.toEnum(std.builtin.Type.Pointer.Size); + + var actual_sentinel: ?Value = null; if (!sentinel_val.isNull()) { - return sema.fail(block, src, "TODO: implement zirReify for pointer with non-null sentinel", .{}); + if (ptr_size == .One or ptr_size == .C) { + return sema.fail(block, src, "sentinels are only allowed on slices and unknown-length pointers", .{}); + } + const sentinel_ptr_val = sentinel_val.castTag(.opt_payload).?.data; + const ptr_ty = try Type.ptr(sema.arena, mod, .{ + .@"addrspace" = .generic, + .pointee_type = child_ty, + }); + actual_sentinel = (try sema.pointerDeref(block, src, sentinel_ptr_val, ptr_ty)).?; } - const ty = try Type.ptr(sema.arena, .{ - .size = size_val.toEnum(std.builtin.TypeInfo.Pointer.Size), + const ty = try Type.ptr(sema.arena, mod, .{ + .size = ptr_size, .mutable = !is_const_val.toBool(), .@"volatile" = is_volatile_val.toBool(), - .@"align" = @intCast(u8, alignment_val.toUnsignedInt()), // TODO: Validate this value. + .@"align" = @intCast(u29, alignment_val.toUnsignedInt(target)), // TODO: Validate this value. .@"addrspace" = address_space_val.toEnum(std.builtin.AddressSpace), .pointee_type = try child_ty.copy(sema.arena), .@"allowzero" = is_allowzero_val.toBool(), - .sentinel = null, + .sentinel = actual_sentinel, + }); + return sema.addType(ty); + }, + .Array => { + const struct_val = union_val.val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // len: comptime_int, + const len_val = struct_val[0]; + // child: type, + const child_val = struct_val[1]; + // sentinel: ?*const anyopaque, + const sentinel_val = struct_val[2]; + + const len = len_val.toUnsignedInt(target); + var buffer: Value.ToTypeBuffer = undefined; + const child_ty = try child_val.toType(&buffer).copy(sema.arena); + const sentinel = if (sentinel_val.castTag(.opt_payload)) |p| blk: { + const ptr_ty = try Type.ptr(sema.arena, mod, .{ + .@"addrspace" = .generic, + .pointee_type = child_ty, + }); + break :blk (try sema.pointerDeref(block, src, p.data, ptr_ty)).?; + } else null; + + const ty = try Type.array(sema.arena, len, sentinel, child_ty, sema.mod); + return sema.addType(ty); + }, + .Optional => { + const struct_val = union_val.val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // child: type, + const child_val = struct_val[0]; + + var buffer: Value.ToTypeBuffer = undefined; + const child_ty = try child_val.toType(&buffer).copy(sema.arena); + + const ty = try Type.optional(sema.arena, child_ty); + return sema.addType(ty); + }, + .ErrorUnion => { + const struct_val = union_val.val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // error_set: type, + const error_set_val = struct_val[0]; + // payload: type, + const payload_val = struct_val[1]; + + var buffer: Value.ToTypeBuffer = undefined; + const error_set_ty = try error_set_val.toType(&buffer).copy(sema.arena); + const payload_ty = try payload_val.toType(&buffer).copy(sema.arena); + + const ty = try Type.Tag.error_union.create(sema.arena, .{ + .error_set = error_set_ty, + .payload = payload_ty, }); return sema.addType(ty); }, - .Array => return sema.fail(block, src, "TODO: Sema.zirReify for Array", .{}), - .Struct => return sema.fail(block, src, "TODO: Sema.zirReify for Struct", .{}), - .Optional => return sema.fail(block, src, "TODO: Sema.zirReify for Optional", .{}), - .ErrorUnion => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorUnion", .{}), - .ErrorSet => return sema.fail(block, src, "TODO: Sema.zirReify for ErrorSet", .{}), - .Enum => return sema.fail(block, src, "TODO: Sema.zirReify for Enum", .{}), - .Union => return sema.fail(block, src, "TODO: Sema.zirReify for Union", .{}), + .ErrorSet => { + const payload_val = union_val.val.optionalValue() orelse + return sema.addType(Type.initTag(.anyerror)); + const slice_val = payload_val.castTag(.slice).?.data; + const decl_index = slice_val.ptr.pointerDecl().?; + try sema.ensureDeclAnalyzed(decl_index); + const decl = mod.declPtr(decl_index); + const array_val = decl.val.castTag(.aggregate).?.data; + + var names: Module.ErrorSet.NameMap = .{}; + try names.ensureUnusedCapacity(sema.arena, array_val.len); + for (array_val) |elem_val| { + const struct_val = elem_val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // error_set: type, + const name_val = struct_val[0]; + const name_str = try name_val.toAllocatedBytes(Type.initTag(.const_slice_u8), sema.arena, sema.mod); + + const kv = try mod.getErrorValue(name_str); + names.putAssumeCapacityNoClobber(kv.key, {}); + } + + // names must be sorted + Module.ErrorSet.sortNames(&names); + const ty = try Type.Tag.error_set_merged.create(sema.arena, names); + return sema.addType(ty); + }, + .Struct => { + // TODO use reflection instead of magic numbers here + const struct_val = union_val.val.castTag(.aggregate).?.data; + // layout: containerlayout, + const layout_val = struct_val[0]; + // fields: []const enumfield, + const fields_val = struct_val[1]; + // decls: []const declaration, + const decls_val = struct_val[2]; + // is_tuple: bool, + const is_tuple_val = struct_val[3]; + + // Decls + if (decls_val.sliceLen(mod) > 0) { + return sema.fail(block, src, "reified structs must have no decls", .{}); + } + + return if (is_tuple_val.toBool()) + try sema.reifyTuple(block, src, fields_val) + else + try sema.reifyStruct(block, inst, src, layout_val, fields_val); + }, + .Enum => { + const struct_val = union_val.val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // layout: ContainerLayout, + const layout_val = struct_val[0]; + // tag_type: type, + const tag_type_val = struct_val[1]; + // fields: []const EnumField, + const fields_val = struct_val[2]; + // decls: []const Declaration, + const decls_val = struct_val[3]; + // is_exhaustive: bool, + const is_exhaustive_val = struct_val[4]; + + // enum layout is always auto + const layout = layout_val.toEnum(std.builtin.Type.ContainerLayout); + if (layout != .Auto) { + return sema.fail(block, src, "reified enums must have a layout .Auto", .{}); + } + + // Decls + if (decls_val.sliceLen(mod) > 0) { + return sema.fail(block, src, "reified enums must have no decls", .{}); + } + + const gpa = sema.gpa; + var new_decl_arena = std.heap.ArenaAllocator.init(gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + // Define our empty enum decl + const enum_obj = try new_decl_arena_allocator.create(Module.EnumFull); + const enum_ty_payload = try new_decl_arena_allocator.create(Type.Payload.EnumFull); + enum_ty_payload.* = .{ + .base = .{ + .tag = if (!is_exhaustive_val.toBool()) + .enum_nonexhaustive + else + .enum_full, + }, + .data = enum_obj, + }; + const enum_ty = Type.initPayload(&enum_ty_payload.base); + const enum_val = try Value.Tag.ty.create(new_decl_arena_allocator, enum_ty); + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ + .ty = Type.type, + .val = enum_val, + }, .anon, "enum", null); + const new_decl = mod.declPtr(new_decl_index); + new_decl.owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); + + // Enum tag type + var buffer: Value.ToTypeBuffer = undefined; + const int_tag_ty = try tag_type_val.toType(&buffer).copy(new_decl_arena_allocator); + + enum_obj.* = .{ + .owner_decl = new_decl_index, + .tag_ty = int_tag_ty, + .tag_ty_inferred = false, + .fields = .{}, + .values = .{}, + .node_offset = src.node_offset.x, + .namespace = .{ + .parent = block.namespace, + .ty = enum_ty, + .file_scope = block.getFileScope(), + }, + }; + + // Fields + const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); + if (fields_len > 0) { + try enum_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); + try enum_obj.values.ensureTotalCapacityContext(new_decl_arena_allocator, fields_len, .{ + .ty = enum_obj.tag_ty, + .mod = mod, + }); + + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); + const field_struct_val = elem_val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // value: comptime_int + const value_val = field_struct_val[1]; + + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + new_decl_arena_allocator, + sema.mod, + ); + + const gop = enum_obj.fields.getOrPutAssumeCapacity(field_name); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate enum tag {s}", .{field_name}); + } + + const copied_tag_val = try value_val.copy(new_decl_arena_allocator); + enum_obj.values.putAssumeCapacityNoClobberContext(copied_tag_val, {}, .{ + .ty = enum_obj.tag_ty, + .mod = mod, + }); + } + } + + try new_decl.finalizeNewArena(&new_decl_arena); + return sema.analyzeDeclVal(block, src, new_decl_index); + }, + .Opaque => { + const struct_val = union_val.val.castTag(.aggregate).?.data; + // decls: []const Declaration, + const decls_val = struct_val[0]; + + // Decls + if (decls_val.sliceLen(mod) > 0) { + return sema.fail(block, src, "reified opaque must have no decls", .{}); + } + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + const opaque_obj = try new_decl_arena_allocator.create(Module.Opaque); + const opaque_ty_payload = try new_decl_arena_allocator.create(Type.Payload.Opaque); + opaque_ty_payload.* = .{ + .base = .{ .tag = .@"opaque" }, + .data = opaque_obj, + }; + const opaque_ty = Type.initPayload(&opaque_ty_payload.base); + const opaque_val = try Value.Tag.ty.create(new_decl_arena_allocator, opaque_ty); + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ + .ty = Type.type, + .val = opaque_val, + }, .anon, "opaque", null); + const new_decl = mod.declPtr(new_decl_index); + new_decl.owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); + + opaque_obj.* = .{ + .owner_decl = new_decl_index, + .node_offset = src.node_offset.x, + .namespace = .{ + .parent = block.namespace, + .ty = opaque_ty, + .file_scope = block.getFileScope(), + }, + }; + + try new_decl.finalizeNewArena(&new_decl_arena); + return sema.analyzeDeclVal(block, src, new_decl_index); + }, + .Union => { + // TODO use reflection instead of magic numbers here + const struct_val = union_val.val.castTag(.aggregate).?.data; + // layout: containerlayout, + const layout_val = struct_val[0]; + // tag_type: ?type, + const tag_type_val = struct_val[1]; + // fields: []const enumfield, + const fields_val = struct_val[2]; + // decls: []const declaration, + const decls_val = struct_val[3]; + + // Decls + if (decls_val.sliceLen(mod) > 0) { + return sema.fail(block, src, "reified unions must have no decls", .{}); + } + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + const union_obj = try new_decl_arena_allocator.create(Module.Union); + const type_tag: Type.Tag = if (!tag_type_val.isNull()) .union_tagged else .@"union"; + const union_payload = try new_decl_arena_allocator.create(Type.Payload.Union); + union_payload.* = .{ + .base = .{ .tag = type_tag }, + .data = union_obj, + }; + const union_ty = Type.initPayload(&union_payload.base); + const new_union_val = try Value.Tag.ty.create(new_decl_arena_allocator, union_ty); + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ + .ty = Type.type, + .val = new_union_val, + }, .anon, "union", null); + const new_decl = mod.declPtr(new_decl_index); + new_decl.owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); + union_obj.* = .{ + .owner_decl = new_decl_index, + .tag_ty = Type.initTag(.@"null"), + .fields = .{}, + .node_offset = src.node_offset.x, + .zir_index = inst, + .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout), + .status = .have_field_types, + .namespace = .{ + .parent = block.namespace, + .ty = union_ty, + .file_scope = block.getFileScope(), + }, + }; + + // Tag type + const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); + union_obj.tag_ty = if (tag_type_val.optionalValue()) |payload_val| blk: { + var buffer: Value.ToTypeBuffer = undefined; + break :blk try payload_val.toType(&buffer).copy(new_decl_arena_allocator); + } else try sema.generateUnionTagTypeSimple(block, fields_len, null); + + // Fields + if (fields_len > 0) { + try union_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); + + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); + const field_struct_val = elem_val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // field_type: type, + const field_type_val = field_struct_val[1]; + // alignment: comptime_int, + const alignment_val = field_struct_val[2]; + + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + new_decl_arena_allocator, + sema.mod, + ); + + const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate union field {s}", .{field_name}); + } + + var buffer: Value.ToTypeBuffer = undefined; + gop.value_ptr.* = .{ + .ty = try field_type_val.toType(&buffer).copy(new_decl_arena_allocator), + .abi_align = @intCast(u32, alignment_val.toUnsignedInt(target)), + }; + } + } + + try new_decl.finalizeNewArena(&new_decl_arena); + return sema.analyzeDeclVal(block, src, new_decl_index); + }, .Fn => return sema.fail(block, src, "TODO: Sema.zirReify for Fn", .{}), .BoundFn => @panic("TODO delete BoundFn from the language"), - .Opaque => return sema.fail(block, src, "TODO: Sema.zirReify for Opaque", .{}), - .Frame => return sema.fail(block, src, "TODO: Sema.zirReify for Frame", .{}), + .Frame => @panic("TODO implement https://github.com/ziglang/zig/issues/10710"), } } +fn reifyTuple( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + fields_val: Value, +) CompileError!Air.Inst.Ref { + const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(sema.mod)); + if (fields_len == 0) return sema.addType(Type.initTag(.empty_struct_literal)); + + const types = try sema.arena.alloc(Type, fields_len); + const values = try sema.arena.alloc(Value, fields_len); + + var used_fields: std.AutoArrayHashMapUnmanaged(u32, void) = .{}; + defer used_fields.deinit(sema.gpa); + try used_fields.ensureTotalCapacity(sema.gpa, fields_len); + + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); + const field_struct_val = elem_val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // field_type: type, + const field_type_val = field_struct_val[1]; + //default_value: ?*const anyopaque, + const default_value_val = field_struct_val[2]; + + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + sema.arena, + sema.mod, + ); + + const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch |err| { + return sema.fail( + block, + src, + "tuple cannot have non-numeric field '{s}': {}", + .{ field_name, err }, + ); + }; + + if (field_index >= fields_len) { + return sema.fail( + block, + src, + "tuple field {} exceeds tuple field count", + .{field_index}, + ); + } + + const gop = used_fields.getOrPutAssumeCapacity(field_index); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate tuple field {}", .{field_index}); + } + + const default_val = if (default_value_val.optionalValue()) |opt_val| blk: { + const payload_val = if (opt_val.pointerDecl()) |opt_decl| + sema.mod.declPtr(opt_decl).val + else + opt_val; + break :blk try payload_val.copy(sema.arena); + } else Value.initTag(.unreachable_value); + + var buffer: Value.ToTypeBuffer = undefined; + types[field_index] = try field_type_val.toType(&buffer).copy(sema.arena); + values[field_index] = default_val; + } + + const ty = try Type.Tag.tuple.create(sema.arena, .{ + .types = types, + .values = values, + }); + return sema.addType(ty); +} + +fn reifyStruct( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + src: LazySrcLoc, + layout_val: Value, + fields_val: Value, +) CompileError!Air.Inst.Ref { + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + const struct_obj = try new_decl_arena_allocator.create(Module.Struct); + const struct_ty = try Type.Tag.@"struct".create(new_decl_arena_allocator, struct_obj); + const new_struct_val = try Value.Tag.ty.create(new_decl_arena_allocator, struct_ty); + const mod = sema.mod; + const new_decl_index = try sema.createAnonymousDeclTypeNamed(block, .{ + .ty = Type.type, + .val = new_struct_val, + }, .anon, "struct", null); + const new_decl = mod.declPtr(new_decl_index); + new_decl.owns_tv = true; + errdefer mod.abortAnonDecl(new_decl_index); + struct_obj.* = .{ + .owner_decl = new_decl_index, + .fields = .{}, + .node_offset = src.node_offset.x, + .zir_index = inst, + .layout = layout_val.toEnum(std.builtin.Type.ContainerLayout), + .status = .have_field_types, + .known_non_opv = false, + .namespace = .{ + .parent = block.namespace, + .ty = struct_ty, + .file_scope = block.getFileScope(), + }, + }; + + const target = mod.getTarget(); + + // Fields + const fields_len = try sema.usizeCast(block, src, fields_val.sliceLen(mod)); + try struct_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); + var i: usize = 0; + while (i < fields_len) : (i += 1) { + const elem_val = try fields_val.elemValue(sema.mod, sema.arena, i); + const field_struct_val = elem_val.castTag(.aggregate).?.data; + // TODO use reflection instead of magic numbers here + // name: []const u8 + const name_val = field_struct_val[0]; + // field_type: type, + const field_type_val = field_struct_val[1]; + //default_value: ?*const anyopaque, + const default_value_val = field_struct_val[2]; + // is_comptime: bool, + const is_comptime_val = field_struct_val[3]; + // alignment: comptime_int, + const alignment_val = field_struct_val[4]; + + const field_name = try name_val.toAllocatedBytes( + Type.initTag(.const_slice_u8), + new_decl_arena_allocator, + mod, + ); + + const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name); + if (gop.found_existing) { + // TODO: better source location + return sema.fail(block, src, "duplicate struct field {s}", .{field_name}); + } + + const default_val = if (default_value_val.optionalValue()) |opt_val| blk: { + const payload_val = if (opt_val.pointerDecl()) |opt_decl| + mod.declPtr(opt_decl).val + else + opt_val; + break :blk try payload_val.copy(new_decl_arena_allocator); + } else Value.initTag(.unreachable_value); + + var buffer: Value.ToTypeBuffer = undefined; + gop.value_ptr.* = .{ + .ty = try field_type_val.toType(&buffer).copy(new_decl_arena_allocator), + .abi_align = @intCast(u32, alignment_val.toUnsignedInt(target)), + .default_val = default_val, + .is_comptime = is_comptime_val.toBool(), + .offset = undefined, + }; + } + + try new_decl.finalizeNewArena(&new_decl_arena); + return sema.analyzeDeclVal(block, src, new_decl_index); +} + fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; @@ -11134,11 +15251,12 @@ fn zirTypeName(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai var anon_decl = try block.startAnonDecl(LazySrcLoc.unneeded); defer anon_decl.deinit(); - const bytes = try ty.nameAlloc(anon_decl.arena()); + const bytes = try ty.nameAllocArena(anon_decl.arena(), sema.mod); const new_decl = try anon_decl.finish( try Type.Tag.array_u8_sentinel_0.create(anon_decl.arena(), bytes.len), try Value.Tag.bytes.create(anon_decl.arena(), bytes[0 .. bytes.len + 1]), + 0, // default alignment ); return sema.analyzeDeclRef(new_decl); @@ -11162,21 +15280,17 @@ fn zirFloatToInt(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const dest_ty = try sema.resolveType(block, ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const operand = try sema.resolveInst(extra.rhs); const operand_ty = sema.typeOf(operand); _ = try sema.checkIntType(block, ty_src, dest_ty); try sema.checkFloatType(block, operand_src, operand_ty); if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { - const target = sema.mod.getTarget(); - const result_val = val.floatToInt(sema.arena, dest_ty, target) catch |err| switch (err) { - error.FloatCannotFit => { - return sema.fail(block, operand_src, "integer value {d} cannot be stored in type '{}'", .{ std.math.floor(val.toFloat(f64)), dest_ty }); - }, - else => |e| return e, - }; + const result_val = try sema.floatToInt(block, operand_src, val, operand_ty, dest_ty); return sema.addConstant(dest_ty, result_val); + } else if (dest_ty.zigTypeTag() == .ComptimeInt) { + return sema.failWithNeededComptime(block, operand_src); } try sema.requireRuntimeBlock(block, operand_src); @@ -11189,7 +15303,7 @@ fn zirIntToFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const dest_ty = try sema.resolveType(block, ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const operand = try sema.resolveInst(extra.rhs); const operand_ty = sema.typeOf(operand); try sema.checkFloatType(block, ty_src, dest_ty); @@ -11197,8 +15311,10 @@ fn zirIntToFloat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { const target = sema.mod.getTarget(); - const result_val = try val.intToFloat(sema.arena, dest_ty, target); + const result_val = try val.intToFloat(sema.arena, operand_ty, dest_ty, target); return sema.addConstant(dest_ty, result_val); + } else if (dest_ty.zigTypeTag() == .ComptimeFloat) { + return sema.failWithNeededComptime(block, operand_src); } try sema.requireRuntimeBlock(block, operand_src); @@ -11212,20 +15328,22 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; - const operand_res = sema.resolveInst(extra.rhs); + const operand_res = try sema.resolveInst(extra.rhs); const operand_coerced = try sema.coerce(block, Type.usize, operand_res, operand_src); const type_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const type_res = try sema.resolveType(block, src, extra.lhs); try sema.checkPtrType(block, type_src, type_res); + try sema.resolveTypeLayout(block, src, type_res.elemType2()); const ptr_align = type_res.ptrAlignment(sema.mod.getTarget()); + const target = sema.mod.getTarget(); if (try sema.resolveDefinedValue(block, operand_src, operand_coerced)) |val| { - const addr = val.toUnsignedInt(); + const addr = val.toUnsignedInt(target); if (!type_res.isAllowzeroPtr() and addr == 0) - return sema.fail(block, operand_src, "pointer type '{}' does not allow address zero", .{type_res}); + return sema.fail(block, operand_src, "pointer type '{}' does not allow address zero", .{type_res.fmt(sema.mod)}); if (addr != 0 and addr % ptr_align != 0) - return sema.fail(block, operand_src, "pointer type '{}' requires aligned address", .{type_res}); + return sema.fail(block, operand_src, "pointer type '{}' requires aligned address", .{type_res.fmt(sema.mod)}); const val_payload = try sema.arena.create(Value.Payload.U64); val_payload.* = .{ @@ -11260,10 +15378,92 @@ fn zirIntToPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai return block.addBitCast(type_res, operand_coerced); } -fn zirErrSetCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirErrSetCast", .{}); +fn zirErrSetCast(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 = LazySrcLoc.nodeOffset(extra.node); + const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; + const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); + const operand = try sema.resolveInst(extra.rhs); + const operand_ty = sema.typeOf(operand); + try sema.checkErrorSetType(block, dest_ty_src, dest_ty); + try sema.checkErrorSetType(block, operand_src, operand_ty); + + // operand must be defined since it can be an invalid error value + const maybe_operand_val = try sema.resolveDefinedValue(block, operand_src, operand); + + if (disjoint: { + // Try avoiding resolving inferred error sets if we can + if (!dest_ty.isAnyError() and dest_ty.errorSetNames().len == 0) break :disjoint true; + if (!operand_ty.isAnyError() and operand_ty.errorSetNames().len == 0) break :disjoint true; + if (dest_ty.isAnyError()) break :disjoint false; + if (operand_ty.isAnyError()) break :disjoint false; + for (dest_ty.errorSetNames()) |dest_err_name| + if (operand_ty.errorSetHasField(dest_err_name)) + break :disjoint false; + + if (dest_ty.tag() != .error_set_inferred and operand_ty.tag() != .error_set_inferred) + break :disjoint true; + + try sema.resolveInferredErrorSetTy(block, dest_ty_src, dest_ty); + try sema.resolveInferredErrorSetTy(block, operand_src, operand_ty); + for (dest_ty.errorSetNames()) |dest_err_name| + if (operand_ty.errorSetHasField(dest_err_name)) + break :disjoint false; + + break :disjoint true; + }) { + const msg = msg: { + const msg = try sema.errMsg( + block, + src, + "error sets '{}' and '{}' have no common errors", + .{ operand_ty.fmt(sema.mod), dest_ty.fmt(sema.mod) }, + ); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, operand_ty); + try sema.addDeclaredHereNote(msg, dest_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + + if (maybe_operand_val) |val| { + if (!dest_ty.isAnyError()) { + const error_name = val.castTag(.@"error").?.data.name; + if (!dest_ty.errorSetHasField(error_name)) { + const msg = msg: { + const msg = try sema.errMsg( + block, + src, + "'error.{s}' not a member of error set '{}'", + .{ error_name, dest_ty.fmt(sema.mod) }, + ); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, dest_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + } + + return sema.addConstant(dest_ty, val); + } + + try sema.requireRuntimeBlock(block, src); + if (block.wantSafety() and !dest_ty.isAnyError()) { + const err_int_inst = try block.addBitCast(Type.u16, operand); + // TODO: Output a switch instead of chained OR's. + var found_match: Air.Inst.Ref = undefined; + for (dest_ty.errorSetNames()) |dest_err_name, i| { + const dest_err_int = (try sema.mod.getErrorValue(dest_err_name)).value; + const dest_err_int_inst = try sema.addIntUnsigned(Type.u16, dest_err_int); + const next_match = try block.addBinOp(.cmp_eq, dest_err_int_inst, err_int_inst); + found_match = if (i == 0) next_match else try block.addBinOp(.bool_or, found_match, next_match); + } + try sema.addSafetyCheck(block, found_match, .invalid_error_code); + } + return block.addBitCast(dest_ty, operand); } fn zirPtrCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -11272,11 +15472,57 @@ fn zirPtrCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const operand = try sema.resolveInst(extra.rhs); const operand_ty = sema.typeOf(operand); + const target = sema.mod.getTarget(); + try sema.checkPtrType(block, dest_ty_src, dest_ty); try sema.checkPtrOperand(block, operand_src, operand_ty); - return sema.coerceCompatiblePtrs(block, dest_ty, operand, operand_src); + + const dest_is_slice = dest_ty.isSlice(); + const operand_is_slice = operand_ty.isSlice(); + if (dest_is_slice and !operand_is_slice) { + return sema.fail(block, dest_ty_src, "illegal pointer cast to slice", .{}); + } + const ptr = if (operand_is_slice and !dest_is_slice) + try sema.analyzeSlicePtr(block, operand_src, operand, operand_ty) + else + operand; + + const dest_elem_ty = dest_ty.elemType2(); + try sema.resolveTypeLayout(block, dest_ty_src, dest_elem_ty); + const dest_align = dest_ty.ptrAlignment(target); + + const operand_elem_ty = operand_ty.elemType2(); + try sema.resolveTypeLayout(block, operand_src, operand_elem_ty); + const operand_align = operand_ty.ptrAlignment(target); + + // If the destination is less aligned than the source, preserve the source alignment + const aligned_dest_ty = if (operand_align <= dest_align) dest_ty else blk: { + // Unwrap the pointer (or pointer-like optional) type, set alignment, and re-wrap into result + if (dest_ty.zigTypeTag() == .Optional) { + var buf: Type.Payload.ElemType = undefined; + var dest_ptr_info = dest_ty.optionalChild(&buf).ptrInfo().data; + dest_ptr_info.@"align" = operand_align; + break :blk try Type.optional(sema.arena, try Type.ptr(sema.arena, sema.mod, dest_ptr_info)); + } else { + var dest_ptr_info = dest_ty.ptrInfo().data; + dest_ptr_info.@"align" = operand_align; + break :blk try Type.ptr(sema.arena, sema.mod, dest_ptr_info); + } + }; + + if (dest_is_slice) { + const operand_elem_size = operand_elem_ty.abiSize(target); + const dest_elem_size = dest_elem_ty.abiSize(target); + if (operand_elem_size != dest_elem_size) { + // note that this is not implemented in stage1 so we should probably wait + // until that codebase is replaced before implementing this in stage2. + return sema.fail(block, dest_ty_src, "TODO: implement @ptrCast between slices changing the length", .{}); + } + } + + return sema.coerceCompatiblePtrs(block, aligned_dest_ty, ptr, operand_src); } fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -11285,58 +15531,78 @@ fn zirTruncate(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Ai const dest_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; - const dest_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); - const operand = sema.resolveInst(extra.rhs); + const dest_scalar_ty = try sema.resolveType(block, dest_ty_src, extra.lhs); + const operand = try sema.resolveInst(extra.rhs); + const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_scalar_ty); const operand_ty = sema.typeOf(operand); - const dest_is_comptime_int = try sema.checkIntType(block, dest_ty_src, dest_ty); - const src_is_comptime_int = try sema.checkIntType(block, operand_src, operand_ty); + const operand_scalar_ty = try sema.checkIntOrVectorAllowComptime(block, operand_ty, operand_src); + const is_vector = operand_ty.zigTypeTag() == .Vector; + const dest_ty = if (is_vector) + try Type.vector(sema.arena, operand_ty.vectorLen(), dest_scalar_ty) + else + dest_scalar_ty; if (dest_is_comptime_int) { return sema.coerce(block, dest_ty, operand, operand_src); } const target = sema.mod.getTarget(); - const dest_info = dest_ty.intInfo(target); + const dest_info = dest_scalar_ty.intInfo(target); - if (dest_info.bits == 0) { - return sema.addConstant(dest_ty, Value.zero); + if (try sema.typeHasOnePossibleValue(block, dest_ty_src, dest_ty)) |val| { + return sema.addConstant(dest_ty, val); } - if (!src_is_comptime_int) { - const src_info = operand_ty.intInfo(target); - if (src_info.bits == 0) { - return sema.addConstant(dest_ty, Value.zero); + if (operand_scalar_ty.zigTypeTag() != .ComptimeInt) { + const operand_info = operand_ty.intInfo(target); + if (try sema.typeHasOnePossibleValue(block, operand_src, operand_ty)) |val| { + return sema.addConstant(operand_ty, val); } - if (src_info.signedness != dest_info.signedness) { + if (operand_info.signedness != dest_info.signedness) { return sema.fail(block, operand_src, "expected {s} integer type, found '{}'", .{ - @tagName(dest_info.signedness), operand_ty, + @tagName(dest_info.signedness), operand_ty.fmt(sema.mod), }); } - if (src_info.bits > 0 and src_info.bits < dest_info.bits) { + if (operand_info.bits < dest_info.bits) { const msg = msg: { const msg = try sema.errMsg( block, src, "destination type '{}' has more bits than source type '{}'", - .{ dest_ty, operand_ty }, + .{ dest_ty.fmt(sema.mod), operand_ty.fmt(sema.mod) }, ); errdefer msg.destroy(sema.gpa); try sema.errNote(block, dest_ty_src, msg, "destination type has {d} bits", .{ dest_info.bits, }); - try sema.errNote(block, operand_src, msg, "source type has {d} bits", .{ - src_info.bits, + try sema.errNote(block, operand_src, msg, "operand type has {d} bits", .{ + operand_info.bits, }); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } } if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { if (val.isUndef()) return sema.addConstUndef(dest_ty); - return sema.addConstant(dest_ty, try val.intTrunc(sema.arena, dest_info.signedness, dest_info.bits)); + if (!is_vector) { + return sema.addConstant( + dest_ty, + try val.intTrunc(operand_ty, sema.arena, dest_info.signedness, dest_info.bits, target), + ); + } + var elem_buf: Value.ElemValueBuffer = undefined; + const elems = try sema.arena.alloc(Value, operand_ty.vectorLen()); + for (elems) |*elem, i| { + const elem_val = val.elemValueBuffer(sema.mod, i, &elem_buf); + elem.* = try elem_val.intTrunc(operand_scalar_ty, sema.arena, dest_info.signedness, dest_info.bits, target); + } + return sema.addConstant( + dest_ty, + try Value.Tag.aggregate.create(sema.arena, elems), + ); } try sema.requireRuntimeBlock(block, src); @@ -11349,7 +15615,7 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const align_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const dest_align = try sema.resolveAlign(block, align_src, extra.lhs); - const ptr = sema.resolveInst(extra.rhs); + const ptr = try sema.resolveInst(extra.rhs); const ptr_ty = sema.typeOf(ptr); // TODO in addition to pointers, this instruction is supposed to work for @@ -11362,7 +15628,7 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A // TODO insert safety check that the alignment is correct const ptr_info = ptr_ty.ptrInfo().data; - const dest_ty = try Type.ptr(sema.arena, .{ + const dest_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = ptr_info.pointee_type, .@"align" = dest_align, .@"addrspace" = ptr_info.@"addrspace", @@ -11374,7 +15640,7 @@ fn zirAlignCast(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A return sema.coerceCompatiblePtrs(block, dest_ty, ptr, ptr_src); } -fn zirClzCtz( +fn zirBitCount( sema: *Sema, block: *Block, inst: Zir.Inst.Index, @@ -11383,20 +15649,14 @@ fn zirClzCtz( ) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); - try checkIntOrVector(sema, block, operand, operand_src); + _ = try checkIntOrVector(sema, block, operand, operand_src); const target = sema.mod.getTarget(); const bits = operand_ty.intInfo(target).bits; - if (bits == 0) { - switch (operand_ty.zigTypeTag()) { - .Vector => return sema.addConstant( - try Type.vector(sema.arena, operand_ty.arrayLen(), Type.comptime_int), - try Value.Tag.repeated.create(sema.arena, Value.zero), - ), - .Int => return Air.Inst.Ref.zero, - else => unreachable, - } + + if (try sema.typeHasOnePossibleValue(block, operand_src, operand_ty)) |val| { + return sema.addConstant(operand_ty, val); } const result_scalar_ty = try Type.smallestUnsignedInt(sema.arena, bits); @@ -11411,13 +15671,13 @@ fn zirClzCtz( const elems = try sema.arena.alloc(Value, vec_len); const scalar_ty = operand_ty.scalarType(); for (elems) |*elem, i| { - const elem_val = val.elemValueBuffer(i, &elem_buf); + const elem_val = val.elemValueBuffer(sema.mod, i, &elem_buf); const count = comptimeOp(elem_val, scalar_ty, target); elem.* = try Value.Tag.int_u64.create(sema.arena, count); } return sema.addConstant( result_ty, - try Value.Tag.array.create(sema.arena, elems), + try Value.Tag.aggregate.create(sema.arena, elems), ); } else { try sema.requireRuntimeBlock(block, operand_src); @@ -11437,44 +15697,110 @@ fn zirClzCtz( } } -fn zirPopCount(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirByteSwap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; - const operand = sema.resolveInst(inst_data.operand); + const operand = try sema.resolveInst(inst_data.operand); const operand_ty = sema.typeOf(operand); - // TODO implement support for vectors - if (operand_ty.zigTypeTag() != .Int) { - return sema.fail(block, ty_src, "expected integer type, found '{}'", .{ - operand_ty, - }); - } + const scalar_ty = try sema.checkIntOrVectorAllowComptime(block, operand_ty, operand_src); const target = sema.mod.getTarget(); - const bits = operand_ty.intInfo(target).bits; - if (bits == 0) return Air.Inst.Ref.zero; + const bits = scalar_ty.intInfo(target).bits; + if (bits % 8 != 0) { + return sema.fail( + block, + ty_src, + "@byteSwap requires the number of bits to be evenly divisible by 8, but {} has {} bits", + .{ scalar_ty.fmt(sema.mod), bits }, + ); + } - const result_ty = try Type.smallestUnsignedInt(sema.arena, bits); + if (try sema.typeHasOnePossibleValue(block, operand_src, operand_ty)) |val| { + return sema.addConstant(operand_ty, val); + } - const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { - if (val.isUndef()) return sema.addConstUndef(result_ty); - const result_val = try val.popCount(operand_ty, target, sema.arena); - return sema.addConstant(result_ty, result_val); - } else operand_src; + switch (operand_ty.zigTypeTag()) { + .Int, .ComptimeInt => { + const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(operand_ty); + const result_val = try val.byteSwap(operand_ty, target, sema.arena); + return sema.addConstant(operand_ty, result_val); + } else operand_src; + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addTyOp(.byte_swap, operand_ty, operand); + }, + .Vector => { + const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) + return sema.addConstUndef(operand_ty); - try sema.requireRuntimeBlock(block, runtime_src); - return block.addTyOp(.popcount, result_ty, operand); -} + const vec_len = operand_ty.vectorLen(); + var elem_buf: Value.ElemValueBuffer = undefined; + const elems = try sema.arena.alloc(Value, vec_len); + for (elems) |*elem, i| { + const elem_val = val.elemValueBuffer(sema.mod, i, &elem_buf); + elem.* = try elem_val.byteSwap(operand_ty, target, sema.arena); + } + return sema.addConstant( + operand_ty, + try Value.Tag.aggregate.create(sema.arena, elems), + ); + } else operand_src; -fn zirByteSwap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { - const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirByteSwap", .{}); + try sema.requireRuntimeBlock(block, runtime_src); + return block.addTyOp(.byte_swap, operand_ty, operand); + }, + else => unreachable, + } } fn zirBitReverse(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirBitReverse", .{}); + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const operand = try sema.resolveInst(inst_data.operand); + const operand_ty = sema.typeOf(operand); + _ = try sema.checkIntOrVectorAllowComptime(block, operand_ty, operand_src); + + if (try sema.typeHasOnePossibleValue(block, operand_src, operand_ty)) |val| { + return sema.addConstant(operand_ty, val); + } + + const target = sema.mod.getTarget(); + switch (operand_ty.zigTypeTag()) { + .Int, .ComptimeInt => { + const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) return sema.addConstUndef(operand_ty); + const result_val = try val.bitReverse(operand_ty, target, sema.arena); + return sema.addConstant(operand_ty, result_val); + } else operand_src; + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addTyOp(.bit_reverse, operand_ty, operand); + }, + .Vector => { + const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| { + if (val.isUndef()) + return sema.addConstUndef(operand_ty); + + const vec_len = operand_ty.vectorLen(); + var elem_buf: Value.ElemValueBuffer = undefined; + const elems = try sema.arena.alloc(Value, vec_len); + for (elems) |*elem, i| { + const elem_val = val.elemValueBuffer(sema.mod, i, &elem_buf); + elem.* = try elem_val.bitReverse(operand_ty, target, sema.arena); + } + return sema.addConstant( + operand_ty, + try Value.Tag.aggregate.create(sema.arena, elems), + ); + } else operand_src; + + try sema.requireRuntimeBlock(block, runtime_src); + return block.addTyOp(.bit_reverse, operand_ty, operand); + }, + else => unreachable, + } } fn zirBitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { @@ -11484,6 +15810,7 @@ fn zirBitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError fn zirOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const offset = try bitOffsetOf(sema, block, inst); + // TODO reminder to make this a compile error for packed structs return sema.addIntUnsigned(Type.comptime_int, offset / 8); } @@ -11496,53 +15823,69 @@ fn bitOffsetOf(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!u6 const ty = try sema.resolveType(block, lhs_src, extra.lhs); const field_name = try sema.resolveConstString(block, rhs_src, extra.rhs); + const target = sema.mod.getTarget(); try sema.resolveTypeLayout(block, lhs_src, ty); if (ty.tag() != .@"struct") { - return sema.fail( - block, - lhs_src, - "expected struct type, found '{}'", - .{ty}, - ); + const msg = msg: { + const msg = try sema.errMsg(block, lhs_src, "expected struct type, found '{}'", .{ty.fmt(sema.mod)}); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); } - const index = ty.structFields().getIndex(field_name) orelse { - return sema.fail( - block, - rhs_src, - "struct '{}' has no field '{s}'", - .{ ty, field_name }, - ); + const fields = ty.structFields(); + const index = fields.getIndex(field_name) orelse { + const msg = msg: { + const msg = try sema.errMsg( + block, + rhs_src, + "struct '{}' has no field '{s}'", + .{ ty.fmt(sema.mod), field_name }, + ); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); }; - const target = sema.mod.getTarget(); - const layout = ty.containerLayout(); - if (layout == .Packed) { - var it = ty.iteratePackedStructOffsets(target); - while (it.next()) |field_offset| { - if (field_offset.field == index) { - return (field_offset.offset * 8) + field_offset.running_bits; - } - } - } else { - var it = ty.iterateStructOffsets(target); - while (it.next()) |field_offset| { - if (field_offset.field == index) { - return field_offset.offset * 8; - } - } + switch (ty.containerLayout()) { + .Packed => { + var bit_sum: u64 = 0; + for (fields.values()) |field, i| { + if (i == index) { + return bit_sum; + } + bit_sum += field.ty.bitSize(target); + } else unreachable; + }, + else => { + var it = ty.iterateStructOffsets(target); + while (it.next()) |field_offset| { + if (field_offset.field == index) { + return field_offset.offset * 8; + } + } else unreachable; + }, } +} - unreachable; +fn checkNamespaceType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!void { + switch (ty.zigTypeTag()) { + .Struct, .Enum, .Union, .Opaque => return, + else => return sema.fail(block, src, "expected struct, enum, union, or opaque; found '{}'", .{ty.fmt(sema.mod)}), + } } /// Returns `true` if the type was a comptime_int. fn checkIntType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool { - switch (ty.zigTypeTag()) { + switch (try ty.zigTypeTagOrPoison()) { .ComptimeInt => return true, .Int => return false, - else => return sema.fail(block, src, "expected integer type, found '{}'", .{ty}), + else => return sema.fail(block, src, "expected integer type, found '{}'", .{ty.fmt(sema.mod)}), } } @@ -11553,14 +15896,14 @@ fn checkPtrOperand( ty: Type, ) CompileError!void { switch (ty.zigTypeTag()) { - .Pointer => {}, + .Pointer => return, .Fn => { const msg = msg: { const msg = try sema.errMsg( block, ty_src, - "expected pointer, found {}", - .{ty}, + "expected pointer, found '{}'", + .{ty.fmt(sema.mod)}, ); errdefer msg.destroy(sema.gpa); @@ -11568,10 +15911,12 @@ fn checkPtrOperand( break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }, - else => return sema.fail(block, ty_src, "expected pointer, found '{}'", .{ty}), + .Optional => if (ty.isPtrLikeOptional()) return, + else => {}, } + return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty.fmt(sema.mod)}); } fn checkPtrType( @@ -11581,14 +15926,14 @@ fn checkPtrType( ty: Type, ) CompileError!void { switch (ty.zigTypeTag()) { - .Pointer => {}, + .Pointer => return, .Fn => { const msg = msg: { const msg = try sema.errMsg( block, ty_src, "expected pointer type, found '{}'", - .{ty}, + .{ty.fmt(sema.mod)}, ); errdefer msg.destroy(sema.gpa); @@ -11596,10 +15941,12 @@ fn checkPtrType( break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }, - else => return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty}), + .Optional => if (ty.isPtrLikeOptional()) return, + else => {}, } + return sema.fail(block, ty_src, "expected pointer type, found '{}'", .{ty.fmt(sema.mod)}); } fn checkVectorElemType( @@ -11612,7 +15959,7 @@ fn checkVectorElemType( .Int, .Float, .Bool => return, else => if (ty.isPtrAtRuntime()) return, } - return sema.fail(block, ty_src, "expected integer, float, bool, or pointer for the vector element type; found '{}'", .{ty}); + return sema.fail(block, ty_src, "expected integer, float, bool, or pointer for the vector element type; found '{}'", .{ty.fmt(sema.mod)}); } fn checkFloatType( @@ -11622,8 +15969,8 @@ fn checkFloatType( ty: Type, ) CompileError!void { switch (ty.zigTypeTag()) { - .ComptimeFloat, .Float => {}, - else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty}), + .ComptimeInt, .ComptimeFloat, .Float => {}, + else => return sema.fail(block, ty_src, "expected float type, found '{}'", .{ty.fmt(sema.mod)}), } } @@ -11639,55 +15986,68 @@ fn checkNumericType( .ComptimeFloat, .Float, .ComptimeInt, .Int => {}, else => |t| return sema.fail(block, ty_src, "expected number, found '{}'", .{t}), }, - else => return sema.fail(block, ty_src, "expected number, found '{}'", .{ty}), + else => return sema.fail(block, ty_src, "expected number, found '{}'", .{ty.fmt(sema.mod)}), } } -fn checkAtomicOperandType( +/// Returns the casted pointer. +fn checkAtomicPtrOperand( sema: *Sema, block: *Block, - ty_src: LazySrcLoc, - ty: Type, -) CompileError!void { - var buffer: Type.Payload.Bits = undefined; + elem_ty: Type, + elem_ty_src: LazySrcLoc, + ptr: Air.Inst.Ref, + ptr_src: LazySrcLoc, + ptr_const: bool, +) CompileError!Air.Inst.Ref { const target = sema.mod.getTarget(); - const max_atomic_bits = target_util.largestAtomicBits(target); - const int_ty = switch (ty.zigTypeTag()) { - .Int => ty, - .Enum => ty.intTagType(&buffer), - .Float => { - const bit_count = ty.floatBits(target); - if (bit_count > max_atomic_bits) { - return sema.fail( - block, - ty_src, - "expected {d}-bit float type or smaller; found {d}-bit float type", - .{ max_atomic_bits, bit_count }, - ); - } - return; - }, - .Bool => return, // Will be treated as `u8`. - else => { - if (ty.isPtrAtRuntime()) return; + var diag: target_util.AtomicPtrAlignmentDiagnostics = .{}; + const alignment = target_util.atomicPtrAlignment(target, elem_ty, &diag) catch |err| switch (err) { + 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, or pointer type; found '{}'", + .{elem_ty.fmt(sema.mod)}, + ), + }; - return sema.fail( - block, - ty_src, - "expected bool, integer, float, enum, or pointer type; found {}", - .{ty}, - ); + var wanted_ptr_data: Type.Payload.Pointer.Data = .{ + .pointee_type = elem_ty, + .@"align" = alignment, + .@"addrspace" = .generic, + .mutable = !ptr_const, + }; + + const ptr_ty = sema.typeOf(ptr); + const ptr_data = switch (try ptr_ty.zigTypeTagOrPoison()) { + .Pointer => ptr_ty.ptrInfo().data, + else => { + const wanted_ptr_ty = try Type.ptr(sema.arena, sema.mod, wanted_ptr_data); + _ = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src); + unreachable; }, }; - const bit_count = int_ty.intInfo(target).bits; - if (bit_count > max_atomic_bits) { - return sema.fail( - block, - ty_src, - "expected {d}-bit integer type or smaller; found {d}-bit integer type", - .{ max_atomic_bits, bit_count }, - ); - } + + wanted_ptr_data.@"addrspace" = ptr_data.@"addrspace"; + wanted_ptr_data.@"allowzero" = ptr_data.@"allowzero"; + wanted_ptr_data.@"volatile" = ptr_data.@"volatile"; + + const wanted_ptr_ty = try Type.ptr(sema.arena, sema.mod, wanted_ptr_data); + const casted_ptr = try sema.coerce(block, wanted_ptr_ty, ptr, ptr_src); + + return casted_ptr; } fn checkPtrIsNotComptimeMutable( @@ -11709,7 +16069,7 @@ fn checkComptimeVarStore( src: LazySrcLoc, decl_ref_mut: Value.Payload.DeclRefMut.Data, ) CompileError!void { - if (decl_ref_mut.runtime_index < block.runtime_index) { + if (@enumToInt(decl_ref_mut.runtime_index) < @enumToInt(block.runtime_index)) { if (block.runtime_cond) |cond_src| { const msg = msg: { const msg = try sema.errMsg(block, src, "store to comptime variable depends on runtime condition", .{}); @@ -11717,7 +16077,7 @@ fn checkComptimeVarStore( try sema.errNote(block, cond_src, msg, "runtime condition here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } if (block.runtime_loop) |loop_src| { const msg = msg: { @@ -11726,7 +16086,7 @@ fn checkComptimeVarStore( try sema.errNote(block, loop_src, msg, "non-inline loop here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } unreachable; } @@ -11737,17 +16097,55 @@ fn checkIntOrVector( block: *Block, operand: Air.Inst.Ref, operand_src: LazySrcLoc, -) CompileError!void { +) CompileError!Type { const operand_ty = sema.typeOf(operand); - const operand_zig_ty_tag = try operand_ty.zigTypeTagOrPoison(); - switch (operand_zig_ty_tag) { - .Vector, .Int => return, + switch (try operand_ty.zigTypeTagOrPoison()) { + .Int => return operand_ty, + .Vector => { + const elem_ty = operand_ty.childType(); + switch (try elem_ty.zigTypeTagOrPoison()) { + .Int => return elem_ty, + else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{ + elem_ty.fmt(sema.mod), + }), + } + }, + else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{ + operand_ty.fmt(sema.mod), + }), + } +} + +fn checkIntOrVectorAllowComptime( + sema: *Sema, + block: *Block, + operand_ty: Type, + operand_src: LazySrcLoc, +) CompileError!Type { + switch (try operand_ty.zigTypeTagOrPoison()) { + .Int, .ComptimeInt => return operand_ty, + .Vector => { + const elem_ty = operand_ty.childType(); + switch (try elem_ty.zigTypeTagOrPoison()) { + .Int, .ComptimeInt => return elem_ty, + else => return sema.fail(block, operand_src, "expected vector of integers; found vector of '{}'", .{ + elem_ty.fmt(sema.mod), + }), + } + }, else => return sema.fail(block, operand_src, "expected integer or vector, found '{}'", .{ - operand_ty, + operand_ty.fmt(sema.mod), }), } } +fn checkErrorSetType(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!void { + switch (ty.zigTypeTag()) { + .ErrorSet => return, + else => return sema.fail(block, src, "expected error set type, found '{}'", .{ty.fmt(sema.mod)}), + } +} + const SimdBinOp = struct { len: ?usize, /// Coerced to `result_ty`. @@ -11772,11 +16170,49 @@ fn checkSimdBinOp( ) CompileError!SimdBinOp { 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); + var vec_len: ?usize = if (lhs_ty.zigTypeTag() == .Vector) lhs_ty.vectorLen() 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.resolveMaybeUndefVal(block, lhs_src, lhs), + .rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs), + .result_ty = result_ty, + .scalar_ty = result_ty.scalarType(), + }; +} + +fn checkVectorizableBinaryOperands( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs_ty: Type, + rhs_ty: Type, + lhs_src: LazySrcLoc, + rhs_src: LazySrcLoc, +) CompileError!void { const lhs_zig_ty_tag = try lhs_ty.zigTypeTagOrPoison(); const rhs_zig_ty_tag = try rhs_ty.zigTypeTagOrPoison(); + 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, + }; - var vec_len: ?usize = null; - if (lhs_zig_ty_tag == .Vector and rhs_zig_ty_tag == .Vector) { + if (lhs_is_vector and rhs_is_vector) { const lhs_len = lhs_ty.arrayLen(); const rhs_len = rhs_ty.arrayLen(); if (lhs_len != rhs_len) { @@ -11787,16 +16223,15 @@ fn checkSimdBinOp( try sema.errNote(block, rhs_src, msg, "length {d} here", .{rhs_len}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } - vec_len = try sema.usizeCast(block, lhs_src, lhs_len); - } else if (lhs_zig_ty_tag == .Vector or rhs_zig_ty_tag == .Vector) { + } else { const msg = msg: { - const msg = try sema.errMsg(block, src, "mixed scalar and vector operands: {} and {}", .{ - lhs_ty, rhs_ty, + const msg = try sema.errMsg(block, src, "mixed scalar and vector operands: '{}' and '{}'", .{ + lhs_ty.fmt(sema.mod), rhs_ty.fmt(sema.mod), }); errdefer msg.destroy(sema.gpa); - if (lhs_zig_ty_tag == .Vector) { + if (lhs_is_vector) { try sema.errNote(block, lhs_src, msg, "vector here", .{}); try sema.errNote(block, rhs_src, msg, "scalar here", .{}); } else { @@ -11805,23 +16240,8 @@ fn checkSimdBinOp( } break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } - 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.resolveMaybeUndefVal(block, lhs_src, lhs), - .rhs_val = try sema.resolveMaybeUndefVal(block, rhs_src, rhs), - .result_ty = result_ty, - .scalar_ty = result_ty.scalarType(), - }; } fn resolveExportOptions( @@ -11831,36 +16251,68 @@ fn resolveExportOptions( zir_ref: Zir.Inst.Ref, ) CompileError!std.builtin.ExportOptions { const export_options_ty = try sema.getBuiltinType(block, src, "ExportOptions"); - const air_ref = sema.resolveInst(zir_ref); - const coerced = try sema.coerce(block, export_options_ty, air_ref, src); - const val = try sema.resolveConstValue(block, src, coerced); - const fields = val.castTag(.@"struct").?.data; - const struct_obj = export_options_ty.castTag(.@"struct").?.data; - const name_index = struct_obj.fields.getIndex("name").?; - const linkage_index = struct_obj.fields.getIndex("linkage").?; - const section_index = struct_obj.fields.getIndex("section").?; - if (!fields[section_index].isNull()) { + const air_ref = try sema.resolveInst(zir_ref); + const options = try sema.coerce(block, export_options_ty, air_ref, src); + + const name_operand = try sema.fieldVal(block, src, options, "name", src); + const name_val = try sema.resolveConstValue(block, src, name_operand); + const name_ty = Type.initTag(.const_slice_u8); + const name = try name_val.toAllocatedBytes(name_ty, sema.arena, sema.mod); + + const linkage_operand = try sema.fieldVal(block, src, options, "linkage", src); + const linkage_val = try sema.resolveConstValue(block, src, linkage_operand); + const linkage = linkage_val.toEnum(std.builtin.GlobalLinkage); + + const section = try sema.fieldVal(block, src, options, "section", src); + const section_val = try sema.resolveConstValue(block, src, section); + + const visibility_operand = try sema.fieldVal(block, src, options, "visibility", src); + const visibility_val = try sema.resolveConstValue(block, src, visibility_operand); + const visibility = visibility_val.toEnum(std.builtin.SymbolVisibility); + + if (name.len < 1) { + return sema.fail(block, src, "exported symbol name cannot be empty", .{}); + } + + if (visibility != .default and linkage == .Internal) { + return sema.fail(block, src, "symbol '{s}' exported with internal linkage has non-default visibility {s}", .{ + name, @tagName(visibility), + }); + } + + if (!section_val.isNull()) { return sema.fail(block, src, "TODO: implement exporting with linksection", .{}); } - const name_ty = Type.initTag(.const_slice_u8); + return std.builtin.ExportOptions{ - .name = try fields[name_index].toAllocatedBytes(name_ty, sema.arena), - .linkage = fields[linkage_index].toEnum(std.builtin.GlobalLinkage), + .name = name, + .linkage = linkage, .section = null, // TODO + .visibility = visibility, }; } +fn resolveBuiltinEnum( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + zir_ref: Zir.Inst.Ref, + comptime name: []const u8, +) CompileError!@field(std.builtin, name) { + const ty = try sema.getBuiltinType(block, src, name); + const air_ref = try sema.resolveInst(zir_ref); + const coerced = try sema.coerce(block, ty, air_ref, src); + const val = try sema.resolveConstValue(block, src, coerced); + return val.toEnum(@field(std.builtin, name)); +} + fn resolveAtomicOrder( sema: *Sema, block: *Block, src: LazySrcLoc, zir_ref: Zir.Inst.Ref, ) CompileError!std.builtin.AtomicOrder { - const atomic_order_ty = try sema.getBuiltinType(block, src, "AtomicOrder"); - const air_ref = sema.resolveInst(zir_ref); - const coerced = try sema.coerce(block, atomic_order_ty, air_ref, src); - const val = try sema.resolveConstValue(block, src, coerced); - return val.toEnum(std.builtin.AtomicOrder); + return resolveBuiltinEnum(sema, block, src, zir_ref, "AtomicOrder"); } fn resolveAtomicRmwOp( @@ -11869,11 +16321,7 @@ fn resolveAtomicRmwOp( src: LazySrcLoc, zir_ref: Zir.Inst.Ref, ) CompileError!std.builtin.AtomicRmwOp { - const atomic_rmw_op_ty = try sema.getBuiltinType(block, src, "AtomicRmwOp"); - const air_ref = sema.resolveInst(zir_ref); - const coerced = try sema.coerce(block, atomic_rmw_op_ty, air_ref, src); - const val = try sema.resolveConstValue(block, src, coerced); - return val.toEnum(std.builtin.AtomicRmwOp); + return resolveBuiltinEnum(sema, block, src, zir_ref, "AtomicRmwOp"); } fn zirCmpxchg( @@ -11893,20 +16341,19 @@ fn zirCmpxchg( const success_order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg4 = inst_data.src_node }; const failure_order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg5 = inst_data.src_node }; // zig fmt: on - const ptr = sema.resolveInst(extra.ptr); - const ptr_ty = sema.typeOf(ptr); - const elem_ty = ptr_ty.elemType(); - try sema.checkAtomicOperandType(block, elem_ty_src, elem_ty); + const expected_value = try sema.resolveInst(extra.expected_value); + const elem_ty = sema.typeOf(expected_value); if (elem_ty.zigTypeTag() == .Float) { return sema.fail( block, elem_ty_src, "expected bool, integer, enum, or pointer type; found '{}'", - .{elem_ty}, + .{elem_ty.fmt(sema.mod)}, ); } - const expected_value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.expected_value), expected_src); - const new_value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.new_value), new_value_src); + 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); const failure_order = try sema.resolveAtomicOrder(block, failure_order_src, extra.failure_order); @@ -11938,8 +16385,9 @@ fn zirCmpxchg( // to become undef as well return sema.addConstUndef(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 = if (stored_val.eql(expected_val, elem_ty)) blk: { + const result_val = if (stored_val.eql(expected_val, elem_ty, sema.mod)) blk: { try sema.storePtr(block, src, ptr, new_value); break :blk Value.@"null"; } else try Value.Tag.opt_payload.create(sema.arena, stored_val); @@ -11973,7 +16421,7 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I const len_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node }; const scalar_src: LazySrcLoc = .{ .node_offset_bin_rhs = inst_data.src_node }; const len = @intCast(u32, try sema.resolveInt(block, len_src, extra.lhs, Type.u32)); - const scalar = sema.resolveInst(extra.rhs); + const scalar = try sema.resolveInst(extra.rhs); const scalar_ty = sema.typeOf(scalar); try sema.checkVectorElemType(block, scalar_src, scalar_ty); const vector_ty = try Type.Tag.vector.create(sema.arena, .{ @@ -11995,35 +16443,365 @@ fn zirSplat(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.I fn zirReduce(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirReduce", .{}); + const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const op_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const operation = try sema.resolveBuiltinEnum(block, op_src, extra.lhs, "ReduceOp"); + const operand = try sema.resolveInst(extra.rhs); + const operand_ty = sema.typeOf(operand); + const target = sema.mod.getTarget(); + + if (operand_ty.zigTypeTag() != .Vector) { + return sema.fail(block, operand_src, "expected vector, found '{}'", .{operand_ty.fmt(sema.mod)}); + } + + const scalar_ty = operand_ty.childType(); + + // Type-check depending on operation. + switch (operation) { + .And, .Or, .Xor => switch (scalar_ty.zigTypeTag()) { + .Int, .Bool => {}, + else => return sema.fail(block, operand_src, "@reduce operation '{s}' requires integer or boolean operand; found '{}'", .{ + @tagName(operation), operand_ty.fmt(sema.mod), + }), + }, + .Min, .Max, .Add, .Mul => switch (scalar_ty.zigTypeTag()) { + .Int, .Float => {}, + else => return sema.fail(block, operand_src, "@reduce operation '{s}' requires integer or float operand; found '{}'", .{ + @tagName(operation), operand_ty.fmt(sema.mod), + }), + }, + } + + const vec_len = operand_ty.vectorLen(); + 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.resolveMaybeUndefVal(block, operand_src, operand)) |operand_val| { + if (operand_val.isUndef()) return sema.addConstUndef(scalar_ty); + + var accum: Value = try operand_val.elemValue(sema.mod, sema.arena, 0); + var elem_buf: Value.ElemValueBuffer = undefined; + var i: u32 = 1; + while (i < vec_len) : (i += 1) { + const elem_val = operand_val.elemValueBuffer(sema.mod, i, &elem_buf); + switch (operation) { + .And => accum = try accum.bitwiseAnd(elem_val, scalar_ty, sema.arena, target), + .Or => accum = try accum.bitwiseOr(elem_val, scalar_ty, sema.arena, target), + .Xor => accum = try accum.bitwiseXor(elem_val, scalar_ty, sema.arena, target), + .Min => accum = accum.numberMin(elem_val, target), + .Max => accum = accum.numberMax(elem_val, target), + .Add => accum = try sema.numberAddWrap(block, operand_src, accum, elem_val, scalar_ty), + .Mul => accum = try accum.numberMulWrap(elem_val, scalar_ty, sema.arena, target), + } + } + return sema.addConstant(scalar_ty, accum); + } + + try sema.requireRuntimeBlock(block, operand_src); + return block.addInst(.{ + .tag = .reduce, + .data = .{ .reduce = .{ + .operand = operand, + .operation = operation, + } }, + }); } fn zirShuffle(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirShuffle", .{}); + const extra = sema.code.extraData(Zir.Inst.Shuffle, inst_data.payload_index).data; + const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const mask_src: LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; + + const elem_ty = try sema.resolveType(block, elem_ty_src, extra.elem_type); + try sema.checkVectorElemType(block, elem_ty_src, elem_ty); + var a = try sema.resolveInst(extra.a); + var 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()) { + .Array, .Vector => sema.typeOf(mask).arrayLen(), + else => return sema.fail(block, mask_src, "expected vector or array, found '{}'", .{sema.typeOf(mask).fmt(sema.mod)}), + }; + mask_ty = try Type.Tag.vector.create(sema.arena, .{ + .len = mask_len, + .elem_type = Type.@"i32", + }); + mask = try sema.coerce(block, mask_ty, mask, mask_src); + const mask_val = try sema.resolveConstMaybeUndefVal(block, mask_src, mask); + return sema.analyzeShuffle(block, inst_data.src_node, elem_ty, a, b, mask_val, @intCast(u32, mask_len)); +} + +fn analyzeShuffle( + sema: *Sema, + block: *Block, + src_node: i32, + elem_ty: Type, + a_arg: Air.Inst.Ref, + b_arg: Air.Inst.Ref, + mask: Value, + mask_len: u32, +) CompileError!Air.Inst.Ref { + const a_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = src_node }; + const b_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = src_node }; + const mask_src: LazySrcLoc = .{ .node_offset_builtin_call_arg3 = src_node }; + var a = a_arg; + var b = b_arg; + + const res_ty = try Type.Tag.vector.create(sema.arena, .{ + .len = mask_len, + .elem_type = elem_ty, + }); + + var maybe_a_len = switch (sema.typeOf(a).zigTypeTag()) { + .Array, .Vector => sema.typeOf(a).arrayLen(), + .Undefined => null, + else => return sema.fail(block, a_src, "expected vector or array with element type '{}', found '{}'", .{ + elem_ty.fmt(sema.mod), + sema.typeOf(a).fmt(sema.mod), + }), + }; + var maybe_b_len = switch (sema.typeOf(b).zigTypeTag()) { + .Array, .Vector => sema.typeOf(b).arrayLen(), + .Undefined => null, + else => return sema.fail(block, b_src, "expected vector or array with element type '{}', found '{}'", .{ + elem_ty.fmt(sema.mod), + sema.typeOf(b).fmt(sema.mod), + }), + }; + if (maybe_a_len == null and maybe_b_len == null) { + return sema.addConstUndef(res_ty); + } + const a_len = maybe_a_len orelse maybe_b_len.?; + const b_len = maybe_b_len orelse a_len; + + const a_ty = try Type.Tag.vector.create(sema.arena, .{ + .len = a_len, + .elem_type = elem_ty, + }); + const b_ty = try Type.Tag.vector.create(sema.arena, .{ + .len = b_len, + .elem_type = elem_ty, + }); + + if (maybe_a_len == null) a = try sema.addConstUndef(a_ty); + if (maybe_b_len == null) b = try sema.addConstUndef(b_ty); + + const operand_info = [2]std.meta.Tuple(&.{ u64, LazySrcLoc, Type }){ + .{ a_len, a_src, a_ty }, + .{ b_len, b_src, b_ty }, + }; + + var i: usize = 0; + while (i < mask_len) : (i += 1) { + var buf: Value.ElemValueBuffer = undefined; + const elem = mask.elemValueBuffer(sema.mod, i, &buf); + if (elem.isUndef()) continue; + const int = elem.toSignedInt(); + var unsigned: u32 = undefined; + var chosen: u32 = undefined; + if (int >= 0) { + unsigned = @intCast(u32, int); + chosen = 0; + } else { + unsigned = @intCast(u32, ~int); + chosen = 1; + } + if (unsigned >= operand_info[chosen][0]) { + const msg = msg: { + const msg = try sema.errMsg(block, mask_src, "mask index '{d}' has out-of-bounds selection", .{i}); + errdefer msg.destroy(sema.gpa); + + try sema.errNote(block, operand_info[chosen][1], msg, "selected index '{d}' out of bounds of '{}'", .{ + unsigned, + operand_info[chosen][2].fmt(sema.mod), + }); + + if (chosen == 0) { + try sema.errNote(block, b_src, msg, "selections from the second vector are specified with negative numbers", .{}); + } + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + } + + if (try sema.resolveMaybeUndefVal(block, a_src, a)) |a_val| { + if (try sema.resolveMaybeUndefVal(block, b_src, b)) |b_val| { + const values = try sema.arena.alloc(Value, mask_len); + + i = 0; + while (i < mask_len) : (i += 1) { + var buf: Value.ElemValueBuffer = undefined; + const mask_elem_val = mask.elemValueBuffer(sema.mod, i, &buf); + if (mask_elem_val.isUndef()) { + values[i] = Value.undef; + continue; + } + const int = mask_elem_val.toSignedInt(); + const unsigned = if (int >= 0) @intCast(u32, int) else @intCast(u32, ~int); + if (int >= 0) { + values[i] = try a_val.elemValue(sema.mod, sema.arena, unsigned); + } else { + values[i] = try b_val.elemValue(sema.mod, sema.arena, unsigned); + } + } + const res_val = try Value.Tag.aggregate.create(sema.arena, values); + return sema.addConstant(res_ty, res_val); + } + } + + // All static analysis passed, and not comptime. + // For runtime codegen, vectors a and b must be the same length. Here we + // recursively @shuffle the smaller vector to append undefined elements + // to it up to the length of the longer vector. This recursion terminates + // in 1 call because these calls to analyzeShuffle guarantee a_len == b_len. + if (a_len != b_len) { + const min_len = std.math.min(a_len, b_len); + const max_src = if (a_len > b_len) a_src else b_src; + const max_len = try sema.usizeCast(block, max_src, std.math.max(a_len, b_len)); + + const expand_mask_values = try sema.arena.alloc(Value, max_len); + i = 0; + while (i < min_len) : (i += 1) { + expand_mask_values[i] = try Value.Tag.int_u64.create(sema.arena, i); + } + while (i < max_len) : (i += 1) { + expand_mask_values[i] = Value.negative_one; + } + const expand_mask = try Value.Tag.aggregate.create(sema.arena, expand_mask_values); + + if (a_len < b_len) { + const undef = try sema.addConstUndef(a_ty); + a = try sema.analyzeShuffle(block, src_node, elem_ty, a, undef, expand_mask, @intCast(u32, max_len)); + } else { + const undef = try sema.addConstUndef(b_ty); + b = try sema.analyzeShuffle(block, src_node, elem_ty, b, undef, expand_mask, @intCast(u32, max_len)); + } + } + + const mask_index = @intCast(u32, sema.air_values.items.len); + try sema.air_values.append(sema.gpa, mask); + return block.addInst(.{ + .tag = .shuffle, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(res_ty), + .payload = try block.sema.addExtra(Air.Shuffle{ + .a = a, + .b = b, + .mask = mask_index, + .mask_len = mask_len, + }), + } }, + }); } fn zirSelect(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirSelect", .{}); + const extra = sema.code.extraData(Zir.Inst.Select, inst_data.payload_index).data; + + const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const pred_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const a_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const b_src: LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; + + 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 (try pred_ty.zigTypeTagOrPoison()) { + .Vector, .Array => pred_ty.arrayLen(), + else => return sema.fail(block, pred_src, "expected vector or array, found '{}'", .{pred_ty.fmt(sema.mod)}), + }; + const vec_len = try sema.usizeCast(block, pred_src, vec_len_u64); + + const bool_vec_ty = try Type.vector(sema.arena, vec_len, Type.bool); + const pred = try sema.coerce(block, bool_vec_ty, pred_uncoerced, pred_src); + + const vec_ty = try Type.vector(sema.arena, vec_len, elem_ty); + 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.resolveMaybeUndefVal(block, pred_src, pred); + const maybe_a = try sema.resolveMaybeUndefVal(block, a_src, a); + const maybe_b = try sema.resolveMaybeUndefVal(block, b_src, b); + + const runtime_src = if (maybe_pred) |pred_val| rs: { + if (pred_val.isUndef()) return sema.addConstUndef(vec_ty); + + if (maybe_a) |a_val| { + if (a_val.isUndef()) return sema.addConstUndef(vec_ty); + + if (maybe_b) |b_val| { + if (b_val.isUndef()) return sema.addConstUndef(vec_ty); + + var buf: Value.ElemValueBuffer = undefined; + const elems = try sema.gpa.alloc(Value, vec_len); + for (elems) |*elem, i| { + const pred_elem_val = pred_val.elemValueBuffer(sema.mod, i, &buf); + const should_choose_a = pred_elem_val.toBool(); + if (should_choose_a) { + elem.* = a_val.elemValueBuffer(sema.mod, i, &buf); + } else { + elem.* = b_val.elemValueBuffer(sema.mod, i, &buf); + } + } + + return sema.addConstant( + vec_ty, + try Value.Tag.aggregate.create(sema.arena, elems), + ); + } else { + break :rs b_src; + } + } else { + if (maybe_b) |b_val| { + if (b_val.isUndef()) return sema.addConstUndef(vec_ty); + } + break :rs a_src; + } + } else rs: { + if (maybe_a) |a_val| { + if (a_val.isUndef()) return sema.addConstUndef(vec_ty); + } + if (maybe_b) |b_val| { + if (b_val.isUndef()) return sema.addConstUndef(vec_ty); + } + break :rs pred_src; + }; + + try sema.requireRuntimeBlock(block, 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)[inst].pl_node; - const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; + const extra = sema.code.extraData(Zir.Inst.AtomicLoad, inst_data.payload_index).data; // zig fmt: off const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; // zig fmt: on - const ptr = sema.resolveInst(extra.lhs); - const ptr_ty = sema.typeOf(ptr); - const elem_ty = ptr_ty.elemType(); - try sema.checkAtomicOperandType(block, elem_ty_src, elem_ty); - const order = try sema.resolveAtomicOrder(block, order_src, extra.rhs); + 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); switch (order) { .Release, .AcqRel => { @@ -12042,7 +16820,7 @@ fn zirAtomicLoad(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError! } if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { - if (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) |elem_val| { + if (try sema.pointerDeref(block, ptr_src, ptr_val, sema.typeOf(ptr))) |elem_val| { return sema.addConstant(elem_ty, elem_val); } } @@ -12062,19 +16840,19 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A const extra = sema.code.extraData(Zir.Inst.AtomicRmw, inst_data.payload_index).data; const src = inst_data.src(); // zig fmt: off - const operand_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const elem_ty_src : LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const op_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; const operand_src : LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg4 = inst_data.src_node }; // zig fmt: on - const ptr = sema.resolveInst(extra.ptr); - const ptr_ty = sema.typeOf(ptr); - const operand_ty = ptr_ty.elemType(); - try sema.checkAtomicOperandType(block, operand_ty_src, operand_ty); + 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 (operand_ty.zigTypeTag()) { + switch (elem_ty.zigTypeTag()) { .Enum => if (op != .Xchg) { return sema.fail(block, op_src, "@atomicRmw with enum only allowed with .Xchg", .{}); }, @@ -12087,7 +16865,6 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A }, else => {}, } - const operand = try sema.coerce(block, operand_ty, sema.resolveInst(extra.operand), operand_src); const order = try sema.resolveAtomicOrder(block, order_src, extra.ordering); if (order == .Unordered) { @@ -12095,8 +16872,8 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A } // special case zero bit types - if (try sema.typeHasOnePossibleValue(block, operand_ty_src, operand_ty)) |val| { - return sema.addConstant(operand_ty, val); + if (try sema.typeHasOnePossibleValue(block, elem_ty_src, elem_ty)) |val| { + return sema.addConstant(elem_ty, val); } const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { @@ -12107,22 +16884,23 @@ fn zirAtomicRmw(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A }; if (ptr_val.isComptimeMutablePtr()) { const target = sema.mod.getTarget(); + 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 stored_val.numberAddWrap(operand_val, operand_ty, sema.arena, target), - .Sub => try stored_val.numberSubWrap(operand_val, operand_ty, sema.arena, target), - .And => try stored_val.bitwiseAnd (operand_val, sema.arena), - .Nand => try stored_val.bitwiseNand (operand_val, operand_ty, sema.arena, target), - .Or => try stored_val.bitwiseOr (operand_val, sema.arena), - .Xor => try stored_val.bitwiseXor (operand_val, sema.arena), - .Max => try stored_val.numberMax (operand_val), - .Min => try stored_val.numberMin (operand_val), + .Add => try sema.numberAddWrap(block, src, stored_val, operand_val, elem_ty), + .Sub => try sema.numberSubWrap(block, src, stored_val, operand_val, elem_ty), + .And => try stored_val.bitwiseAnd (operand_val, elem_ty, sema.arena, target), + .Nand => try stored_val.bitwiseNand (operand_val, elem_ty, sema.arena, target), + .Or => try stored_val.bitwiseOr (operand_val, elem_ty, sema.arena, target), + .Xor => try stored_val.bitwiseXor (operand_val, elem_ty, sema.arena, target), + .Max => stored_val.numberMax (operand_val, target), + .Min => stored_val.numberMin (operand_val, target), // zig fmt: on }; - try sema.storePtrVal(block, src, ptr_val, new_val, operand_ty); - return sema.addConstant(operand_ty, stored_val); + try sema.storePtrVal(block, src, ptr_val, new_val, elem_ty); + return sema.addConstant(elem_ty, stored_val); } else break :rs ptr_src; } else ptr_src; @@ -12146,15 +16924,15 @@ fn zirAtomicStore(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError const extra = sema.code.extraData(Zir.Inst.AtomicStore, inst_data.payload_index).data; const src = inst_data.src(); // zig fmt: off - const operand_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const elem_ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const ptr_src : LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const operand_src : LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; const order_src : LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; // zig fmt: on - const ptr = sema.resolveInst(extra.ptr); - const operand_ty = sema.typeOf(ptr).elemType(); - try sema.checkAtomicOperandType(block, operand_ty_src, operand_ty); - const operand = try sema.coerce(block, operand_ty, sema.resolveInst(extra.operand), operand_src); + 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); const air_tag: Air.Inst.Tag = switch (order) { @@ -12177,43 +16955,282 @@ fn zirAtomicStore(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError fn zirMulAdd(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.MulAdd, inst_data.payload_index).data; const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirMulAdd", .{}); + + const mulend1_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const mulend2_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const addend_src: LazySrcLoc = .{ .node_offset_builtin_call_arg3 = inst_data.src_node }; + + 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 target = sema.mod.getTarget(); + + const maybe_mulend1 = try sema.resolveMaybeUndefVal(block, mulend1_src, mulend1); + const maybe_mulend2 = try sema.resolveMaybeUndefVal(block, mulend2_src, mulend2); + const maybe_addend = try sema.resolveMaybeUndefVal(block, addend_src, addend); + + switch (ty.zigTypeTag()) { + .ComptimeFloat, .Float, .Vector => {}, + else => return sema.fail(block, src, "expected vector of floats or float type, found '{}'", .{ty.fmt(sema.mod)}), + } + + const runtime_src = if (maybe_mulend1) |mulend1_val| rs: { + if (maybe_mulend2) |mulend2_val| { + if (mulend2_val.isUndef()) return sema.addConstUndef(ty); + + if (maybe_addend) |addend_val| { + if (addend_val.isUndef()) return sema.addConstUndef(ty); + const result_val = try Value.mulAdd(ty, mulend1_val, mulend2_val, addend_val, sema.arena, target); + return sema.addConstant(ty, result_val); + } else { + break :rs addend_src; + } + } else { + if (maybe_addend) |addend_val| { + if (addend_val.isUndef()) return sema.addConstUndef(ty); + } + break :rs mulend2_src; + } + } else rs: { + if (maybe_mulend2) |mulend2_val| { + if (mulend2_val.isUndef()) return sema.addConstUndef(ty); + } + if (maybe_addend) |addend_val| { + if (addend_val.isUndef()) return sema.addConstUndef(ty); + } + break :rs mulend1_src; + }; + + try sema.requireRuntimeBlock(block, 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 inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirBuiltinCall", .{}); -} + const tracy = trace(@src()); + defer tracy.end(); -fn zirFieldPtrType(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirFieldPtrType", .{}); + const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const func_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const args_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + const call_src = inst_data.src(); + + const extra = sema.code.extraData(Zir.Inst.BuiltinCall, inst_data.payload_index).data; + var func = try sema.resolveInst(extra.callee); + const options = try sema.resolveInst(extra.options); + const args = try sema.resolveInst(extra.args); + + const wanted_modifier: std.builtin.CallOptions.Modifier = modifier: { + const call_options_ty = try sema.getBuiltinType(block, options_src, "CallOptions"); + const coerced_options = try sema.coerce(block, call_options_ty, options, options_src); + + const modifier = try sema.fieldVal(block, options_src, coerced_options, "modifier", options_src); + const modifier_val = try sema.resolveConstValue(block, options_src, modifier); + + const stack = try sema.fieldVal(block, options_src, coerced_options, "stack", options_src); + const stack_val = try sema.resolveConstValue(block, options_src, stack); + + if (!stack_val.isNull()) { + return sema.fail(block, options_src, "TODO: implement @call with stack", .{}); + } + break :modifier modifier_val.toEnum(std.builtin.CallOptions.Modifier); + }; + + const is_comptime = extra.flags.is_comptime or block.is_comptime; + + const modifier: std.builtin.CallOptions.Modifier = switch (wanted_modifier) { + // These can be upgraded to comptime or nosuspend calls. + .auto, .never_tail, .no_async => m: { + if (is_comptime) { + if (wanted_modifier == .never_tail) { + return sema.fail(block, options_src, "unable to perform 'never_tail' call at compile-time", .{}); + } + break :m .compile_time; + } + if (extra.flags.is_nosuspend) { + break :m .no_async; + } + break :m wanted_modifier; + }, + // These can be upgraded to comptime. nosuspend bit can be safely ignored. + .always_tail, .always_inline, .compile_time => m: { + _ = (try sema.resolveDefinedValue(block, func_src, func)) orelse { + return sema.fail(block, func_src, "modifier '{s}' requires a comptime-known function", .{@tagName(wanted_modifier)}); + }; + + if (is_comptime) { + break :m .compile_time; + } + break :m wanted_modifier; + }, + .async_kw => m: { + if (extra.flags.is_nosuspend) { + return sema.fail(block, options_src, "modifier 'async_kw' cannot be used inside nosuspend block", .{}); + } + if (is_comptime) { + return sema.fail(block, options_src, "modifier 'async_kw' cannot be used in combination with comptime function call", .{}); + } + break :m wanted_modifier; + }, + .never_inline => m: { + if (is_comptime) { + return sema.fail(block, options_src, "unable to perform 'never_inline' call at compile-time", .{}); + } + break :m wanted_modifier; + }, + }; + + const args_ty = sema.typeOf(args); + if (!args_ty.isTuple() and args_ty.tag() != .empty_struct_literal) { + return sema.fail(block, args_src, "expected a tuple, found '{}'", .{args_ty.fmt(sema.mod)}); + } + + var resolved_args: []Air.Inst.Ref = undefined; + + // Desugar bound functions here + if (sema.typeOf(func).tag() == .bound_fn) { + const bound_func = try sema.resolveValue(block, func_src, func); + const bound_data = &bound_func.cast(Value.Payload.BoundFn).?.data; + func = bound_data.func_inst; + resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount() + 1); + resolved_args[0] = bound_data.arg0_inst; + for (resolved_args[1..]) |*resolved, i| { + resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty); + } + } else { + resolved_args = try sema.arena.alloc(Air.Inst.Ref, args_ty.structFieldCount()); + for (resolved_args) |*resolved, i| { + resolved.* = try sema.tupleFieldValByIndex(block, args_src, args, @intCast(u32, i), args_ty); + } + } + const ensure_result_used = extra.flags.ensure_result_used; + return sema.analyzeCall(block, func, func_src, call_src, modifier, ensure_result_used, resolved_args); } fn zirFieldParentPtr(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const extra = sema.code.extraData(Zir.Inst.FieldParentPtr, inst_data.payload_index).data; const src = inst_data.src(); - return sema.fail(block, src, "TODO: Sema.zirFieldParentPtr", .{}); + const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; + const name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; + const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; + + const struct_ty = try sema.resolveType(block, ty_src, extra.parent_type); + const field_name = try sema.resolveConstString(block, name_src, extra.field_name); + const field_ptr = try sema.resolveInst(extra.field_ptr); + const field_ptr_ty = sema.typeOf(field_ptr); + + if (struct_ty.zigTypeTag() != .Struct) { + return sema.fail(block, ty_src, "expected struct type, found '{}'", .{struct_ty.fmt(sema.mod)}); + } + try sema.resolveTypeLayout(block, ty_src, struct_ty); + + const struct_obj = struct_ty.castTag(.@"struct").?.data; + const field_index = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadStructFieldAccess(block, struct_obj, name_src, field_name); + + try sema.checkPtrOperand(block, ptr_src, field_ptr_ty); + const field = struct_obj.fields.values()[field_index]; + const field_ptr_ty_info = field_ptr_ty.ptrInfo().data; + + var ptr_ty_data: Type.Payload.Pointer.Data = .{ + .pointee_type = field.ty, + .mutable = field_ptr_ty_info.mutable, + .@"addrspace" = field_ptr_ty_info.@"addrspace", + }; + + if (struct_obj.layout == .Packed) { + return sema.fail(block, src, "TODO handle packed structs with @fieldParentPtr", .{}); + } else { + ptr_ty_data.@"align" = field.abi_align; + } + + const actual_field_ptr_ty = try Type.ptr(sema.arena, sema.mod, ptr_ty_data); + const casted_field_ptr = try sema.coerce(block, actual_field_ptr_ty, field_ptr, ptr_src); + + ptr_ty_data.pointee_type = struct_ty; + const result_ptr = try Type.ptr(sema.arena, sema.mod, ptr_ty_data); + + if (try sema.resolveDefinedValue(block, src, casted_field_ptr)) |field_ptr_val| { + const payload = field_ptr_val.castTag(.field_ptr) orelse { + return sema.fail(block, ptr_src, "pointer value not based on parent struct", .{}); + }; + if (payload.data.field_index != field_index) { + const msg = msg: { + const msg = try sema.errMsg( + block, + src, + "field '{s}' has index '{d}' but pointer value is index '{d}' of struct '{}'", + .{ + field_name, + field_index, + payload.data.field_index, + struct_ty.fmt(sema.mod), + }, + ); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, struct_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + return sema.addConstant(result_ptr, payload.data.container_ptr); + } + + try sema.requireRuntimeBlock(block, src); + return block.addInst(.{ + .tag = .field_parent_ptr, + .data = .{ .ty_pl = .{ + .ty = try sema.addType(result_ptr), + .payload = try block.sema.addExtra(Air.FieldParentPtr{ + .field_ptr = casted_field_ptr, + .field_index = @intCast(u32, field_index), + }), + } }, + }); } fn zirMinMax( sema: *Sema, block: *Block, inst: Zir.Inst.Index, - air_tag: Air.Inst.Tag, + comptime air_tag: Air.Inst.Tag, ) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].pl_node; const extra = sema.code.extraData(Zir.Inst.Bin, inst_data.payload_index).data; const src = inst_data.src(); const lhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const rhs_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; - const lhs = sema.resolveInst(extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + const lhs = try sema.resolveInst(extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); try sema.checkNumericType(block, lhs_src, sema.typeOf(lhs)); try sema.checkNumericType(block, rhs_src, sema.typeOf(rhs)); + return sema.analyzeMinMax(block, src, lhs, rhs, air_tag, lhs_src, rhs_src); +} + +fn analyzeMinMax( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Air.Inst.Ref, + rhs: Air.Inst.Ref, + comptime air_tag: Air.Inst.Tag, + lhs_src: LazySrcLoc, + rhs_src: LazySrcLoc, +) CompileError!Air.Inst.Ref { const simd_op = try sema.checkSimdBinOp(block, src, lhs, rhs, lhs_src, rhs_src); // TODO @maximum(max_int, undefined) should return max_int @@ -12230,21 +17247,22 @@ fn zirMinMax( .max => Value.numberMax, else => unreachable, }; + const target = sema.mod.getTarget(); const vec_len = simd_op.len orelse { - const result_val = try opFunc(lhs_val, rhs_val); + const result_val = opFunc(lhs_val, rhs_val, target); return sema.addConstant(simd_op.result_ty, result_val); }; var lhs_buf: Value.ElemValueBuffer = undefined; var rhs_buf: Value.ElemValueBuffer = undefined; const elems = try sema.arena.alloc(Value, vec_len); for (elems) |*elem, i| { - const lhs_elem_val = lhs_val.elemValueBuffer(i, &lhs_buf); - const rhs_elem_val = rhs_val.elemValueBuffer(i, &rhs_buf); - elem.* = try opFunc(lhs_elem_val, rhs_elem_val); + const lhs_elem_val = lhs_val.elemValueBuffer(sema.mod, i, &lhs_buf); + const rhs_elem_val = rhs_val.elemValueBuffer(sema.mod, i, &rhs_buf); + elem.* = opFunc(lhs_elem_val, rhs_elem_val, target); } return sema.addConstant( simd_op.result_ty, - try Value.Tag.array.create(sema.arena, elems), + try Value.Tag.aggregate.create(sema.arena, elems), ); } else rs: { if (simd_op.rhs_val) |rhs_val| { @@ -12264,19 +17282,19 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const src_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; - const dest_ptr = sema.resolveInst(extra.dest); + const dest_ptr = try sema.resolveInst(extra.dest); const dest_ptr_ty = sema.typeOf(dest_ptr); try sema.checkPtrOperand(block, dest_src, dest_ptr_ty); if (dest_ptr_ty.isConstPtr()) { - return sema.fail(block, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty}); + return sema.fail(block, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty.fmt(sema.mod)}); } - const uncasted_src_ptr = sema.resolveInst(extra.source); + const uncasted_src_ptr = try sema.resolveInst(extra.source); const uncasted_src_ptr_ty = sema.typeOf(uncasted_src_ptr); try sema.checkPtrOperand(block, src_src, uncasted_src_ptr_ty); const src_ptr_info = uncasted_src_ptr_ty.ptrInfo().data; - const wanted_src_ptr_ty = try Type.ptr(sema.arena, .{ + const wanted_src_ptr_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = dest_ptr_ty.elemType2(), .@"align" = src_ptr_info.@"align", .@"addrspace" = src_ptr_info.@"addrspace", @@ -12286,15 +17304,13 @@ fn zirMemcpy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void .size = .Many, }); const src_ptr = try sema.coerce(block, wanted_src_ptr_ty, uncasted_src_ptr, src_src); - const len = try sema.coerce(block, Type.usize, sema.resolveInst(extra.byte_count), len_src); - - const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr); - const maybe_src_ptr_val = try sema.resolveDefinedValue(block, src_src, src_ptr); - const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len); + const len = try sema.coerce(block, Type.usize, try sema.resolveInst(extra.byte_count), len_src); - const runtime_src = if (maybe_dest_ptr_val) |dest_ptr_val| rs: { - if (maybe_src_ptr_val) |src_ptr_val| { - if (maybe_len_val) |len_val| { + const runtime_src = if (try sema.resolveDefinedValue(block, dest_src, dest_ptr)) |dest_ptr_val| rs: { + if (!dest_ptr_val.isComptimeMutablePtr()) break :rs dest_src; + if (try sema.resolveDefinedValue(block, src_src, src_ptr)) |src_ptr_val| { + if (!src_ptr_val.isComptimeMutablePtr()) break :rs src_src; + if (try sema.resolveDefinedValue(block, len_src, len)) |len_val| { _ = dest_ptr_val; _ = src_ptr_val; _ = len_val; @@ -12323,21 +17339,19 @@ fn zirMemset(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!void const dest_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node }; const value_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node }; const len_src: LazySrcLoc = .{ .node_offset_builtin_call_arg2 = inst_data.src_node }; - const dest_ptr = sema.resolveInst(extra.dest); + const dest_ptr = try sema.resolveInst(extra.dest); const dest_ptr_ty = sema.typeOf(dest_ptr); try sema.checkPtrOperand(block, dest_src, dest_ptr_ty); if (dest_ptr_ty.isConstPtr()) { - return sema.fail(block, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty}); + return sema.fail(block, dest_src, "cannot store through const pointer '{}'", .{dest_ptr_ty.fmt(sema.mod)}); } const elem_ty = dest_ptr_ty.elemType2(); - const value = try sema.coerce(block, elem_ty, sema.resolveInst(extra.byte), value_src); - const len = try sema.coerce(block, Type.usize, sema.resolveInst(extra.byte_count), len_src); - - const maybe_dest_ptr_val = try sema.resolveDefinedValue(block, dest_src, dest_ptr); - const maybe_len_val = try sema.resolveDefinedValue(block, len_src, len); + const value = try sema.coerce(block, elem_ty, try sema.resolveInst(extra.byte), value_src); + const len = try sema.coerce(block, Type.usize, try sema.resolveInst(extra.byte_count), len_src); - const runtime_src = if (maybe_dest_ptr_val) |ptr_val| rs: { - if (maybe_len_val) |len_val| { + const runtime_src = if (try sema.resolveDefinedValue(block, dest_src, dest_ptr)) |ptr_val| rs: { + if (!ptr_val.isComptimeMutablePtr()) break :rs dest_src; + if (try sema.resolveDefinedValue(block, len_src, len)) |len_val| { if (try sema.resolveMaybeUndefVal(block, value_src, value)) |val| { _ = ptr_val; _ = len_val; @@ -12376,15 +17390,24 @@ fn zirAwait( sema: *Sema, block: *Block, inst: Zir.Inst.Index, - is_nosuspend: bool, ) CompileError!Air.Inst.Ref { const inst_data = sema.code.instructions.items(.data)[inst].un_node; const src = inst_data.src(); - _ = is_nosuspend; return sema.fail(block, src, "TODO: Sema.zirAwait", .{}); } +fn zirAwaitNosuspend( + 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 = LazySrcLoc.nodeOffset(extra.node); + + return sema.fail(block, src, "TODO: Sema.zirAwaitNosuspend", .{}); +} + fn zirVarExtended( sema: *Sema, block: *Block, @@ -12418,7 +17441,7 @@ fn zirVarExtended( const uncasted_init: Air.Inst.Ref = if (small.has_init) blk: { const init_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - break :blk sema.resolveInst(init_ref); + break :blk try sema.resolveInst(init_ref); } else .none; const have_ty = extra.data.var_type != .none; @@ -12439,25 +17462,27 @@ fn zirVarExtended( try sema.validateVarType(block, mut_src, var_ty, small.is_extern); - if (lib_name != null) { - // Look at the sema code for functions which has this logic, it just needs to - // be extracted and shared by both var and func - return sema.fail(block, src, "TODO: handle var with lib_name in Sema", .{}); - } - const new_var = try sema.gpa.create(Module.Var); + errdefer sema.gpa.destroy(new_var); log.debug("created variable {*} owner_decl: {*} ({s})", .{ new_var, sema.owner_decl, sema.owner_decl.name, }); new_var.* = .{ - .owner_decl = sema.owner_decl, + .owner_decl = sema.owner_decl_index, .init = init_val, .is_extern = small.is_extern, .is_mutable = true, // TODO get rid of this unused field .is_threadlocal = small.is_threadlocal, + .is_weak_linkage = false, + .lib_name = null, }; + + if (lib_name) |lname| { + new_var.lib_name = try sema.handleExternLibName(block, ty_src, lname); + } + const result = try sema.addConstant( var_ty, try Value.Tag.variable.create(sema.arena, new_var), @@ -12465,45 +17490,164 @@ fn zirVarExtended( return result; } -fn zirFuncExtended( - sema: *Sema, - block: *Block, - extended: Zir.Inst.Extended.InstData, - inst: Zir.Inst.Index, -) CompileError!Air.Inst.Ref { +fn zirFuncFancy(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { const tracy = trace(@src()); defer tracy.end(); - const extra = sema.code.extraData(Zir.Inst.ExtendedFunc, extended.operand); - const src: LazySrcLoc = .{ .node_offset = extra.data.src_node }; - const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = extra.data.src_node }; + const inst_data = sema.code.instructions.items(.data)[inst].pl_node; + const src = inst_data.src(); + const extra = sema.code.extraData(Zir.Inst.FuncFancy, inst_data.payload_index); + const target = sema.mod.getTarget(); + const align_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at align - const small = @bitCast(Zir.Inst.ExtendedFunc.Small, extended.small); + const addrspace_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at addrspace + const section_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at section + const cc_src: LazySrcLoc = .{ .node_offset_fn_type_cc = inst_data.src_node }; + const ret_src: LazySrcLoc = src; // TODO add a LazySrcLoc that points at the return type var extra_index: usize = extra.end; - const lib_name: ?[]const u8 = if (small.has_lib_name) blk: { + const lib_name: ?[]const u8 = if (extra.data.bits.has_lib_name) blk: { const lib_name = sema.code.nullTerminatedString(sema.code.extra[extra_index]); extra_index += 1; break :blk lib_name; } else null; - const cc: std.builtin.CallingConvention = if (small.has_cc) blk: { + const @"align": ?u32 = if (extra.data.bits.has_align_body) blk: { + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + + const val = try sema.resolveGenericBody(block, align_src, body, inst, Type.u29); + if (val.tag() == .generic_poison) { + break :blk null; + } + const alignment = @intCast(u32, val.toUnsignedInt(target)); + if (alignment == target_util.defaultFunctionAlignment(target)) { + break :blk 0; + } else { + break :blk alignment; + } + } else if (extra.data.bits.has_align_ref) blk: { + const align_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const align_tv = sema.resolveInstConst(block, align_src, align_ref) catch |err| switch (err) { + error.GenericPoison => { + break :blk null; + }, + else => |e| return e, + }; + const alignment = @intCast(u32, align_tv.val.toUnsignedInt(target)); + if (alignment == target_util.defaultFunctionAlignment(target)) { + break :blk 0; + } else { + break :blk alignment; + } + } else 0; + + const @"addrspace": ?std.builtin.AddressSpace = if (extra.data.bits.has_addrspace_body) blk: { + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + + const addrspace_ty = try sema.getBuiltinType(block, addrspace_src, "AddressSpace"); + const val = try sema.resolveGenericBody(block, addrspace_src, body, inst, addrspace_ty); + if (val.tag() == .generic_poison) { + break :blk null; + } + break :blk val.toEnum(std.builtin.AddressSpace); + } else if (extra.data.bits.has_addrspace_ref) blk: { + const addrspace_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const addrspace_tv = sema.resolveInstConst(block, addrspace_src, addrspace_ref) catch |err| switch (err) { + error.GenericPoison => { + break :blk null; + }, + else => |e| return e, + }; + break :blk addrspace_tv.val.toEnum(std.builtin.AddressSpace); + } else target_util.defaultAddressSpace(target, .function); + + const @"linksection": FuncLinkSection = if (extra.data.bits.has_section_body) blk: { + const body_len = sema.code.extra[extra_index]; + extra_index += 1; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + + const val = try sema.resolveGenericBody(block, section_src, body, inst, Type.initTag(.const_slice_u8)); + if (val.tag() == .generic_poison) { + break :blk FuncLinkSection{ .generic = {} }; + } + _ = val; + return sema.fail(block, section_src, "TODO implement linksection on functions", .{}); + } else if (extra.data.bits.has_section_ref) blk: { + const section_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const section_tv = sema.resolveInstConst(block, section_src, section_ref) catch |err| switch (err) { + error.GenericPoison => { + break :blk FuncLinkSection{ .generic = {} }; + }, + else => |e| return e, + }; + _ = section_tv; + return sema.fail(block, section_src, "TODO implement linksection on functions", .{}); + } else FuncLinkSection{ .default = {} }; + + 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.extra[extra_index..][0..body_len]; + extra_index += body.len; + + const cc_ty = try sema.getBuiltinType(block, addrspace_src, "CallingConvention"); + const val = try sema.resolveGenericBody(block, cc_src, body, inst, cc_ty); + if (val.tag() == .generic_poison) { + break :blk null; + } + break :blk val.toEnum(std.builtin.CallingConvention); + } else if (extra.data.bits.has_cc_ref) blk: { const cc_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); extra_index += 1; - const cc_tv = try sema.resolveInstConst(block, cc_src, cc_ref); + const cc_tv = sema.resolveInstConst(block, cc_src, cc_ref) catch |err| switch (err) { + error.GenericPoison => { + break :blk null; + }, + else => |e| return e, + }; break :blk cc_tv.val.toEnum(std.builtin.CallingConvention); - } else .Unspecified; + } else std.builtin.CallingConvention.Unspecified; - const align_val: Value = if (small.has_align) blk: { - const align_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + 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 align_tv = try sema.resolveInstConst(block, align_src, align_ref); - break :blk align_tv.val; - } else Value.@"null"; + const body = sema.code.extra[extra_index..][0..body_len]; + extra_index += body.len; + + const val = try sema.resolveGenericBody(block, ret_src, body, inst, Type.type); + var buffer: Value.ToTypeBuffer = undefined; + const ty = try val.toType(&buffer).copy(sema.arena); + break :blk ty; + } else if (extra.data.bits.has_ret_ty_ref) blk: { + const ret_ty_ref = @intToEnum(Zir.Inst.Ref, sema.code.extra[extra_index]); + extra_index += 1; + const ret_ty_tv = sema.resolveInstConst(block, ret_src, ret_ty_ref) catch |err| switch (err) { + error.GenericPoison => { + break :blk Type.initTag(.generic_poison); + }, + else => |e| return e, + }; + var buffer: Value.ToTypeBuffer = undefined; + const ty = try ret_ty_tv.val.toType(&buffer).copy(sema.arena); + break :blk ty; + } else Type.void; - const ret_ty_body = sema.code.extra[extra_index..][0..extra.data.ret_body_len]; - extra_index += ret_ty_body.len; + 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; const has_body = extra.data.body_len != 0; @@ -12512,23 +17656,26 @@ fn zirFuncExtended( src_locs = sema.code.extraData(Zir.Inst.Func.SrcLocs, extra_index).data; } - const is_var_args = small.is_var_args; - const is_inferred_error = small.is_inferred_error; - const is_extern = small.is_extern; + const is_var_args = extra.data.bits.is_var_args; + const is_inferred_error = extra.data.bits.is_inferred_error; + const is_extern = extra.data.bits.is_extern; return sema.funcCommon( block, - extra.data.src_node, + inst_data.src_node, inst, - ret_ty_body, + @"align", + @"addrspace", + @"linksection", cc, - align_val, + ret_ty, is_var_args, is_inferred_error, is_extern, has_body, src_locs, lib_name, + noalias_bits, ); } @@ -12538,7 +17685,7 @@ fn zirCUndef( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; + const src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; const name = try sema.resolveConstString(block, src, extra.operand); try block.c_import_buf.?.writer().print("#undefine {s}\n", .{name}); @@ -12551,7 +17698,7 @@ fn zirCInclude( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; + const src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; const name = try sema.resolveConstString(block, src, extra.operand); try block.c_import_buf.?.writer().print("#include <{s}>\n", .{name}); @@ -12564,12 +17711,13 @@ fn zirCDefine( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; + const name_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const val_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; - const name = try sema.resolveConstString(block, src, extra.lhs); - const rhs = sema.resolveInst(extra.rhs); + const name = try sema.resolveConstString(block, name_src, extra.lhs); + const rhs = try sema.resolveInst(extra.rhs); if (sema.typeOf(rhs).zigTypeTag() != .Void) { - const value = try sema.resolveConstString(block, src, extra.rhs); + const value = try sema.resolveConstString(block, val_src, extra.rhs); try block.c_import_buf.?.writer().print("#define {s} {s}\n", .{ name, value }); } else { try block.c_import_buf.?.writer().print("#define {s}\n", .{name}); @@ -12583,8 +17731,22 @@ fn zirWasmMemorySize( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.UnNode, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.fail(block, src, "TODO: implement Sema.zirWasmMemorySize", .{}); + const index_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const builtin_src = LazySrcLoc.nodeOffset(extra.node); + const target = sema.mod.getTarget(); + if (!target.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 = @intCast(u32, try sema.resolveInt(block, index_src, extra.operand, Type.u32)); + try sema.requireRuntimeBlock(block, builtin_src); + return block.addInst(.{ + .tag = .wasm_memory_size, + .data = .{ .pl_op = .{ + .operand = .none, + .payload = index, + } }, + }); } fn zirWasmMemoryGrow( @@ -12593,8 +17755,25 @@ fn zirWasmMemoryGrow( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.fail(block, src, "TODO: implement Sema.zirWasmMemoryGrow", .{}); + const builtin_src = LazySrcLoc.nodeOffset(extra.node); + const index_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const delta_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; + const target = sema.mod.getTarget(); + if (!target.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 = @intCast(u32, try sema.resolveInt(block, index_src, extra.lhs, Type.u32)); + const delta = try sema.coerce(block, Type.u32, try sema.resolveInst(extra.rhs), delta_src); + + try sema.requireRuntimeBlock(block, builtin_src); + return block.addInst(.{ + .tag = .wasm_memory_grow, + .data = .{ .pl_op = .{ + .operand = delta, + .payload = index, + } }, + }); } fn zirPrefetch( @@ -12606,9 +17785,10 @@ fn zirPrefetch( const ptr_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; const opts_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; const options_ty = try sema.getBuiltinType(block, opts_src, "PrefetchOptions"); - const ptr = sema.resolveInst(extra.lhs); + const ptr = try sema.resolveInst(extra.lhs); try sema.checkPtrOperand(block, ptr_src, sema.typeOf(ptr)); - const options = try sema.coerce(block, options_ty, sema.resolveInst(extra.rhs), opts_src); + const options = try sema.coerce(block, options_ty, try sema.resolveInst(extra.rhs), opts_src); + const target = sema.mod.getTarget(); const rw = try sema.fieldVal(block, opts_src, options, "rw", opts_src); const rw_val = try sema.resolveConstValue(block, opts_src, rw); @@ -12616,7 +17796,7 @@ fn zirPrefetch( const locality = try sema.fieldVal(block, opts_src, options, "locality", opts_src); const locality_val = try sema.resolveConstValue(block, opts_src, locality); - const locality_int = @intCast(u2, locality_val.toUnsignedInt()); + const locality_int = @intCast(u2, locality_val.toUnsignedInt(target)); const cache = try sema.fieldVal(block, opts_src, options, "cache", opts_src); const cache_val = try sema.resolveConstValue(block, opts_src, cache); @@ -12643,12 +17823,111 @@ fn zirBuiltinExtern( extended: Zir.Inst.Extended.InstData, ) CompileError!Air.Inst.Ref { const extra = sema.code.extraData(Zir.Inst.BinNode, extended.operand).data; - const src: LazySrcLoc = .{ .node_offset = extra.node }; - return sema.fail(block, src, "TODO: implement Sema.zirBuiltinExtern", .{}); + const src = LazySrcLoc.nodeOffset(extra.node); + const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = extra.node }; + const options_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = extra.node }; + + var ty = try sema.resolveType(block, ty_src, extra.lhs); + const options_inst = try sema.resolveInst(extra.rhs); + const mod = sema.mod; + + const options = options: { + const extern_options_ty = try sema.getBuiltinType(block, options_src, "ExternOptions"); + const options = try sema.coerce(block, extern_options_ty, options_inst, options_src); + + const name = try sema.fieldVal(block, options_src, options, "name", options_src); + const name_val = try sema.resolveConstValue(block, options_src, name); + + const library_name_inst = try sema.fieldVal(block, options_src, options, "library_name", options_src); + const library_name_val = try sema.resolveConstValue(block, options_src, library_name_inst); + + const linkage = try sema.fieldVal(block, options_src, options, "linkage", options_src); + const linkage_val = try sema.resolveConstValue(block, options_src, linkage); + + const is_thread_local = try sema.fieldVal(block, options_src, options, "is_thread_local", options_src); + const is_thread_local_val = try sema.resolveConstValue(block, options_src, is_thread_local); + + var library_name: ?[]const u8 = null; + if (!library_name_val.isNull()) { + const payload = library_name_val.castTag(.opt_payload).?.data; + library_name = try payload.toAllocatedBytes(Type.initTag(.const_slice_u8), sema.arena, mod); + } + + break :options std.builtin.ExternOptions{ + .name = try name_val.toAllocatedBytes(Type.initTag(.const_slice_u8), sema.arena, mod), + .library_name = library_name, + .linkage = linkage_val.toEnum(std.builtin.GlobalLinkage), + .is_thread_local = is_thread_local_val.toBool(), + }; + }; + + if (!ty.isPtrAtRuntime()) { + return sema.fail(block, options_src, "expected (optional) pointer", .{}); + } + + if (options.name.len == 0) { + return sema.fail(block, options_src, "extern symbol name cannot be empty", .{}); + } + + if (options.linkage != .Weak and options.linkage != .Strong) { + return sema.fail(block, options_src, "extern symbol must use strong or weak linkage", .{}); + } + + if (options.linkage == .Weak and !ty.ptrAllowsZero()) { + ty = try Type.optional(sema.arena, ty); + } + + // TODO check duplicate extern + + const new_decl_index = try mod.allocateNewDecl(sema.owner_decl.src_namespace, sema.owner_decl.src_node, null); + errdefer mod.destroyDecl(new_decl_index); + const new_decl = mod.declPtr(new_decl_index); + new_decl.name = try sema.gpa.dupeZ(u8, options.name); + + var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); + errdefer new_decl_arena.deinit(); + const new_decl_arena_allocator = new_decl_arena.allocator(); + + const new_var = try new_decl_arena_allocator.create(Module.Var); + errdefer new_decl_arena_allocator.destroy(new_var); + + new_var.* = .{ + .owner_decl = sema.owner_decl_index, + .init = Value.initTag(.unreachable_value), + .is_extern = true, + .is_mutable = false, // TODO get rid of this unused field + .is_threadlocal = options.is_thread_local, + .is_weak_linkage = options.linkage == .Weak, + .lib_name = null, + }; + + if (options.library_name) |library_name| { + if (library_name.len == 0) { + return sema.fail(block, options_src, "library name name cannot be empty", .{}); + } + new_var.lib_name = try sema.handleExternLibName(block, options_src, library_name); + } + + new_decl.src_line = sema.owner_decl.src_line; + new_decl.ty = try ty.copy(new_decl_arena_allocator); + new_decl.val = try Value.Tag.variable.create(new_decl_arena_allocator, new_var); + new_decl.@"align" = 0; + new_decl.@"linksection" = null; + new_decl.has_tv = true; + new_decl.analysis = .complete; + new_decl.generation = mod.generation; + + const arena_state = try new_decl_arena_allocator.create(std.heap.ArenaAllocator.State); + arena_state.* = new_decl_arena.state; + new_decl.value_arena = arena_state; + + const ref = try sema.analyzeDeclRef(new_decl_index); + try sema.requireRuntimeBlock(block, src); + return block.addBitCast(ty, ref); } fn requireFunctionBlock(sema: *Sema, block: *Block, src: LazySrcLoc) !void { - if (sema.func == null) { + if (sema.func == null and !block.is_typeof and !block.is_coerce_result_ptr) { return sema.fail(block, src, "instruction illegal outside function body", .{}); } } @@ -12668,6 +17947,29 @@ fn validateVarType( var_ty: Type, is_extern: bool, ) CompileError!void { + if (try sema.validateRunTimeType(block, src, var_ty, is_extern)) return; + + const mod = sema.mod; + + const msg = msg: { + const msg = try sema.errMsg(block, src, "variable of type '{}' must be const or comptime", .{var_ty.fmt(mod)}); + errdefer msg.destroy(sema.gpa); + + const src_decl = mod.declPtr(block.src_decl); + try sema.explainWhyTypeIsComptime(block, src, msg, src.toSrcLoc(src_decl), var_ty); + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); +} + +fn validateRunTimeType( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + var_ty: Type, + is_extern: bool, +) CompileError!bool { var ty = var_ty; while (true) switch (ty.zigTypeTag()) { .Bool, @@ -12678,7 +17980,7 @@ fn validateVarType( .Frame, .AnyFrame, .Void, - => return, + => return true, .BoundFn, .ComptimeFloat, @@ -12689,21 +17991,22 @@ fn validateVarType( .Undefined, .Null, .Fn, - => break, + => return false, .Pointer => { const elem_ty = ty.childType(); switch (elem_ty.zigTypeTag()) { - .Opaque, .Fn => return, + .Opaque => return true, + .Fn => return elem_ty.isFnOrHasRuntimeBits(), else => ty = elem_ty, } }, - .Opaque => if (is_extern) return else break, + .Opaque => return is_extern, .Optional => { var buf: Type.Payload.ElemType = undefined; const child_ty = ty.optionalChild(&buf); - return validateVarType(sema, block, src, child_ty, is_extern); + return validateRunTimeType(sema, block, src, child_ty, is_extern); }, .Array, .Vector => ty = ty.elemType(), @@ -12711,23 +18014,10 @@ fn validateVarType( .Struct, .Union => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); - if (try sema.typeRequiresComptime(block, src, resolved_ty)) { - break; - } else { - return; - } + const needs_comptime = try sema.typeRequiresComptime(block, src, resolved_ty); + return !needs_comptime; }, - } else unreachable; // TODO should not need else unreachable - - const msg = msg: { - const msg = try sema.errMsg(block, src, "variable of type '{}' must be const or comptime", .{var_ty}); - errdefer msg.destroy(sema.gpa); - - try sema.explainWhyTypeIsComptime(block, src, msg, src.toSrcLoc(block.src_decl), var_ty); - - break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); } fn explainWhyTypeIsComptime( @@ -12752,7 +18042,7 @@ fn explainWhyTypeIsComptime( .Fn => { try mod.errNoteNonLazy(src_loc, msg, "use '*const {}' for a function pointer type", .{ - ty, + ty.fmt(sema.mod), }); }, @@ -12771,7 +18061,25 @@ fn explainWhyTypeIsComptime( .Optional, => return, - .Pointer, .Array, .Vector => { + .Array, .Vector => { + try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType()); + }, + .Pointer => { + const elem_ty = ty.elemType2(); + if (elem_ty.zigTypeTag() == .Fn) { + const fn_info = elem_ty.fnInfo(); + if (fn_info.is_generic) { + try mod.errNoteNonLazy(src_loc, msg, "function is generic", .{}); + } + switch (fn_info.cc) { + .Inline => try mod.errNoteNonLazy(src_loc, msg, "function has inline calling convention", .{}), + else => {}, + } + if (fn_info.return_type.comptimeOnly()) { + try mod.errNoteNonLazy(src_loc, msg, "function has a comptime-only return type", .{}); + } + return; + } try sema.explainWhyTypeIsComptime(block, src, msg, src_loc, ty.elemType()); }, @@ -12783,7 +18091,7 @@ fn explainWhyTypeIsComptime( if (ty.castTag(.@"struct")) |payload| { const struct_obj = payload.data; for (struct_obj.fields.values()) |field, i| { - const field_src_loc = struct_obj.fieldSrcLoc(sema.gpa, .{ + const field_src_loc = struct_obj.fieldSrcLoc(sema.mod, .{ .index = i, .range = .type, }); @@ -12800,7 +18108,7 @@ fn explainWhyTypeIsComptime( if (ty.cast(Type.Payload.Union)) |payload| { const union_obj = payload.data; for (union_obj.fields.values()) |field, i| { - const field_src_loc = union_obj.fieldSrcLoc(sema.gpa, .{ + const field_src_loc = union_obj.fieldSrcLoc(sema.mod, .{ .index = i, .range = .type, }); @@ -12817,10 +18125,12 @@ fn explainWhyTypeIsComptime( pub const PanicId = enum { unreach, unwrap_null, - unwrap_errunion, cast_to_null, incorrect_alignment, invalid_error_code, + cast_truncated_data, + integer_overflow, + shl_overflow, }; fn addSafetyCheck( @@ -12846,6 +18156,17 @@ fn addSafetyCheck( _ = try sema.safetyPanic(&fail_block, .unneeded, 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 + @@ -12914,20 +18235,104 @@ fn panicWithMsg( const panic_fn = try sema.getBuiltin(block, src, "panic"); const unresolved_stack_trace_ty = try sema.getBuiltinType(block, src, "StackTrace"); const stack_trace_ty = try sema.resolveTypeFields(block, src, unresolved_stack_trace_ty); - const ptr_stack_trace_ty = try Type.ptr(arena, .{ + const target = mod.getTarget(); + const ptr_stack_trace_ty = try Type.ptr(arena, mod, .{ .pointee_type = stack_trace_ty, - .@"addrspace" = target_util.defaultAddressSpace(mod.getTarget(), .global_constant), // TODO might need a place that is more dynamic + .@"addrspace" = target_util.defaultAddressSpace(target, .global_constant), // TODO might need a place that is more dynamic }); const null_stack_trace = try sema.addConstant( try Type.optional(arena, ptr_stack_trace_ty), Value.@"null", ); - const args = try arena.create([2]Air.Inst.Ref); - args.* = .{ msg_inst, null_stack_trace }; - _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, args); + const args: [2]Air.Inst.Ref = .{ msg_inst, null_stack_trace }; + _ = try sema.analyzeCall(block, panic_fn, src, src, .auto, false, &args); return always_noreturn; } +fn panicUnwrapError( + 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 { + const ok = try parent_block.addUnOp(is_non_err_tag, operand); + const gpa = sema.gpa; + + var fail_block: Block = .{ + .parent = parent_block, + .sema = sema, + .src_decl = parent_block.src_decl, + .namespace = parent_block.namespace, + .wip_capture_scope = parent_block.wip_capture_scope, + .instructions = .{}, + .inlining = parent_block.inlining, + .is_comptime = parent_block.is_comptime, + }; + + defer fail_block.instructions.deinit(gpa); + + { + const this_feature_is_implemented_in_the_backend = + sema.mod.comp.bin_file.options.use_llvm; + + if (!this_feature_is_implemented_in_the_backend) { + // TODO implement this feature in all the backends and then delete this branch + _ = try fail_block.addNoOp(.breakpoint); + _ = try fail_block.addNoOp(.unreach); + } else { + const panic_fn = try sema.getBuiltin(&fail_block, src, "panicUnwrapError"); + const err = try fail_block.addTyOp(unwrap_err_tag, Type.anyerror, operand); + const err_return_trace = try sema.getErrorReturnTrace(&fail_block, src); + const args: [2]Air.Inst.Ref = .{ err_return_trace, err }; + _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args); + } + } + try sema.addSafetyCheckExtra(parent_block, ok, &fail_block); +} + +fn panicIndexOutOfBounds( + sema: *Sema, + parent_block: *Block, + src: LazySrcLoc, + index: Air.Inst.Ref, + len: Air.Inst.Ref, + cmp_op: Air.Inst.Tag, +) !void { + const ok = try parent_block.addBinOp(cmp_op, index, len); + const gpa = sema.gpa; + + var fail_block: Block = .{ + .parent = parent_block, + .sema = sema, + .src_decl = parent_block.src_decl, + .namespace = parent_block.namespace, + .wip_capture_scope = parent_block.wip_capture_scope, + .instructions = .{}, + .inlining = parent_block.inlining, + .is_comptime = parent_block.is_comptime, + }; + + defer fail_block.instructions.deinit(gpa); + + { + const this_feature_is_implemented_in_the_backend = + sema.mod.comp.bin_file.options.use_llvm; + + if (!this_feature_is_implemented_in_the_backend) { + // TODO implement this feature in all the backends and then delete this branch + _ = try fail_block.addNoOp(.breakpoint); + _ = try fail_block.addNoOp(.unreach); + } else { + const panic_fn = try sema.getBuiltin(&fail_block, src, "panicOutOfBounds"); + const args: [2]Air.Inst.Ref = .{ index, len }; + _ = try sema.analyzeCall(&fail_block, panic_fn, src, src, .auto, false, &args); + } + } + try sema.addSafetyCheckExtra(parent_block, ok, &fail_block); +} + fn safetyPanic( sema: *Sema, block: *Block, @@ -12937,10 +18342,12 @@ fn safetyPanic( const msg = switch (panic_id) { .unreach => "reached unreachable code", .unwrap_null => "attempt to use null value", - .unwrap_errunion => "unreachable error occurred", .cast_to_null => "cast causes pointer to be null", .incorrect_alignment => "incorrect alignment", .invalid_error_code => "invalid error code", + .cast_truncated_data => "integer cast truncated bits", + .integer_overflow => "integer overflow", + .shl_overflow => "left shift overflowed bits", }; const msg_inst = msg_inst: { @@ -12951,6 +18358,7 @@ fn safetyPanic( break :msg_inst try sema.analyzeDeclRef(try anon_decl.finish( try Type.Tag.array_u8.create(anon_decl.arena(), msg.len), try Value.Tag.bytes.create(anon_decl.arena(), msg), + 0, // default alignment )); }; @@ -12961,7 +18369,6 @@ fn safetyPanic( fn emitBackwardBranch(sema: *Sema, block: *Block, src: LazySrcLoc) !void { sema.branch_count += 1; if (sema.branch_count > sema.branch_quota) { - // TODO show the "called from here" stack return sema.fail(block, src, "evaluation exceeded {d} backwards branches", .{sema.branch_quota}); } } @@ -12995,7 +18402,7 @@ fn fieldVal( .Array => { if (mem.eql(u8, field_name, "len")) { return sema.addConstant( - Type.initTag(.comptime_int), + Type.usize, try Value.Tag.int_u64.create(arena, inner_ty.arrayLen()), ); } else { @@ -13003,30 +18410,47 @@ fn fieldVal( block, field_name_src, "no member named '{s}' in '{}'", - .{ field_name, object_ty }, + .{ field_name, object_ty.fmt(sema.mod) }, ); } }, - .Pointer => if (inner_ty.isSlice()) { - if (mem.eql(u8, field_name, "ptr")) { - const slice = if (is_pointer_to) - try sema.analyzeLoad(block, src, object, object_src) - else - object; - return sema.analyzeSlicePtr(block, src, slice, inner_ty, object_src); - } else if (mem.eql(u8, field_name, "len")) { - 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 '{s}' in '{}'", - .{ field_name, object_ty }, - ); + .Pointer => { + const ptr_info = inner_ty.ptrInfo().data; + if (ptr_info.size == .Slice) { + if (mem.eql(u8, field_name, "ptr")) { + 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 (mem.eql(u8, field_name, "len")) { + 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 '{s}' in '{}'", + .{ field_name, object_ty.fmt(sema.mod) }, + ); + } + } else if (ptr_info.pointee_type.zigTypeTag() == .Array) { + if (mem.eql(u8, field_name, "len")) { + return sema.addConstant( + Type.usize, + try Value.Tag.int_u64.create(arena, ptr_info.pointee_type.arrayLen()), + ); + } else { + return sema.fail( + block, + field_name_src, + "no member named '{s}' in '{}'", + .{ field_name, ptr_info.pointee_type.fmt(sema.mod) }, + ); + } } }, .Type => { @@ -13039,15 +18463,21 @@ fn fieldVal( var to_type_buffer: Value.ToTypeBuffer = undefined; const child_type = val.toType(&to_type_buffer); - switch (child_type.zigTypeTag()) { + switch (try child_type.zigTypeTagOrPoison()) { .ErrorSet => { const name: []const u8 = if (child_type.castTag(.error_set)) |payload| blk: { if (payload.data.names.getEntry(field_name)) |entry| { break :blk entry.key_ptr.*; } - return sema.fail(block, src, "no error named '{s}' in '{}'", .{ - field_name, child_type, - }); + const msg = msg: { + const msg = try sema.errMsg(block, src, "no error named '{s}' in '{}'", .{ + field_name, child_type.fmt(sema.mod), + }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, child_type); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); } else (try sema.mod.getErrorValue(field_name)).key; return sema.addConstant( @@ -13100,10 +18530,19 @@ fn fieldVal( else => unreachable, }; return sema.fail(block, src, "{s} '{}' has no member named '{s}'", .{ - kw_name, child_type, field_name, + kw_name, child_type.fmt(sema.mod), field_name, }); }, - else => return sema.fail(block, src, "type '{}' has no members", .{child_type}), + else => { + const msg = msg: { + const msg = try sema.errMsg(block, src, "type '{}' has no members", .{child_type.fmt(sema.mod)}); + errdefer msg.destroy(sema.gpa); + if (child_type.isSlice()) try sema.errNote(block, src, msg, "slice values have 'len' and 'ptr' members", .{}); + if (child_type.zigTypeTag() == .Array) try sema.errNote(block, src, msg, "array values have 'len' member", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + }, } }, .Struct => if (is_pointer_to) { @@ -13122,7 +18561,7 @@ fn fieldVal( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access", .{object_ty}); + return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); } fn fieldPtr( @@ -13140,7 +18579,7 @@ fn fieldPtr( const object_ptr_ty = sema.typeOf(object_ptr); const object_ty = switch (object_ptr_ty.zigTypeTag()) { .Pointer => object_ptr_ty.elemType(), - else => return sema.fail(block, object_ptr_src, "expected pointer, found '{}'", .{object_ptr_ty}), + else => return sema.fail(block, object_ptr_src, "expected pointer, found '{}'", .{object_ptr_ty.fmt(sema.mod)}), }; // Zig allows dereferencing a single pointer during field lookup. Note that @@ -13159,15 +18598,16 @@ fn fieldPtr( var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); return sema.analyzeDeclRef(try anon_decl.finish( - Type.initTag(.comptime_int), + Type.usize, try Value.Tag.int_u64.create(anon_decl.arena(), inner_ty.arrayLen()), + 0, // default alignment )); } else { return sema.fail( block, field_name_src, "no member named '{s}' in '{}'", - .{ field_name, object_ty }, + .{ field_name, object_ty.fmt(sema.mod) }, ); } }, @@ -13181,49 +18621,51 @@ fn fieldPtr( const buf = try sema.arena.create(Type.SlicePtrFieldTypeBuffer); const slice_ptr_ty = inner_ty.slicePtrFieldType(buf); - if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| { - var anon_decl = try block.startAnonDecl(src); - defer anon_decl.deinit(); - - return sema.analyzeDeclRef(try anon_decl.finish( - try slice_ptr_ty.copy(anon_decl.arena()), - try val.slicePtr().copy(anon_decl.arena()), - )); - } - try sema.requireRuntimeBlock(block, src); - - const result_ty = try Type.ptr(sema.arena, .{ + const result_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = slice_ptr_ty, .mutable = object_ptr_ty.ptrIsMutable(), .@"addrspace" = object_ptr_ty.ptrAddressSpace(), }); - return block.addTyOp(.ptr_slice_ptr_ptr, result_ty, inner_ptr); - } else if (mem.eql(u8, field_name, "len")) { if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| { - var anon_decl = try block.startAnonDecl(src); - defer anon_decl.deinit(); - - return sema.analyzeDeclRef(try anon_decl.finish( - Type.usize, - try Value.Tag.int_u64.create(anon_decl.arena(), val.sliceLen()), - )); + return sema.addConstant( + result_ty, + try Value.Tag.field_ptr.create(sema.arena, .{ + .container_ptr = val, + .container_ty = inner_ty, + .field_index = Value.Payload.Slice.ptr_index, + }), + ); } try sema.requireRuntimeBlock(block, src); - const result_ty = try Type.ptr(sema.arena, .{ + return block.addTyOp(.ptr_slice_ptr_ptr, result_ty, inner_ptr); + } else if (mem.eql(u8, field_name, "len")) { + const result_ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = Type.usize, .mutable = object_ptr_ty.ptrIsMutable(), .@"addrspace" = object_ptr_ty.ptrAddressSpace(), }); + if (try sema.resolveDefinedValue(block, object_ptr_src, inner_ptr)) |val| { + return sema.addConstant( + result_ty, + try Value.Tag.field_ptr.create(sema.arena, .{ + .container_ptr = val, + .container_ty = inner_ty, + .field_index = Value.Payload.Slice.len_index, + }), + ); + } + try sema.requireRuntimeBlock(block, src); + return block.addTyOp(.ptr_slice_len_ptr, result_ty, inner_ptr); } else { return sema.fail( block, field_name_src, "no member named '{s}' in '{}'", - .{ field_name, object_ty }, + .{ field_name, object_ty.fmt(sema.mod) }, ); } }, @@ -13247,7 +18689,7 @@ fn fieldPtr( break :blk entry.key_ptr.*; } return sema.fail(block, src, "no error named '{s}' in '{}'", .{ - field_name, child_type, + field_name, child_type.fmt(sema.mod), }); } else (try sema.mod.getErrorValue(field_name)).key; @@ -13256,6 +18698,7 @@ fn fieldPtr( return sema.analyzeDeclRef(try anon_decl.finish( try child_type.copy(anon_decl.arena()), try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }), + 0, // default alignment )); }, .Union => { @@ -13272,6 +18715,7 @@ fn fieldPtr( return sema.analyzeDeclRef(try anon_decl.finish( try enum_ty.copy(anon_decl.arena()), try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + 0, // default alignment )); } } @@ -13292,6 +18736,7 @@ fn fieldPtr( return sema.analyzeDeclRef(try anon_decl.finish( try child_type.copy(anon_decl.arena()), try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32), + 0, // default alignment )); }, .Struct, .Opaque => { @@ -13302,7 +18747,7 @@ fn fieldPtr( } return sema.failWithBadMemberAccess(block, child_type, field_name_src, field_name); }, - else => return sema.fail(block, src, "type '{}' has no members", .{child_type}), + else => return sema.fail(block, src, "type '{}' has no members", .{child_type.fmt(sema.mod)}), } }, .Struct => { @@ -13321,7 +18766,7 @@ fn fieldPtr( }, else => {}, } - return sema.fail(block, src, "type '{}' does not support field access (fieldPtr, {}.{s})", .{ object_ty, object_ptr_ty, field_name }); + return sema.fail(block, src, "type '{}' does not support field access", .{object_ty.fmt(sema.mod)}); } fn fieldCallBind( @@ -13340,7 +18785,7 @@ fn fieldCallBind( const inner_ty = if (raw_ptr_ty.zigTypeTag() == .Pointer and raw_ptr_ty.ptrSize() == .One) raw_ptr_ty.childType() else - return sema.fail(block, raw_ptr_src, "expected single pointer, found '{}'", .{raw_ptr_ty}); + return sema.fail(block, raw_ptr_src, "expected single pointer, found '{}'", .{raw_ptr_ty.fmt(sema.mod)}); // Optionally dereference a second pointer to get the concrete type. const is_double_ptr = inner_ty.zigTypeTag() == .Pointer and inner_ty.ptrSize() == .One; @@ -13398,8 +18843,9 @@ fn fieldCallBind( if (first_param_tag == .var_args_param or first_param_tag == .generic_poison or ( first_param_type.zigTypeTag() == .Pointer and - first_param_type.ptrSize() == .One and - first_param_type.childType().eql(concrete_ty))) + (first_param_type.ptrSize() == .One or + first_param_type.ptrSize() == .C) and + first_param_type.childType().eql(concrete_ty, sema.mod))) { // zig fmt: on // TODO: bound fn calls on rvalues should probably @@ -13410,7 +18856,7 @@ fn fieldCallBind( .arg0_inst = object_ptr, }); return sema.addConstant(ty, value); - } else if (first_param_type.eql(concrete_ty)) { + } else if (first_param_type.eql(concrete_ty, sema.mod)) { var deref = try sema.analyzeLoad(block, src, object_ptr, src); const ty = Type.Tag.bound_fn.init(); const value = try Value.Tag.bound_fn.create(arena, .{ @@ -13426,7 +18872,7 @@ fn fieldCallBind( else => {}, } - return sema.fail(block, src, "type '{}' has no field or member function named '{s}'", .{ concrete_ty, field_name }); + return sema.fail(block, src, "type '{}' has no field or member function named '{s}'", .{ concrete_ty.fmt(sema.mod), field_name }); } fn finishFieldCallBind( @@ -13439,7 +18885,7 @@ fn finishFieldCallBind( object_ptr: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const arena = sema.arena; - const ptr_field_ty = try Type.ptr(arena, .{ + const ptr_field_ty = try Type.ptr(arena, sema.mod, .{ .pointee_type = field_ty, .mutable = ptr_ty.ptrIsMutable(), .@"addrspace" = ptr_ty.ptrAddressSpace(), @@ -13450,6 +18896,7 @@ fn finishFieldCallBind( ptr_field_ty, try Value.Tag.field_ptr.create(arena, .{ .container_ptr = struct_ptr_val, + .container_ty = ptr_ty.childType(), .field_index = field_index, }), ); @@ -13467,9 +18914,10 @@ fn namespaceLookup( src: LazySrcLoc, namespace: *Namespace, decl_name: []const u8, -) CompileError!?*Decl { +) CompileError!?Decl.Index { const gpa = sema.gpa; - if (try sema.lookupInNamespace(block, src, namespace, decl_name, true)) |decl| { + if (try sema.lookupInNamespace(block, src, namespace, decl_name, true)) |decl_index| { + const decl = sema.mod.declPtr(decl_index); if (!decl.is_pub and decl.getFileScope() != block.getFileScope()) { const msg = msg: { const msg = try sema.errMsg(block, src, "'{s}' is not marked 'pub'", .{ @@ -13479,9 +18927,9 @@ fn namespaceLookup( try sema.mod.errNoteNonLazy(decl.srcLoc(), msg, "declared here", .{}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } - return decl; + return decl_index; } return null; } @@ -13520,6 +18968,20 @@ fn structFieldPtr( assert(unresolved_struct_ty.zigTypeTag() == .Struct); const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty); + try sema.resolveStructLayout(block, src, struct_ty); + + if (struct_ty.isTuple()) { + if (mem.eql(u8, field_name, "len")) { + const len_inst = try sema.addIntUnsigned(Type.usize, struct_ty.structFieldCount()); + 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); + } else if (struct_ty.isAnonStruct()) { + const field_index = try sema.anonStructFieldIndex(block, struct_ty, field_name, field_name_src); + return sema.tupleFieldPtr(block, src, struct_ptr, field_name_src, field_index); + } + const struct_obj = struct_ty.castTag(.@"struct").?.data; const field_index_big = struct_obj.fields.getIndex(field_name) orelse @@ -13539,68 +19001,85 @@ fn structFieldPtrByIndex( field_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { const field = struct_obj.fields.values()[field_index]; - const struct_ptr_ty = sema.typeOf(struct_ptr); + const struct_ptr_ty_info = struct_ptr_ty.ptrInfo().data; + var ptr_ty_data: Type.Payload.Pointer.Data = .{ .pointee_type = field.ty, - .mutable = struct_ptr_ty.ptrIsMutable(), - .@"addrspace" = struct_ptr_ty.ptrAddressSpace(), + .mutable = struct_ptr_ty_info.mutable, + .@"addrspace" = struct_ptr_ty_info.@"addrspace", }; - // TODO handle when the struct pointer is overaligned, we should return a potentially - // over-aligned field pointer too. - if (struct_obj.layout == .Packed) p: { - const target = sema.mod.getTarget(); - comptime assert(Type.packed_struct_layout_version == 1); - var offset: u64 = 0; + const target = sema.mod.getTarget(); + + if (struct_obj.layout == .Packed) { + comptime assert(Type.packed_struct_layout_version == 2); + var running_bits: u16 = 0; for (struct_obj.fields.values()) |f, i| { if (!(try sema.typeHasRuntimeBits(block, field_src, f.ty))) continue; - const field_align = f.packedAlignment(); - if (field_align == 0) { - if (i == field_index) { - ptr_ty_data.bit_offset = running_bits; - } - running_bits += @intCast(u16, f.ty.bitSize(target)); - } else { - if (running_bits != 0) { - var int_payload: Type.Payload.Bits = .{ - .base = .{ .tag = .int_unsigned }, - .data = running_bits, - }; - const int_ty: Type = .{ .ptr_otherwise = &int_payload.base }; - if (i > field_index) { - ptr_ty_data.host_size = @intCast(u16, int_ty.abiSize(target)); - break :p; - } - const int_align = int_ty.abiAlignment(target); - offset = std.mem.alignForwardGeneric(u64, offset, int_align); - offset += int_ty.abiSize(target); - running_bits = 0; - } - offset = std.mem.alignForwardGeneric(u64, offset, field_align); - if (i == field_index) { - break :p; - } - offset += f.ty.abiSize(target); + if (i == field_index) { + ptr_ty_data.bit_offset = running_bits; } + running_bits += @intCast(u16, f.ty.bitSize(target)); } - assert(running_bits != 0); - var int_payload: Type.Payload.Bits = .{ - .base = .{ .tag = .int_unsigned }, - .data = running_bits, - }; - const int_ty: Type = .{ .ptr_otherwise = &int_payload.base }; - ptr_ty_data.host_size = @intCast(u16, int_ty.abiSize(target)); + ptr_ty_data.host_size = (running_bits + 7) / 8; + + // If this is a packed struct embedded in another one, we need to offset + // the bits against each other. + if (struct_ptr_ty_info.host_size != 0) { + ptr_ty_data.host_size = struct_ptr_ty_info.host_size; + ptr_ty_data.bit_offset += struct_ptr_ty_info.bit_offset; + } + + const parent_align = if (struct_ptr_ty_info.@"align" != 0) + struct_ptr_ty_info.@"align" + else + struct_ptr_ty_info.pointee_type.abiAlignment(target); + ptr_ty_data.@"align" = parent_align; + + // If the field happens to be byte-aligned, simplify the pointer type. + // The pointee type bit size must match its ABI byte size so that loads and stores + // do not interfere with the surrounding packed bits. + // We do not attempt this with big-endian targets yet because of nested + // structs and floats. I need to double-check the desired behavior for big endian + // targets before adding the necessary complications to this code. This will not + // cause miscompilations; it only means the field pointer uses bit masking when it + // might not be strictly necessary. + if (parent_align != 0 and ptr_ty_data.bit_offset % 8 == 0 and + target.cpu.arch.endian() == .Little) + { + const elem_size_bytes = ptr_ty_data.pointee_type.abiSize(target); + const elem_size_bits = ptr_ty_data.pointee_type.bitSize(target); + if (elem_size_bytes * 8 == elem_size_bits) { + const byte_offset = ptr_ty_data.bit_offset / 8; + const new_align = @as(u32, 1) << @intCast(u5, @ctz(u64, byte_offset | parent_align)); + ptr_ty_data.bit_offset = 0; + ptr_ty_data.host_size = 0; + ptr_ty_data.@"align" = new_align; + } + } + } else { + ptr_ty_data.@"align" = field.abi_align; + } + + const ptr_field_ty = try Type.ptr(sema.arena, sema.mod, ptr_ty_data); + + if (field.is_comptime) { + const val = try Value.Tag.comptime_field_ptr.create(sema.arena, .{ + .field_ty = try field.ty.copy(sema.arena), + .field_val = try field.default_val.copy(sema.arena), + }); + return sema.addConstant(ptr_field_ty, val); } - const ptr_field_ty = try Type.ptr(sema.arena, ptr_ty_data); if (try sema.resolveDefinedValue(block, src, struct_ptr)) |struct_ptr_val| { return sema.addConstant( ptr_field_ty, try Value.Tag.field_ptr.create(sema.arena, .{ .container_ptr = struct_ptr_val, + .container_ty = struct_ptr_ty.childType(), .field_index = field_index, }), ); @@ -13622,25 +19101,104 @@ fn structFieldVal( assert(unresolved_struct_ty.zigTypeTag() == .Struct); const struct_ty = try sema.resolveTypeFields(block, src, unresolved_struct_ty); - const struct_obj = struct_ty.castTag(.@"struct").?.data; + switch (struct_ty.tag()) { + .tuple, .empty_struct_literal => return sema.tupleFieldVal(block, src, struct_byval, field_name, field_name_src, struct_ty), + .anon_struct => { + const field_index = try sema.anonStructFieldIndex(block, struct_ty, field_name, field_name_src); + return tupleFieldValByIndex(sema, block, src, struct_byval, field_index, struct_ty); + }, + .@"struct" => { + const struct_obj = struct_ty.castTag(.@"struct").?.data; - const field_index_usize = struct_obj.fields.getIndex(field_name) orelse - return sema.failWithBadStructFieldAccess(block, struct_obj, field_name_src, field_name); - const field_index = @intCast(u32, field_index_usize); - const field = struct_obj.fields.values()[field_index]; + const field_index_usize = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadStructFieldAccess(block, struct_obj, field_name_src, field_name); + const field_index = @intCast(u32, field_index_usize); + const field = struct_obj.fields.values()[field_index]; - if (try sema.resolveMaybeUndefVal(block, src, struct_byval)) |struct_val| { - if (struct_val.isUndef()) return sema.addConstUndef(field.ty); - if ((try sema.typeHasOnePossibleValue(block, src, field.ty))) |opv| { - return sema.addConstant(field.ty, opv); - } + if (field.is_comptime) { + return sema.addConstant(field.ty, field.default_val); + } - const field_values = struct_val.castTag(.@"struct").?.data; - return sema.addConstant(field.ty, field_values[field_index]); + if (try sema.resolveMaybeUndefVal(block, src, struct_byval)) |struct_val| { + if (struct_val.isUndef()) return sema.addConstUndef(field.ty); + if ((try sema.typeHasOnePossibleValue(block, src, field.ty))) |opv| { + return sema.addConstant(field.ty, opv); + } + + const field_values = struct_val.castTag(.aggregate).?.data; + return sema.addConstant(field.ty, field_values[field_index]); + } + + try sema.requireRuntimeBlock(block, src); + return block.addStructFieldVal(struct_byval, field_index, field.ty); + }, + else => unreachable, + } +} + +fn tupleFieldVal( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + tuple_byval: Air.Inst.Ref, + field_name: []const u8, + field_name_src: LazySrcLoc, + tuple_ty: Type, +) CompileError!Air.Inst.Ref { + if (mem.eql(u8, field_name, "len")) { + return sema.addIntUnsigned(Type.usize, tuple_ty.structFieldCount()); + } + const field_index = try sema.tupleFieldIndex(block, tuple_ty, field_name, field_name_src); + return tupleFieldValByIndex(sema, block, src, tuple_byval, field_index, tuple_ty); +} + +/// Don't forget to check for "len" before calling this. +fn tupleFieldIndex( + sema: *Sema, + block: *Block, + tuple_ty: Type, + field_name: []const u8, + field_name_src: LazySrcLoc, +) CompileError!u32 { + const field_index = std.fmt.parseUnsigned(u32, field_name, 10) catch |err| { + return sema.fail(block, field_name_src, "tuple '{}' has no such field '{s}': {s}", .{ + tuple_ty.fmt(sema.mod), field_name, @errorName(err), + }); + }; + if (field_index >= tuple_ty.structFieldCount()) { + return sema.fail(block, field_name_src, "tuple '{}' has no such field '{s}'", .{ + tuple_ty.fmt(sema.mod), field_name, + }); + } + return field_index; +} + +fn tupleFieldValByIndex( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + tuple_byval: Air.Inst.Ref, + field_index: u32, + tuple_ty: Type, +) CompileError!Air.Inst.Ref { + const tuple = tuple_ty.tupleFields(); + const field_ty = tuple.types[field_index]; + + if (tuple.values[field_index].tag() != .unreachable_value) { + return sema.addConstant(field_ty, tuple.values[field_index]); + } + + if (try sema.resolveMaybeUndefVal(block, src, tuple_byval)) |tuple_val| { + if (tuple_val.isUndef()) return sema.addConstUndef(field_ty); + if ((try sema.typeHasOnePossibleValue(block, src, field_ty))) |opv| { + return sema.addConstant(field_ty, opv); + } + const field_values = tuple_val.castTag(.aggregate).?.data; + return sema.addConstant(field_ty, field_values[field_index]); } try sema.requireRuntimeBlock(block, src); - return block.addStructFieldVal(struct_byval, field_index, field.ty); + return block.addStructFieldVal(tuple_byval, field_index, field_ty); } fn unionFieldPtr( @@ -13658,24 +19216,46 @@ fn unionFieldPtr( const union_ptr_ty = sema.typeOf(union_ptr); const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty); const union_obj = union_ty.cast(Type.Payload.Union).?.data; - - const field_index_big = union_obj.fields.getIndex(field_name) orelse - return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); - const field_index = @intCast(u32, field_index_big); - + const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src); const field = union_obj.fields.values()[field_index]; - const ptr_field_ty = try Type.ptr(arena, .{ + const ptr_field_ty = try Type.ptr(arena, sema.mod, .{ .pointee_type = field.ty, .mutable = union_ptr_ty.ptrIsMutable(), .@"addrspace" = union_ptr_ty.ptrAddressSpace(), }); if (try sema.resolveDefinedValue(block, src, union_ptr)) |union_ptr_val| { - // TODO detect inactive union field and emit compile error + switch (union_obj.layout) { + .Auto => { + // TODO emit the access of inactive union field error commented out below. + // In order to do that, we need to first solve the problem that AstGen + // emits field_ptr instructions in order to initialize union values. + // In such case we need to know that the field_ptr instruction (which is + // calling this unionFieldPtr function) is *initializing* the union, + // in which case we would skip this check, and in fact we would actually + // set the union tag here and the payload to undefined. + + //const tag_and_val = union_val.castTag(.@"union").?.data; + //var field_tag_buf: Value.Payload.U32 = .{ + // .base = .{ .tag = .enum_field_index }, + // .data = field_index, + //}; + //const field_tag = Value.initPayload(&field_tag_buf.base); + //const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, mod); + //if (!tag_matches) { + // // TODO enhance this saying which one was active + // // and which one was accessed, and showing where the union was declared. + // return sema.fail(block, src, "access of inactive union field", .{}); + //} + // TODO add runtime safety check for the active tag + }, + .Packed, .Extern => {}, + } return sema.addConstant( ptr_field_ty, try Value.Tag.field_ptr.create(arena, .{ .container_ptr = union_ptr_val, + .container_ty = union_ty, .field_index = field_index, }), ); @@ -13698,18 +19278,45 @@ fn unionFieldVal( const union_ty = try sema.resolveTypeFields(block, src, unresolved_union_ty); const union_obj = union_ty.cast(Type.Payload.Union).?.data; - - const field_index_usize = union_obj.fields.getIndex(field_name) orelse - return sema.failWithBadUnionFieldAccess(block, union_obj, field_name_src, field_name); - const field_index = @intCast(u32, field_index_usize); + const field_index = try sema.unionFieldIndex(block, union_ty, field_name, field_name_src); const field = union_obj.fields.values()[field_index]; if (try sema.resolveMaybeUndefVal(block, src, union_byval)) |union_val| { if (union_val.isUndef()) return sema.addConstUndef(field.ty); - // TODO detect inactive union field and emit compile error - const active_val = union_val.castTag(.@"union").?.data.val; - return sema.addConstant(field.ty, active_val); + const tag_and_val = union_val.castTag(.@"union").?.data; + var field_tag_buf: Value.Payload.U32 = .{ + .base = .{ .tag = .enum_field_index }, + .data = field_index, + }; + const field_tag = Value.initPayload(&field_tag_buf.base); + const tag_matches = tag_and_val.tag.eql(field_tag, union_obj.tag_ty, sema.mod); + switch (union_obj.layout) { + .Auto => { + if (tag_matches) { + return sema.addConstant(field.ty, tag_and_val.val); + } else { + const msg = msg: { + const active_index = tag_and_val.tag.castTag(.enum_field_index).?.data; + const active_field_name = union_obj.fields.keys()[active_index]; + const msg = try sema.errMsg(block, src, "access of union field '{s}' while field '{s}' is active", .{ field_name, active_field_name }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + }, + .Packed, .Extern => { + if (tag_matches) { + return sema.addConstant(field.ty, tag_and_val.val); + } else { + const old_ty = union_ty.unionFieldType(tag_and_val.tag, sema.mod); + const new_val = try sema.bitCastVal(block, src, tag_and_val.val, old_ty, field.ty, 0); + return sema.addConstant(field.ty, new_val); + } + }, + } } try sema.requireRuntimeBlock(block, src); @@ -13720,62 +19327,57 @@ fn elemPtr( sema: *Sema, block: *Block, src: LazySrcLoc, - array_ptr: Air.Inst.Ref, + indexable_ptr: Air.Inst.Ref, elem_index: Air.Inst.Ref, elem_index_src: LazySrcLoc, + init: bool, ) CompileError!Air.Inst.Ref { - const array_ptr_src = src; // TODO better source location - const array_ptr_ty = sema.typeOf(array_ptr); - const array_ty = switch (array_ptr_ty.zigTypeTag()) { - .Pointer => array_ptr_ty.elemType(), - else => return sema.fail(block, array_ptr_src, "expected pointer, found '{}'", .{array_ptr_ty}), + const indexable_ptr_src = src; // TODO better source location + const indexable_ptr_ty = sema.typeOf(indexable_ptr); + const target = sema.mod.getTarget(); + const indexable_ty = switch (indexable_ptr_ty.zigTypeTag()) { + .Pointer => indexable_ptr_ty.elemType(), + else => return sema.fail(block, indexable_ptr_src, "expected pointer, found '{}'", .{indexable_ptr_ty.fmt(sema.mod)}), }; - if (!array_ty.isIndexable()) { - return sema.fail(block, src, "array access of non-indexable type '{}'", .{array_ty}); + if (!indexable_ty.isIndexable()) { + return sema.fail(block, src, "element access of non-indexable type '{}'", .{indexable_ty.fmt(sema.mod)}); } - switch (array_ty.zigTypeTag()) { + switch (indexable_ty.zigTypeTag()) { .Pointer => { - // In all below cases, we have to deref the ptr operand to get the actual array pointer. - const array = try sema.analyzeLoad(block, array_ptr_src, array_ptr, array_ptr_src); - const result_ty = try array_ty.elemPtrType(sema.arena); - switch (array_ty.ptrSize()) { - .Slice => { - const maybe_slice_val = try sema.resolveDefinedValue(block, array_ptr_src, array); - const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index); - const runtime_src = if (maybe_slice_val) |slice_val| rs: { - const index_val = maybe_index_val orelse break :rs elem_index_src; - const index = @intCast(usize, index_val.toUnsignedInt()); - const elem_ptr = try slice_val.elemPtr(sema.arena, index); - return sema.addConstant(result_ty, elem_ptr); - } else array_ptr_src; - - try sema.requireRuntimeBlock(block, runtime_src); - return block.addSliceElemPtr(array, elem_index, result_ty); - }, + // In all below cases, we have to deref the ptr operand to get the actual indexable pointer. + const indexable = try sema.analyzeLoad(block, indexable_ptr_src, indexable_ptr, indexable_ptr_src); + switch (indexable_ty.ptrSize()) { + .Slice => return sema.elemPtrSlice(block, indexable_ptr_src, indexable, elem_index_src, elem_index), .Many, .C => { - const maybe_ptr_val = try sema.resolveDefinedValue(block, array_ptr_src, array); + const maybe_ptr_val = try sema.resolveDefinedValue(block, indexable_ptr_src, indexable); const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index); - const runtime_src = rs: { - const ptr_val = maybe_ptr_val orelse break :rs array_ptr_src; + const ptr_val = maybe_ptr_val orelse break :rs indexable_ptr_src; const index_val = maybe_index_val orelse break :rs elem_index_src; - const index = @intCast(usize, index_val.toUnsignedInt()); - const elem_ptr = try ptr_val.elemPtr(sema.arena, index); + const index = @intCast(usize, index_val.toUnsignedInt(target)); + const elem_ptr = try ptr_val.elemPtr(indexable_ty, sema.arena, index, sema.mod); + const result_ty = try sema.elemPtrType(indexable_ty, index); return sema.addConstant(result_ty, elem_ptr); }; + const result_ty = try sema.elemPtrType(indexable_ty, null); try sema.requireRuntimeBlock(block, runtime_src); - return block.addPtrElemPtr(array, elem_index, result_ty); + return block.addPtrElemPtr(indexable, elem_index, result_ty); }, .One => { - assert(array_ty.childType().zigTypeTag() == .Array); // Guaranteed by isIndexable - return sema.elemPtrArray(block, array_ptr_src, array, elem_index, elem_index_src); + assert(indexable_ty.childType().zigTypeTag() == .Array); // Guaranteed by isIndexable + return sema.elemPtrArray(block, indexable_ptr_src, indexable, elem_index_src, elem_index, init); }, } }, - .Array => return sema.elemPtrArray(block, array_ptr_src, array_ptr, elem_index, elem_index_src), - .Vector => return sema.fail(block, src, "TODO implement Sema for elemPtr for vector", .{}), + .Array, .Vector => return sema.elemPtrArray(block, indexable_ptr_src, indexable_ptr, elem_index_src, elem_index, init), + .Struct => { + // Tuple field access. + const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index); + const index = @intCast(u32, index_val.toUnsignedInt(target)); + return sema.tupleFieldPtr(block, src, indexable_ptr, elem_index_src, index); + }, else => unreachable, } } @@ -13784,93 +19386,131 @@ fn elemVal( sema: *Sema, block: *Block, src: LazySrcLoc, - array: Air.Inst.Ref, + indexable: Air.Inst.Ref, elem_index_uncasted: Air.Inst.Ref, elem_index_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - const array_src = src; // TODO better source location - const array_ty = sema.typeOf(array); + const indexable_src = src; // TODO better source location + const indexable_ty = sema.typeOf(indexable); + const target = sema.mod.getTarget(); - if (!array_ty.isIndexable()) { - return sema.fail(block, src, "array access of non-indexable type '{}'", .{array_ty}); + if (!indexable_ty.isIndexable()) { + return sema.fail(block, src, "element access of non-indexable type '{}'", .{indexable_ty.fmt(sema.mod)}); } // 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, Type.usize, elem_index_uncasted, elem_index_src); - switch (array_ty.zigTypeTag()) { - .Pointer => switch (array_ty.ptrSize()) { - .Slice => { - const maybe_slice_val = try sema.resolveDefinedValue(block, array_src, array); - const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index); - const runtime_src = if (maybe_slice_val) |slice_val| rs: { - const index_val = maybe_index_val orelse break :rs elem_index_src; - const index = @intCast(usize, index_val.toUnsignedInt()); - const elem_val = try slice_val.elemValue(sema.arena, index); - return sema.addConstant(array_ty.elemType2(), elem_val); - } else array_src; - - try sema.requireRuntimeBlock(block, runtime_src); - return block.addBinOp(.slice_elem_val, array, elem_index); - }, + switch (indexable_ty.zigTypeTag()) { + .Pointer => switch (indexable_ty.ptrSize()) { + .Slice => return sema.elemValSlice(block, indexable_src, indexable, elem_index_src, elem_index), .Many, .C => { - const maybe_ptr_val = try sema.resolveDefinedValue(block, array_src, array); + 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 runtime_src = rs: { - const ptr_val = maybe_ptr_val orelse break :rs array_src; + const indexable_val = maybe_indexable_val orelse break :rs indexable_src; const index_val = maybe_index_val orelse break :rs elem_index_src; - const index = @intCast(usize, index_val.toUnsignedInt()); - const maybe_array_val = try sema.pointerDeref(block, array_src, ptr_val, array_ty); - const array_val = maybe_array_val orelse break :rs array_src; - const elem_val = try array_val.elemValue(sema.arena, index); - return sema.addConstant(array_ty.elemType2(), elem_val); + const index = @intCast(usize, index_val.toUnsignedInt(target)); + const elem_ptr_val = try indexable_val.elemPtr(indexable_ty, sema.arena, index, sema.mod); + if (try sema.pointerDeref(block, indexable_src, elem_ptr_val, indexable_ty)) |elem_val| { + return sema.addConstant(indexable_ty.elemType2(), elem_val); + } + break :rs indexable_src; }; try sema.requireRuntimeBlock(block, runtime_src); - return block.addBinOp(.ptr_elem_val, array, elem_index); + return block.addBinOp(.ptr_elem_val, indexable, elem_index); }, .One => { - assert(array_ty.childType().zigTypeTag() == .Array); // Guaranteed by isIndexable - const elem_ptr = try sema.elemPtr(block, array_src, array, elem_index, elem_index_src); - return sema.analyzeLoad(block, array_src, elem_ptr, elem_index_src); + assert(indexable_ty.childType().zigTypeTag() == .Array); // Guaranteed by isIndexable + const elem_ptr = try sema.elemPtr(block, indexable_src, indexable, elem_index, elem_index_src, false); + return sema.analyzeLoad(block, indexable_src, elem_ptr, elem_index_src); }, }, - .Array => return elemValArray(sema, block, array, elem_index, array_src, elem_index_src), + .Array => return elemValArray(sema, block, indexable_src, indexable, elem_index_src, elem_index), .Vector => { // TODO: If the index is a vector, the result should be a vector. - return elemValArray(sema, block, array, elem_index, array_src, elem_index_src); + return elemValArray(sema, block, indexable_src, indexable, elem_index_src, elem_index); }, .Struct => { // Tuple field access. const index_val = try sema.resolveConstValue(block, elem_index_src, elem_index); - const index = @intCast(u32, index_val.toUnsignedInt()); - return tupleField(sema, block, array, index, array_src, elem_index_src); + const index = @intCast(u32, index_val.toUnsignedInt(target)); + return tupleField(sema, block, indexable_src, indexable, elem_index_src, index); }, else => unreachable, } } -fn tupleField( +fn tupleFieldPtr( sema: *Sema, block: *Block, - tuple: Air.Inst.Ref, + tuple_ptr_src: LazySrcLoc, + tuple_ptr: Air.Inst.Ref, + field_index_src: LazySrcLoc, field_index: u32, +) CompileError!Air.Inst.Ref { + const tuple_ptr_ty = sema.typeOf(tuple_ptr); + const tuple_ty = tuple_ptr_ty.childType(); + const tuple_fields = tuple_ty.tupleFields(); + + if (tuple_fields.types.len == 0) { + return sema.fail(block, tuple_ptr_src, "indexing into empty tuple is not allowed", .{}); + } + + if (field_index >= tuple_fields.types.len) { + return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{ + field_index, tuple_fields.types.len, + }); + } + + const field_ty = tuple_fields.types[field_index]; + const ptr_field_ty = try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = field_ty, + .mutable = tuple_ptr_ty.ptrIsMutable(), + .@"addrspace" = tuple_ptr_ty.ptrAddressSpace(), + }); + + if (try sema.resolveMaybeUndefVal(block, tuple_ptr_src, tuple_ptr)) |tuple_ptr_val| { + return sema.addConstant( + ptr_field_ty, + try Value.Tag.field_ptr.create(sema.arena, .{ + .container_ptr = tuple_ptr_val, + .container_ty = tuple_ty, + .field_index = field_index, + }), + ); + } + + try sema.requireRuntimeBlock(block, 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 tuple_ty = sema.typeOf(tuple); - const tuple_info = tuple_ty.castTag(.tuple).?.data; + const tuple_fields = tuple_ty.tupleFields(); - if (field_index > tuple_info.types.len) { + if (tuple_fields.types.len == 0) { + return sema.fail(block, tuple_src, "indexing into empty tuple is not allowed", .{}); + } + + if (field_index >= tuple_fields.types.len) { return sema.fail(block, field_index_src, "index {d} outside tuple of length {d}", .{ - field_index, tuple_info.types.len, + field_index, tuple_fields.types.len, }); } - const field_ty = tuple_info.types[field_index]; - const field_val = tuple_info.values[field_index]; + const field_ty = tuple_fields.types[field_index]; + const field_val = tuple_fields.values[field_index]; if (field_val.tag() != .unreachable_value) { return sema.addConstant(field_ty, field_val); // comptime field @@ -13878,7 +19518,7 @@ fn tupleField( if (try sema.resolveMaybeUndefVal(block, tuple_src, tuple)) |tuple_val| { if (tuple_val.isUndef()) return sema.addConstUndef(field_ty); - const field_values = tuple_val.castTag(.@"struct").?.data; + const field_values = tuple_val.castTag(.aggregate).?.data; return sema.addConstant(field_ty, field_values[field_index]); } @@ -13889,56 +19529,262 @@ fn tupleField( fn elemValArray( sema: *Sema, block: *Block, - array: Air.Inst.Ref, - elem_index: Air.Inst.Ref, array_src: LazySrcLoc, + array: Air.Inst.Ref, elem_index_src: LazySrcLoc, + elem_index: Air.Inst.Ref, ) CompileError!Air.Inst.Ref { const array_ty = sema.typeOf(array); - if (try sema.resolveMaybeUndefVal(block, array_src, array)) |array_val| { - const elem_ty = array_ty.childType(); - if (array_val.isUndef()) return sema.addConstUndef(elem_ty); - const maybe_index_val = try sema.resolveDefinedValue(block, elem_index_src, elem_index); - if (maybe_index_val) |index_val| { - const index = @intCast(usize, index_val.toUnsignedInt()); - const len = array_ty.arrayLenIncludingSentinel(); - if (index >= len) { - return sema.fail(block, elem_index_src, "index {d} outside array of length {d}", .{ - index, len, - }); + const array_sent = array_ty.sentinel(); + const array_len = array_ty.arrayLen(); + const array_len_s = array_len + @boolToInt(array_sent != null); + const elem_ty = array_ty.childType(); + + 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.resolveMaybeUndefVal(block, array_src, 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); + const target = sema.mod.getTarget(); + + if (maybe_index_val) |index_val| { + const index = @intCast(usize, index_val.toUnsignedInt(target)); + if (array_sent) |s| { + if (index == array_len) { + return sema.addConstant(elem_ty, s); } - const elem_val = try array_val.elemValue(sema.arena, index); + } + 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()) { + return sema.addConstUndef(elem_ty); + } + if (maybe_index_val) |index_val| { + const index = @intCast(usize, index_val.toUnsignedInt(target)); + const elem_val = try array_val.elemValue(sema.mod, sema.arena, index); return sema.addConstant(elem_ty, elem_val); } } - try sema.requireRuntimeBlock(block, array_src); + + const valid_rt = try sema.validateRunTimeType(block, elem_index_src, elem_ty, false); + if (!valid_rt) { + const msg = msg: { + const msg = try sema.errMsg( + block, + elem_index_src, + "values of type '{}' must be comptime known, but index value is runtime known", + .{array_ty.fmt(sema.mod)}, + ); + errdefer msg.destroy(sema.gpa); + + const src_decl = sema.mod.declPtr(block.src_decl); + try sema.explainWhyTypeIsComptime(block, elem_index_src, msg, array_src.toSrcLoc(src_decl), array_ty); + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + + const runtime_src = if (maybe_undef_array_val != null) elem_index_src else array_src; + try sema.requireRuntimeBlock(block, runtime_src); + if (block.wantSafety()) { + // Runtime check is only needed if unable to comptime check + if (maybe_index_val == null) { + const len_inst = try sema.addIntUnsigned(Type.usize, array_len); + const cmp_op: Air.Inst.Tag = if (array_sent != null) .cmp_lte else .cmp_lt; + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); + } + } 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: Air.Inst.Ref, elem_index_src: LazySrcLoc, + elem_index: Air.Inst.Ref, + init: bool, ) CompileError!Air.Inst.Ref { + const target = sema.mod.getTarget(); const array_ptr_ty = sema.typeOf(array_ptr); - const result_ty = try array_ptr_ty.elemPtrType(sema.arena); + const array_ty = array_ptr_ty.childType(); + const array_sent = array_ty.sentinel() != null; + const array_len = array_ty.arrayLen(); + const array_len_s = array_len + @boolToInt(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.resolveMaybeUndefVal(block, array_ptr_src, 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, index_val.toUnsignedInt(target)); + 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; + + const elem_ptr_ty = try sema.elemPtrType(array_ptr_ty, offset); - if (try sema.resolveDefinedValue(block, src, array_ptr)) |array_ptr_val| { - if (try sema.resolveDefinedValue(block, elem_index_src, elem_index)) |index_val| { - // Both array pointer and index are compile-time known. - const index_u64 = index_val.toUnsignedInt(); - // @intCast here because it would have been impossible to construct a value that - // required a larger index. - const elem_ptr = try array_ptr_val.elemPtr(sema.arena, @intCast(usize, index_u64)); - return sema.addConstant(result_ty, elem_ptr); + if (maybe_undef_array_ptr_val) |array_ptr_val| { + if (array_ptr_val.isUndef()) { + return sema.addConstUndef(elem_ptr_ty); + } + if (offset) |index| { + const elem_ptr = try array_ptr_val.elemPtr(array_ptr_ty, sema.arena, index, sema.mod); + return sema.addConstant(elem_ptr_ty, elem_ptr); } } - // TODO safety check for array bounds - try sema.requireRuntimeBlock(block, src); - return block.addPtrElemPtr(array_ptr, elem_index, result_ty); + + const valid_rt = try sema.validateRunTimeType(block, elem_index_src, array_ty.elemType2(), false); + if (!valid_rt and !init) { + const msg = msg: { + const msg = try sema.errMsg( + block, + elem_index_src, + "values of type '{}' must be comptime known, but index value is runtime known", + .{array_ty.fmt(sema.mod)}, + ); + errdefer msg.destroy(sema.gpa); + + const src_decl = sema.mod.declPtr(block.src_decl); + try sema.explainWhyTypeIsComptime(block, elem_index_src, msg, array_ptr_src.toSrcLoc(src_decl), array_ty); + + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + + const runtime_src = if (maybe_undef_array_ptr_val != null) elem_index_src else array_ptr_src; + try sema.requireRuntimeBlock(block, runtime_src); + + // Runtime check is only needed if unable to comptime check. + if (block.wantSafety() and offset == null) { + const len_inst = try sema.addIntUnsigned(Type.usize, array_len); + const cmp_op: Air.Inst.Tag = if (array_sent) .cmp_lte else .cmp_lt; + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); + } + + return block.addPtrElemPtr(array_ptr, elem_index, elem_ptr_ty); +} + +fn elemValSlice( + sema: *Sema, + block: *Block, + slice_src: LazySrcLoc, + slice: Air.Inst.Ref, + elem_index_src: LazySrcLoc, + elem_index: Air.Inst.Ref, +) CompileError!Air.Inst.Ref { + const slice_ty = sema.typeOf(slice); + const slice_sent = slice_ty.sentinel() != null; + const elem_ty = slice_ty.elemType2(); + 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); + const target = sema.mod.getTarget(); + + if (maybe_slice_val) |slice_val| { + runtime_src = elem_index_src; + const slice_len = slice_val.sliceLen(sema.mod); + const slice_len_s = slice_len + @boolToInt(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 = @intCast(usize, index_val.toUnsignedInt(target)); + 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.elemPtr(slice_ty, sema.arena, index, sema.mod); + if (try sema.pointerDeref(block, slice_src, elem_ptr_val, slice_ty)) |elem_val| { + return sema.addConstant(elem_ty, elem_val); + } + runtime_src = slice_src; + } + } + + try sema.requireRuntimeBlock(block, runtime_src); + if (block.wantSafety()) { + const len_inst = if (maybe_slice_val) |slice_val| + try sema.addIntUnsigned(Type.usize, slice_val.sliceLen(sema.mod)) + else + try block.addTyOp(.slice_len, Type.usize, slice); + const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt; + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); + } + try sema.queueFullTypeResolution(sema.typeOf(slice)); + return block.addBinOp(.slice_elem_val, slice, elem_index); +} + +fn elemPtrSlice( + sema: *Sema, + block: *Block, + slice_src: LazySrcLoc, + slice: Air.Inst.Ref, + elem_index_src: LazySrcLoc, + elem_index: Air.Inst.Ref, +) CompileError!Air.Inst.Ref { + const target = sema.mod.getTarget(); + const slice_ty = sema.typeOf(slice); + const slice_sent = slice_ty.sentinel() != null; + + const maybe_undef_slice_val = try sema.resolveMaybeUndefVal(block, slice_src, 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, index_val.toUnsignedInt(target)); + break :o index; + } else null; + + const elem_ptr_ty = try sema.elemPtrType(slice_ty, null); + + if (maybe_undef_slice_val) |slice_val| { + if (slice_val.isUndef()) { + return sema.addConstUndef(elem_ptr_ty); + } + const slice_len = slice_val.sliceLen(sema.mod); + const slice_len_s = slice_len + @boolToInt(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.elemPtr(slice_ty, sema.arena, index, sema.mod); + return sema.addConstant(elem_ptr_ty, elem_ptr_val); + } + } + + const runtime_src = if (maybe_undef_slice_val != null) elem_index_src else slice_src; + try sema.requireRuntimeBlock(block, runtime_src); + if (block.wantSafety()) { + const len_inst = len: { + if (maybe_undef_slice_val) |slice_val| + if (!slice_val.isUndef()) + break :len try sema.addIntUnsigned(Type.usize, slice_val.sliceLen(sema.mod)); + break :len try block.addTyOp(.slice_len, Type.usize, slice); + }; + const cmp_op: Air.Inst.Tag = if (slice_sent) .cmp_lte else .cmp_lt; + try sema.panicIndexOutOfBounds(block, elem_index_src, elem_index, len_inst, cmp_op); + } + return block.addSliceElemPtr(slice, elem_index, elem_ptr_ty); } fn coerce( @@ -13956,16 +19802,17 @@ fn coerce( const dest_ty_src = inst_src; // TODO better source location const dest_ty = try sema.resolveTypeFields(block, dest_ty_src, dest_ty_unresolved); const inst_ty = try sema.resolveTypeFields(block, inst_src, sema.typeOf(inst)); + const target = sema.mod.getTarget(); // If the types are the same, we can return the operand. - if (dest_ty.eql(inst_ty)) + if (dest_ty.eql(inst_ty, sema.mod)) return inst; const arena = sema.arena; - const target = sema.mod.getTarget(); + const maybe_inst_val = try sema.resolveMaybeUndefVal(block, inst_src, inst); const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_ty, inst_ty, false, target, dest_ty_src, inst_src); if (in_memory_result == .ok) { - if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { + if (maybe_inst_val) |val| { // Keep the comptime Value representation; take the new type. return sema.addConstant(dest_ty, val); } @@ -13973,21 +19820,28 @@ fn coerce( return block.addBitCast(dest_ty, inst); } - // undefined to anything - if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { - if (val.isUndef() or inst_ty.zigTypeTag() == .Undefined) { - return sema.addConstant(dest_ty, val); - } - } - assert(inst_ty.zigTypeTag() != .Undefined); + const is_undef = if (maybe_inst_val) |val| val.isUndef() else false; switch (dest_ty.zigTypeTag()) { .Optional => { + // undefined sets the optional bit also to undefined. + if (is_undef) { + return sema.addConstUndef(dest_ty); + } + // null to ?T if (inst_ty.zigTypeTag() == .Null) { return sema.addConstant(dest_ty, Value.@"null"); } + // cast from ?*T and ?[*]T to ?*anyopaque + // but don't do it if the source type is a double pointer + if (dest_ty.isPtrLikeOptional() and dest_ty.elemType2().tag() == .anyopaque and + inst_ty.isPtrLikeOptional() and inst_ty.elemType2().zigTypeTag() != .Pointer) + { + return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); + } + // T to ?T const child_type = try dest_ty.optionalChildAlloc(sema.arena); const intermediate = try sema.coerce(block, child_type, inst, inst_src); @@ -14029,8 +19883,7 @@ fn coerce( const array_ty = inst_ty.childType(); if (array_ty.zigTypeTag() != .Array) break :src_array_ptr; const len0 = array_ty.arrayLen() == 0; - // We resolve here so that the backend has the layout of the elem type. - const array_elem_type = try sema.resolveTypeFields(block, inst_src, array_ty.childType()); + const array_elem_type = array_ty.childType(); const dest_is_mut = dest_info.mutable; if (inst_ty.isConstPtr() and dest_is_mut and !len0) break :src_array_ptr; if (inst_ty.isVolatilePtr() and !dest_info.@"volatile") break :src_array_ptr; @@ -14057,7 +19910,7 @@ fn coerce( // *[N:s]T to [*]T if (dest_info.sentinel) |dst_sentinel| { if (array_ty.sentinel()) |src_sentinel| { - if (src_sentinel.eql(dst_sentinel, dst_elem_type)) { + if (src_sentinel.eql(dst_sentinel, dst_elem_type, sema.mod)) { return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); } } @@ -14084,9 +19937,17 @@ fn coerce( return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); } - // coercion to C pointer - if (dest_info.size == .C) { - switch (inst_ty.zigTypeTag()) { + // cast from *T and [*]T to *anyopaque + // but don't do it if the source type is a double pointer + if (dest_info.pointee_type.tag() == .anyopaque and inst_ty.zigTypeTag() == .Pointer and + inst_ty.childType().zigTypeTag() != .Pointer) + { + return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); + } + + switch (dest_info.size) { + // coercion to C pointer + .C => switch (inst_ty.zigTypeTag()) { .Null => { return sema.addConstant(dest_ty, Value.@"null"); }, @@ -14104,7 +19965,6 @@ fn coerce( }, .Pointer => p: { const inst_info = inst_ty.ptrInfo().data; - if (inst_info.size == .Slice) break :p; switch (try sema.coerceInMemoryAllowed( block, dest_info.pointee_type, @@ -14117,43 +19977,117 @@ fn coerce( .ok => {}, .no_match => break :p, } + if (inst_info.size == .Slice) { + if (dest_info.sentinel == null or inst_info.sentinel == null or + !dest_info.sentinel.?.eql(inst_info.sentinel.?, dest_info.pointee_type, sema.mod)) + 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 => switch (dest_info.pointee_type.zigTypeTag()) { + .Union => { + // pointer to anonymous struct to pointer to union + if (inst_ty.isSinglePointer() and + inst_ty.childType().isAnonStruct() and + !dest_info.mutable) + { + return sema.coerceAnonStructToUnionPtrs(block, dest_ty, dest_ty_src, inst, inst_src); + } + }, + .Struct => { + // pointer to anonymous struct to pointer to struct + if (inst_ty.isSinglePointer() and + inst_ty.childType().isAnonStruct() and + !dest_info.mutable) + { + return sema.coerceAnonStructToStructPtrs(block, dest_ty, dest_ty_src, inst, inst_src); + } + }, + .Array => { + // pointer to tuple to pointer to array + if (inst_ty.isSinglePointer() and + inst_ty.childType().isTuple() and + !dest_info.mutable) + { + return sema.coerceTupleToArrayPtrs(block, dest_ty, dest_ty_src, inst, inst_src); + } + }, + else => {}, + }, + .Slice => { + // pointer to tuple to slice + if (inst_ty.isSinglePointer() and + inst_ty.childType().isTuple() and + (!dest_info.mutable or inst_ty.ptrIsMutable() or inst_ty.childType().tupleFields().types.len == 0) and + dest_info.size == .Slice) + { + return sema.coerceTupleToSlicePtrs(block, dest_ty, dest_ty_src, 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.pointee_type.tag() == .anyopaque and inst_ty.zigTypeTag() == .Pointer and - inst_ty.childType().zigTypeTag() != .Pointer) - { - return sema.coerceCompatiblePtrs(block, dest_ty, inst, inst_src); - } + // empty tuple to zero-length slice + // note that this allows coercing to a mutable slice. + if (inst_ty.isSinglePointer() and + inst_ty.childType().tag() == .empty_struct_literal and + dest_info.size == .Slice) + { + const slice_val = try Value.Tag.slice.create(sema.arena, .{ + .ptr = Value.undef, + .len = Value.zero, + }); + return sema.addConstant(dest_ty, slice_val); + } + }, + .Many => p: { + if (!inst_ty.isSlice()) break :p; + const inst_info = inst_ty.ptrInfo().data; + + switch (try sema.coerceInMemoryAllowed( + block, + dest_info.pointee_type, + inst_info.pointee_type, + dest_info.mutable, + target, + dest_ty_src, + inst_src, + )) { + .ok => {}, + .no_match => break :p, + } + + if (dest_info.sentinel == null or inst_info.sentinel == null or + !dest_info.sentinel.?.eql(inst_info.sentinel.?, dest_info.pointee_type, sema.mod)) + break :p; - // This will give an extra hint on top of what the bottom of this func would provide. - try sema.checkPtrOperand(block, dest_ty_src, inst_ty); + const slice_ptr = try sema.analyzeSlicePtr(block, inst_src, inst, inst_ty); + return sema.coerceCompatiblePtrs(block, dest_ty, slice_ptr, inst_src); + }, + } }, .Int, .ComptimeInt => switch (inst_ty.zigTypeTag()) { .Float, .ComptimeFloat => float: { const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :float; if (val.floatHasFraction()) { - return sema.fail(block, inst_src, "fractional component prevents float value {} from coercion to type '{}'", .{ val, dest_ty }); + return sema.fail( + block, + inst_src, + "fractional component prevents float value '{}' from coercion to type '{}'", + .{ val.fmtValue(inst_ty, sema.mod), dest_ty.fmt(sema.mod) }, + ); } - const result_val = val.floatToInt(sema.arena, dest_ty, target) catch |err| switch (err) { - error.FloatCannotFit => { - return sema.fail(block, inst_src, "integer value {d} cannot be stored in type '{}'", .{ std.math.floor(val.toFloat(f64)), dest_ty }); - }, - else => |e| return e, - }; + const result_val = try sema.floatToInt(block, inst_src, val, inst_ty, dest_ty); return try sema.addConstant(dest_ty, result_val); }, .Int, .ComptimeInt => { if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { // comptime known integer to other number - if (!val.intFitsInType(dest_ty, target)) { - return sema.fail(block, inst_src, "type {} cannot represent integer value {}", .{ dest_ty, val }); + if (!(try sema.intFitsInType(block, inst_src, val, dest_ty, null))) { + return sema.fail(block, inst_src, "type '{}' cannot represent integer value '{}'", .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) }); } return try sema.addConstant(dest_ty, val); } @@ -14169,23 +20103,26 @@ fn coerce( return block.addTyOp(.intcast, dest_ty, inst); } }, + .Undefined => { + return sema.addConstUndef(dest_ty); + }, else => {}, }, .Float, .ComptimeFloat => switch (inst_ty.zigTypeTag()) { .ComptimeFloat => { const val = try sema.resolveConstValue(block, inst_src, inst); - const result_val = try val.floatCast(sema.arena, dest_ty); + const result_val = try val.floatCast(sema.arena, dest_ty, target); return try sema.addConstant(dest_ty, result_val); }, .Float => { if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { - const result_val = try val.floatCast(sema.arena, dest_ty); - if (!val.eql(result_val, dest_ty)) { + const result_val = try val.floatCast(sema.arena, dest_ty, target); + if (!val.eql(result_val, dest_ty, sema.mod)) { return sema.fail( block, inst_src, - "type {} cannot represent float value {}", - .{ dest_ty, val }, + "type '{}' cannot represent float value '{}'", + .{ dest_ty.fmt(sema.mod), val.fmtValue(inst_ty, sema.mod) }, ); } return try sema.addConstant(dest_ty, result_val); @@ -14201,19 +20138,22 @@ fn coerce( }, .Int, .ComptimeInt => int: { const val = (try sema.resolveDefinedValue(block, inst_src, inst)) orelse break :int; - const result_val = try val.intToFloat(sema.arena, dest_ty, target); + const result_val = try val.intToFloat(sema.arena, inst_ty, dest_ty, target); // TODO implement this compile error //const int_again_val = try result_val.floatToInt(sema.arena, inst_ty); - //if (!int_again_val.eql(val, inst_ty)) { + //if (!int_again_val.eql(val, inst_ty, mod)) { // return sema.fail( // block, // inst_src, - // "type {} cannot represent integer value {}", - // .{ dest_ty, val }, + // "type '{}' cannot represent integer value '{}'", + // .{ dest_ty.fmt(sema.mod), val }, // ); //} return try sema.addConstant(dest_ty, result_val); }, + .Undefined => { + return sema.addConstUndef(dest_ty); + }, else => {}, }, .Enum => switch (inst_ty.zigTypeTag()) { @@ -14227,18 +20167,18 @@ fn coerce( block, inst_src, "enum '{}' has no field named '{s}'", - .{ dest_ty, bytes }, + .{ dest_ty.fmt(sema.mod), bytes }, ); errdefer msg.destroy(sema.gpa); try sema.mod.errNoteNonLazy( - dest_ty.declSrcLoc(), + dest_ty.declSrcLoc(sema.mod), msg, "enum declared here", .{}, ); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }; return sema.addConstant( dest_ty, @@ -14248,18 +20188,59 @@ fn coerce( .Union => blk: { // union to its own tag type const union_tag_ty = inst_ty.unionTagType() orelse break :blk; - if (union_tag_ty.eql(dest_ty)) { + if (union_tag_ty.eql(dest_ty, sema.mod)) { return sema.unionToTag(block, dest_ty, inst, inst_src); } }, + .Undefined => { + return sema.addConstUndef(dest_ty); + }, else => {}, }, - .ErrorUnion => { - // T to E!T or E to E!T - return sema.wrapErrorUnion(block, dest_ty, inst, inst_src); + .ErrorUnion => switch (inst_ty.zigTypeTag()) { + .ErrorUnion => { + if (maybe_inst_val) |inst_val| { + switch (inst_val.tag()) { + .undef => return sema.addConstUndef(dest_ty), + .eu_payload => { + const payload = try sema.addConstant( + inst_ty.errorUnionPayload(), + inst_val.castTag(.eu_payload).?.data, + ); + return sema.wrapErrorUnionPayload(block, dest_ty, payload, inst_src); + }, + else => { + const error_set = try sema.addConstant( + inst_ty.errorUnionSet(), + inst_val, + ); + return sema.wrapErrorUnionSet(block, dest_ty, error_set, inst_src); + }, + } + } + }, + .ErrorSet => { + // E to E!T + return sema.wrapErrorUnionSet(block, dest_ty, inst, inst_src); + }, + .Undefined => { + return sema.addConstUndef(dest_ty); + }, + else => { + // T to E!T + return sema.wrapErrorUnionPayload(block, dest_ty, inst, inst_src); + }, }, .Union => switch (inst_ty.zigTypeTag()) { .Enum, .EnumLiteral => return sema.coerceEnumToUnion(block, dest_ty, dest_ty_src, inst, inst_src), + .Struct => { + if (inst_ty.isAnonStruct()) { + return sema.coerceAnonStructToUnion(block, dest_ty, dest_ty_src, inst, inst_src); + } + }, + .Undefined => { + return sema.addConstUndef(dest_ty); + }, else => {}, }, .Array => switch (inst_ty.zigTypeTag()) { @@ -14272,21 +20253,42 @@ fn coerce( return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src); } }, + .Undefined => { + return sema.addConstUndef(dest_ty); + }, else => {}, }, .Vector => switch (inst_ty.zigTypeTag()) { .Array, .Vector => return sema.coerceArrayLike(block, dest_ty, dest_ty_src, inst, inst_src), + .Struct => { + if (inst_ty.isTuple()) { + return sema.coerceTupleToArray(block, dest_ty, dest_ty_src, inst, inst_src); + } + }, + .Undefined => { + return sema.addConstUndef(dest_ty); + }, else => {}, }, .Struct => { if (inst == .empty_struct) { return structInitEmpty(sema, block, dest_ty, dest_ty_src, inst_src); } + if (inst_ty.isTupleOrAnonStruct()) { + return sema.coerceTupleToStruct(block, dest_ty, dest_ty_src, inst, inst_src); + } }, else => {}, } - return sema.fail(block, inst_src, "expected {}, found {}", .{ dest_ty, inst_ty }); + // 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 (is_undef) { + return sema.addConstUndef(dest_ty); + } + + return sema.fail(block, inst_src, "expected type '{}', found '{}'", .{ dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod) }); } const InMemoryCoercionResult = enum { @@ -14315,14 +20317,36 @@ fn coerceInMemoryAllowed( dest_src: LazySrcLoc, src_src: LazySrcLoc, ) CompileError!InMemoryCoercionResult { - if (dest_ty.eql(src_ty)) + if (dest_ty.eql(src_ty, sema.mod)) return .ok; + // Differently-named integers with the same number of bits. + if (dest_ty.zigTypeTag() == .Int and src_ty.zigTypeTag() == .Int) { + const dest_info = dest_ty.intInfo(target); + const src_info = src_ty.intInfo(target); + if (dest_info.signedness == src_info.signedness and + dest_info.bits == src_info.bits) + { + return .ok; + } + } + + // Differently-named floats with the same number of bits. + if (dest_ty.zigTypeTag() == .Float and src_ty.zigTypeTag() == .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 var dest_buf: Type.Payload.ElemType = undefined; var src_buf: Type.Payload.ElemType = undefined; - if (try sema.typePtrOrOptionalPtrTy(block, dest_ty, &dest_buf, dest_src)) |dest_ptr_ty| { - if (try sema.typePtrOrOptionalPtrTy(block, src_ty, &src_buf, src_src)) |src_ptr_ty| { + const maybe_dest_ptr_ty = try sema.typePtrOrOptionalPtrTy(block, dest_ty, &dest_buf, dest_src); + const maybe_src_ptr_ty = try sema.typePtrOrOptionalPtrTy(block, src_ty, &src_buf, src_src); + 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); } } @@ -14351,7 +20375,7 @@ fn coerceInMemoryAllowed( // Error Sets if (dest_tag == .ErrorSet and src_tag == .ErrorSet) { - return try sema.coerceInMemoryAllowedErrorSets(dest_ty, src_ty); + return try sema.coerceInMemoryAllowedErrorSets(block, dest_ty, src_ty, dest_src, src_src); } // Arrays @@ -14366,23 +20390,55 @@ fn coerceInMemoryAllowed( } const ok_sent = dest_info.sentinel == null or (src_info.sentinel != null and - dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.elem_type)); + dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.elem_type, sema.mod)); if (!ok_sent) { return .no_match; } return .ok; } - // TODO: non-pointer-like optionals - // TODO: vectors + // Vectors + if (dest_tag == .Vector and src_tag == .Vector) vectors: { + const dest_len = dest_ty.vectorLen(); + const src_len = src_ty.vectorLen(); + if (dest_len != src_len) break :vectors; + + const dest_elem_ty = dest_ty.scalarType(); + const src_elem_ty = src_ty.scalarType(); + const child = try sema.coerceInMemoryAllowed(block, dest_elem_ty, src_elem_ty, dest_is_mut, target, dest_src, src_src); + if (child == .no_match) break :vectors; + + return .ok; + } + + // Optionals + if (dest_tag == .Optional and src_tag == .Optional) optionals: { + if ((maybe_dest_ptr_ty != null) != (maybe_src_ptr_ty != null)) { + // TODO "optional type child '{}' cannot cast into optional type '{}'" + return .no_match; + } + const dest_child_type = dest_ty.optionalChild(&dest_buf); + const src_child_type = src_ty.optionalChild(&src_buf); + + const child = try sema.coerceInMemoryAllowed(block, dest_child_type, src_child_type, dest_is_mut, target, dest_src, src_src); + if (child == .no_match) { + // TODO "optional type child '{}' cannot cast into optional type child '{}'" + break :optionals; + } + + return .ok; + } return .no_match; } fn coerceInMemoryAllowedErrorSets( sema: *Sema, + block: *Block, dest_ty: Type, src_ty: Type, + dest_src: LazySrcLoc, + src_src: LazySrcLoc, ) !InMemoryCoercionResult { // Coercion to `anyerror`. Note that this check can return false negatives // in case the error sets did not get resolved. @@ -14390,24 +20446,50 @@ fn coerceInMemoryAllowedErrorSets( return .ok; } - // If both are inferred error sets of functions, and - // the dest includes the source function, the coercion is OK. - // This check is important because it works without forcing a full resolution - // of inferred error sets. - if (src_ty.castTag(.error_set_inferred)) |src_payload| { - if (dest_ty.castTag(.error_set_inferred)) |dst_payload| { - const src_func = src_payload.data.func; - const dst_func = dst_payload.data.func; + if (dest_ty.castTag(.error_set_inferred)) |dst_payload| { + const dst_ies = dst_payload.data; + // We will make an effort to return `ok` without resolving either error set, to + // avoid unnecessary "unable to resolve error set" dependency loop errors. + switch (src_ty.tag()) { + .error_set_inferred => { + // If both are inferred error sets of functions, and + // the dest includes the source function, the coercion is OK. + // This check is important because it works without forcing a full resolution + // of inferred error sets. + const src_ies = src_ty.castTag(.error_set_inferred).?.data; + + if (dst_ies.inferred_error_sets.contains(src_ies)) { + return .ok; + } + }, + .error_set_single => { + const name = src_ty.castTag(.error_set_single).?.data; + if (dst_ies.errors.contains(name)) return .ok; + }, + .error_set_merged => { + const names = src_ty.castTag(.error_set_merged).?.data.keys(); + for (names) |name| { + if (!dst_ies.errors.contains(name)) break; + } else return .ok; + }, + .error_set => { + const names = src_ty.castTag(.error_set).?.data.names.keys(); + for (names) |name| { + if (!dst_ies.errors.contains(name)) break; + } else return .ok; + }, + .anyerror => {}, + else => unreachable, + } - if (src_func == dst_func or dst_payload.data.inferred_error_sets.contains(src_payload.data)) { - return .ok; - } - return .no_match; + if (dst_ies.func == sema.owner_func) { + // We are trying to coerce an error set to the current function's + // inferred error set. + try dst_ies.addErrorSet(sema.gpa, src_ty); + return .ok; } - } - if (dest_ty.castTag(.error_set_inferred)) |payload| { - try sema.resolveInferredErrorSet(payload.data); + try sema.resolveInferredErrorSet(block, dest_src, dst_payload.data); // isAnyError might have changed from a false negative to a true positive after resolution. if (dest_ty.isAnyError()) { return .ok; @@ -14418,16 +20500,15 @@ fn coerceInMemoryAllowedErrorSets( .error_set_inferred => { const src_data = src_ty.castTag(.error_set_inferred).?.data; - try sema.resolveInferredErrorSet(src_data); + try sema.resolveInferredErrorSet(block, src_src, src_data); // src anyerror status might have changed after the resolution. if (src_ty.isAnyError()) { // dest_ty.isAnyError() == true is already checked for at this point. return .no_match; } - var it = src_data.errors.keyIterator(); - while (it.next()) |name_ptr| { - if (!dest_ty.errorSetHasField(name_ptr.*)) { + for (src_data.errors.keys()) |key| { + if (!dest_ty.errorSetHasField(key)) { return .no_match; } } @@ -14552,7 +20633,7 @@ fn coerceInMemoryAllowedPtrs( const ok_sent = dest_info.sentinel == null or src_info.size == .C or (src_info.sentinel != null and - dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type)); + dest_info.sentinel.?.eql(src_info.sentinel.?, dest_info.pointee_type, sema.mod)); if (!ok_sent) { return .no_match; } @@ -14591,15 +20672,28 @@ fn coerceInMemoryAllowedPtrs( // 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.@"align" != 0 or dest_info.@"align" != 0 or - !dest_info.pointee_type.eql(src_info.pointee_type)) - { - const src_align = src_info.@"align"; - const dest_align = dest_info.@"align"; + alignment: { + if (src_info.@"align" == 0 and dest_info.@"align" == 0 and + dest_info.pointee_type.eql(src_info.pointee_type, sema.mod)) + { + break :alignment; + } + + const src_align = if (src_info.@"align" != 0) + src_info.@"align" + else + src_info.pointee_type.abiAlignment(target); + + const dest_align = if (dest_info.@"align" != 0) + dest_info.@"align" + else + dest_info.pointee_type.abiAlignment(target); if (dest_align > src_align) { return .no_match; } + + break :alignment; } return .ok; @@ -14650,26 +20744,45 @@ fn storePtr2( // 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.castTag(.tuple)) |payload| { - const tuple_fields_len = payload.data.types.len; - var i: u32 = 0; - while (i < tuple_fields_len) : (i += 1) { + if (operand_ty.isTuple() and elem_ty.zigTypeTag() == .Array) { + const tuple = operand_ty.tupleFields(); + for (tuple.types) |_, i_usize| { + const i = @intCast(u32, i_usize); const elem_src = operand_src; // TODO better source location - const elem = try tupleField(sema, block, uncasted_operand, i, operand_src, elem_src); + const elem = try tupleField(sema, block, operand_src, uncasted_operand, elem_src, i); const elem_index = try sema.addIntUnsigned(Type.usize, i); - const elem_ptr = try sema.elemPtr(block, ptr_src, ptr, elem_index, elem_src); + const elem_ptr = try sema.elemPtr(block, ptr_src, ptr, elem_index, elem_src, false); try sema.storePtr2(block, src, elem_ptr, elem_src, elem, elem_src, .store); } return; } - const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src); - if ((try sema.typeHasOnePossibleValue(block, src, elem_ty)) != null) + // TODO do the same thing for anon structs as for tuples above. + // However, beware of the need to handle missing/extra fields. + + // Detect if we are storing an array operand to a bitcasted vector pointer. + // If so, we instead reach through the bitcasted pointer to the vector pointer, + // bitcast the array operand to a vector, and then lower this as a store of + // a vector value to a vector pointer. This generally results in better code, + // as well as working around an LLVM bug: + // https://github.com/ziglang/zig/issues/11154 + if (sema.obtainBitCastedVectorPtr(ptr)) |vector_ptr| { + const vector_ty = sema.typeOf(vector_ptr).childType(); + const vector = try sema.coerce(block, vector_ty, uncasted_operand, operand_src); + try sema.storePtr2(block, src, vector_ptr, ptr_src, vector, operand_src, .store); return; + } + + const operand = try sema.coerce(block, elem_ty, uncasted_operand, operand_src); + const maybe_operand_val = try sema.resolveMaybeUndefVal(block, operand_src, operand); const runtime_src = if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| rs: { - const maybe_operand_val = try sema.resolveMaybeUndefVal(block, operand_src, operand); const operand_val = maybe_operand_val orelse { try sema.checkPtrIsNotComptimeMutable(block, ptr_val, ptr_src, operand_src); break :rs operand_src; @@ -14680,13 +20793,58 @@ fn storePtr2( } else break :rs ptr_src; } else ptr_src; + // 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(block, src, elem_ty)) != null) + return; + // TODO handle if the element type requires comptime + if (air_tag == .bitcast) { + // `air_tag == .bitcast` is used as a special case for `zirCoerceResultPtr` + // to avoid calling `requireRuntimeBlock` for the dummy block. + _ = try block.addBinOp(.store, ptr, operand); + return; + } + try sema.requireRuntimeBlock(block, runtime_src); - try sema.resolveTypeLayout(block, src, elem_ty); + try sema.queueFullTypeResolution(elem_ty); _ = try block.addBinOp(air_tag, ptr, operand); } +/// Traverse an arbitrary number of bitcasted pointers and return the underyling vector +/// pointer. Only if the final element type matches the vector element type, and the +/// lengths match. +fn obtainBitCastedVectorPtr(sema: *Sema, ptr: Air.Inst.Ref) ?Air.Inst.Ref { + const array_ty = sema.typeOf(ptr).childType(); + if (array_ty.zigTypeTag() != .Array) return null; + var ptr_inst = Air.refToIndex(ptr) orelse return null; + const air_datas = sema.air_instructions.items(.data); + const air_tags = sema.air_instructions.items(.tag); + const prev_ptr = while (air_tags[ptr_inst] == .bitcast) { + const prev_ptr = air_datas[ptr_inst].ty_op.operand; + const prev_ptr_ty = sema.typeOf(prev_ptr); + const prev_ptr_child_ty = switch (prev_ptr_ty.tag()) { + .single_mut_pointer => prev_ptr_ty.castTag(.single_mut_pointer).?.data, + .pointer => prev_ptr_ty.castTag(.pointer).?.data.pointee_type, + else => return null, + }; + if (prev_ptr_child_ty.zigTypeTag() == .Vector) break prev_ptr; + ptr_inst = Air.refToIndex(prev_ptr) orelse return null; + } else return null; + + // We have a pointer-to-array and a pointer-to-vector. If the elements and + // lengths match, return the result. + const vector_ty = sema.typeOf(prev_ptr).childType(); + if (array_ty.childType().eql(vector_ty.childType(), sema.mod) and + array_ty.arrayLen() == vector_ty.vectorLen()) + { + return prev_ptr; + } else { + return null; + } +} + /// Call when you have Value objects rather than Air instructions, and you want to /// assert the store must be done at comptime. fn storePtrVal( @@ -14697,30 +20855,79 @@ fn storePtrVal( operand_val: Value, operand_ty: Type, ) !void { - var kit = try beginComptimePtrMutation(sema, block, src, ptr_val); - try sema.checkComptimeVarStore(block, src, kit.decl_ref_mut); + var mut_kit = try beginComptimePtrMutation(sema, block, src, ptr_val, operand_ty); + try sema.checkComptimeVarStore(block, src, mut_kit.decl_ref_mut); + + switch (mut_kit.pointee) { + .direct => |val_ptr| { + if (mut_kit.decl_ref_mut.runtime_index == .comptime_field_ptr) { + if (!operand_val.eql(val_ptr.*, operand_ty, sema.mod)) { + // TODO add note showing where default value is provided + return sema.fail(block, src, "value stored in comptime field does not match the default value of the field", .{}); + } + return; + } + const arena = mut_kit.beginArena(sema.mod); + defer mut_kit.finishArena(sema.mod); - const bitcasted_val = try sema.bitCastVal(block, src, operand_val, operand_ty, kit.ty); + val_ptr.* = try operand_val.copy(arena); + }, + .reinterpret => |reinterpret| { + const target = sema.mod.getTarget(); + const abi_size = try sema.usizeCast(block, src, mut_kit.ty.abiSize(target)); + const buffer = try sema.gpa.alloc(u8, abi_size); + defer sema.gpa.free(buffer); + reinterpret.val_ptr.*.writeToMemory(mut_kit.ty, sema.mod, buffer); + operand_val.writeToMemory(operand_ty, sema.mod, buffer[reinterpret.byte_offset..]); - const arena = kit.beginArena(sema.gpa); - defer kit.finishArena(); + const arena = mut_kit.beginArena(sema.mod); + defer mut_kit.finishArena(sema.mod); - kit.val.* = try bitcasted_val.copy(arena); + reinterpret.val_ptr.* = try Value.readFromMemory(mut_kit.ty, sema.mod, buffer, arena); + }, + .bad_decl_ty, .bad_ptr_ty => { + // TODO show the decl declaration site in a note and explain whether the decl + // or the pointer is the problematic type + return sema.fail(block, src, "comptime mutation of a reinterpreted pointer requires type '{}' to have a well-defined memory layout", .{mut_kit.ty.fmt(sema.mod)}); + }, + } } const ComptimePtrMutationKit = struct { decl_ref_mut: Value.Payload.DeclRefMut.Data, - val: *Value, + pointee: union(enum) { + /// The pointer type matches the actual comptime Value so a direct + /// modification is possible. + direct: *Value, + /// The largest parent Value containing pointee and having a well-defined memory layout. + /// This is used for bitcasting, if direct dereferencing failed. + reinterpret: struct { + val_ptr: *Value, + byte_offset: usize, + }, + /// If the root decl could not be used as parent, this means `ty` is the type that + /// caused that by not having a well-defined layout. + /// This one means the Decl that owns the value trying to be modified does not + /// have a well defined memory layout. + bad_decl_ty, + /// If the root decl could not be used as parent, this means `ty` is the type that + /// caused that by not having a well-defined layout. + /// This one means the pointer type that is being stored through does not + /// have a well defined memory layout. + bad_ptr_ty, + }, ty: Type, decl_arena: std.heap.ArenaAllocator = undefined, - fn beginArena(self: *ComptimePtrMutationKit, gpa: Allocator) Allocator { - self.decl_arena = self.decl_ref_mut.decl.value_arena.?.promote(gpa); + fn beginArena(self: *ComptimePtrMutationKit, mod: *Module) Allocator { + const decl = mod.declPtr(self.decl_ref_mut.decl_index); + self.decl_arena = decl.value_arena.?.promote(mod.gpa); return self.decl_arena.allocator(); } - fn finishArena(self: *ComptimePtrMutationKit) void { - self.decl_ref_mut.decl.value_arena.?.* = self.decl_arena.state; + fn finishArena(self: *ComptimePtrMutationKit, mod: *Module) void { + const decl = mod.declPtr(self.decl_ref_mut.decl_index); + decl.value_arena.?.* = self.decl_arena.state; self.decl_arena = undefined; } }; @@ -14730,349 +20937,750 @@ fn beginComptimePtrMutation( block: *Block, src: LazySrcLoc, ptr_val: Value, + ptr_elem_ty: Type, ) CompileError!ComptimePtrMutationKit { + const target = sema.mod.getTarget(); switch (ptr_val.tag()) { .decl_ref_mut => { const decl_ref_mut = ptr_val.castTag(.decl_ref_mut).?.data; - return ComptimePtrMutationKit{ - .decl_ref_mut = decl_ref_mut, - .val = &decl_ref_mut.decl.val, - .ty = decl_ref_mut.decl.ty, - }; + const decl = sema.mod.declPtr(decl_ref_mut.decl_index); + return beginComptimePtrMutationInner(sema, block, src, decl.ty, &decl.val, ptr_elem_ty, decl_ref_mut); + }, + .comptime_field_ptr => { + const payload = ptr_val.castTag(.comptime_field_ptr).?.data; + const duped = try sema.arena.create(Value); + duped.* = payload.field_val; + return beginComptimePtrMutationInner(sema, block, src, payload.field_ty, duped, ptr_elem_ty, .{ + .decl_index = @intToEnum(Module.Decl.Index, 0), + .runtime_index = .comptime_field_ptr, + }); }, .elem_ptr => { const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; - var parent = try beginComptimePtrMutation(sema, block, src, elem_ptr.array_ptr); - switch (parent.ty.zigTypeTag()) { - .Array, .Vector => { - const check_len = parent.ty.arrayLenIncludingSentinel(); - if (elem_ptr.index >= check_len) { - // TODO have the parent include the decl so we can say "declared here" - return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{ - elem_ptr.index, check_len, - }); - } - const elem_ty = parent.ty.childType(); - switch (parent.val.tag()) { - .undef => { - // An array has been initialized to undefined at comptime and now we - // are for the first time setting an element. We must change the representation - // of the array from `undef` to `array`. - const arena = parent.beginArena(sema.gpa); - defer parent.finishArena(); - - const array_len_including_sentinel = - try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); - const elems = try arena.alloc(Value, array_len_including_sentinel); - mem.set(Value, elems, Value.undef); - - parent.val.* = try Value.Tag.array.create(arena, elems); + var parent = try beginComptimePtrMutation(sema, block, src, elem_ptr.array_ptr, elem_ptr.elem_ty); + switch (parent.pointee) { + .direct => |val_ptr| switch (parent.ty.zigTypeTag()) { + .Array, .Vector => { + const check_len = parent.ty.arrayLenIncludingSentinel(); + if (elem_ptr.index >= check_len) { + // TODO have the parent include the decl so we can say "declared here" + return sema.fail(block, src, "comptime store of index {d} out of bounds of array length {d}", .{ + elem_ptr.index, check_len, + }); + } + const elem_ty = parent.ty.childType(); + switch (val_ptr.tag()) { + .undef => { + // An array has been initialized to undefined at comptime and now we + // are for the first time setting an element. We must change the representation + // of the array from `undef` to `array`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const array_len_including_sentinel = + try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); + const elems = try arena.alloc(Value, array_len_including_sentinel); + mem.set(Value, elems, Value.undef); + + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); + + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .bytes => { + // An array is memory-optimized to store a slice of bytes, but we are about + // to modify an individual field and the representation has to change. + // If we wanted to avoid this, there would need to be special detection + // elsewhere to identify when writing a value to an array element that is stored + // using the `bytes` tag, and handle it without making a call to this function. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const bytes = val_ptr.castTag(.bytes).?.data; + const dest_len = parent.ty.arrayLenIncludingSentinel(); + // bytes.len may be one greater than dest_len because of the case when + // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted. + assert(bytes.len >= dest_len); + const elems = try arena.alloc(Value, @intCast(usize, dest_len)); + for (elems) |*elem, i| { + elem.* = try Value.Tag.int_u64.create(arena, bytes[i]); + } - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, - .bytes => { - // An array is memory-optimized to store a slice of bytes, but we are about - // to modify an individual field and the representation has to change. - // If we wanted to avoid this, there would need to be special detection - // elsewhere to identify when writing a value to an array element that is stored - // using the `bytes` tag, and handle it without making a call to this function. - const arena = parent.beginArena(sema.gpa); - defer parent.finishArena(); - - const bytes = parent.val.castTag(.bytes).?.data; - const dest_len = parent.ty.arrayLenIncludingSentinel(); - // bytes.len may be one greater than dest_len because of the case when - // assigning `[N:S]T` to `[N]T`. This is allowed; the sentinel is omitted. - assert(bytes.len >= dest_len); - const elems = try arena.alloc(Value, @intCast(usize, dest_len)); - for (elems) |*elem, i| { - elem.* = try Value.Tag.int_u64.create(arena, bytes[i]); - } + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - parent.val.* = try Value.Tag.array.create(arena, elems); + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .str_lit => { + // An array is memory-optimized to store a slice of bytes, but we are about + // to modify an individual field and the representation has to change. + // If we wanted to avoid this, there would need to be special detection + // elsewhere to identify when writing a value to an array element that is stored + // using the `str_lit` tag, and handle it without making a call to this function. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const str_lit = val_ptr.castTag(.str_lit).?.data; + const dest_len = parent.ty.arrayLenIncludingSentinel(); + const bytes = sema.mod.string_literal_bytes.items[str_lit.index..][0..str_lit.len]; + const elems = try arena.alloc(Value, @intCast(usize, dest_len)); + for (bytes) |byte, i| { + elems[i] = try Value.Tag.int_u64.create(arena, byte); + } + if (parent.ty.sentinel()) |sent_val| { + assert(elems.len == bytes.len + 1); + elems[bytes.len] = sent_val; + } - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, - .repeated => { - // An array is memory-optimized to store only a single element value, and - // that value is understood to be the same for the entire length of the array. - // However, now we want to modify an individual field and so the - // representation has to change. If we wanted to avoid this, there would - // need to be special detection elsewhere to identify when writing a value to an - // array element that is stored using the `repeated` tag, and handle it - // without making a call to this function. - const arena = parent.beginArena(sema.gpa); - defer parent.finishArena(); - - const repeated_val = try parent.val.castTag(.repeated).?.data.copy(arena); - const array_len_including_sentinel = - try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); - const elems = try arena.alloc(Value, array_len_including_sentinel); - mem.set(Value, elems, repeated_val); - - parent.val.* = try Value.Tag.array.create(arena, elems); + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &elems[elem_ptr.index], - .ty = elem_ty, - }; - }, + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .repeated => { + // An array is memory-optimized to store only a single element value, and + // that value is understood to be the same for the entire length of the array. + // However, now we want to modify an individual field and so the + // representation has to change. If we wanted to avoid this, there would + // need to be special detection elsewhere to identify when writing a value to an + // array element that is stored using the `repeated` tag, and handle it + // without making a call to this function. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const repeated_val = try val_ptr.castTag(.repeated).?.data.copy(arena); + const array_len_including_sentinel = + try sema.usizeCast(block, src, parent.ty.arrayLenIncludingSentinel()); + const elems = try arena.alloc(Value, array_len_including_sentinel); + mem.set(Value, elems, repeated_val); + + val_ptr.* = try Value.Tag.aggregate.create(arena, elems); + + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &elems[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, - .array => return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.array).?.data[elem_ptr.index], - .ty = elem_ty, - }, + .aggregate => return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + &val_ptr.castTag(.aggregate).?.data[elem_ptr.index], + ptr_elem_ty, + parent.decl_ref_mut, + ), + + .the_only_possible_value => { + const duped = try sema.arena.create(Value); + duped.* = Value.initTag(.the_only_possible_value); + return beginComptimePtrMutationInner( + sema, + block, + src, + elem_ty, + duped, + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, - else => unreachable, - } + else => unreachable, + } + }, + else => { + if (elem_ptr.index != 0) { + // TODO include a "declared here" note for the decl + return sema.fail(block, src, "out of bounds comptime store of index {d}", .{ + elem_ptr.index, + }); + } + return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty, + val_ptr, + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, }, - else => { - if (elem_ptr.index != 0) { - // TODO include a "declared here" note for the decl - return sema.fail(block, src, "out of bounds comptime store of index {d}", .{ - elem_ptr.index, - }); + .reinterpret => |reinterpret| { + if (!elem_ptr.elem_ty.hasWellDefinedLayout()) { + // Even though the parent value type has well-defined memory layout, our + // pointer type does not. + return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .bad_ptr_ty, + .ty = elem_ptr.elem_ty, + }; } + + const elem_abi_size_u64 = try sema.typeAbiSize(block, src, elem_ptr.elem_ty); + const elem_abi_size = try sema.usizeCast(block, src, elem_abi_size_u64); return ComptimePtrMutationKit{ .decl_ref_mut = parent.decl_ref_mut, - .val = parent.val, + .pointee = .{ .reinterpret = .{ + .val_ptr = reinterpret.val_ptr, + .byte_offset = reinterpret.byte_offset + elem_abi_size * elem_ptr.index, + } }, .ty = parent.ty, }; }, + .bad_decl_ty, .bad_ptr_ty => return parent, } }, .field_ptr => { const field_ptr = ptr_val.castTag(.field_ptr).?.data; - var parent = try beginComptimePtrMutation(sema, block, src, field_ptr.container_ptr); const field_index = @intCast(u32, field_ptr.field_index); - const field_ty = parent.ty.structFieldType(field_index); - switch (parent.val.tag()) { - .undef => { - // A struct or union has been initialized to undefined at comptime and now we - // are for the first time setting a field. We must change the representation - // of the struct/union from `undef` to `struct`/`union`. - const arena = parent.beginArena(sema.gpa); - defer parent.finishArena(); - - switch (parent.ty.zigTypeTag()) { - .Struct => { - const fields = try arena.alloc(Value, parent.ty.structFieldCount()); - mem.set(Value, fields, Value.undef); - - parent.val.* = try Value.Tag.@"struct".create(arena, fields); + + var parent = try beginComptimePtrMutation(sema, block, src, field_ptr.container_ptr, field_ptr.container_ty); + switch (parent.pointee) { + .direct => |val_ptr| switch (val_ptr.tag()) { + .undef => { + // A struct or union has been initialized to undefined at comptime and now we + // are for the first time setting a field. We must change the representation + // of the struct/union from `undef` to `struct`/`union`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + switch (parent.ty.zigTypeTag()) { + .Struct => { + const fields = try arena.alloc(Value, parent.ty.structFieldCount()); + mem.set(Value, fields, Value.undef); + + val_ptr.* = try Value.Tag.aggregate.create(arena, fields); + + return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &fields[field_index], + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .Union => { + const payload = try arena.create(Value.Payload.Union); + payload.* = .{ .data = .{ + .tag = try Value.Tag.enum_field_index.create(arena, field_index), + .val = Value.undef, + } }; + + val_ptr.* = Value.initPayload(&payload.base); + + return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &payload.data.val, + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .Pointer => { + assert(parent.ty.isSlice()); + val_ptr.* = try Value.Tag.slice.create(arena, .{ + .ptr = Value.undef, + .len = Value.undef, + }); + + switch (field_index) { + Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.slicePtrFieldType(try sema.arena.create(Type.SlicePtrFieldTypeBuffer)), + &val_ptr.castTag(.slice).?.data.ptr, + ptr_elem_ty, + parent.decl_ref_mut, + ), + Value.Payload.Slice.len_index => return beginComptimePtrMutationInner( + sema, + block, + src, + Type.usize, + &val_ptr.castTag(.slice).?.data.len, + ptr_elem_ty, + parent.decl_ref_mut, + ), + + else => unreachable, + } + }, + else => unreachable, + } + }, + .aggregate => return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &val_ptr.castTag(.aggregate).?.data[field_index], + ptr_elem_ty, + parent.decl_ref_mut, + ), + + .@"union" => { + // We need to set the active field of the union. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const payload = &val_ptr.castTag(.@"union").?.data; + payload.tag = try Value.Tag.enum_field_index.create(arena, field_index); + + return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.structFieldType(field_index), + &payload.val, + ptr_elem_ty, + parent.decl_ref_mut, + ); + }, + .slice => switch (field_index) { + Value.Payload.Slice.ptr_index => return beginComptimePtrMutationInner( + sema, + block, + src, + parent.ty.slicePtrFieldType(try sema.arena.create(Type.SlicePtrFieldTypeBuffer)), + &val_ptr.castTag(.slice).?.data.ptr, + ptr_elem_ty, + parent.decl_ref_mut, + ), + + Value.Payload.Slice.len_index => return beginComptimePtrMutationInner( + sema, + block, + src, + Type.usize, + &val_ptr.castTag(.slice).?.data.len, + ptr_elem_ty, + parent.decl_ref_mut, + ), + + else => unreachable, + }, + + else => unreachable, + }, + .reinterpret => |reinterpret| { + const field_offset_u64 = field_ptr.container_ty.structFieldOffset(field_index, target); + const field_offset = try sema.usizeCast(block, src, field_offset_u64); + return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .reinterpret = .{ + .val_ptr = reinterpret.val_ptr, + .byte_offset = reinterpret.byte_offset + field_offset, + } }, + .ty = parent.ty, + }; + }, + .bad_decl_ty, .bad_ptr_ty => return parent, + } + }, + .eu_payload_ptr => { + const eu_ptr = ptr_val.castTag(.eu_payload_ptr).?.data; + var parent = try beginComptimePtrMutation(sema, block, src, eu_ptr.container_ptr, eu_ptr.container_ty); + switch (parent.pointee) { + .direct => |val_ptr| { + const payload_ty = parent.ty.errorUnionPayload(); + switch (val_ptr.tag()) { + else => { + // An error union has been initialized to undefined at comptime and now we + // are for the first time setting the payload. We must change the + // representation of the error union from `undef` to `opt_payload`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const payload = try arena.create(Value.Payload.SubValue); + payload.* = .{ + .base = .{ .tag = .eu_payload }, + .data = Value.undef, + }; + + val_ptr.* = Value.initPayload(&payload.base); return ComptimePtrMutationKit{ .decl_ref_mut = parent.decl_ref_mut, - .val = &fields[field_index], - .ty = field_ty, + .pointee = .{ .direct = &payload.data }, + .ty = payload_ty, }; }, - .Union => { - const payload = try arena.create(Value.Payload.Union); - payload.* = .{ .data = .{ - .tag = try Value.Tag.enum_field_index.create(arena, field_index), - .val = Value.undef, - } }; + .eu_payload => return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .direct = &val_ptr.castTag(.eu_payload).?.data }, + .ty = payload_ty, + }, + } + }, + .bad_decl_ty, .bad_ptr_ty => return parent, + // Even though the parent value type has well-defined memory layout, our + // pointer type does not. + .reinterpret => return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .bad_ptr_ty, + .ty = eu_ptr.container_ty, + }, + } + }, + .opt_payload_ptr => { + const opt_ptr = ptr_val.castTag(.opt_payload_ptr).?.data; + var parent = try beginComptimePtrMutation(sema, block, src, opt_ptr.container_ptr, opt_ptr.container_ty); + switch (parent.pointee) { + .direct => |val_ptr| { + const payload_ty = try parent.ty.optionalChildAlloc(sema.arena); + switch (val_ptr.tag()) { + .undef, .null_value => { + // An optional has been initialized to undefined at comptime and now we + // are for the first time setting the payload. We must change the + // representation of the optional from `undef` to `opt_payload`. + const arena = parent.beginArena(sema.mod); + defer parent.finishArena(sema.mod); + + const payload = try arena.create(Value.Payload.SubValue); + payload.* = .{ + .base = .{ .tag = .opt_payload }, + .data = Value.undef, + }; - parent.val.* = Value.initPayload(&payload.base); + val_ptr.* = Value.initPayload(&payload.base); return ComptimePtrMutationKit{ .decl_ref_mut = parent.decl_ref_mut, - .val = &payload.data.val, - .ty = field_ty, + .pointee = .{ .direct = &payload.data }, + .ty = payload_ty, }; }, + .opt_payload => return ComptimePtrMutationKit{ + .decl_ref_mut = parent.decl_ref_mut, + .pointee = .{ .direct = &val_ptr.castTag(.opt_payload).?.data }, + .ty = payload_ty, + }, + else => unreachable, } }, - .@"struct" => return ComptimePtrMutationKit{ + .bad_decl_ty, .bad_ptr_ty => return parent, + // Even though the parent value type has well-defined memory layout, our + // pointer type does not. + .reinterpret => return ComptimePtrMutationKit{ .decl_ref_mut = parent.decl_ref_mut, - .val = &parent.val.castTag(.@"struct").?.data[field_index], - .ty = field_ty, - }, - .@"union" => { - // We need to set the active field of the union. - const arena = parent.beginArena(sema.gpa); - defer parent.finishArena(); - - const payload = &parent.val.castTag(.@"union").?.data; - payload.tag = try Value.Tag.enum_field_index.create(arena, field_index); - - return ComptimePtrMutationKit{ - .decl_ref_mut = parent.decl_ref_mut, - .val = &payload.val, - .ty = field_ty, - }; + .pointee = .bad_ptr_ty, + .ty = opt_ptr.container_ty, }, - - else => unreachable, } }, - .eu_payload_ptr => return sema.fail(block, src, "TODO comptime store to eu_payload_ptr", .{}), - .opt_payload_ptr => return sema.fail(block, src, "TODO comptime store opt_payload_ptr", .{}), .decl_ref => unreachable, // isComptimeMutablePtr() has been checked already else => unreachable, } } +fn beginComptimePtrMutationInner( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + decl_ty: Type, + decl_val: *Value, + ptr_elem_ty: Type, + decl_ref_mut: Value.Payload.DeclRefMut.Data, +) CompileError!ComptimePtrMutationKit { + const target = sema.mod.getTarget(); + const coerce_ok = (try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_ty, true, target, src, src)) == .ok; + if (coerce_ok) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .direct = decl_val }, + .ty = decl_ty, + }; + } + + // Handle the case that the decl is an array and we're actually trying to point to an element. + if (decl_ty.isArrayOrVector()) { + const decl_elem_ty = decl_ty.childType(); + if ((try sema.coerceInMemoryAllowed(block, ptr_elem_ty, decl_elem_ty, true, target, src, src)) == .ok) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .direct = decl_val }, + .ty = decl_ty, + }; + } + } + + if (!decl_ty.hasWellDefinedLayout()) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .bad_decl_ty = {} }, + .ty = decl_ty, + }; + } + if (!ptr_elem_ty.hasWellDefinedLayout()) { + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .bad_ptr_ty = {} }, + .ty = ptr_elem_ty, + }; + } + return ComptimePtrMutationKit{ + .decl_ref_mut = decl_ref_mut, + .pointee = .{ .reinterpret = .{ + .val_ptr = decl_val, + .byte_offset = 0, + } }, + .ty = decl_ty, + }; +} + +const TypedValueAndOffset = struct { + tv: TypedValue, + byte_offset: usize, +}; + const ComptimePtrLoadKit = struct { - /// The Value of the Decl that owns this memory. - root_val: Value, - /// Parent Value. - val: Value, - /// The Type of the parent Value. - ty: Type, - /// The starting byte offset of `val` from `root_val`. - /// If the type does not have a well-defined memory layout, this is null. - byte_offset: ?usize, - /// Whether the `root_val` could be mutated by further + /// The Value and Type corresponding to the pointee of the provided pointer. + /// If a direct dereference is not possible, this is null. + pointee: ?TypedValue, + /// The largest parent Value containing `pointee` and having a well-defined memory layout. + /// This is used for bitcasting, if direct dereferencing failed (i.e. `pointee` is null). + parent: ?TypedValueAndOffset, + /// Whether the `pointee` could be mutated by further /// semantic analysis and a copy must be performed. is_mutable: bool, + /// If the root decl could not be used as `parent`, this is the type that + /// caused that by not having a well-defined layout + ty_without_well_defined_layout: ?Type, }; const ComptimePtrLoadError = CompileError || error{ RuntimeLoad, }; +/// If `maybe_array_ty` is provided, it will be used to directly dereference an +/// .elem_ptr of type T to a value of [N]T, if necessary. fn beginComptimePtrLoad( sema: *Sema, block: *Block, src: LazySrcLoc, ptr_val: Value, + maybe_array_ty: ?Type, ) ComptimePtrLoadError!ComptimePtrLoadKit { const target = sema.mod.getTarget(); - switch (ptr_val.tag()) { - .decl_ref => { - const decl = ptr_val.castTag(.decl_ref).?.data; - const decl_val = try decl.value(); - if (decl_val.tag() == .variable) return error.RuntimeLoad; - return ComptimePtrLoadKit{ - .root_val = decl_val, - .val = decl_val, - .ty = decl.ty, - .byte_offset = 0, - .is_mutable = false, + var deref: ComptimePtrLoadKit = switch (ptr_val.tag()) { + .decl_ref, + .decl_ref_mut, + => blk: { + const decl_index = switch (ptr_val.tag()) { + .decl_ref => ptr_val.castTag(.decl_ref).?.data, + .decl_ref_mut => ptr_val.castTag(.decl_ref_mut).?.data.decl_index, + else => unreachable, }; - }, - .decl_ref_mut => { - const decl = ptr_val.castTag(.decl_ref_mut).?.data.decl; - const decl_val = try decl.value(); - if (decl_val.tag() == .variable) return error.RuntimeLoad; - return ComptimePtrLoadKit{ - .root_val = decl_val, - .val = decl_val, - .ty = decl.ty, - .byte_offset = 0, - .is_mutable = true, + const is_mutable = ptr_val.tag() == .decl_ref_mut; + const decl = sema.mod.declPtr(decl_index); + const decl_tv = try decl.typedValue(); + if (decl_tv.val.tag() == .variable) return error.RuntimeLoad; + + const layout_defined = decl.ty.hasWellDefinedLayout(); + break :blk ComptimePtrLoadKit{ + .parent = if (layout_defined) .{ .tv = decl_tv, .byte_offset = 0 } else null, + .pointee = decl_tv, + .is_mutable = is_mutable, + .ty_without_well_defined_layout = if (!layout_defined) decl.ty else null, }; }, - .elem_ptr => { + + .elem_ptr => blk: { const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; - const parent = try beginComptimePtrLoad(sema, block, src, elem_ptr.array_ptr); - switch (parent.ty.zigTypeTag()) { - .Array, .Vector => { - const check_len = parent.ty.arrayLenIncludingSentinel(); - if (elem_ptr.index >= check_len) { - // TODO have the parent include the decl so we can say "declared here" - return sema.fail(block, src, "comptime load of index {d} out of bounds of array length {d}", .{ - elem_ptr.index, check_len, - }); + const elem_ty = elem_ptr.elem_ty; + var deref = try beginComptimePtrLoad(sema, block, src, elem_ptr.array_ptr, null); + + // This code assumes that elem_ptrs have been "flattened" in order for direct dereference + // to succeed, meaning that elem ptrs of the same elem_ty are coalesced. Here we check that + // our parent is not an elem_ptr with the same elem_ty, since that would be "unflattened" + if (elem_ptr.array_ptr.castTag(.elem_ptr)) |parent_elem_ptr| { + assert(!(parent_elem_ptr.data.elem_ty.eql(elem_ty, sema.mod))); + } + + if (elem_ptr.index != 0) { + if (elem_ty.hasWellDefinedLayout()) { + if (deref.parent) |*parent| { + // Update the byte offset (in-place) + const elem_size = try sema.typeAbiSize(block, src, elem_ty); + const offset = parent.byte_offset + elem_size * elem_ptr.index; + parent.byte_offset = try sema.usizeCast(block, src, offset); } - const elem_ty = parent.ty.childType(); - const byte_offset: ?usize = bo: { - if (try sema.typeRequiresComptime(block, src, elem_ty)) { - break :bo null; - } else { - if (parent.byte_offset) |off| { - try sema.resolveTypeLayout(block, src, elem_ty); - const elem_size = elem_ty.abiSize(target); - break :bo try sema.usizeCast(block, src, off + elem_size * elem_ptr.index); - } else { - break :bo null; - } - } - }; - return ComptimePtrLoadKit{ - .root_val = parent.root_val, - .val = try parent.val.elemValue(sema.arena, elem_ptr.index), + } else { + deref.parent = null; + deref.ty_without_well_defined_layout = elem_ty; + } + } + + // If we're loading an elem_ptr that was derived from a different type + // than the true type of the underlying decl, we cannot deref directly + const ty_matches = if (deref.pointee != null and deref.pointee.?.ty.isArrayOrVector()) x: { + const deref_elem_ty = deref.pointee.?.ty.childType(); + break :x (try sema.coerceInMemoryAllowed(block, deref_elem_ty, elem_ty, false, target, src, src)) == .ok or + (try sema.coerceInMemoryAllowed(block, elem_ty, deref_elem_ty, false, target, src, src)) == .ok; + } else false; + if (!ty_matches) { + deref.pointee = null; + break :blk deref; + } + + var array_tv = deref.pointee.?; + const check_len = array_tv.ty.arrayLenIncludingSentinel(); + if (maybe_array_ty) |load_ty| { + // It's possible that we're loading a [N]T, in which case we'd like to slice + // the pointee array directly from our parent array. + if (load_ty.isArrayOrVector() and load_ty.childType().eql(elem_ty, sema.mod)) { + const N = try sema.usizeCast(block, src, load_ty.arrayLenIncludingSentinel()); + deref.pointee = if (elem_ptr.index + N <= check_len) TypedValue{ + .ty = try Type.array(sema.arena, N, null, elem_ty, sema.mod), + .val = try array_tv.val.sliceArray(sema.mod, sema.arena, elem_ptr.index, elem_ptr.index + N), + } else null; + break :blk deref; + } + } + + if (elem_ptr.index >= check_len) { + deref.pointee = null; + break :blk deref; + } + if (elem_ptr.index == check_len - 1) { + if (array_tv.ty.sentinel()) |sent| { + deref.pointee = TypedValue{ .ty = elem_ty, - .byte_offset = byte_offset, - .is_mutable = parent.is_mutable, - }; - }, - else => { - if (elem_ptr.index != 0) { - // TODO have the parent include the decl so we can say "declared here" - return sema.fail(block, src, "out of bounds comptime load of index {d}", .{ - elem_ptr.index, - }); - } - return ComptimePtrLoadKit{ - .root_val = parent.root_val, - .val = parent.val, - .ty = parent.ty, - .byte_offset = parent.byte_offset, - .is_mutable = parent.is_mutable, + .val = sent, }; - }, + break :blk deref; + } } + deref.pointee = TypedValue{ + .ty = elem_ty, + .val = try array_tv.val.elemValue(sema.mod, sema.arena, elem_ptr.index), + }; + break :blk deref; }, - .field_ptr => { + + .field_ptr => blk: { const field_ptr = ptr_val.castTag(.field_ptr).?.data; - const parent = try beginComptimePtrLoad(sema, block, src, field_ptr.container_ptr); const field_index = @intCast(u32, field_ptr.field_index); - const byte_offset: ?usize = bo: { - if (try sema.typeRequiresComptime(block, src, parent.ty)) { - break :bo null; - } else { - if (parent.byte_offset) |off| { - try sema.resolveTypeLayout(block, src, parent.ty); - const field_offset = parent.ty.structFieldOffset(field_index, target); - break :bo try sema.usizeCast(block, src, off + field_offset); - } else { - break :bo null; - } + var deref = try beginComptimePtrLoad(sema, block, src, field_ptr.container_ptr, field_ptr.container_ty); + + if (field_ptr.container_ty.hasWellDefinedLayout()) { + const struct_ty = field_ptr.container_ty.castTag(.@"struct"); + if (struct_ty != null and struct_ty.?.data.layout == .Packed) { + // packed structs are not byte addressable + deref.parent = null; + } else if (deref.parent) |*parent| { + // Update the byte offset (in-place) + try sema.resolveTypeLayout(block, src, field_ptr.container_ty); + const field_offset = field_ptr.container_ty.structFieldOffset(field_index, target); + parent.byte_offset = try sema.usizeCast(block, src, parent.byte_offset + field_offset); } - }; - return ComptimePtrLoadKit{ - .root_val = parent.root_val, - .val = try parent.val.fieldValue(sema.arena, field_index), - .ty = parent.ty.structFieldType(field_index), - .byte_offset = byte_offset, - .is_mutable = parent.is_mutable, - }; - }, - .eu_payload_ptr => { - const err_union_ptr = ptr_val.castTag(.eu_payload_ptr).?.data; - const parent = try beginComptimePtrLoad(sema, block, src, err_union_ptr); - return ComptimePtrLoadKit{ - .root_val = parent.root_val, - .val = parent.val.castTag(.eu_payload).?.data, - .ty = parent.ty.errorUnionPayload(), - .byte_offset = null, - .is_mutable = parent.is_mutable, - }; + } else { + deref.parent = null; + deref.ty_without_well_defined_layout = field_ptr.container_ty; + } + + const tv = &(deref.pointee orelse { + deref.pointee = null; + break :blk deref; + }); + const coerce_in_mem_ok = + (try sema.coerceInMemoryAllowed(block, field_ptr.container_ty, tv.ty, false, target, src, src)) == .ok or + (try sema.coerceInMemoryAllowed(block, tv.ty, field_ptr.container_ty, false, target, src, src)) == .ok; + if (!coerce_in_mem_ok) { + deref.pointee = null; + break :blk deref; + } + + if (field_ptr.container_ty.isSlice()) { + const slice_val = tv.val.castTag(.slice).?.data; + deref.pointee = switch (field_index) { + Value.Payload.Slice.ptr_index => TypedValue{ + .ty = field_ptr.container_ty.slicePtrFieldType(try sema.arena.create(Type.SlicePtrFieldTypeBuffer)), + .val = slice_val.ptr, + }, + Value.Payload.Slice.len_index => TypedValue{ + .ty = Type.usize, + .val = slice_val.len, + }, + else => unreachable, + }; + } else { + const field_ty = field_ptr.container_ty.structFieldType(field_index); + deref.pointee = TypedValue{ + .ty = field_ty, + .val = tv.val.fieldValue(tv.ty, field_index), + }; + } + break :blk deref; }, - .opt_payload_ptr => { - const opt_ptr = ptr_val.castTag(.opt_payload_ptr).?.data; - const parent = try beginComptimePtrLoad(sema, block, src, opt_ptr); - return ComptimePtrLoadKit{ - .root_val = parent.root_val, - .val = parent.val.castTag(.opt_payload).?.data, - .ty = try parent.ty.optionalChildAlloc(sema.arena), - .byte_offset = null, - .is_mutable = parent.is_mutable, + + .opt_payload_ptr, + .eu_payload_ptr, + => blk: { + const payload_ptr = ptr_val.cast(Value.Payload.PayloadPtr).?.data; + const payload_ty = switch (ptr_val.tag()) { + .eu_payload_ptr => payload_ptr.container_ty.errorUnionPayload(), + .opt_payload_ptr => try payload_ptr.container_ty.optionalChildAlloc(sema.arena), + else => unreachable, }; + var deref = try beginComptimePtrLoad(sema, block, src, payload_ptr.container_ptr, payload_ptr.container_ty); + + // eu_payload_ptr and opt_payload_ptr never have a well-defined layout + if (deref.parent != null) { + deref.parent = null; + deref.ty_without_well_defined_layout = payload_ptr.container_ty; + } + + if (deref.pointee) |*tv| { + const coerce_in_mem_ok = + (try sema.coerceInMemoryAllowed(block, payload_ptr.container_ty, tv.ty, false, target, src, src)) == .ok or + (try sema.coerceInMemoryAllowed(block, tv.ty, payload_ptr.container_ty, false, target, src, src)) == .ok; + if (coerce_in_mem_ok) { + const payload_val = switch (ptr_val.tag()) { + .eu_payload_ptr => tv.val.castTag(.eu_payload).?.data, + .opt_payload_ptr => tv.val.castTag(.opt_payload).?.data, + else => unreachable, + }; + tv.* = TypedValue{ .ty = payload_ty, .val = payload_val }; + break :blk deref; + } + } + deref.pointee = null; + break :blk deref; }, .zero, @@ -15087,44 +21695,69 @@ fn beginComptimePtrLoad( => return error.RuntimeLoad, else => unreachable, + }; + + if (deref.pointee) |tv| { + if (deref.parent == null and tv.ty.hasWellDefinedLayout()) { + deref.parent = .{ .tv = tv, .byte_offset = 0 }; + } } + return deref; } fn bitCast( sema: *Sema, block: *Block, - dest_ty: Type, + dest_ty_unresolved: Type, inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - // TODO validate the type size and other compile errors + const dest_ty = try sema.resolveTypeFields(block, inst_src, dest_ty_unresolved); + try sema.resolveTypeLayout(block, inst_src, dest_ty); + + const old_ty = try sema.resolveTypeFields(block, inst_src, sema.typeOf(inst)); + try sema.resolveTypeLayout(block, inst_src, old_ty); + + const target = sema.mod.getTarget(); + const dest_bits = dest_ty.bitSize(target); + const old_bits = old_ty.bitSize(target); + + if (old_bits != dest_bits) { + return sema.fail(block, inst_src, "@bitCast size mismatch: destination type '{}' has {d} bits but source type '{}' has {d} bits", .{ + dest_ty.fmt(sema.mod), + dest_bits, + old_ty.fmt(sema.mod), + old_bits, + }); + } + if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { - const old_ty = sema.typeOf(inst); - const result_val = try sema.bitCastVal(block, inst_src, val, old_ty, dest_ty); + const result_val = try sema.bitCastVal(block, inst_src, val, old_ty, dest_ty, 0); return sema.addConstant(dest_ty, result_val); } try sema.requireRuntimeBlock(block, inst_src); return block.addBitCast(dest_ty, inst); } -pub fn bitCastVal( +fn bitCastVal( sema: *Sema, block: *Block, src: LazySrcLoc, val: Value, old_ty: Type, new_ty: Type, + buffer_offset: usize, ) !Value { - if (old_ty.eql(new_ty)) return val; + const target = sema.mod.getTarget(); + if (old_ty.eql(new_ty, sema.mod)) return val; // For types with well-defined memory layouts, we serialize them a byte buffer, // then deserialize to the new type. - const target = sema.mod.getTarget(); const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(target)); const buffer = try sema.gpa.alloc(u8, abi_size); defer sema.gpa.free(buffer); - val.writeToMemory(old_ty, target, buffer); - return Value.readFromMemory(new_ty, target, buffer, sema.arena); + val.writeToMemory(old_ty, sema.mod, buffer); + return Value.readFromMemory(new_ty, sema.mod, buffer[buffer_offset..], sema.arena); } fn coerceArrayPtrToSlice( @@ -15134,7 +21767,7 @@ fn coerceArrayPtrToSlice( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { - if (try sema.resolveDefinedValue(block, inst_src, inst)) |val| { + if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { const ptr_array_ty = sema.typeOf(inst); const array_ty = ptr_array_ty.childType(); const slice_val = try Value.Tag.slice.create(sema.arena, .{ @@ -15154,6 +21787,7 @@ fn coerceCompatiblePtrs( inst: Air.Inst.Ref, inst_src: LazySrcLoc, ) !Air.Inst.Ref { + // TODO check const/volatile/alignment if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { // The comptime Value representation is compatible with both types. return sema.addConstant(dest_ty, val); @@ -15174,45 +21808,46 @@ fn coerceEnumToUnion( const tag_ty = union_ty.unionTagType() orelse { const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "expected {}, found {}", .{ - union_ty, inst_ty, + const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ + union_ty.fmt(sema.mod), inst_ty.fmt(sema.mod), }); errdefer msg.destroy(sema.gpa); try sema.errNote(block, union_ty_src, msg, "cannot coerce enum to untagged union", .{}); try sema.addDeclaredHereNote(msg, union_ty); break :msg msg; }; - return sema.failWithOwnedErrorMsg(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 union_obj = union_ty.cast(Type.Payload.Union).?.data; - const field_index = union_obj.tag_ty.enumTagFieldIndex(val) orelse { + const field_index = union_obj.tag_ty.enumTagFieldIndex(val, sema.mod) orelse { const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "union {} has no tag with value {}", .{ - union_ty, val, + const msg = try sema.errMsg(block, inst_src, "union '{}' has no tag with value '{}'", .{ + union_ty.fmt(sema.mod), val.fmtValue(tag_ty, sema.mod), }); errdefer msg.destroy(sema.gpa); try sema.addDeclaredHereNote(msg, union_ty); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }; const field = union_obj.fields.values()[field_index]; const field_ty = try sema.resolveTypeFields(block, inst_src, field.ty); const opv = (try sema.typeHasOnePossibleValue(block, inst_src, field_ty)) orelse { - // TODO resolve the field names and include in the error message, - // also instead of 'union declared here' make it 'field "foo" declared here'. const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "coercion to union {} must initialize {} field", .{ - union_ty, field_ty, + const field_name = union_obj.fields.keys()[field_index]; + const msg = try sema.errMsg(block, inst_src, "coercion from enum '{}' to union '{}' must initialize '{}' field '{s}'", .{ + inst_ty.fmt(sema.mod), union_ty.fmt(sema.mod), field_ty.fmt(sema.mod), field_name, }); errdefer msg.destroy(sema.gpa); + + try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' declared here", .{field_name}); try sema.addDeclaredHereNote(msg, union_ty); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); }; return sema.addConstant(union_ty, try Value.Tag.@"union".create(sema.arena, .{ @@ -15225,14 +21860,14 @@ fn coerceEnumToUnion( if (tag_ty.isNonexhaustiveEnum()) { const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "runtime coercion to union {} from non-exhaustive enum", .{ - union_ty, + const msg = try sema.errMsg(block, inst_src, "runtime coercion to union '{}' from non-exhaustive enum", .{ + union_ty.fmt(sema.mod), }); errdefer msg.destroy(sema.gpa); try sema.addDeclaredHereNote(msg, tag_ty); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } // If the union has all fields 0 bits, the union value is just the enum value. @@ -15240,17 +21875,90 @@ fn coerceEnumToUnion( return block.addBitCast(union_ty, enum_tag); } - // TODO resolve the field names and add a hint that says "field 'foo' has type 'bar'" - // instead of the "union declared here" hint const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "runtime coercion to union {} which has non-void fields", .{ - union_ty, - }); + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + const msg = try sema.errMsg( + block, + inst_src, + "runtime coercion from enum '{}' to union '{}' which has non-void fields", + .{ tag_ty.fmt(sema.mod), union_ty.fmt(sema.mod) }, + ); errdefer msg.destroy(sema.gpa); + + var it = union_obj.fields.iterator(); + var field_index: usize = 0; + while (it.next()) |field| { + const field_name = field.key_ptr.*; + const field_ty = field.value_ptr.ty; + try sema.addFieldErrNote(block, union_ty, field_index, msg, "field '{s}' has type '{}'", .{ field_name, field_ty.fmt(sema.mod) }); + field_index += 1; + } try sema.addDeclaredHereNote(msg, union_ty); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); +} + +fn coerceAnonStructToUnion( + sema: *Sema, + block: *Block, + union_ty: Type, + union_ty_src: LazySrcLoc, + inst: Air.Inst.Ref, + inst_src: LazySrcLoc, +) !Air.Inst.Ref { + const inst_ty = sema.typeOf(inst); + const anon_struct = inst_ty.castTag(.anon_struct).?.data; + if (anon_struct.types.len != 1) { + const msg = msg: { + const msg = try sema.errMsg( + block, + inst_src, + "cannot initialize multiple union fields at once, unions can only have one active field", + .{}, + ); + errdefer msg.destroy(sema.gpa); + + // TODO add notes for where the anon struct was created to point out + // the extra fields. + + try sema.addDeclaredHereNote(msg, union_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + + const field_name = anon_struct.names[0]; + const init = try sema.structFieldVal(block, inst_src, inst, field_name, inst_src, inst_ty); + return sema.unionInit(block, init, inst_src, union_ty, union_ty_src, field_name, inst_src); +} + +fn coerceAnonStructToUnionPtrs( + sema: *Sema, + block: *Block, + ptr_union_ty: Type, + union_ty_src: LazySrcLoc, + ptr_anon_struct: Air.Inst.Ref, + anon_struct_src: LazySrcLoc, +) !Air.Inst.Ref { + const union_ty = ptr_union_ty.childType(); + const anon_struct = try sema.analyzeLoad(block, anon_struct_src, ptr_anon_struct, anon_struct_src); + const union_inst = try sema.coerceAnonStructToUnion(block, union_ty, union_ty_src, anon_struct, anon_struct_src); + return sema.analyzeRef(block, union_ty_src, union_inst); +} + +fn coerceAnonStructToStructPtrs( + sema: *Sema, + block: *Block, + ptr_struct_ty: Type, + struct_ty_src: LazySrcLoc, + ptr_anon_struct: Air.Inst.Ref, + anon_struct_src: LazySrcLoc, +) !Air.Inst.Ref { + const struct_ty = ptr_struct_ty.childType(); + const anon_struct = try sema.analyzeLoad(block, anon_struct_src, ptr_anon_struct, anon_struct_src); + const struct_inst = try sema.coerceTupleToStruct(block, struct_ty, struct_ty_src, anon_struct, anon_struct_src); + return sema.analyzeRef(block, struct_ty_src, struct_inst); } /// If the lengths match, coerces element-wise. @@ -15265,21 +21973,21 @@ fn coerceArrayLike( const inst_ty = sema.typeOf(inst); const inst_len = inst_ty.arrayLen(); const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen()); + const target = sema.mod.getTarget(); if (dest_len != inst_len) { const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "expected {}, found {}", .{ - dest_ty, inst_ty, + const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ + dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod), }); errdefer msg.destroy(sema.gpa); try sema.errNote(block, dest_ty_src, msg, "destination has length {d}", .{dest_len}); try sema.errNote(block, inst_src, msg, "source has length {d}", .{inst_len}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } - const target = sema.mod.getTarget(); const dest_elem_ty = dest_ty.childType(); const inst_elem_ty = inst_ty.childType(); const in_memory_result = try sema.coerceInMemoryAllowed(block, dest_elem_ty, inst_elem_ty, false, target, dest_ty_src, inst_src); @@ -15302,7 +22010,7 @@ fn coerceArrayLike( try Value.Tag.int_u64.create(sema.arena, i), ); const elem_src = inst_src; // TODO better source location - const elem_ref = try elemValArray(sema, block, inst, index_ref, inst_src, elem_src); + const elem_ref = try elemValArray(sema, block, inst_src, inst, elem_src, index_ref); const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src); element_refs[i] = coerced; if (runtime_src == null) { @@ -15316,12 +22024,12 @@ fn coerceArrayLike( if (runtime_src) |rs| { try sema.requireRuntimeBlock(block, rs); - return block.addVectorInit(dest_ty, element_refs); + return block.addAggregateInit(dest_ty, element_refs); } return sema.addConstant( dest_ty, - try Value.Tag.array.create(sema.arena, element_vals), + try Value.Tag.aggregate.create(sema.arena, element_vals), ); } @@ -15336,30 +22044,36 @@ fn coerceTupleToArray( ) !Air.Inst.Ref { const inst_ty = sema.typeOf(inst); const inst_len = inst_ty.arrayLen(); - const dest_len = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLen()); + const dest_len = dest_ty.arrayLen(); if (dest_len != inst_len) { const msg = msg: { - const msg = try sema.errMsg(block, inst_src, "expected {}, found {}", .{ - dest_ty, inst_ty, + const msg = try sema.errMsg(block, inst_src, "expected type '{}', found '{}'", .{ + dest_ty.fmt(sema.mod), inst_ty.fmt(sema.mod), }); errdefer msg.destroy(sema.gpa); try sema.errNote(block, dest_ty_src, msg, "destination has length {d}", .{dest_len}); try sema.errNote(block, inst_src, msg, "source has length {d}", .{inst_len}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } - const element_vals = try sema.arena.alloc(Value, dest_len); - const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_len); + const dest_elems = try sema.usizeCast(block, dest_ty_src, dest_ty.arrayLenIncludingSentinel()); + const element_vals = try sema.arena.alloc(Value, dest_elems); + const element_refs = try sema.arena.alloc(Air.Inst.Ref, dest_elems); const dest_elem_ty = dest_ty.childType(); var runtime_src: ?LazySrcLoc = null; for (element_vals) |*elem, i_usize| { const i = @intCast(u32, i_usize); + if (i_usize == inst_len) { + elem.* = dest_ty.sentinel().?; + element_refs[i] = try sema.addConstant(dest_elem_ty, elem.*); + break; + } const elem_src = inst_src; // TODO better source location - const elem_ref = try tupleField(sema, block, inst, i, inst_src, elem_src); + const elem_ref = try tupleField(sema, block, inst_src, inst, elem_src, i); const coerced = try sema.coerce(block, dest_elem_ty, elem_ref, elem_src); element_refs[i] = coerced; if (runtime_src == null) { @@ -15373,12 +22087,143 @@ fn coerceTupleToArray( if (runtime_src) |rs| { try sema.requireRuntimeBlock(block, rs); - return block.addVectorInit(dest_ty, element_refs); + return block.addAggregateInit(dest_ty, element_refs); } return sema.addConstant( dest_ty, - try Value.Tag.array.create(sema.arena, element_vals), + try Value.Tag.aggregate.create(sema.arena, element_vals), + ); +} + +/// 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 tuple_ty = sema.typeOf(ptr_tuple).childType(); + const tuple = try sema.analyzeLoad(block, tuple_src, ptr_tuple, tuple_src); + const slice_info = slice_ty.ptrInfo().data; + const array_ty = try Type.array(sema.arena, tuple_ty.structFieldCount(), slice_info.sentinel, slice_info.pointee_type, sema.mod); + const array_inst = try sema.coerceTupleToArray(block, array_ty, slice_ty_src, tuple, tuple_src); + if (slice_info.@"align" != 0) { + 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 tuple = try sema.analyzeLoad(block, tuple_src, ptr_tuple, tuple_src); + const ptr_info = ptr_array_ty.ptrInfo().data; + const array_ty = ptr_info.pointee_type; + const array_inst = try sema.coerceTupleToArray(block, array_ty, array_ty_src, tuple, tuple_src); + if (ptr_info.@"align" != 0) { + 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; +} + +/// Handles both tuples and anon struct literals. Coerces field-wise. Reports +/// errors for both extra fields and missing fields. +fn coerceTupleToStruct( + sema: *Sema, + block: *Block, + dest_ty: Type, + dest_ty_src: LazySrcLoc, + inst: Air.Inst.Ref, + inst_src: LazySrcLoc, +) !Air.Inst.Ref { + const struct_ty = try sema.resolveTypeFields(block, dest_ty_src, dest_ty); + + if (struct_ty.isTupleOrAnonStruct()) { + return sema.fail(block, dest_ty_src, "TODO: implement coercion from tuples to tuples", .{}); + } + + const fields = struct_ty.structFields(); + const field_vals = try sema.arena.alloc(Value, fields.count()); + const field_refs = try sema.arena.alloc(Air.Inst.Ref, field_vals.len); + mem.set(Air.Inst.Ref, field_refs, .none); + + const inst_ty = sema.typeOf(inst); + const tuple = inst_ty.tupleFields(); + var runtime_src: ?LazySrcLoc = null; + for (tuple.types) |_, i_usize| { + const i = @intCast(u32, i_usize); + const field_src = inst_src; // TODO better source location + const field_name = if (inst_ty.castTag(.anon_struct)) |payload| + payload.data.names[i] + else + try std.fmt.allocPrint(sema.arena, "{d}", .{i}); + const field_index = try sema.structFieldIndex(block, struct_ty, field_name, field_src); + const field = fields.values()[field_index]; + if (field.is_comptime) { + return sema.fail(block, dest_ty_src, "TODO: implement coercion from tuples to structs when one of the destination struct fields is comptime", .{}); + } + const elem_ref = try tupleField(sema, block, inst_src, inst, field_src, i); + const coerced = try sema.coerce(block, field.ty, elem_ref, field_src); + field_refs[field_index] = coerced; + if (runtime_src == null) { + if (try sema.resolveMaybeUndefVal(block, field_src, coerced)) |field_val| { + field_vals[field_index] = field_val; + } else { + runtime_src = field_src; + } + } + } + + // Populate default field values and report errors for missing fields. + var root_msg: ?*Module.ErrorMsg = null; + + for (field_refs) |*field_ref, i| { + if (field_ref.* != .none) continue; + + const field_name = fields.keys()[i]; + const field = fields.values()[i]; + const field_src = inst_src; // TODO better source location + if (field.default_val.tag() == .unreachable_value) { + const template = "missing struct field: {s}"; + const args = .{field_name}; + if (root_msg) |msg| { + try sema.errNote(block, field_src, msg, template, args); + } else { + root_msg = try sema.errMsg(block, field_src, template, args); + } + continue; + } + if (runtime_src == null) { + field_vals[i] = field.default_val; + } else { + field_ref.* = try sema.addConstant(field.ty, field.default_val); + } + } + + if (root_msg) |msg| { + try sema.addDeclaredHereNote(msg, struct_ty); + return sema.failWithOwnedErrorMsg(block, msg); + } + + if (runtime_src) |rs| { + try sema.requireRuntimeBlock(block, rs); + return block.addAggregateInit(struct_ty, field_refs); + } + + return sema.addConstant( + struct_ty, + try Value.Tag.aggregate.create(sema.arena, field_vals), ); } @@ -15386,23 +22231,34 @@ fn analyzeDeclVal( sema: *Sema, block: *Block, src: LazySrcLoc, - decl: *Decl, + decl_index: Decl.Index, ) CompileError!Air.Inst.Ref { - if (sema.decl_val_table.get(decl)) |result| { + if (sema.decl_val_table.get(decl_index)) |result| { return result; } - const decl_ref = try sema.analyzeDeclRef(decl); + const decl_ref = try sema.analyzeDeclRef(decl_index); const result = try sema.analyzeLoad(block, src, decl_ref, src); if (Air.refToIndex(result)) |index| { - if (sema.air_instructions.items(.tag)[index] == .constant) { - try sema.decl_val_table.put(sema.gpa, decl, result); + if (sema.air_instructions.items(.tag)[index] == .constant and !block.is_typeof) { + try sema.decl_val_table.put(sema.gpa, decl_index, result); } } return result; } -fn ensureDeclAnalyzed(sema: *Sema, decl: *Decl) CompileError!void { - sema.mod.ensureDeclAnalyzed(decl) catch |err| { +fn ensureDeclAnalyzed(sema: *Sema, decl_index: Decl.Index) CompileError!void { + sema.mod.ensureDeclAnalyzed(decl_index) catch |err| { + if (sema.owner_func) |owner_func| { + owner_func.state = .dependency_failure; + } else { + sema.owner_decl.analysis = .dependency_failure; + } + return err; + }; +} + +fn ensureFuncBodyAnalyzed(sema: *Sema, func: *Module.Fn) CompileError!void { + sema.mod.ensureFuncBodyAnalyzed(func) catch |err| { if (sema.owner_func) |owner_func| { owner_func.state = .dependency_failure; } else { @@ -15412,32 +22268,48 @@ fn ensureDeclAnalyzed(sema: *Sema, decl: *Decl) CompileError!void { }; } -fn analyzeDeclRef(sema: *Sema, decl: *Decl) CompileError!Air.Inst.Ref { - try sema.mod.declareDeclDependency(sema.owner_decl, decl); - try sema.ensureDeclAnalyzed(decl); +fn refValue(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, val: Value) !Value { + var anon_decl = try block.startAnonDecl(src); + defer anon_decl.deinit(); + const decl = try anon_decl.finish( + try ty.copy(anon_decl.arena()), + try val.copy(anon_decl.arena()), + 0, // default alignment + ); + try sema.mod.declareDeclDependency(sema.owner_decl_index, decl); + return try Value.Tag.decl_ref.create(sema.arena, decl); +} + +fn optRefValue(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, opt_val: ?Value) !Value { + const val = opt_val orelse return Value.@"null"; + const ptr_val = try refValue(sema, block, src, ty, val); + const result = try Value.Tag.opt_payload.create(sema.arena, ptr_val); + return result; +} +fn analyzeDeclRef(sema: *Sema, decl_index: Decl.Index) CompileError!Air.Inst.Ref { + try sema.mod.declareDeclDependency(sema.owner_decl_index, decl_index); + try sema.ensureDeclAnalyzed(decl_index); + + const decl = sema.mod.declPtr(decl_index); const decl_tv = try decl.typedValue(); if (decl_tv.val.castTag(.variable)) |payload| { const variable = payload.data; - const alignment: u32 = if (decl.align_val.tag() == .null_value) - 0 - else - @intCast(u32, decl.align_val.toUnsignedInt()); - const ty = try Type.ptr(sema.arena, .{ + const ty = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = decl_tv.ty, .mutable = variable.is_mutable, .@"addrspace" = decl.@"addrspace", - .@"align" = alignment, + .@"align" = decl.@"align", }); - return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl)); + return sema.addConstant(ty, try Value.Tag.decl_ref.create(sema.arena, decl_index)); } return sema.addConstant( - try Type.ptr(sema.arena, .{ + try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = decl_tv.ty, .mutable = false, .@"addrspace" = decl.@"addrspace", }), - try Value.Tag.decl_ref.create(sema.arena, decl), + try Value.Tag.decl_ref.create(sema.arena, decl_index), ); } @@ -15455,17 +22327,18 @@ fn analyzeRef( return sema.analyzeDeclRef(try anon_decl.finish( try operand_ty.copy(anon_decl.arena()), try val.copy(anon_decl.arena()), + 0, // default alignment )); } try sema.requireRuntimeBlock(block, src); const address_space = target_util.defaultAddressSpace(sema.mod.getTarget(), .local); - const ptr_type = try Type.ptr(sema.arena, .{ + const ptr_type = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = operand_ty, .mutable = false, .@"addrspace" = address_space, }); - const mut_ptr_type = try Type.ptr(sema.arena, .{ + const mut_ptr_type = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = operand_ty, .@"addrspace" = address_space, }); @@ -15486,14 +22359,25 @@ fn analyzeLoad( const ptr_ty = sema.typeOf(ptr); const elem_ty = switch (ptr_ty.zigTypeTag()) { .Pointer => ptr_ty.childType(), - else => return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ty}), + else => return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ty.fmt(sema.mod)}), }; + + if (try sema.typeHasOnePossibleValue(block, src, elem_ty)) |opv| { + return sema.addConstant(elem_ty, opv); + } + if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| { if (try sema.pointerDeref(block, ptr_src, ptr_val, ptr_ty)) |elem_val| { return sema.addConstant(elem_ty, elem_val); } + if (block.is_typeof) { + return sema.addConstUndef(elem_ty); + } } + const valid_rt = try sema.validateRunTimeType(block, src, elem_ty, false); + if (!valid_rt) return sema.failWithNeededComptime(block, src); + try sema.requireRuntimeBlock(block, src); return block.addTyOp(.load, elem_ty, ptr); } @@ -15501,19 +22385,17 @@ fn analyzeLoad( fn analyzeSlicePtr( sema: *Sema, block: *Block, - src: LazySrcLoc, + slice_src: LazySrcLoc, slice: Air.Inst.Ref, slice_ty: Type, - slice_src: LazySrcLoc, ) CompileError!Air.Inst.Ref { const buf = try sema.arena.create(Type.SlicePtrFieldTypeBuffer); const result_ty = slice_ty.slicePtrFieldType(buf); - if (try sema.resolveMaybeUndefVal(block, slice_src, slice)) |val| { if (val.isUndef()) return sema.addConstUndef(result_ty); return sema.addConstant(result_ty, val.slicePtr()); } - try sema.requireRuntimeBlock(block, src); + try sema.requireRuntimeBlock(block, slice_src); return block.addTyOp(.slice_ptr, result_ty, slice); } @@ -15527,7 +22409,7 @@ fn analyzeSliceLen( if (slice_val.isUndef()) { return sema.addConstUndef(Type.usize); } - return sema.addIntUnsigned(Type.usize, slice_val.sliceLen()); + return sema.addIntUnsigned(Type.usize, slice_val.sliceLen(sema.mod)); } try sema.requireRuntimeBlock(block, src); return block.addTyOp(.slice_len, Type.usize, slice_inst); @@ -15558,7 +22440,7 @@ fn analyzeIsNull( return block.addUnOp(air_tag, operand); } -fn analyzeIsNonErr( +fn analyzeIsNonErrComptimeOnly( sema: *Sema, block: *Block, src: LazySrcLoc, @@ -15569,10 +22451,62 @@ fn analyzeIsNonErr( if (ot != .ErrorSet and ot != .ErrorUnion) return Air.Inst.Ref.bool_true; if (ot == .ErrorSet) return Air.Inst.Ref.bool_false; assert(ot == .ErrorUnion); - const result_ty = Type.bool; - if (try sema.resolveMaybeUndefVal(block, src, operand)) |err_union| { + + if (Air.refToIndex(operand)) |operand_inst| { + const air_tags = sema.air_instructions.items(.tag); + if (air_tags[operand_inst] == .wrap_errunion_payload) { + return Air.Inst.Ref.bool_true; + } + } + + const maybe_operand_val = try sema.resolveMaybeUndefVal(block, src, 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 = operand_ty.errorUnionSet(); + switch (set_ty.tag()) { + .anyerror => {}, + .error_set_inferred => 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. + const ies = set_ty.castTag(.error_set_inferred).?.data; + if (ies.is_anyerror) break :blk; + if (ies.errors.count() != 0) break :blk; + if (maybe_operand_val == null) { + // Try to avoid resolving inferred error set if possible. + if (ies.errors.count() != 0) break :blk; + if (ies.is_anyerror) break :blk; + var it = ies.inferred_error_sets.keyIterator(); + while (it.next()) |other_error_set_ptr| { + const other_ies: *Module.Fn.InferredErrorSet = other_error_set_ptr.*; + if (ies == other_ies) continue; + try sema.resolveInferredErrorSet(block, src, other_ies); + if (other_ies.is_anyerror) { + ies.is_anyerror = true; + ies.is_resolved = true; + break :blk; + } + + if (other_ies.errors.count() != 0) break :blk; + } + if (ies.func == sema.owner_func) { + // We're checking the inferred errorset of the current function and none of + // its child inferred error sets contained any errors meaning that any value + // so far with this type can't contain errors either. + return Air.Inst.Ref.bool_true; + } + try sema.resolveInferredErrorSet(block, src, ies); + if (ies.is_anyerror) break :blk; + if (ies.errors.count() == 0) return Air.Inst.Ref.bool_true; + } + }, + else => if (set_ty.errorSetNames().len == 0) return Air.Inst.Ref.bool_true, + } + + if (maybe_operand_val) |err_union| { if (err_union.isUndef()) { - return sema.addConstUndef(result_ty); + return sema.addConstUndef(Type.bool); } if (err_union.getError() == null) { return Air.Inst.Ref.bool_true; @@ -15580,8 +22514,22 @@ fn analyzeIsNonErr( return Air.Inst.Ref.bool_false; } } - try sema.requireRuntimeBlock(block, src); - return block.addUnOp(.is_non_err, operand); + return Air.Inst.Ref.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); + return block.addUnOp(.is_non_err, operand); + } else { + return result; + } } fn analyzeSlice( @@ -15600,21 +22548,27 @@ fn analyzeSlice( // 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 target = sema.mod.getTarget(); const ptr_ptr_child_ty = switch (ptr_ptr_ty.zigTypeTag()) { .Pointer => ptr_ptr_ty.elemType(), - else => return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ptr_ty}), + else => return sema.fail(block, ptr_src, "expected pointer, found '{}'", .{ptr_ptr_ty.fmt(sema.mod)}), }; + const mod = sema.mod; var array_ty = ptr_ptr_child_ty; var slice_ty = ptr_ptr_ty; var ptr_or_slice = ptr_ptr; var elem_ty = ptr_ptr_child_ty.childType(); + var ptr_sentinel: ?Value = null; switch (ptr_ptr_child_ty.zigTypeTag()) { - .Array => {}, + .Array => { + ptr_sentinel = ptr_ptr_child_ty.sentinel(); + }, .Pointer => switch (ptr_ptr_child_ty.ptrSize()) { .One => { const double_child_ty = ptr_ptr_child_ty.childType(); if (double_child_ty.zigTypeTag() == .Array) { + ptr_sentinel = double_child_ty.sentinel(); ptr_or_slice = try sema.analyzeLoad(block, src, ptr_ptr, ptr_src); slice_ty = ptr_ptr_child_ty; array_ty = double_child_ty; @@ -15624,67 +22578,178 @@ fn analyzeSlice( } }, .Many, .C => { + ptr_sentinel = ptr_ptr_child_ty.sentinel(); 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(); + + if (ptr_ptr_child_ty.ptrSize() == .C) { + if (try sema.resolveDefinedValue(block, ptr_src, ptr_or_slice)) |ptr_val| { + if (ptr_val.isNull()) { + return sema.fail(block, ptr_src, "slice of null pointer", .{}); + } + } + } }, .Slice => { + ptr_sentinel = ptr_ptr_child_ty.sentinel(); 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(); }, }, - else => return sema.fail(block, ptr_src, "slice of non-array type '{}'", .{ptr_ptr_child_ty}), + else => return sema.fail(block, ptr_src, "slice of non-array type '{}'", .{ptr_ptr_child_ty.fmt(mod)}), } const ptr = if (slice_ty.isSlice()) - try sema.analyzeSlicePtr(block, src, ptr_or_slice, slice_ty, ptr_src) + try sema.analyzeSlicePtr(block, ptr_src, ptr_or_slice, slice_ty) else ptr_or_slice; const start = try sema.coerce(block, Type.usize, uncasted_start, start_src); const new_ptr = try analyzePtrArithmetic(sema, block, src, ptr, start, .ptr_add, ptr_src, start_src); + // 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 (uncasted_end_opt != .none) { - break :e try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); - } - if (array_ty.zigTypeTag() == .Array) { - break :e try sema.addConstant( - Type.usize, - try Value.Tag.int_u64.create(sema.arena, array_ty.arrayLen()), - ); + const len_val = try Value.Tag.int_u64.create(sema.arena, array_ty.arrayLen()); + + if (!end_is_len) { + const end = try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); + if (try sema.resolveMaybeUndefVal(block, end_src, end)) |end_val| { + const len_s_val = try Value.Tag.int_u64.create( + sema.arena, + array_ty.arrayLenIncludingSentinel(), + ); + if (try sema.compare(block, src, end_val, .gt, len_s_val, Type.usize)) { + const sentinel_label: []const u8 = if (array_ty.sentinel() != null) + " +1 (sentinel)" + else + ""; + + return sema.fail( + block, + end_src, + "end index {} out of bounds for array of length {}{s}", + .{ + end_val.fmtValue(Type.usize, mod), + len_val.fmtValue(Type.usize, mod), + 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, Type.usize, mod)) { + end_is_len = true; + } + } + break :e end; + } + + break :e try sema.addConstant(Type.usize, len_val); } else if (slice_ty.isSlice()) { + if (!end_is_len) { + const end = try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); + if (try sema.resolveDefinedValue(block, end_src, end)) |end_val| { + if (try sema.resolveDefinedValue(block, src, ptr_or_slice)) |slice_val| { + const has_sentinel = slice_ty.sentinel() != null; + var int_payload: Value.Payload.U64 = .{ + .base = .{ .tag = .int_u64 }, + .data = slice_val.sliceLen(mod) + @boolToInt(has_sentinel), + }; + const slice_len_val = Value.initPayload(&int_payload.base); + if (try sema.compare(block, src, end_val, .gt, slice_len_val, Type.usize)) { + const sentinel_label: []const u8 = if (has_sentinel) + " +1 (sentinel)" + else + ""; + + return sema.fail( + block, + end_src, + "end index {} out of bounds for slice of length {d}{s}", + .{ + end_val.fmtValue(Type.usize, mod), + slice_val.sliceLen(mod), + sentinel_label, + }, + ); + } + + // If the slice has a sentinel, we subtract one so that + // end_is_len is only true if it equals the length WITHOUT + // the sentinel, so we don't add a sentinel type. + if (has_sentinel) { + int_payload.data -= 1; + } + + if (end_val.eql(slice_len_val, Type.usize, mod)) { + end_is_len = true; + } + } + } + break :e end; + } break :e try sema.analyzeSliceLen(block, src, ptr_or_slice); } + if (!end_is_len) { + break :e try sema.coerce(block, Type.usize, uncasted_end_opt, end_src); + } return sema.fail(block, end_src, "slice of pointer must include end value", .{}); }; - const slice_sentinel = if (sentinel_opt != .none) blk: { - const casted = try sema.coerce(block, elem_ty, sentinel_opt, sentinel_src); - break :blk try sema.resolveConstValue(block, sentinel_src, casted); - } else null; + const sentinel = s: { + if (sentinel_opt != .none) { + const casted = try sema.coerce(block, elem_ty, sentinel_opt, sentinel_src); + break :s try sema.resolveConstValue(block, sentinel_src, casted); + } + // 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 new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src); + // requirement: start <= end + if (try sema.resolveDefinedValue(block, src, end)) |end_val| { + if (try sema.resolveDefinedValue(block, src, start)) |start_val| { + if (try sema.compare(block, src, start_val, .gt, end_val, Type.usize)) { + return sema.fail( + block, + start_src, + "start index {} is larger than end index {}", + .{ + start_val.fmtValue(Type.usize, mod), + end_val.fmtValue(Type.usize, mod), + }, + ); + } + } + } + const new_len = try sema.analyzeArithmetic(block, .sub, end, start, src, end_src, start_src); const opt_new_len_val = try sema.resolveDefinedValue(block, src, new_len); const new_ptr_ty_info = sema.typeOf(new_ptr).ptrInfo().data; const new_allowzero = new_ptr_ty_info.@"allowzero" and sema.typeOf(ptr).ptrSize() != .C; if (opt_new_len_val) |new_len_val| { - const new_len_int = new_len_val.toUnsignedInt(); + const new_len_int = new_len_val.toUnsignedInt(target); - const sentinel = if (array_ty.zigTypeTag() == .Array and new_len_int == array_ty.arrayLen()) - array_ty.sentinel() - else - slice_sentinel; - - const return_ty = try Type.ptr(sema.arena, .{ - .pointee_type = try Type.array(sema.arena, new_len_int, sentinel, elem_ty), + const return_ty = try Type.ptr(sema.arena, mod, .{ + .pointee_type = try Type.array(sema.arena, new_len_int, sentinel, elem_ty, mod), .sentinel = null, .@"align" = new_ptr_ty_info.@"align", .@"addrspace" = new_ptr_ty_info.@"addrspace", @@ -15711,9 +22776,9 @@ fn analyzeSlice( return sema.fail(block, ptr_src, "non-zero length slice of undefined pointer", .{}); } - const return_ty = try Type.ptr(sema.arena, .{ + const return_ty = try Type.ptr(sema.arena, mod, .{ .pointee_type = elem_ty, - .sentinel = slice_sentinel, + .sentinel = sentinel, .@"align" = new_ptr_ty_info.@"align", .@"addrspace" = new_ptr_ty_info.@"addrspace", .mutable = new_ptr_ty_info.mutable, @@ -15723,6 +22788,36 @@ fn analyzeSlice( }); try sema.requireRuntimeBlock(block, src); + if (block.wantSafety()) { + // requirement: slicing C ptr is non-null + if (ptr_ptr_child_ty.isCPtr()) { + const is_non_null = try sema.analyzeIsNull(block, ptr_src, ptr, true); + try sema.addSafetyCheck(block, is_non_null, .unwrap_null); + } + + // requirement: end <= len + const opt_len_inst = if (array_ty.zigTypeTag() == .Array) + try sema.addIntUnsigned(Type.usize, array_ty.arrayLenIncludingSentinel()) + else if (slice_ty.isSlice()) 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 sema.addIntUnsigned(Type.usize, slice_val.sliceLen(mod)); + } + + const slice_len_inst = try block.addTyOp(.slice_len, Type.usize, ptr_or_slice); + if (slice_ty.sentinel() == 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); + } else null; + if (opt_len_inst) |len_inst| { + try sema.panicIndexOutOfBounds(block, src, end, len_inst, .cmp_lte); + } + + // requirement: start <= end + try sema.panicIndexOutOfBounds(block, src, start, end, .cmp_lte); + } return block.addInst(.{ .tag = .slice, .data = .{ .ty_pl = .{ @@ -15740,33 +22835,34 @@ fn cmpNumeric( sema: *Sema, block: *Block, src: LazySrcLoc, - lhs: Air.Inst.Ref, - rhs: Air.Inst.Ref, + 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 lhs_ty = sema.typeOf(lhs); - const rhs_ty = sema.typeOf(rhs); + const lhs_ty = sema.typeOf(uncasted_lhs); + const rhs_ty = sema.typeOf(uncasted_rhs); assert(lhs_ty.isNumeric()); assert(rhs_ty.isNumeric()); const lhs_ty_tag = lhs_ty.zigTypeTag(); const rhs_ty_tag = rhs_ty.zigTypeTag(); + const target = sema.mod.getTarget(); - if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) { - if (lhs_ty.arrayLen() != rhs_ty.arrayLen()) { - return sema.fail(block, src, "vector length mismatch: {d} and {d}", .{ - lhs_ty.arrayLen(), rhs_ty.arrayLen(), - }); - } - return sema.fail(block, src, "TODO implement support for vectors in cmpNumeric", .{}); - } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) { - return sema.fail(block, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ - lhs_ty, rhs_ty, - }); - } + // One exception to heterogeneous comparison: comptime_float needs to + // coerce to fixed-width float. + + const lhs = if (lhs_ty_tag == .ComptimeFloat 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 == .ComptimeFloat) + try sema.coerce(block, lhs_ty, uncasted_rhs, rhs_src) + else + uncasted_rhs; const runtime_src: LazySrcLoc = src: { if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { @@ -15774,7 +22870,14 @@ fn cmpNumeric( if (lhs_val.isUndef() or rhs_val.isUndef()) { return sema.addConstUndef(Type.bool); } - if (Value.compareHetero(lhs_val, op, rhs_val)) { + if (lhs_val.isNan() or rhs_val.isNan()) { + if (op == std.math.CompareOperator.neq) { + return Air.Inst.Ref.bool_true; + } else { + return Air.Inst.Ref.bool_false; + } + } + if (try Value.compareHeteroAdvanced(lhs_val, op, rhs_val, target, sema.kit(block, src))) { return Air.Inst.Ref.bool_true; } else { return Air.Inst.Ref.bool_false; @@ -15803,9 +22906,10 @@ fn cmpNumeric( .Float, .ComptimeFloat => true, else => false, }; - const target = sema.mod.getTarget(); + if (lhs_is_float and rhs_is_float) { - // Implicit cast the smaller one to the larger one. + // 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 == .ComptimeFloat) { break :x rhs_ty; @@ -15829,11 +22933,11 @@ fn cmpNumeric( // 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 (try sema.resolveDefinedValue(block, lhs_src, lhs)) |lhs_val| - lhs_val.compareWithZero(.lt) + (try lhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) else (lhs_ty.isRuntimeFloat() or lhs_ty.isSignedInt()); const rhs_is_signed = if (try sema.resolveDefinedValue(block, rhs_src, rhs)) |rhs_val| - rhs_val.compareWithZero(.lt) + (try rhs_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) else (rhs_ty.isRuntimeFloat() or rhs_ty.isSignedInt()); const dest_int_is_signed = lhs_is_signed or rhs_is_signed; @@ -15844,30 +22948,41 @@ fn cmpNumeric( if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { if (lhs_val.isUndef()) return sema.addConstUndef(Type.bool); - const is_unsigned = if (lhs_is_float) x: { + if (!rhs_is_signed) { + switch (lhs_val.orderAgainstZero()) { + .gt => {}, + .eq => switch (op) { // LHS = 0, RHS is unsigned + .lte => return Air.Inst.Ref.bool_true, + .gt => return Air.Inst.Ref.bool_false, + else => {}, + }, + .lt => switch (op) { // LHS < 0, RHS is unsigned + .neq, .lt, .lte => return Air.Inst.Ref.bool_true, + .eq, .gt, .gte => return Air.Inst.Ref.bool_false, + }, + } + } + if (lhs_is_float) { var bigint_space: Value.BigIntSpace = undefined; - var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(sema.gpa); + var bigint = try lhs_val.toBigInt(&bigint_space, target).toManaged(sema.gpa); defer bigint.deinit(); - const zcmp = lhs_val.orderAgainstZero(); if (lhs_val.floatHasFraction()) { switch (op) { .eq => return Air.Inst.Ref.bool_false, .neq => return Air.Inst.Ref.bool_true, else => {}, } - if (zcmp == .lt) { - try bigint.addScalar(bigint.toConst(), -1); + if (lhs_is_signed) { + try bigint.addScalar(&bigint, -1); } else { - try bigint.addScalar(bigint.toConst(), 1); + try bigint.addScalar(&bigint, 1); } } lhs_bits = bigint.toConst().bitCountTwosComp(); - break :x (zcmp != .lt); - } else x: { + } else { lhs_bits = lhs_val.intBitCountTwosComp(target); - break :x (lhs_val.orderAgainstZero() != .lt); - }; - lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed); + } + lhs_bits += @boolToInt(!lhs_is_signed and dest_int_is_signed); } else if (lhs_is_float) { dest_float_type = lhs_ty; } else { @@ -15879,30 +22994,41 @@ fn cmpNumeric( if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { if (rhs_val.isUndef()) return sema.addConstUndef(Type.bool); - const is_unsigned = if (rhs_is_float) x: { + if (!lhs_is_signed) { + switch (rhs_val.orderAgainstZero()) { + .gt => {}, + .eq => switch (op) { // RHS = 0, LHS is unsigned + .gte => return Air.Inst.Ref.bool_true, + .lt => return Air.Inst.Ref.bool_false, + else => {}, + }, + .lt => switch (op) { // RHS < 0, LHS is unsigned + .neq, .gt, .gte => return Air.Inst.Ref.bool_true, + .eq, .lt, .lte => return Air.Inst.Ref.bool_false, + }, + } + } + if (rhs_is_float) { var bigint_space: Value.BigIntSpace = undefined; - var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(sema.gpa); + var bigint = try rhs_val.toBigInt(&bigint_space, target).toManaged(sema.gpa); defer bigint.deinit(); - const zcmp = rhs_val.orderAgainstZero(); if (rhs_val.floatHasFraction()) { switch (op) { .eq => return Air.Inst.Ref.bool_false, .neq => return Air.Inst.Ref.bool_true, else => {}, } - if (zcmp == .lt) { - try bigint.addScalar(bigint.toConst(), -1); + if (rhs_is_signed) { + try bigint.addScalar(&bigint, -1); } else { - try bigint.addScalar(bigint.toConst(), 1); + try bigint.addScalar(&bigint, 1); } } rhs_bits = bigint.toConst().bitCountTwosComp(); - break :x (zcmp != .lt); - } else x: { + } else { rhs_bits = rhs_val.intBitCountTwosComp(target); - break :x (rhs_val.orderAgainstZero() != .lt); - }; - rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed); + } + rhs_bits += @boolToInt(!rhs_is_signed and dest_int_is_signed); } else if (rhs_is_float) { dest_float_type = rhs_ty; } else { @@ -15912,9 +23038,7 @@ fn cmpNumeric( const dest_ty = if (dest_float_type) |ft| ft else blk: { const max_bits = std.math.max(lhs_bits, rhs_bits); - const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) { - error.Overflow => return sema.fail(block, src, "{d} exceeds maximum integer bit count", .{max_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 Module.makeIntType(sema.arena, signedness, casted_bits); }; @@ -15924,6 +23048,46 @@ fn cmpNumeric( return block.addBinOp(Air.Inst.Tag.fromCmpOp(op), casted_lhs, casted_rhs); } +/// 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 lhs_ty = sema.typeOf(lhs); + const rhs_ty = sema.typeOf(rhs); + assert(lhs_ty.zigTypeTag() == .Vector); + assert(rhs_ty.zigTypeTag() == .Vector); + try sema.checkVectorizableBinaryOperands(block, src, lhs_ty, rhs_ty, lhs_src, rhs_src); + + const result_ty = try Type.vector(sema.arena, lhs_ty.vectorLen(), Type.@"bool"); + + const runtime_src: LazySrcLoc = src: { + if (try sema.resolveMaybeUndefVal(block, lhs_src, lhs)) |lhs_val| { + if (try sema.resolveMaybeUndefVal(block, rhs_src, rhs)) |rhs_val| { + if (lhs_val.isUndef() or rhs_val.isUndef()) { + return sema.addConstUndef(result_ty); + } + const cmp_val = try sema.compareVector(block, src, lhs_val, op, rhs_val, lhs_ty); + return sema.addConstant(result_ty, cmp_val); + } else { + break :src rhs_src; + } + } else { + break :src lhs_src; + } + }; + + try sema.requireRuntimeBlock(block, runtime_src); + const result_ty_inst = try sema.addType(result_ty); + return block.addCmpVector(lhs, rhs, op, result_ty_inst); +} + fn wrapOptional( sema: *Sema, block: *Block, @@ -15939,7 +23103,24 @@ fn wrapOptional( return block.addTyOp(.wrap_optional, dest_ty, inst); } -fn wrapErrorUnion( +fn wrapErrorUnionPayload( + sema: *Sema, + block: *Block, + dest_ty: Type, + inst: Air.Inst.Ref, + inst_src: LazySrcLoc, +) !Air.Inst.Ref { + const dest_payload_ty = dest_ty.errorUnionPayload(); + const coerced = try sema.coerce(block, dest_payload_ty, inst, inst_src); + if (try sema.resolveMaybeUndefVal(block, inst_src, coerced)) |val| { + return sema.addConstant(dest_ty, try Value.Tag.eu_payload.create(sema.arena, val)); + } + try sema.requireRuntimeBlock(block, inst_src); + try sema.queueFullTypeResolution(dest_payload_ty); + return block.addTyOp(.wrap_errunion_payload, dest_ty, coerced); +} + +fn wrapErrorUnionSet( sema: *Sema, block: *Block, dest_ty: Type, @@ -15948,12 +23129,7 @@ fn wrapErrorUnion( ) !Air.Inst.Ref { const inst_ty = sema.typeOf(inst); const dest_err_set_ty = dest_ty.errorUnionSet(); - const dest_payload_ty = dest_ty.errorUnionPayload(); if (try sema.resolveMaybeUndefVal(block, inst_src, inst)) |val| { - if (inst_ty.zigTypeTag() != .ErrorSet) { - _ = try sema.coerce(block, dest_payload_ty, inst, inst_src); - return sema.addConstant(dest_ty, try Value.Tag.eu_payload.create(sema.arena, val)); - } switch (dest_err_set_ty.tag()) { .anyerror => {}, .error_set_single => ok: { @@ -15971,10 +23147,16 @@ fn wrapErrorUnion( }, .error_set_inferred => ok: { const expected_name = val.castTag(.@"error").?.data.name; - const data = dest_err_set_ty.castTag(.error_set_inferred).?.data; - try sema.resolveInferredErrorSet(data); - if (data.is_anyerror) break :ok; - if (data.errors.contains(expected_name)) break :ok; + const ies = dest_err_set_ty.castTag(.error_set_inferred).?.data; + + // We carefully do this in an order that avoids unnecessarily + // resolving the destination error set type. + if (ies.is_anyerror) break :ok; + if (ies.errors.contains(expected_name)) break :ok; + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, dest_err_set_ty, inst_ty, inst_src, inst_src)) { + break :ok; + } + return sema.failWithErrorSetCodeMissing(block, inst_src, dest_err_set_ty, inst_ty); }, .error_set_merged => { @@ -15990,15 +23172,8 @@ fn wrapErrorUnion( } try sema.requireRuntimeBlock(block, inst_src); - - // we are coercing from E to E!T - if (inst_ty.zigTypeTag() == .ErrorSet) { - var coerced = try sema.coerce(block, dest_err_set_ty, inst, inst_src); - return block.addTyOp(.wrap_errunion_err, dest_ty, coerced); - } else { - var coerced = try sema.coerce(block, dest_payload_ty, inst, inst_src); - return block.addTyOp(.wrap_errunion_payload, dest_ty, coerced); - } + const coerced = try sema.coerce(block, dest_err_set_ty, inst, inst_src); + return block.addTyOp(.wrap_errunion_err, dest_ty, coerced); } fn unionToTag( @@ -16022,7 +23197,7 @@ fn resolvePeerTypes( sema: *Sema, block: *Block, src: LazySrcLoc, - instructions: []Air.Inst.Ref, + instructions: []const Air.Inst.Ref, candidate_srcs: Module.PeerTypeCandidateSrc, ) !Type { switch (instructions.len) { @@ -16034,15 +23209,24 @@ fn resolvePeerTypes( const target = sema.mod.getTarget(); var chosen = instructions[0]; + // If this is non-null then it does the following thing, depending on the chosen zigTypeTag(). + // * ErrorSet: this is an override + // * ErrorUnion: this is an override of the error set only + // * other: at the end we make an ErrorUnion with the other thing and this + var err_set_ty: ?Type = null; var any_are_null = false; + var seen_const = false; + var convert_to_slice = false; var chosen_i: usize = 0; for (instructions[1..]) |candidate, candidate_i| { const candidate_ty = sema.typeOf(candidate); const chosen_ty = sema.typeOf(chosen); - if (candidate_ty.eql(chosen_ty)) + + const candidate_ty_tag = try candidate_ty.zigTypeTagOrPoison(); + const chosen_ty_tag = try chosen_ty.zigTypeTagOrPoison(); + + if (candidate_ty.eql(chosen_ty, sema.mod)) continue; - const candidate_ty_tag = candidate_ty.zigTypeTag(); - const chosen_ty_tag = chosen_ty.zigTypeTag(); switch (candidate_ty_tag) { .NoReturn, .Undefined => continue, @@ -16121,30 +23305,289 @@ fn resolvePeerTypes( }, else => {}, }, - .Pointer => { - if (candidate_ty.ptrSize() == .C) { - if (chosen_ty_tag == .Int or chosen_ty_tag == .ComptimeInt) { + .ErrorSet => switch (chosen_ty_tag) { + .ErrorSet => { + // If chosen is superset of candidate, keep it. + // If candidate is superset of chosen, switch it. + // If neither is a superset, merge errors. + const chosen_set_ty = err_set_ty orelse chosen_ty; + + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) { + continue; + } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) { + err_set_ty = null; chosen = candidate; chosen_i = candidate_i + 1; continue; } - if (chosen_ty_tag == .Pointer and chosen_ty.ptrSize() != .Slice) { + + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); + continue; + }, + .ErrorUnion => { + const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); + + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) { continue; } - } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) { + err_set_ty = candidate_ty; + continue; + } + + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); + continue; + }, + else => { + if (err_set_ty) |chosen_set_ty| { + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_ty, src, src)) { + continue; + } + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_ty, chosen_set_ty, src, src)) { + err_set_ty = candidate_ty; + continue; + } + + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_ty); + continue; + } else { + err_set_ty = candidate_ty; + continue; + } + }, }, - .Optional => { - var opt_child_buf: Type.Payload.ElemType = undefined; - const opt_child_ty = candidate_ty.optionalChild(&opt_child_buf); - if ((try sema.coerceInMemoryAllowed(block, opt_child_ty, chosen_ty, false, target, src, src)) == .ok) { + .ErrorUnion => switch (chosen_ty_tag) { + .ErrorSet => { + const chosen_set_ty = err_set_ty orelse chosen_ty; + const candidate_set_ty = candidate_ty.errorUnionSet(); + + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) { + err_set_ty = chosen_set_ty; + } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) { + err_set_ty = null; + } else { + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); + } chosen = candidate; chosen_i = candidate_i + 1; continue; + }, + + .ErrorUnion => { + const chosen_payload_ty = chosen_ty.errorUnionPayload(); + const candidate_payload_ty = candidate_ty.errorUnionPayload(); + + const coerce_chosen = (try sema.coerceInMemoryAllowed(block, chosen_payload_ty, candidate_payload_ty, false, target, src, src)) == .ok; + const coerce_candidate = (try sema.coerceInMemoryAllowed(block, candidate_payload_ty, chosen_payload_ty, false, target, src, src)) == .ok; + + if (coerce_chosen or coerce_candidate) { + // If we can coerce to the candidate, we switch to that + // type. This is the same logic as the bare (non-union) + // coercion check we do at the top of this func. + if (coerce_candidate) { + chosen = candidate; + chosen_i = candidate_i + 1; + } + + const chosen_set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); + const candidate_set_ty = candidate_ty.errorUnionSet(); + + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) { + err_set_ty = chosen_set_ty; + } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) { + err_set_ty = candidate_set_ty; + } else { + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); + } + continue; + } + }, + + else => { + if (err_set_ty) |chosen_set_ty| { + const candidate_set_ty = candidate_ty.errorUnionSet(); + if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, chosen_set_ty, candidate_set_ty, src, src)) { + err_set_ty = chosen_set_ty; + } else if (.ok == try sema.coerceInMemoryAllowedErrorSets(block, candidate_set_ty, chosen_set_ty, src, src)) { + err_set_ty = null; + } else { + err_set_ty = try chosen_set_ty.errorSetMerge(sema.arena, candidate_set_ty); + } + } + seen_const = seen_const or chosen_ty.isConstPtr(); + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + }, + .Pointer => { + const cand_info = candidate_ty.ptrInfo().data; + switch (chosen_ty_tag) { + .Pointer => { + const chosen_info = chosen_ty.ptrInfo().data; + + seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable; + + // *[N]T to [*]T + // *[N]T to []T + if ((cand_info.size == .Many or cand_info.size == .Slice) and + chosen_info.size == .One and + chosen_info.pointee_type.zigTypeTag() == .Array) + { + // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` + convert_to_slice = false; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + if (cand_info.size == .One and + cand_info.pointee_type.zigTypeTag() == .Array and + (chosen_info.size == .Many or chosen_info.size == .Slice)) + { + // In case we see i.e.: `*[1]T`, `*[2]T`, `[*]T` + convert_to_slice = false; + continue; + } + + // *[N]T and *[M]T + // Verify both are single-pointers to arrays. + // Keep the one whose element type can be coerced into. + if (chosen_info.size == .One and + cand_info.size == .One and + chosen_info.pointee_type.zigTypeTag() == .Array and + cand_info.pointee_type.zigTypeTag() == .Array) + { + const chosen_elem_ty = chosen_info.pointee_type.childType(); + const cand_elem_ty = cand_info.pointee_type.childType(); + + const chosen_ok = .ok == try sema.coerceInMemoryAllowed(block, chosen_elem_ty, cand_elem_ty, chosen_info.mutable, target, src, src); + if (chosen_ok) { + convert_to_slice = true; + continue; + } + + const cand_ok = .ok == try sema.coerceInMemoryAllowed(block, cand_elem_ty, chosen_elem_ty, cand_info.mutable, target, src, src); + if (cand_ok) { + convert_to_slice = true; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + + // They're both bad. Report error. + // In the future we probably want to use the + // coerceInMemoryAllowed error reporting mechanism, + // however, for now we just fall through for the + // "incompatible types" error below. + } + + // [*c]T and any other pointer size + // Whichever element type can coerce to the other one, is + // the one we will keep. If they're both OK then we keep the + // C pointer since it matches both single and many pointers. + if (cand_info.size == .C or chosen_info.size == .C) { + const cand_ok = .ok == try sema.coerceInMemoryAllowed(block, cand_info.pointee_type, chosen_info.pointee_type, cand_info.mutable, target, src, src); + const chosen_ok = .ok == try sema.coerceInMemoryAllowed(block, chosen_info.pointee_type, cand_info.pointee_type, chosen_info.mutable, target, src, src); + + if (cand_ok) { + if (chosen_ok) { + if (chosen_info.size == .C) { + continue; + } else { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + } else { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + } else { + if (chosen_ok) { + continue; + } else { + // They're both bad. Report error. + // In the future we probably want to use the + // coerceInMemoryAllowed error reporting mechanism, + // however, for now we just fall through for the + // "incompatible types" error below. + } + } + } + }, + .Int, .ComptimeInt => { + if (cand_info.size == .C) { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + }, + .Optional => { + var opt_child_buf: Type.Payload.ElemType = undefined; + const chosen_ptr_ty = chosen_ty.optionalChild(&opt_child_buf); + if (chosen_ptr_ty.zigTypeTag() == .Pointer) { + const chosen_info = chosen_ptr_ty.ptrInfo().data; + + seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable; + + // *[N]T to ?![*]T + // *[N]T to ?![]T + if (cand_info.size == .One and + cand_info.pointee_type.zigTypeTag() == .Array and + (chosen_info.size == .Many or chosen_info.size == .Slice)) + { + continue; + } + } + }, + .ErrorUnion => { + const chosen_ptr_ty = chosen_ty.errorUnionPayload(); + if (chosen_ptr_ty.zigTypeTag() == .Pointer) { + const chosen_info = chosen_ptr_ty.ptrInfo().data; + + seen_const = seen_const or !chosen_info.mutable or !cand_info.mutable; + + // *[N]T to E![*]T + // *[N]T to E![]T + if (cand_info.size == .One and + cand_info.pointee_type.zigTypeTag() == .Array and + (chosen_info.size == .Many or chosen_info.size == .Slice)) + { + continue; + } + } + }, + else => {}, } + }, + .Optional => { + var opt_child_buf: Type.Payload.ElemType = undefined; + const opt_child_ty = candidate_ty.optionalChild(&opt_child_buf); if ((try sema.coerceInMemoryAllowed(block, chosen_ty, opt_child_ty, false, target, src, src)) == .ok) { + seen_const = seen_const or opt_child_ty.isConstPtr(); any_are_null = true; continue; } + + seen_const = seen_const or chosen_ty.isConstPtr(); + any_are_null = false; + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + .Vector => switch (chosen_ty_tag) { + .Array => { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + }, + else => {}, + }, + .Array => switch (chosen_ty_tag) { + .Vector => continue, + else => {}, }, else => {}, } @@ -16174,65 +23617,187 @@ fn resolvePeerTypes( continue; } }, + .ErrorUnion => { + const payload_ty = chosen_ty.errorUnionPayload(); + if ((try sema.coerceInMemoryAllowed(block, payload_ty, candidate_ty, false, target, src, src)) == .ok) { + continue; + } + }, else => {}, } + // If the candidate can coerce into our chosen type, we're done. + // If the chosen type can coerce into the candidate, use that. + if ((try sema.coerceInMemoryAllowed(block, chosen_ty, candidate_ty, false, target, src, src)) == .ok) { + continue; + } + if ((try sema.coerceInMemoryAllowed(block, candidate_ty, chosen_ty, false, target, src, src)) == .ok) { + chosen = candidate; + chosen_i = candidate_i + 1; + continue; + } + // At this point, we hit a compile error. We need to recover // the source locations. const chosen_src = candidate_srcs.resolve( sema.gpa, - block.src_decl, + sema.mod.declPtr(block.src_decl), chosen_i, ); const candidate_src = candidate_srcs.resolve( sema.gpa, - block.src_decl, + sema.mod.declPtr(block.src_decl), candidate_i + 1, ); const msg = msg: { - const msg = try sema.errMsg(block, src, "incompatible types: '{}' and '{}'", .{ chosen_ty, candidate_ty }); + const msg = try sema.errMsg(block, src, "incompatible types: '{}' and '{}'", .{ + chosen_ty.fmt(sema.mod), + candidate_ty.fmt(sema.mod), + }); errdefer msg.destroy(sema.gpa); if (chosen_src) |src_loc| - try sema.errNote(block, src_loc, msg, "type '{}' here", .{chosen_ty}); + try sema.errNote(block, src_loc, msg, "type '{}' here", .{chosen_ty.fmt(sema.mod)}); if (candidate_src) |src_loc| - try sema.errNote(block, src_loc, msg, "type '{}' here", .{candidate_ty}); + try sema.errNote(block, src_loc, msg, "type '{}' here", .{candidate_ty.fmt(sema.mod)}); break :msg msg; }; - return sema.failWithOwnedErrorMsg(msg); + return sema.failWithOwnedErrorMsg(block, msg); } const chosen_ty = sema.typeOf(chosen); - if (any_are_null) { + if (convert_to_slice) { + // turn *[N]T => []T + const chosen_child_ty = chosen_ty.childType(); + var info = chosen_ty.ptrInfo(); + info.data.sentinel = chosen_child_ty.sentinel(); + info.data.size = .Slice; + info.data.mutable = !(seen_const or chosen_child_ty.isConstPtr()); + info.data.pointee_type = chosen_child_ty.elemType2(); + + const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); + const opt_ptr_ty = if (any_are_null) + try Type.optional(sema.arena, new_ptr_ty) + else + new_ptr_ty; + const set_ty = err_set_ty orelse return opt_ptr_ty; + return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, sema.mod); + } + + if (seen_const) { + // turn []T => []const T switch (chosen_ty.zigTypeTag()) { - .Null, .Optional => return chosen_ty, - else => return Type.optional(sema.arena, chosen_ty), + .ErrorUnion => { + const ptr_ty = chosen_ty.errorUnionPayload(); + var info = ptr_ty.ptrInfo(); + info.data.mutable = false; + const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); + const opt_ptr_ty = if (any_are_null) + try Type.optional(sema.arena, new_ptr_ty) + else + new_ptr_ty; + const set_ty = err_set_ty orelse chosen_ty.errorUnionSet(); + return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, sema.mod); + }, + .Pointer => { + var info = chosen_ty.ptrInfo(); + info.data.mutable = false; + const new_ptr_ty = try Type.ptr(sema.arena, sema.mod, info.data); + const opt_ptr_ty = if (any_are_null) + try Type.optional(sema.arena, new_ptr_ty) + else + new_ptr_ty; + const set_ty = err_set_ty orelse return opt_ptr_ty; + return try Type.errorUnion(sema.arena, set_ty, opt_ptr_ty, sema.mod); + }, + else => return chosen_ty, } } + if (any_are_null) { + const opt_ty = switch (chosen_ty.zigTypeTag()) { + .Null, .Optional => chosen_ty, + else => try Type.optional(sema.arena, chosen_ty), + }; + const set_ty = err_set_ty orelse return opt_ty; + return try Type.errorUnion(sema.arena, set_ty, opt_ty, sema.mod); + } + + if (err_set_ty) |ty| switch (chosen_ty.zigTypeTag()) { + .ErrorSet => return ty, + .ErrorUnion => { + const payload_ty = chosen_ty.errorUnionPayload(); + return try Type.errorUnion(sema.arena, ty, payload_ty, sema.mod); + }, + else => return try Type.errorUnion(sema.arena, ty, chosen_ty, sema.mod), + }; + return chosen_ty; } +pub fn resolveFnTypes( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + fn_info: Type.Payload.Function.Data, +) CompileError!void { + try sema.resolveTypeFully(block, src, fn_info.return_type); + + if (sema.mod.comp.bin_file.options.error_return_tracing and fn_info.return_type.isError()) { + // Ensure the type exists so that backends can assume that. + _ = try sema.getBuiltinType(block, src, "StackTrace"); + } + + for (fn_info.param_types) |param_ty| { + try sema.resolveTypeFully(block, src, param_ty); + } +} + +/// Make it so that calling hash() and eql() on `val` will not assert due +/// to a type not having its layout resolved. +fn resolveLazyValue( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + val: Value, +) CompileError!void { + switch (val.tag()) { + .lazy_align => { + const ty = val.castTag(.lazy_align).?.data; + return sema.resolveTypeLayout(block, src, ty); + }, + else => return, + } +} + pub fn resolveTypeLayout( sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, ) CompileError!void { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); + switch (ty.zigTypeTag()) { .Struct => return sema.resolveStructLayout(block, src, ty), .Union => return sema.resolveUnionLayout(block, src, ty), .Array => { + if (ty.arrayLenIncludingSentinel() == 0) return; const elem_ty = ty.childType(); return sema.resolveTypeLayout(block, src, elem_ty); }, .Optional => { var buf: Type.Payload.ElemType = undefined; const payload_ty = ty.optionalChild(&buf); + // In case of querying the ABI alignment of this optional, we will ask + // for hasRuntimeBits() of the payload type, so we need "requires comptime" + // to be known already before this function returns. + _ = try sema.typeRequiresComptime(block, src, payload_ty); return sema.resolveTypeLayout(block, src, payload_ty); }, .ErrorUnion => { @@ -16255,7 +23820,7 @@ fn resolveStructLayout( switch (struct_obj.status) { .none, .have_field_types => {}, .field_types_wip, .layout_wip => { - return sema.fail(block, src, "struct {} depends on itself", .{ty}); + return sema.fail(block, src, "struct '{}' depends on itself", .{ty.fmt(sema.mod)}); }, .have_layout, .fully_resolved_wip, .fully_resolved => return, } @@ -16264,6 +23829,13 @@ fn resolveStructLayout( try sema.resolveTypeLayout(block, src, field.ty); } struct_obj.status = .have_layout; + + // In case of querying the ABI alignment of this struct, we will ask + // for hasRuntimeBits() of each field, so we need "requires comptime" + // to be known already before this function returns. + for (struct_obj.fields.values()) |field| { + _ = try sema.typeRequiresComptime(block, src, field.ty); + } } // otherwise it's a tuple; no need to resolve anything } @@ -16279,7 +23851,7 @@ fn resolveUnionLayout( switch (union_obj.status) { .none, .have_field_types => {}, .field_types_wip, .layout_wip => { - return sema.fail(block, src, "union {} depends on itself", .{ty}); + return sema.fail(block, src, "union '{}' depends on itself", .{ty.fmt(sema.mod)}); }, .have_layout, .fully_resolved_wip, .fully_resolved => return, } @@ -16290,7 +23862,9 @@ fn resolveUnionLayout( union_obj.status = .have_layout; } -fn resolveTypeFully( +/// Returns `error.AnalysisFail` if any of the types (recursively) failed to +/// be resolved. +pub fn resolveTypeFully( sema: *Sema, block: *Block, src: LazySrcLoc, @@ -16301,7 +23875,17 @@ fn resolveTypeFully( const child_ty = try sema.resolveTypeFields(block, src, ty.childType()); return resolveTypeFully(sema, block, src, child_ty); }, - .Struct => return resolveStructFully(sema, block, src, ty), + .Struct => switch (ty.tag()) { + .@"struct" => return resolveStructFully(sema, block, src, ty), + .tuple, .anon_struct => { + const tuple = ty.tupleFields(); + + for (tuple.types) |field_ty| { + try sema.resolveTypeFully(block, src, field_ty); + } + }, + else => {}, + }, .Union => return resolveUnionFully(sema, block, src, ty), .Array => return resolveTypeFully(sema, block, src, ty.childType()), .Optional => { @@ -16309,6 +23893,20 @@ fn resolveTypeFully( return resolveTypeFully(sema, block, src, ty.optionalChild(&buf)); }, .ErrorUnion => return resolveTypeFully(sema, block, src, ty.errorUnionPayload()), + .Fn => { + const info = ty.fnInfo(); + if (info.is_generic) { + // Resolving of generic function types is defeerred to when + // the function is instantiated. + return; + } + for (info.param_types) |param_ty| { + const param_ty_src = src; // TODO better source location + try sema.resolveTypeFully(block, param_ty_src, param_ty); + } + const return_ty_src = src; // TODO better source location + try sema.resolveTypeFully(block, return_ty_src, info.return_type); + }, else => {}, } } @@ -16322,19 +23920,30 @@ fn resolveStructFully( try resolveStructLayout(sema, block, src, ty); const resolved_ty = try sema.resolveTypeFields(block, src, ty); - const struct_obj = resolved_ty.castTag(.@"struct").?.data; + const payload = resolved_ty.castTag(.@"struct").?; + const struct_obj = payload.data; + switch (struct_obj.status) { .none, .have_field_types, .field_types_wip, .layout_wip, .have_layout => {}, .fully_resolved_wip, .fully_resolved => return, } - // 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 - struct_obj.status = .fully_resolved_wip; - for (struct_obj.fields.values()) |field| { - try sema.resolveTypeFully(block, src, field.ty); + { + // 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. + const prev_status = struct_obj.status; + errdefer struct_obj.status = prev_status; + + struct_obj.status = .fully_resolved_wip; + for (struct_obj.fields.values()) |field| { + try sema.resolveTypeFully(block, src, field.ty); + } + struct_obj.status = .fully_resolved; } - struct_obj.status = .fully_resolved; + + // And let's not forget comptime-only status. + _ = try sema.typeRequiresComptime(block, src, ty); } fn resolveUnionFully( @@ -16352,15 +23961,27 @@ fn resolveUnionFully( .fully_resolved_wip, .fully_resolved => return, } - // Same goes for unions (see comment about structs) - union_obj.status = .fully_resolved_wip; - for (union_obj.fields.values()) |field| { - try sema.resolveTypeFully(block, src, field.ty); + { + // 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.status; + errdefer union_obj.status = prev_status; + + union_obj.status = .fully_resolved_wip; + for (union_obj.fields.values()) |field| { + try sema.resolveTypeFully(block, src, field.ty); + } + union_obj.status = .fully_resolved; } - union_obj.status = .fully_resolved; + + // And let's not forget comptime-only status. + _ = try sema.typeRequiresComptime(block, src, ty); } -fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!Type { +pub fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!Type { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); switch (ty.tag()) { .@"struct" => { const struct_obj = ty.castTag(.@"struct").?.data; @@ -16372,7 +23993,7 @@ fn resolveTypeFields(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) Comp try sema.resolveTypeFieldsUnion(block, src, ty, union_obj); return ty; }, - .type_info => return sema.resolveBuiltinTypeFields(block, src, "TypeInfo"), + .type_info => return sema.resolveBuiltinTypeFields(block, src, "Type"), .extern_options => return sema.resolveBuiltinTypeFields(block, src, "ExternOptions"), .export_options => return sema.resolveBuiltinTypeFields(block, src, "ExportOptions"), .atomic_order => return sema.resolveBuiltinTypeFields(block, src, "AtomicOrder"), @@ -16398,7 +24019,7 @@ fn resolveTypeFieldsStruct( switch (struct_obj.status) { .none => {}, .field_types_wip => { - return sema.fail(block, src, "struct {} depends on itself", .{ty}); + return sema.fail(block, src, "struct '{}' depends on itself", .{ty.fmt(sema.mod)}); }, .have_field_types, .have_layout, @@ -16428,7 +24049,7 @@ fn resolveTypeFieldsUnion( switch (union_obj.status) { .none => {}, .field_types_wip => { - return sema.fail(block, src, "union {} depends on itself", .{ty}); + return sema.fail(block, src, "union '{}' depends on itself", .{ty.fmt(sema.mod)}); }, .have_field_types, .have_layout, @@ -16439,7 +24060,7 @@ fn resolveTypeFieldsUnion( } union_obj.status = .field_types_wip; - try semaUnionFields(sema.mod, union_obj); + try semaUnionFields(block, sema.mod, union_obj); union_obj.status = .have_field_types; } @@ -16453,40 +24074,57 @@ fn resolveBuiltinTypeFields( return sema.resolveTypeFields(block, src, resolved_ty); } -fn resolveInferredErrorSet(sema: *Sema, inferred_error_set: *Module.Fn.InferredErrorSet) CompileError!void { - // Ensuring that a particular decl is analyzed does not neccesarily mean that - // it's error set is inferred, so traverse all of them to get the complete - // picture. - // Note: We want to skip re-resolving the current function, as recursion - // doesn't change the error set. We can just check for state == .in_progress for this. - // TODO: Is that correct? +fn resolveInferredErrorSet( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ies: *Module.Fn.InferredErrorSet, +) CompileError!void { + if (ies.is_resolved) return; - if (inferred_error_set.is_resolved) { - return; + if (ies.func.state == .in_progress) { + return sema.fail(block, src, "unable to resolve inferred error set", .{}); } - var it = inferred_error_set.inferred_error_sets.keyIterator(); - while (it.next()) |other_error_set_ptr| { - const func = other_error_set_ptr.*.func; - const decl = func.*.owner_decl; + // 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 a new InferredErrorSet object, which points to the same + // `*Module.Fn`. Not only is the function not relevant to the inferred error set + // in this case, it may be a generic function which would cause an assertion failure + // if we called `ensureFuncBodyAnalyzed` on it here. + const ies_func_owner_decl = sema.mod.declPtr(ies.func.owner_decl); + if (ies_func_owner_decl.ty.fnInfo().return_type.errorUnionSet().castTag(.error_set_inferred).?.data == ies) { + // 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. + try sema.ensureFuncBodyAnalyzed(ies.func); + } - if (func.*.state == .in_progress) { - // Recursion, doesn't alter current error set, keep going. - continue; - } + ies.is_resolved = true; - try sema.ensureDeclAnalyzed(decl); // To ensure that all dependencies are properly added to the set. - try sema.resolveInferredErrorSet(other_error_set_ptr.*); + var it = ies.inferred_error_sets.keyIterator(); + while (it.next()) |other_error_set_ptr| { + const other_ies: *Module.Fn.InferredErrorSet = other_error_set_ptr.*; + if (ies == other_ies) continue; + try sema.resolveInferredErrorSet(block, src, other_ies); - var error_it = other_error_set_ptr.*.errors.keyIterator(); - while (error_it.next()) |entry| { - try inferred_error_set.errors.put(sema.gpa, entry.*, {}); + for (other_ies.errors.keys()) |key| { + try ies.errors.put(sema.gpa, key, {}); } - if (other_error_set_ptr.*.is_anyerror) - inferred_error_set.is_anyerror = true; + if (other_ies.is_anyerror) + ies.is_anyerror = true; } +} - inferred_error_set.is_resolved = true; +fn resolveInferredErrorSetTy( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ty: Type, +) CompileError!void { + if (ty.castTag(.error_set_inferred)) |inferred| { + try sema.resolveInferredErrorSet(block, src, inferred.data); + } } fn semaStructFields( @@ -16497,14 +24135,14 @@ fn semaStructFields( defer tracy.end(); const gpa = mod.gpa; - const decl = struct_obj.owner_decl; + const decl_index = struct_obj.owner_decl; const zir = struct_obj.namespace.file_scope.zir; const extended = zir.instructions.items(.data)[struct_obj.zir_index].extended; assert(extended.opcode == .struct_decl); const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small); var extra_index: usize = extended.operand; - const src: LazySrcLoc = .{ .node_offset = struct_obj.node_offset }; + const src = LazySrcLoc.nodeOffset(struct_obj.node_offset); extra_index += @boolToInt(small.has_src_node); const body_len = if (small.has_body_len) blk: { @@ -16537,6 +24175,7 @@ fn semaStructFields( } extra_index += body.len; + const decl = mod.declPtr(decl_index); var decl_arena = decl.value_arena.?.promote(gpa); defer decl.value_arena.?.* = decl_arena.state; const decl_arena_allocator = decl_arena.allocator(); @@ -16551,6 +24190,7 @@ fn semaStructFields( .perm_arena = decl_arena_allocator, .code = zir, .owner_decl = decl, + .owner_decl_index = decl_index, .func = null, .fn_ret_ty = Type.void, .owner_func = null, @@ -16563,7 +24203,7 @@ fn semaStructFields( var block_scope: Block = .{ .parent = null, .sema = &sema, - .src_decl = decl, + .src_decl = decl_index, .namespace = &struct_obj.namespace, .wip_capture_scope = wip_captures.scope, .instructions = .{}, @@ -16576,7 +24216,7 @@ fn semaStructFields( } if (body.len != 0) { - _ = try sema.analyzeBody(&block_scope, body); + try sema.analyzeBody(&block_scope, body); } try wip_captures.finalize(); @@ -16624,11 +24264,32 @@ fn semaStructFields( // But only resolve the source location if we need to emit a compile error. try sema.resolveType(&block_scope, src, field_type_ref); + // TODO emit compile errors for invalid field types + // such as arrays and pointers inside packed structs. + + if (field_ty.tag() == .generic_poison) { + return error.GenericPoison; + } + const gop = struct_obj.fields.getOrPutAssumeCapacity(field_name); - assert(!gop.found_existing); + if (gop.found_existing) { + const msg = msg: { + const tree = try sema.getAstTree(&block_scope); + const field_src = enumFieldSrcLoc(decl, tree.*, struct_obj.node_offset, field_i); + const msg = try sema.errMsg(&block_scope, field_src, "duplicate struct field: '{s}'", .{field_name}); + errdefer msg.destroy(gpa); + + const prev_field_index = struct_obj.fields.getIndex(field_name).?; + const prev_field_src = enumFieldSrcLoc(decl, tree.*, struct_obj.node_offset, prev_field_index); + try sema.mod.errNoteNonLazy(prev_field_src.toSrcLoc(decl), msg, "other field here", .{}); + try sema.errNote(&block_scope, src, msg, "struct declared here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(&block_scope, msg); + } gop.value_ptr.* = .{ .ty = try field_ty.copy(decl_arena_allocator), - .abi_align = Value.initTag(.abi_align_default), + .abi_align = 0, .default_val = Value.initTag(.unreachable_value), .is_comptime = is_comptime, .offset = undefined, @@ -16640,13 +24301,12 @@ fn semaStructFields( // TODO: if we need to report an error here, use a source location // that points to this alignment expression rather than the struct. // But only resolve the source location if we need to emit a compile error. - const abi_align_val = (try sema.resolveInstConst(&block_scope, src, align_ref)).val; - gop.value_ptr.abi_align = try abi_align_val.copy(decl_arena_allocator); + gop.value_ptr.abi_align = try sema.resolveAlign(&block_scope, src, align_ref); } if (has_default) { const default_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); extra_index += 1; - const default_inst = sema.resolveInst(default_ref); + const default_inst = try sema.resolveInst(default_ref); // TODO: if we need to report an error here, use a source location // that points to this default value expression rather than the struct. // But only resolve the source location if we need to emit a compile error. @@ -16657,19 +24317,19 @@ fn semaStructFields( } } -fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { +fn semaUnionFields(block: *Block, mod: *Module, union_obj: *Module.Union) CompileError!void { const tracy = trace(@src()); defer tracy.end(); const gpa = mod.gpa; - const decl = union_obj.owner_decl; + const decl_index = union_obj.owner_decl; const zir = union_obj.namespace.file_scope.zir; const extended = zir.instructions.items(.data)[union_obj.zir_index].extended; assert(extended.opcode == .union_decl); const small = @bitCast(Zir.Inst.UnionDecl.Small, extended.small); var extra_index: usize = extended.operand; - const src: LazySrcLoc = .{ .node_offset = union_obj.node_offset }; + const src = LazySrcLoc.nodeOffset(union_obj.node_offset); extra_index += @boolToInt(small.has_src_node); const tag_type_ref: Zir.Inst.Ref = if (small.has_tag_type) blk: { @@ -16708,8 +24368,10 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { } extra_index += body.len; - var decl_arena = union_obj.owner_decl.value_arena.?.promote(gpa); - defer union_obj.owner_decl.value_arena.?.* = decl_arena.state; + const decl = mod.declPtr(decl_index); + + var decl_arena = decl.value_arena.?.promote(gpa); + defer decl.value_arena.?.* = decl_arena.state; const decl_arena_allocator = decl_arena.allocator(); var analysis_arena = std.heap.ArenaAllocator.init(gpa); @@ -16722,6 +24384,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { .perm_arena = decl_arena_allocator, .code = zir, .owner_decl = decl, + .owner_decl_index = decl_index, .func = null, .fn_ret_ty = Type.void, .owner_func = null, @@ -16734,7 +24397,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { var block_scope: Block = .{ .parent = null, .sema = &sema, - .src_decl = decl, + .src_decl = decl_index, .namespace = &union_obj.namespace, .wip_capture_scope = wip_captures.scope, .instructions = .{}, @@ -16747,7 +24410,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { } if (body.len != 0) { - _ = try sema.analyzeBody(&block_scope, body); + try sema.analyzeBody(&block_scope, body); } try wip_captures.finalize(); @@ -16757,24 +24420,29 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { var int_tag_ty: Type = undefined; var enum_field_names: ?*Module.EnumNumbered.NameMap = null; var enum_value_map: ?*Module.EnumNumbered.ValueMap = null; + var tag_ty_field_names: ?Module.EnumFull.NameMap = null; if (tag_type_ref != .none) { const provided_ty = try sema.resolveType(&block_scope, 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; - union_obj.tag_ty = try sema.generateUnionTagTypeNumbered(&block_scope, fields_len, provided_ty); + union_obj.tag_ty = try sema.generateUnionTagTypeNumbered(&block_scope, fields_len, provided_ty, union_obj); const enum_obj = union_obj.tag_ty.castTag(.enum_numbered).?.data; enum_field_names = &enum_obj.fields; enum_value_map = &enum_obj.values; } else { // The provided type is the enum tag type. union_obj.tag_ty = try provided_ty.copy(decl_arena_allocator); + // The fields of the union must match the enum exactly. + // Store a copy of the enum field names so we can check for + // missing or extraneous fields later. + tag_ty_field_names = try union_obj.tag_ty.enumFields().clone(sema.arena); } } 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). - union_obj.tag_ty = try sema.generateUnionTagTypeSimple(&block_scope, fields_len); + union_obj.tag_ty = try sema.generateUnionTagTypeSimple(&block_scope, fields_len, union_obj); enum_field_names = &union_obj.tag_ty.castTag(.enum_simple).?.data.fields; } @@ -16785,6 +24453,7 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { extra_index += bit_bags_count; var cur_bit_bag: u32 = undefined; var field_i: u32 = 0; + var last_tag_val: ?Value = null; while (field_i < fields_len) : (field_i += 1) { if (field_i % fields_per_u32 == 0) { cur_bit_bag = zir.extra[bit_bag_index]; @@ -16821,19 +24490,36 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { const tag_ref: Zir.Inst.Ref = if (has_tag) blk: { const tag_ref = @intToEnum(Zir.Inst.Ref, zir.extra[extra_index]); extra_index += 1; - break :blk sema.resolveInst(tag_ref); + break :blk try sema.resolveInst(tag_ref); } else .none; if (enum_value_map) |map| { - const tag_src = src; // TODO better source location - const coerced = try sema.coerce(&block_scope, int_tag_ty, tag_ref, tag_src); - const val = try sema.resolveConstValue(&block_scope, tag_src, coerced); - - // This puts the memory into the union arena, not the enum arena, but - // it is OK since they share the same lifetime. - const copied_val = try val.copy(decl_arena_allocator); + if (tag_ref != .none) { + const tag_src = src; // TODO better source location + const coerced = try sema.coerce(&block_scope, int_tag_ty, tag_ref, tag_src); + const val = try sema.resolveConstValue(&block_scope, tag_src, coerced); + last_tag_val = val; + + // This puts the memory into the union arena, not the enum arena, but + // it is OK since they share the same lifetime. + const copied_val = try val.copy(decl_arena_allocator); + map.putAssumeCapacityContext(copied_val, {}, .{ + .ty = int_tag_ty, + .mod = mod, + }); + } else { + const val = if (last_tag_val) |val| + try sema.intAdd(block, src, val, Value.one, int_tag_ty) + else + Value.zero; + last_tag_val = val; - map.putAssumeCapacityContext(copied_val, {}, .{ .ty = int_tag_ty }); + const copied_val = try val.copy(decl_arena_allocator); + map.putAssumeCapacityContext(copied_val, {}, .{ + .ty = int_tag_ty, + .mod = mod, + }); + } } // This string needs to outlive the ZIR code. @@ -16852,21 +24538,70 @@ fn semaUnionFields(mod: *Module, union_obj: *Module.Union) CompileError!void { // But only resolve the source location if we need to emit a compile error. try sema.resolveType(&block_scope, src, field_type_ref); + if (field_ty.tag() == .generic_poison) { + return error.GenericPoison; + } + const gop = union_obj.fields.getOrPutAssumeCapacity(field_name); - assert(!gop.found_existing); + if (gop.found_existing) { + const msg = msg: { + const tree = try sema.getAstTree(&block_scope); + const field_src = enumFieldSrcLoc(decl, tree.*, union_obj.node_offset, field_i); + const msg = try sema.errMsg(&block_scope, field_src, "duplicate union field: '{s}'", .{field_name}); + errdefer msg.destroy(gpa); + + const prev_field_index = union_obj.fields.getIndex(field_name).?; + const prev_field_src = enumFieldSrcLoc(decl, tree.*, union_obj.node_offset, prev_field_index); + try sema.mod.errNoteNonLazy(prev_field_src.toSrcLoc(decl), msg, "other field here", .{}); + try sema.errNote(&block_scope, src, msg, "union declared here", .{}); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(&block_scope, msg); + } + + if (tag_ty_field_names) |*names| { + const enum_has_field = names.orderedRemove(field_name); + if (!enum_has_field) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "enum '{}' has no field named '{s}'", .{ union_obj.tag_ty.fmt(sema.mod), field_name }); + errdefer msg.destroy(sema.gpa); + try sema.addDeclaredHereNote(msg, union_obj.tag_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); + } + } + gop.value_ptr.* = .{ .ty = try field_ty.copy(decl_arena_allocator), - .abi_align = Value.initTag(.abi_align_default), + .abi_align = 0, }; if (align_ref != .none) { // TODO: if we need to report an error here, use a source location // that points to this alignment expression rather than the struct. // But only resolve the source location if we need to emit a compile error. - const abi_align_val = (try sema.resolveInstConst(&block_scope, src, align_ref)).val; - gop.value_ptr.abi_align = try abi_align_val.copy(decl_arena_allocator); + gop.value_ptr.abi_align = try sema.resolveAlign(&block_scope, src, align_ref); } else { - gop.value_ptr.abi_align = Value.initTag(.abi_align_default); + gop.value_ptr.abi_align = 0; + } + } + + if (tag_ty_field_names) |names| { + if (names.count() > 0) { + const msg = msg: { + const msg = try sema.errMsg(block, src, "enum field(s) missing in union", .{}); + errdefer msg.destroy(sema.gpa); + + const enum_ty = union_obj.tag_ty; + for (names.keys()) |field_name| { + const field_index = enum_ty.enumFieldIndex(field_name).?; + try sema.addFieldErrNote(block, enum_ty, field_index, msg, "field '{s}' missing, declared here", .{field_name}); + } + try sema.addDeclaredHereNote(msg, union_obj.tag_ty); + break :msg msg; + }; + return sema.failWithOwnedErrorMsg(block, msg); } } } @@ -16876,6 +24611,7 @@ fn generateUnionTagTypeNumbered( block: *Block, fields_len: u32, int_ty: Type, + union_obj: *Module.Union, ) !Type { const mod = sema.mod; @@ -16891,16 +24627,28 @@ fn generateUnionTagTypeNumbered( }; const enum_ty = Type.initPayload(&enum_ty_payload.base); const enum_val = try Value.Tag.ty.create(new_decl_arena_allocator, enum_ty); - // TODO better type name - const new_decl = try mod.createAnonymousDecl(block, .{ + + const src_decl = mod.declPtr(block.src_decl); + const new_decl_index = try mod.allocateNewDecl(block.namespace, src_decl.src_node, block.wip_capture_scope); + errdefer mod.destroyDecl(new_decl_index); + const name = name: { + const fqn = try union_obj.getFullyQualifiedName(mod); + defer sema.gpa.free(fqn); + break :name try std.fmt.allocPrintZ(mod.gpa, "@typeInfo({s}).Union.tag_type.?", .{fqn}); + }; + try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, block.namespace, .{ .ty = Type.type, .val = enum_val, - }); + }, name); + sema.mod.declPtr(new_decl_index).name_fully_qualified = true; + + const new_decl = mod.declPtr(new_decl_index); new_decl.owns_tv = true; - errdefer mod.abortAnonDecl(new_decl); + new_decl.name_fully_qualified = true; + errdefer mod.abortAnonDecl(new_decl_index); enum_obj.* = .{ - .owner_decl = new_decl, + .owner_decl = new_decl_index, .tag_ty = int_ty, .fields = .{}, .values = .{}, @@ -16908,12 +24656,15 @@ fn generateUnionTagTypeNumbered( }; // Here we pre-allocate the maps using the decl arena. try enum_obj.fields.ensureTotalCapacity(new_decl_arena_allocator, fields_len); - try enum_obj.values.ensureTotalCapacityContext(new_decl_arena_allocator, fields_len, .{ .ty = int_ty }); + try enum_obj.values.ensureTotalCapacityContext(new_decl_arena_allocator, fields_len, .{ + .ty = int_ty, + .mod = mod, + }); try new_decl.finalizeNewArena(&new_decl_arena); return enum_ty; } -fn generateUnionTagTypeSimple(sema: *Sema, block: *Block, fields_len: u32) !Type { +fn generateUnionTagTypeSimple(sema: *Sema, block: *Block, fields_len: usize, maybe_union_obj: ?*Module.Union) !Type { const mod = sema.mod; var new_decl_arena = std.heap.ArenaAllocator.init(sema.gpa); @@ -16928,16 +24679,36 @@ fn generateUnionTagTypeSimple(sema: *Sema, block: *Block, fields_len: u32) !Type }; const enum_ty = Type.initPayload(&enum_ty_payload.base); const enum_val = try Value.Tag.ty.create(new_decl_arena_allocator, enum_ty); - // TODO better type name - const new_decl = try mod.createAnonymousDecl(block, .{ - .ty = Type.type, - .val = enum_val, - }); + + const new_decl_index = new_decl_index: { + const union_obj = maybe_union_obj orelse { + break :new_decl_index try mod.createAnonymousDecl(block, .{ + .ty = Type.type, + .val = enum_val, + }); + }; + const src_decl = mod.declPtr(block.src_decl); + const new_decl_index = try mod.allocateNewDecl(block.namespace, src_decl.src_node, block.wip_capture_scope); + errdefer mod.destroyDecl(new_decl_index); + const name = name: { + const fqn = try union_obj.getFullyQualifiedName(mod); + defer sema.gpa.free(fqn); + break :name try std.fmt.allocPrintZ(mod.gpa, "@typeInfo({s}).Union.tag_type.?", .{fqn}); + }; + try mod.initNewAnonDecl(new_decl_index, src_decl.src_line, block.namespace, .{ + .ty = Type.type, + .val = enum_val, + }, name); + sema.mod.declPtr(new_decl_index).name_fully_qualified = true; + break :new_decl_index new_decl_index; + }; + + const new_decl = mod.declPtr(new_decl_index); new_decl.owns_tv = true; - errdefer mod.abortAnonDecl(new_decl); + errdefer mod.abortAnonDecl(new_decl_index); enum_obj.* = .{ - .owner_decl = new_decl, + .owner_decl = new_decl_index, .fields = .{}, .node_offset = 0, }; @@ -16959,7 +24730,7 @@ fn getBuiltin( const opt_builtin_inst = try sema.namespaceLookupRef( block, src, - std_file.root_decl.?.src_namespace, + mod.declPtr(std_file.root_decl.unwrap().?).src_namespace, "builtin", ); const builtin_inst = try sema.analyzeLoad(block, src, opt_builtin_inst.?, src); @@ -16980,7 +24751,9 @@ fn getBuiltinType( name: []const u8, ) CompileError!Type { const ty_inst = try sema.getBuiltin(block, src, name); - return sema.analyzeAsType(block, src, ty_inst); + const result_ty = try sema.analyzeAsType(block, src, ty_inst); + try sema.queueFullTypeResolution(result_ty); + return result_ty; } /// There is another implementation of this in `Type.onePossibleValue`. This one @@ -17008,6 +24781,7 @@ pub fn typeHasOnePossibleValue( .i8, .u16, .i16, + .u29, .u32, .i32, .u64, @@ -17027,6 +24801,10 @@ pub fn typeHasOnePossibleValue( .bool, .type, .anyerror, + .error_set_single, + .error_set, + .error_set_merged, + .error_union, .fn_noreturn_no_args, .fn_void_no_args, .fn_naked_noreturn_no_args, @@ -17040,16 +24818,11 @@ pub fn typeHasOnePossibleValue( .const_slice, .mut_slice, .anyopaque, - .optional, .optional_single_mut_pointer, .optional_single_const_pointer, .enum_literal, .anyerror_void_error_union, - .error_union, - .error_set, - .error_set_single, .error_set_inferred, - .error_set_merged, .@"opaque", .var_args_param, .manyptr_u8, @@ -17078,10 +24851,21 @@ pub fn typeHasOnePossibleValue( .bound_fn, => return null, + .optional => { + var buf: Type.Payload.ElemType = undefined; + const child_ty = ty.optionalChild(&buf); + if (child_ty.isNoReturn()) { + return Value.@"null"; + } else { + return null; + } + }, + .@"struct" => { const resolved_ty = try sema.resolveTypeFields(block, src, ty); const s = resolved_ty.castTag(.@"struct").?.data; for (s.fields.values()) |value| { + if (value.is_comptime) continue; if ((try sema.typeHasOnePossibleValue(block, src, value.ty)) == null) { return null; } @@ -17089,8 +24873,8 @@ pub fn typeHasOnePossibleValue( return Value.initTag(.empty_struct_value); }, - .tuple => { - const tuple = ty.castTag(.tuple).?.data; + .tuple, .anon_struct => { + const tuple = ty.tupleFields(); for (tuple.values) |val| { if (val.tag() == .unreachable_value) { return null; // non-comptime field @@ -17226,7 +25010,7 @@ fn enumFieldSrcLoc( .container_field, => { if (it_index == field_index) { - return .{ .node_offset = decl.nodeIndexToRelative(member_node) }; + return LazySrcLoc.nodeOffset(decl.nodeIndexToRelative(member_node)); } it_index += 1; }, @@ -17241,7 +25025,7 @@ fn typeOf(sema: *Sema, inst: Air.Inst.Ref) Type { return sema.getTmpAir().typeOf(inst); } -fn getTmpAir(sema: Sema) Air { +pub fn getTmpAir(sema: Sema) Air { return .{ .instructions = sema.air_instructions.slice(), .extra = sema.air_extra.items, @@ -17255,6 +25039,7 @@ pub fn addType(sema: *Sema, ty: Type) !Air.Inst.Ref { .u8 => return .u8_type, .i8 => return .i8_type, .u16 => return .u16_type, + .u29 => return .u29_type, .i16 => return .i16_type, .u32 => return .u32_type, .i32 => return .i32_type, @@ -17324,8 +25109,16 @@ fn addIntUnsigned(sema: *Sema, ty: Type, int: u64) CompileError!Air.Inst.Ref { return sema.addConstant(ty, try Value.Tag.int_u64.create(sema.arena, int)); } +fn addBool(sema: *Sema, ty: Type, boolean: bool) CompileError!Air.Inst.Ref { + return switch (ty.zigTypeTag()) { + .Vector => sema.addConstant(ty, try Value.Tag.repeated.create(sema.arena, Value.makeBool(boolean))), + .Bool => try sema.resolveInst(if (boolean) .bool_true else .bool_false), + else => unreachable, + }; +} + fn addConstUndef(sema: *Sema, ty: Type) CompileError!Air.Inst.Ref { - return sema.addConstant(ty, Value.initTag(.undef)); + return sema.addConstant(ty, Value.undef); } pub fn addConstant(sema: *Sema, ty: Type, val: Value) SemaError!Air.Inst.Ref { @@ -17363,7 +25156,7 @@ pub fn addExtraAssumeCapacity(sema: *Sema, extra: anytype) u32 { } fn appendRefsAssumeCapacity(sema: *Sema, refs: []const Air.Inst.Ref) void { - const coerced = @bitCast([]const u32, refs); + const coerced = @ptrCast([]const u32, refs); sema.air_extra.appendSliceAssumeCapacity(coerced); } @@ -17395,7 +25188,7 @@ fn analyzeComptimeAlloc( // Needed to make an anon decl with type `var_type` (the `finish()` call below). _ = try sema.typeHasOnePossibleValue(block, src, var_type); - const ptr_type = try Type.ptr(sema.arena, .{ + const ptr_type = try Type.ptr(sema.arena, sema.mod, .{ .pointee_type = var_type, .@"addrspace" = target_util.defaultAddressSpace(sema.mod.getTarget(), .global_constant), .@"align" = alignment, @@ -17404,24 +25197,21 @@ fn analyzeComptimeAlloc( var anon_decl = try block.startAnonDecl(src); defer anon_decl.deinit(); - const align_val = if (alignment == 0) - Value.@"null" - else - try Value.Tag.int_u64.create(anon_decl.arena(), alignment); - - const decl = try anon_decl.finish( + const decl_index = try anon_decl.finish( try var_type.copy(anon_decl.arena()), // There will be stores before the first load, but they may be to sub-elements or // sub-fields. So we need to initialize with undef to allow the mechanism to expand // into fields/elements and have those overridden with stored values. Value.undef, + alignment, ); - decl.align_val = align_val; + const decl = sema.mod.declPtr(decl_index); + decl.@"align" = alignment; - try sema.mod.declareDeclDependency(sema.owner_decl, decl); + try sema.mod.declareDeclDependency(sema.owner_decl_index, decl_index); return sema.addConstant(ptr_type, try Value.Tag.decl_ref_mut.create(sema.arena, .{ .runtime_index = block.runtime_index, - .decl = decl, + .decl_index = decl_index, })); } @@ -17454,10 +25244,14 @@ pub fn analyzeAddrspace( const address_space = addrspace_tv.val.toEnum(std.builtin.AddressSpace); const target = sema.mod.getTarget(); const arch = target.cpu.arch; + const is_gpu = arch == .nvptx or arch == .nvptx64; const supported = switch (address_space) { .generic => true, .gs, .fs, .ss => (arch == .i386 or arch == .x86_64) and ctx == .pointer, + // TODO: check that .shared and .local are left uninitialized + .global, .param, .shared, .local => is_gpu, + .constant => is_gpu and (ctx == .constant), }; if (!supported) { @@ -17468,7 +25262,6 @@ pub fn analyzeAddrspace( .constant => "constant values", .pointer => "pointers", }; - return sema.fail( block, src, @@ -17483,51 +25276,61 @@ pub fn analyzeAddrspace( /// 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 { - const target = sema.mod.getTarget(); const load_ty = ptr_ty.childType(); - const parent = sema.beginComptimePtrLoad(block, src, ptr_val) catch |err| switch (err) { + const target = sema.mod.getTarget(); + const deref = sema.beginComptimePtrLoad(block, src, ptr_val, load_ty) catch |err| switch (err) { error.RuntimeLoad => return null, else => |e| return e, }; - // We have a Value that lines up in virtual memory exactly with what we want to load. - // If the Type is in-memory coercable to `load_ty`, it may be returned without modifications. - const coerce_in_mem_ok = - (try sema.coerceInMemoryAllowed(block, load_ty, parent.ty, false, target, src, src)) == .ok or - (try sema.coerceInMemoryAllowed(block, parent.ty, load_ty, false, target, src, src)) == .ok; - if (coerce_in_mem_ok) { - if (parent.is_mutable) { - // The decl whose value we are obtaining here may be overwritten with - // a different value upon further semantic analysis, which would - // invalidate this memory. So we must copy here. - return try parent.val.copy(sema.arena); + + if (deref.pointee) |tv| { + const coerce_in_mem_ok = + (try sema.coerceInMemoryAllowed(block, load_ty, tv.ty, false, target, src, src)) == .ok or + (try sema.coerceInMemoryAllowed(block, tv.ty, load_ty, false, target, src, src)) == .ok; + if (coerce_in_mem_ok) { + // We have a Value that lines up in virtual memory exactly with what we want to load, + // and it is in-memory coercible to load_ty. It may be returned without modifications. + if (deref.is_mutable) { + // The decl whose value we are obtaining here may be overwritten with + // a different value upon further semantic analysis, which would + // invalidate this memory. So we must copy here. + return try tv.val.copy(sema.arena); + } + return tv.val; } - return parent.val; } - // The type is not in-memory coercable, so it must be bitcasted according - // to the pointer type we are performing the load through. + // The type is not in-memory coercible or the direct dereference failed, so it must + // be bitcast according to the pointer type we are performing the load through. + if (!load_ty.hasWellDefinedLayout()) + return sema.fail(block, src, "comptime dereference requires '{}' to have a well-defined layout, but it does not.", .{load_ty.fmt(sema.mod)}); - // TODO emit a compile error if the types are not allowed to be bitcasted + const load_sz = try sema.typeAbiSize(block, src, load_ty); - if (parent.ty.abiSize(target) >= load_ty.abiSize(target)) { - // The Type it is stored as in the compiler has an ABI size greater or equal to - // the ABI size of `load_ty`. We may perform the bitcast based on - // `parent.val` alone (more efficient). - return try sema.bitCastVal(block, src, parent.val, parent.ty, load_ty); - } + // Try the smaller bit-cast first, since that's more efficient than using the larger `parent` + if (deref.pointee) |tv| if (load_sz <= try sema.typeAbiSize(block, src, tv.ty)) + return try sema.bitCastVal(block, src, tv.val, tv.ty, load_ty, 0); - // The Type it is stored as in the compiler has an ABI size less than the ABI size - // of `load_ty`. The bitcast must be performed based on the `parent.root_val` - // and reinterpreted starting at `parent.byte_offset`. - return sema.fail(block, src, "TODO: implement bitcast with index offset", .{}); + // If that fails, try to bit-cast from the largest parent value with a well-defined layout + if (deref.parent) |parent| if (load_sz + parent.byte_offset <= try sema.typeAbiSize(block, src, parent.tv.ty)) + return try sema.bitCastVal(block, src, parent.tv.val, parent.tv.ty, load_ty, parent.byte_offset); + + if (deref.ty_without_well_defined_layout) |bad_ty| { + // We got no parent for bit-casting, or the parent we got was too small. Either way, the problem + // is that some type we encountered when de-referencing does not have a well-defined layout. + return sema.fail(block, src, "comptime dereference requires '{}' to have a well-defined layout, but it does not.", .{bad_ty.fmt(sema.mod)}); + } else { + // If all encountered types had well-defined layouts, the parent is the root decl and it just + // wasn't big enough for the load. + return sema.fail(block, src, "dereference of '{}' exceeds bounds of containing decl of type '{}'", .{ ptr_ty.fmt(sema.mod), deref.parent.?.tv.ty.fmt(sema.mod) }); + } } /// 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 { - return std.math.cast(usize, int) catch |err| switch (err) { - error.Overflow => return sema.fail(block, src, "expression produces integer value {d} which is too big for this compiler implementation to handle", .{int}), - }; + 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, @@ -17556,6 +25359,7 @@ fn typePtrOrOptionalPtrTy( .many_mut_pointer, .manyptr_u8, .manyptr_const_u8, + .manyptr_const_u8_sentinel_0, => return ty, .pointer => switch (ty.ptrSize()) { @@ -17595,13 +25399,18 @@ fn typePtrOrOptionalPtrTy( /// This function returns false negatives when structs and unions are having their /// field types resolved. /// TODO assert the return value matches `ty.comptimeOnly` -fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool { +/// TODO merge these implementations together with the "advanced"/sema_kit pattern seen +/// elsewhere in value.zig +pub fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); return switch (ty.tag()) { .u1, .u8, .i8, .u16, .i16, + .u29, .u32, .i32, .u64, @@ -17713,10 +25522,11 @@ fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) C return sema.typeRequiresComptime(block, src, ty.optionalChild(&buf)); }, - .tuple => { - const tuple = ty.castTag(.tuple).?.data; - for (tuple.types) |field_ty| { - if (try sema.typeRequiresComptime(block, src, field_ty)) { + .tuple, .anon_struct => { + const tuple = ty.tupleFields(); + for (tuple.types) |field_ty, i| { + const have_comptime_val = tuple.values[i].tag() != .unreachable_value; + if (!have_comptime_val and try sema.typeRequiresComptime(block, src, field_ty)) { return true; } } @@ -17736,6 +25546,7 @@ fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) C struct_obj.requires_comptime = .wip; for (struct_obj.fields.values()) |field| { + if (field.is_comptime) continue; if (try sema.typeRequiresComptime(block, src, field.ty)) { struct_obj.requires_comptime = .yes; return true; @@ -17788,9 +25599,33 @@ fn typeRequiresComptime(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) C } pub fn typeHasRuntimeBits(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!bool { - if ((try sema.typeHasOnePossibleValue(block, src, ty)) != null) return false; - if (try sema.typeRequiresComptime(block, src, ty)) return false; - return true; + return ty.hasRuntimeBitsAdvanced(false, sema.kit(block, src)); +} + +fn typeAbiSize(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) !u64 { + try sema.resolveTypeLayout(block, src, ty); + const target = sema.mod.getTarget(); + return ty.abiSize(target); +} + +fn typeAbiAlignment(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) CompileError!u32 { + const target = sema.mod.getTarget(); + return (try ty.abiAlignmentAdvanced(target, .{ .sema_kit = sema.kit(block, src) })).scalar; +} + +/// Not valid to call for packed unions. +/// Keep implementation in sync with `Module.Union.Field.normalAlignment`. +fn unionFieldAlignment( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + field: Module.Union.Field, +) !u32 { + if (field.abi_align == 0) { + return sema.typeAbiAlignment(block, src, field.ty); + } else { + return field.abi_align; + } } /// Synchronize logic with `Type.isFnOrHasRuntimeBits`. @@ -17808,3 +25643,803 @@ pub fn fnHasRuntimeBits(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type) C } return true; } + +fn unionFieldIndex( + sema: *Sema, + block: *Block, + unresolved_union_ty: Type, + field_name: []const u8, + field_src: LazySrcLoc, +) !u32 { + const union_ty = try sema.resolveTypeFields(block, field_src, unresolved_union_ty); + const union_obj = union_ty.cast(Type.Payload.Union).?.data; + const field_index_usize = union_obj.fields.getIndex(field_name) orelse + return sema.failWithBadUnionFieldAccess(block, union_obj, field_src, field_name); + return @intCast(u32, field_index_usize); +} + +fn structFieldIndex( + sema: *Sema, + block: *Block, + unresolved_struct_ty: Type, + field_name: []const u8, + field_src: LazySrcLoc, +) !u32 { + const struct_ty = try sema.resolveTypeFields(block, field_src, unresolved_struct_ty); + const struct_obj = struct_ty.castTag(.@"struct").?.data; + const field_index_usize = struct_obj.fields.getIndex(field_name) orelse + return sema.failWithBadStructFieldAccess(block, struct_obj, field_src, field_name); + return @intCast(u32, field_index_usize); +} + +fn anonStructFieldIndex( + sema: *Sema, + block: *Block, + struct_ty: Type, + field_name: []const u8, + field_src: LazySrcLoc, +) !u32 { + const anon_struct = struct_ty.castTag(.anon_struct).?.data; + for (anon_struct.names) |name, i| { + if (mem.eql(u8, name, field_name)) { + return @intCast(u32, i); + } + } + return sema.fail(block, field_src, "anonymous struct '{}' has no such field '{s}'", .{ + struct_ty.fmt(sema.mod), field_name, + }); +} + +fn kit(sema: *Sema, block: *Block, src: LazySrcLoc) Module.WipAnalysis { + return .{ .sema = sema, .block = block, .src = src }; +} + +fn queueFullTypeResolution(sema: *Sema, ty: Type) !void { + const inst_ref = try sema.addType(ty); + try sema.types_to_resolve.append(sema.gpa, inst_ref); +} + +fn intAdd(sema: *Sema, block: *Block, src: LazySrcLoc, lhs: Value, rhs: Value, ty: Type) !Value { + if (ty.zigTypeTag() == .Vector) { + const result_data = try sema.arena.alloc(Value, ty.vectorLen()); + for (result_data) |*scalar, i| { + scalar.* = try sema.intAddScalar(block, src, lhs.indexVectorlike(i), rhs.indexVectorlike(i)); + } + return Value.Tag.aggregate.create(sema.arena, result_data); + } + return sema.intAddScalar(block, src, lhs, rhs); +} + +fn intAddScalar(sema: *Sema, block: *Block, src: LazySrcLoc, lhs: Value, rhs: Value) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const target = sema.mod.getTarget(); + const lhs_bigint = try lhs.toBigIntAdvanced(&lhs_space, target, sema.kit(block, src)); + const rhs_bigint = try rhs.toBigIntAdvanced(&rhs_space, target, sema.kit(block, src)); + const limbs = try sema.arena.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, + ); + var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.add(lhs_bigint, rhs_bigint); + return Value.fromBigInt(sema.arena, result_bigint.toConst()); +} + +/// Supports both (vectors of) floats and ints; handles undefined scalars. +fn numberAddWrap( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value { + if (ty.zigTypeTag() == .Vector) { + const result_data = try sema.arena.alloc(Value, ty.vectorLen()); + for (result_data) |*scalar, i| { + scalar.* = try sema.numberAddWrapScalar(block, src, lhs.indexVectorlike(i), rhs.indexVectorlike(i), ty.scalarType()); + } + return Value.Tag.aggregate.create(sema.arena, result_data); + } + return sema.numberAddWrapScalar(block, src, lhs, rhs, ty); +} + +/// Supports both floats and ints; handles undefined. +fn numberAddWrapScalar( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.zigTypeTag() == .ComptimeInt) { + return sema.intAdd(block, src, lhs, rhs, ty); + } + + if (ty.isAnyFloat()) { + return sema.floatAdd(lhs, rhs, ty); + } + + const overflow_result = try sema.intAddWithOverflow(block, src, lhs, rhs, ty); + return overflow_result.wrapped_result; +} + +fn intSub( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value { + if (ty.zigTypeTag() == .Vector) { + const result_data = try sema.arena.alloc(Value, ty.vectorLen()); + for (result_data) |*scalar, i| { + scalar.* = try sema.intSubScalar(block, src, lhs.indexVectorlike(i), rhs.indexVectorlike(i)); + } + return Value.Tag.aggregate.create(sema.arena, result_data); + } + return sema.intSubScalar(block, src, lhs, rhs); +} + +fn intSubScalar(sema: *Sema, block: *Block, src: LazySrcLoc, lhs: Value, rhs: Value) !Value { + // TODO is this a performance issue? maybe we should try the operation without + // resorting to BigInt first. + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const target = sema.mod.getTarget(); + const lhs_bigint = try lhs.toBigIntAdvanced(&lhs_space, target, sema.kit(block, src)); + const rhs_bigint = try rhs.toBigIntAdvanced(&rhs_space, target, sema.kit(block, src)); + const limbs = try sema.arena.alloc( + std.math.big.Limb, + std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1, + ); + var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + result_bigint.sub(lhs_bigint, rhs_bigint); + return Value.fromBigInt(sema.arena, result_bigint.toConst()); +} + +/// Supports both (vectors of) floats and ints; handles undefined scalars. +fn numberSubWrap( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value { + if (ty.zigTypeTag() == .Vector) { + const result_data = try sema.arena.alloc(Value, ty.vectorLen()); + for (result_data) |*scalar, i| { + scalar.* = try sema.numberSubWrapScalar(block, src, lhs.indexVectorlike(i), rhs.indexVectorlike(i), ty.scalarType()); + } + return Value.Tag.aggregate.create(sema.arena, result_data); + } + return sema.numberSubWrapScalar(block, src, lhs, rhs, ty); +} + +/// Supports both floats and ints; handles undefined. +fn numberSubWrapScalar( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value { + if (lhs.isUndef() or rhs.isUndef()) return Value.initTag(.undef); + + if (ty.zigTypeTag() == .ComptimeInt) { + return sema.intSub(block, src, lhs, rhs, ty); + } + + if (ty.isAnyFloat()) { + return sema.floatSub(lhs, rhs, ty); + } + + const overflow_result = try sema.intSubWithOverflow(block, src, lhs, rhs, ty); + return overflow_result.wrapped_result; +} + +fn floatAdd( + sema: *Sema, + lhs: Value, + rhs: Value, + float_type: Type, +) !Value { + if (float_type.zigTypeTag() == .Vector) { + const result_data = try sema.arena.alloc(Value, float_type.vectorLen()); + for (result_data) |*scalar, i| { + scalar.* = try sema.floatAddScalar(lhs.indexVectorlike(i), rhs.indexVectorlike(i), float_type.scalarType()); + } + return Value.Tag.aggregate.create(sema.arena, result_data); + } + return sema.floatAddScalar(lhs, rhs, float_type); +} + +fn floatAddScalar( + sema: *Sema, + lhs: Value, + rhs: Value, + float_type: Type, +) !Value { + const target = sema.mod.getTarget(); + switch (float_type.floatBits(target)) { + 16 => { + const lhs_val = lhs.toFloat(f16); + const rhs_val = rhs.toFloat(f16); + return Value.Tag.float_16.create(sema.arena, lhs_val + rhs_val); + }, + 32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(sema.arena, lhs_val + rhs_val); + }, + 64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(sema.arena, lhs_val + rhs_val); + }, + 80 => { + const lhs_val = lhs.toFloat(f80); + const rhs_val = rhs.toFloat(f80); + return Value.Tag.float_80.create(sema.arena, lhs_val + rhs_val); + }, + 128 => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(sema.arena, lhs_val + rhs_val); + }, + else => unreachable, + } +} + +fn floatSub( + sema: *Sema, + lhs: Value, + rhs: Value, + float_type: Type, +) !Value { + if (float_type.zigTypeTag() == .Vector) { + const result_data = try sema.arena.alloc(Value, float_type.vectorLen()); + for (result_data) |*scalar, i| { + scalar.* = try sema.floatSubScalar(lhs.indexVectorlike(i), rhs.indexVectorlike(i), float_type.scalarType()); + } + return Value.Tag.aggregate.create(sema.arena, result_data); + } + return sema.floatSubScalar(lhs, rhs, float_type); +} + +fn floatSubScalar( + sema: *Sema, + lhs: Value, + rhs: Value, + float_type: Type, +) !Value { + const target = sema.mod.getTarget(); + switch (float_type.floatBits(target)) { + 16 => { + const lhs_val = lhs.toFloat(f16); + const rhs_val = rhs.toFloat(f16); + return Value.Tag.float_16.create(sema.arena, lhs_val - rhs_val); + }, + 32 => { + const lhs_val = lhs.toFloat(f32); + const rhs_val = rhs.toFloat(f32); + return Value.Tag.float_32.create(sema.arena, lhs_val - rhs_val); + }, + 64 => { + const lhs_val = lhs.toFloat(f64); + const rhs_val = rhs.toFloat(f64); + return Value.Tag.float_64.create(sema.arena, lhs_val - rhs_val); + }, + 80 => { + const lhs_val = lhs.toFloat(f80); + const rhs_val = rhs.toFloat(f80); + return Value.Tag.float_80.create(sema.arena, lhs_val - rhs_val); + }, + 128 => { + const lhs_val = lhs.toFloat(f128); + const rhs_val = rhs.toFloat(f128); + return Value.Tag.float_128.create(sema.arena, lhs_val - rhs_val); + }, + else => unreachable, + } +} + +fn intSubWithOverflow( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value.OverflowArithmeticResult { + if (ty.zigTypeTag() == .Vector) { + const overflowed_data = try sema.arena.alloc(Value, ty.vectorLen()); + const result_data = try sema.arena.alloc(Value, ty.vectorLen()); + for (result_data) |*scalar, i| { + const of_math_result = try sema.intSubWithOverflowScalar(block, src, lhs.indexVectorlike(i), rhs.indexVectorlike(i), ty.scalarType()); + overflowed_data[i] = of_math_result.overflowed; + scalar.* = of_math_result.wrapped_result; + } + return Value.OverflowArithmeticResult{ + .overflowed = try Value.Tag.aggregate.create(sema.arena, overflowed_data), + .wrapped_result = try Value.Tag.aggregate.create(sema.arena, result_data), + }; + } + return sema.intSubWithOverflowScalar(block, src, lhs, rhs, ty); +} + +fn intSubWithOverflowScalar( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value.OverflowArithmeticResult { + const target = sema.mod.getTarget(); + const info = ty.intInfo(target); + + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = try lhs.toBigIntAdvanced(&lhs_space, target, sema.kit(block, src)); + const rhs_bigint = try rhs.toBigIntAdvanced(&rhs_space, target, sema.kit(block, src)); + const limbs = try sema.arena.alloc( + std.math.big.Limb, + std.math.big.int.calcTwosCompLimbCount(info.bits), + ); + var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits); + const wrapped_result = try Value.fromBigInt(sema.arena, result_bigint.toConst()); + return Value.OverflowArithmeticResult{ + .overflowed = Value.makeBool(overflowed), + .wrapped_result = wrapped_result, + }; +} + +fn floatToInt( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + val: Value, + float_ty: Type, + int_ty: Type, +) CompileError!Value { + if (float_ty.zigTypeTag() == .Vector) { + const elem_ty = float_ty.childType(); + const result_data = try sema.arena.alloc(Value, float_ty.vectorLen()); + for (result_data) |*scalar, i| { + scalar.* = try sema.floatToIntScalar(block, src, val.indexVectorlike(i), elem_ty, int_ty.scalarType()); + } + return Value.Tag.aggregate.create(sema.arena, result_data); + } + return sema.floatToIntScalar(block, src, val, float_ty, int_ty); +} + +fn floatToIntScalar( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + val: Value, + float_ty: Type, + int_ty: Type, +) CompileError!Value { + const Limb = std.math.big.Limb; + + const float = val.toFloat(f128); + if (std.math.isNan(float)) { + return sema.fail(block, src, "float value NaN cannot be stored in integer type '{}'", .{ + int_ty.fmt(sema.mod), + }); + } + if (std.math.isInf(float)) { + return sema.fail(block, src, "float value Inf cannot be stored in integer type '{}'", .{ + int_ty.fmt(sema.mod), + }); + } + + const is_negative = std.math.signbit(float); + const floored = @floor(@fabs(float)); + + var rational = try std.math.big.Rational.init(sema.arena); + defer rational.deinit(); + rational.setFloat(f128, floored) catch |err| switch (err) { + error.NonFiniteFloat => unreachable, + error.OutOfMemory => return error.OutOfMemory, + }; + + // The float is reduced in rational.setFloat, so we assert that denominator is equal to one + const big_one = std.math.big.int.Const{ .limbs = &.{1}, .positive = true }; + assert(rational.q.toConst().eqAbs(big_one)); + + const result_limbs = try sema.arena.dupe(Limb, rational.p.toConst().limbs); + const result = if (is_negative) + try Value.Tag.int_big_negative.create(sema.arena, result_limbs) + else + try Value.Tag.int_big_positive.create(sema.arena, result_limbs); + + if (!(try sema.intFitsInType(block, src, result, int_ty, null))) { + return sema.fail(block, src, "float value '{}' cannot be stored in integer type '{}'", .{ + val.fmtValue(float_ty, sema.mod), int_ty.fmt(sema.mod), + }); + } + return result; +} + +/// Asserts the value is an integer, and the destination type is ComptimeInt or Int. +/// Vectors are also accepted. Vector results are reduced with AND. +fn intFitsInType( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + self: Value, + ty: Type, + vector_index: ?*usize, +) CompileError!bool { + const target = sema.mod.getTarget(); + switch (self.tag()) { + .zero, + .undef, + .bool_false, + => return true, + + .one, + .bool_true, + => switch (ty.zigTypeTag()) { + .Int => { + const info = ty.intInfo(target); + return switch (info.signedness) { + .signed => info.bits >= 2, + .unsigned => info.bits >= 1, + }; + }, + .ComptimeInt => return true, + else => unreachable, + }, + + .lazy_align => { + const info = ty.intInfo(target); + const max_needed_bits = @as(u16, 16) + @boolToInt(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 sema.typeAbiAlignment(block, src, self.castTag(.lazy_align).?.data); + if (x == 0) return true; + const actual_needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signedness == .signed); + return info.bits >= actual_needed_bits; + }, + .lazy_size => { + const info = ty.intInfo(target); + const max_needed_bits = @as(u16, 64) + @boolToInt(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 sema.typeAbiSize(block, src, self.castTag(.lazy_size).?.data); + if (x == 0) return true; + const actual_needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signedness == .signed); + return info.bits >= actual_needed_bits; + }, + + .int_u64 => switch (ty.zigTypeTag()) { + .Int => { + const x = self.castTag(.int_u64).?.data; + if (x == 0) return true; + const info = ty.intInfo(target); + const needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signedness == .signed); + return info.bits >= needed_bits; + }, + .ComptimeInt => return true, + else => unreachable, + }, + .int_i64 => switch (ty.zigTypeTag()) { + .Int => { + const x = self.castTag(.int_i64).?.data; + if (x == 0) return true; + const info = ty.intInfo(target); + if (info.signedness == .unsigned and x < 0) + return false; + var buffer: Value.BigIntSpace = undefined; + return (try self.toBigIntAdvanced(&buffer, target, sema.kit(block, src))).fitsInTwosComp(info.signedness, info.bits); + }, + .ComptimeInt => return true, + else => unreachable, + }, + .int_big_positive => switch (ty.zigTypeTag()) { + .Int => { + const info = ty.intInfo(target); + return self.castTag(.int_big_positive).?.asBigInt().fitsInTwosComp(info.signedness, info.bits); + }, + .ComptimeInt => return true, + else => unreachable, + }, + .int_big_negative => switch (ty.zigTypeTag()) { + .Int => { + const info = ty.intInfo(target); + return self.castTag(.int_big_negative).?.asBigInt().fitsInTwosComp(info.signedness, info.bits); + }, + .ComptimeInt => return true, + else => unreachable, + }, + + .the_only_possible_value => { + assert(ty.intInfo(target).bits == 0); + return true; + }, + + .decl_ref_mut, + .extern_fn, + .decl_ref, + .function, + .variable, + => switch (ty.zigTypeTag()) { + .Int => { + const info = ty.intInfo(target); + const ptr_bits = target.cpu.arch.ptrBitWidth(); + return switch (info.signedness) { + .signed => info.bits > ptr_bits, + .unsigned => info.bits >= ptr_bits, + }; + }, + .ComptimeInt => return true, + else => unreachable, + }, + + .aggregate => { + assert(ty.zigTypeTag() == .Vector); + for (self.castTag(.aggregate).?.data) |elem, i| { + if (!(try sema.intFitsInType(block, src, elem, ty.scalarType(), null))) { + if (vector_index) |some| some.* = i; + return false; + } + } + return true; + }, + + else => unreachable, + } +} + +fn intInRange( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + tag_ty: Type, + int_val: Value, + end: usize, +) !bool { + if (try int_val.compareWithZeroAdvanced(.lt, sema.kit(block, src))) return false; + var end_payload: Value.Payload.U64 = .{ + .base = .{ .tag = .int_u64 }, + .data = end, + }; + const end_val = Value.initPayload(&end_payload.base); + if (try sema.compare(block, src, int_val, .gte, end_val, tag_ty)) return false; + return true; +} + +/// Asserts the type is an enum. +fn enumHasInt( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + ty: Type, + int: Value, +) CompileError!bool { + switch (ty.tag()) { + .enum_nonexhaustive => return sema.intFitsInType(block, src, int, ty, null), + .enum_full => { + const enum_full = ty.castTag(.enum_full).?.data; + const tag_ty = enum_full.tag_ty; + if (enum_full.values.count() == 0) { + return intInRange(sema, block, src, tag_ty, int, enum_full.fields.count()); + } else { + return enum_full.values.containsContext(int, .{ + .ty = tag_ty, + .mod = sema.mod, + }); + } + }, + .enum_numbered => { + const enum_obj = ty.castTag(.enum_numbered).?.data; + const tag_ty = enum_obj.tag_ty; + if (enum_obj.values.count() == 0) { + return intInRange(sema, block, src, tag_ty, int, enum_obj.fields.count()); + } else { + return enum_obj.values.containsContext(int, .{ + .ty = tag_ty, + .mod = sema.mod, + }); + } + }, + .enum_simple => { + const enum_simple = ty.castTag(.enum_simple).?.data; + const fields_len = enum_simple.fields.count(); + const bits = std.math.log2_int_ceil(usize, fields_len); + var buffer: Type.Payload.Bits = .{ + .base = .{ .tag = .int_unsigned }, + .data = bits, + }; + const tag_ty = Type.initPayload(&buffer.base); + return intInRange(sema, block, src, tag_ty, int, fields_len); + }, + .atomic_order, + .atomic_rmw_op, + .calling_convention, + .address_space, + .float_mode, + .reduce_op, + .call_options, + .prefetch_options, + .export_options, + .extern_options, + => unreachable, + + else => unreachable, + } +} + +fn intAddWithOverflow( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value.OverflowArithmeticResult { + if (ty.zigTypeTag() == .Vector) { + const overflowed_data = try sema.arena.alloc(Value, ty.vectorLen()); + const result_data = try sema.arena.alloc(Value, ty.vectorLen()); + for (result_data) |*scalar, i| { + const of_math_result = try sema.intAddWithOverflowScalar(block, src, lhs.indexVectorlike(i), rhs.indexVectorlike(i), ty.scalarType()); + overflowed_data[i] = of_math_result.overflowed; + scalar.* = of_math_result.wrapped_result; + } + return Value.OverflowArithmeticResult{ + .overflowed = try Value.Tag.aggregate.create(sema.arena, overflowed_data), + .wrapped_result = try Value.Tag.aggregate.create(sema.arena, result_data), + }; + } + return sema.intAddWithOverflowScalar(block, src, lhs, rhs, ty); +} + +fn intAddWithOverflowScalar( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) !Value.OverflowArithmeticResult { + const target = sema.mod.getTarget(); + const info = ty.intInfo(target); + + var lhs_space: Value.BigIntSpace = undefined; + var rhs_space: Value.BigIntSpace = undefined; + const lhs_bigint = try lhs.toBigIntAdvanced(&lhs_space, target, sema.kit(block, src)); + const rhs_bigint = try rhs.toBigIntAdvanced(&rhs_space, target, sema.kit(block, src)); + const limbs = try sema.arena.alloc( + std.math.big.Limb, + std.math.big.int.calcTwosCompLimbCount(info.bits), + ); + var result_bigint = std.math.big.int.Mutable{ .limbs = limbs, .positive = undefined, .len = undefined }; + const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, info.signedness, info.bits); + const result = try Value.fromBigInt(sema.arena, result_bigint.toConst()); + return Value.OverflowArithmeticResult{ + .overflowed = Value.makeBool(overflowed), + .wrapped_result = result, + }; +} + +/// Asserts the values are comparable. Both operands have type `ty`. +/// Vector results will be reduced with AND. +fn compare( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + op: std.math.CompareOperator, + rhs: Value, + ty: Type, +) CompileError!bool { + if (ty.zigTypeTag() == .Vector) { + var i: usize = 0; + while (i < ty.vectorLen()) : (i += 1) { + if (!(try sema.compareScalar(block, src, lhs.indexVectorlike(i), op, rhs.indexVectorlike(i), ty.scalarType()))) { + return false; + } + } + return true; + } + return sema.compareScalar(block, src, lhs, op, rhs, ty); +} + +/// Asserts the values are comparable. Both operands have type `ty`. +fn compareScalar( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + op: std.math.CompareOperator, + rhs: Value, + ty: Type, +) CompileError!bool { + switch (op) { + .eq => return sema.valuesEqual(block, src, lhs, rhs, ty), + .neq => return !(try sema.valuesEqual(block, src, lhs, rhs, ty)), + else => return Value.compareHeteroAdvanced(lhs, op, rhs, sema.mod.getTarget(), sema.kit(block, src)), + } +} + +fn valuesEqual( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + rhs: Value, + ty: Type, +) CompileError!bool { + return Value.eqlAdvanced(lhs, rhs, ty, sema.mod, sema.kit(block, src)); +} + +/// Asserts the values are comparable vectors of type `ty`. +fn compareVector( + sema: *Sema, + block: *Block, + src: LazySrcLoc, + lhs: Value, + op: std.math.CompareOperator, + rhs: Value, + ty: Type, +) !Value { + assert(ty.zigTypeTag() == .Vector); + const result_data = try sema.arena.alloc(Value, ty.vectorLen()); + for (result_data) |*scalar, i| { + const res_bool = try sema.compareScalar(block, src, lhs.indexVectorlike(i), op, rhs.indexVectorlike(i), ty.scalarType()); + scalar.* = Value.makeBool(res_bool); + } + return Value.Tag.aggregate.create(sema.arena, result_data); +} + +/// Returns the type of a pointer to an element. +/// Asserts that the type is a pointer, and that the element type is indexable. +/// For *[N]T, return *T +/// For [*]T, returns *T +/// For []T, returns *T +/// Handles const-ness and address spaces in particular. +/// This code is duplicated in `analyzePtrArithmetic`. +fn elemPtrType(sema: *Sema, ptr_ty: Type, offset: ?usize) !Type { + const ptr_info = ptr_ty.ptrInfo().data; + const elem_ty = ptr_ty.elemType2(); + const allow_zero = ptr_info.@"allowzero" and (offset orelse 0) == 0; + const alignment: u32 = a: { + // Calculate the new pointer alignment. + if (ptr_info.@"align" == 0) { + // ABI-aligned pointer. Any pointer arithmetic maintains the same ABI-alignedness. + break :a 0; + } + // If the addend is not a comptime-known value we can still count on + // it being a multiple of the type size. + const target = sema.mod.getTarget(); + const elem_size = elem_ty.abiSize(target); + const addend = if (offset) |off| elem_size * off 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 = @as(u32, 1) << @intCast(u5, @ctz(u64, addend | ptr_info.@"align")); + break :a new_align; + }; + return try Type.ptr(sema.arena, sema.mod, .{ + .pointee_type = elem_ty, + .mutable = ptr_info.mutable, + .@"addrspace" = ptr_info.@"addrspace", + .@"allowzero" = allow_zero, + .@"volatile" = ptr_info.@"volatile", + .@"align" = alignment, + }); +} |
