From 0965724e31666d156ca96375eee591380f3c9042 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 27 Jul 2020 22:44:18 -0700 Subject: [PATCH] self-hosted: refactor some code out of Module.zig into zir_sema.zig This makes sense from an organizational point of view, as explained by this new doc comment at the top of the new file: //! Semantic analysis of ZIR instructions. //! This file operates on a `Module` instance, transforming untyped ZIR //! instructions into semantically-analyzed IR instructions. It does type //! checking, comptime control flow, and safety-check generation. This is the //! the heart of the Zig compiler. //! When deciding if something goes into this file or into Module, here is a //! guiding principle: if it has to do with (untyped) ZIR instructions, it goes //! here. If the analysis operates on typed IR instructions, it goes in Module. Before: 4009 src-self-hosted/Module.zig After: 2776 src-self-hosted/Module.zig 1128 src-self-hosted/zir_sema.zig This should be sufficient to avoid the situation we have in stage1 where ir.cpp is 32,516 lines. --- src-self-hosted/Module.zig | 1337 ++-------------------------------- src-self-hosted/astgen.zig | 250 +++++-- src-self-hosted/zir_sema.zig | 1128 ++++++++++++++++++++++++++++ 3 files changed, 1364 insertions(+), 1351 deletions(-) create mode 100644 src-self-hosted/zir_sema.zig diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 36d64b3b08..4c0bb23c6d 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -20,6 +20,7 @@ const ast = std.zig.ast; const trace = @import("tracy.zig").trace; const liveness = @import("liveness.zig"); const astgen = @import("astgen.zig"); +const zir_sema = @import("zir_sema.zig"); /// General-purpose allocator. Used for both temporary and long-term storage. gpa: *Allocator, @@ -246,7 +247,7 @@ pub const Decl = struct { std.debug.warn("\n", .{}); } - fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { + pub fn typedValueManaged(self: *Decl) ?*TypedValue.Managed { switch (self.typed_value) { .most_recent => |*x| return x, .never_succeeded => return null, @@ -1085,7 +1086,7 @@ pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void { }; } -fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { +pub fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { const tracy = trace(@src()); defer tracy.end(); @@ -1129,7 +1130,7 @@ fn ensureDeclAnalyzed(self: *Module, decl: *Decl) InnerError!void { }; const type_changed = if (self.root_scope.cast(Scope.ZIRModule)) |zir_module| - try self.analyzeZirDecl(decl, zir_module.contents.module.decls[decl.src_index]) + try zir_sema.analyzeZirDecl(self, decl, zir_module.contents.module.decls[decl.src_index]) else self.astGenAndAnalyzeDecl(decl) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -1205,7 +1206,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { const param_types = try fn_type_scope.arena.alloc(*zir.Inst, param_decls.len); const fn_src = tree.token_locs[fn_proto.fn_token].start; - const type_type = try self.addZIRInstConst(&fn_type_scope.base, fn_src, .{ + const type_type = try astgen.addZIRInstConst(self, &fn_type_scope.base, fn_src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.type_type), }); @@ -1244,11 +1245,11 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; const return_type_inst = try astgen.expr(self, &fn_type_scope.base, type_type_rl, return_type_expr); - const fn_type_inst = try self.addZIRInst(&fn_type_scope.base, fn_src, zir.Inst.FnType, .{ + const fn_type_inst = try astgen.addZIRInst(self, &fn_type_scope.base, fn_src, zir.Inst.FnType, .{ .return_type = return_type_inst, .param_types = param_types, }, .{}); - _ = try self.addZIRUnOp(&fn_type_scope.base, fn_src, .@"return", fn_type_inst); + _ = try astgen.addZIRUnOp(self, &fn_type_scope.base, fn_src, .@"return", fn_type_inst); // We need the memory for the Type to go into the arena for the Decl var decl_arena = std.heap.ArenaAllocator.init(self.gpa); @@ -1264,7 +1265,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { }; defer block_scope.instructions.deinit(self.gpa); - const fn_type = try self.analyzeBodyValueAsType(&block_scope, .{ + const fn_type = try zir_sema.analyzeBodyValueAsType(self, &block_scope, .{ .instructions = fn_type_scope.instructions.items, }); const new_func = try decl_arena.allocator.create(Fn); @@ -1317,7 +1318,7 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { !gen_scope.instructions.items[gen_scope.instructions.items.len - 1].tag.isNoReturn())) { const src = tree.token_locs[body_block.rbrace].start; - _ = try self.addZIRNoOp(&gen_scope.base, src, .returnvoid); + _ = try astgen.addZIRNoOp(self, &gen_scope.base, src, .returnvoid); } const fn_zir = try gen_scope_arena.allocator.create(Fn.ZIR); @@ -1387,19 +1388,6 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { } } -fn analyzeBodyValueAsType(self: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type { - try self.analyzeBody(&block_scope.base, body); - for (block_scope.instructions.items) |inst| { - if (inst.castTag(.ret)) |ret| { - const val = try self.resolveConstValue(&block_scope.base, ret.operand); - return val.toType(); - } else { - return self.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); - } - } - unreachable; -} - fn declareDeclDependency(self: *Module, depender: *Decl, dependee: *Decl) !void { try depender.dependencies.ensureCapacity(self.gpa, depender.dependencies.items().len + 1); try dependee.dependants.ensureCapacity(self.gpa, dependee.dependants.items().len + 1); @@ -1600,7 +1588,7 @@ fn analyzeRootZIRModule(self: *Module, root_scope: *Scope.ZIRModule) !void { } } for (exports_to_resolve.items) |export_decl| { - _ = try self.resolveZirDecl(&root_scope.base, export_decl); + _ = try zir_sema.resolveZirDecl(self, &root_scope.base, export_decl); } // Handle explicitly deleted decls from the source code. Not to be confused // with when we delete decls because they are no longer referenced. @@ -1705,7 +1693,7 @@ fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { func.analysis = .{ .in_progress = {} }; //std.debug.warn("set {} to in_progress\n", .{decl.name}); - try self.analyzeBody(&inner_block.base, fn_zir.body); + try zir_sema.analyzeBody(self, &inner_block.base, fn_zir.body); const instructions = try arena.allocator.dupe(*Inst, inner_block.instructions.items); func.analysis = .{ .success = .{ .instructions = instructions } }; @@ -1758,131 +1746,18 @@ fn createNewDecl( return new_decl; } -fn analyzeZirDecl(self: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { - var decl_scope: Scope.DeclAnalysis = .{ - .decl = decl, - .arena = std.heap.ArenaAllocator.init(self.gpa), - }; - errdefer decl_scope.arena.deinit(); - - decl.analysis = .in_progress; - - const typed_value = try self.analyzeConstInst(&decl_scope.base, src_decl.inst); - const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); - - var prev_type_has_bits = false; - var type_changed = true; - - if (decl.typedValueManaged()) |tvm| { - prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); - type_changed = !tvm.typed_value.ty.eql(typed_value.ty); - - tvm.deinit(self.gpa); - } - - arena_state.* = decl_scope.arena.state; - decl.typed_value = .{ - .most_recent = .{ - .typed_value = typed_value, - .arena = arena_state, - }, - }; - decl.analysis = .complete; - decl.generation = self.generation; - if (typed_value.ty.hasCodeGenBits()) { - // We don't fully codegen the decl until later, but we do need to reserve a global - // offset table index for it. This allows us to codegen decls out of dependency order, - // increasing how many computations can be done in parallel. - try self.bin_file.allocateDeclIndexes(decl); - try self.work_queue.writeItem(.{ .codegen_decl = decl }); - } else if (prev_type_has_bits) { - self.bin_file.freeDecl(decl); - } - - return type_changed; -} - -fn resolveZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - const zir_module = self.root_scope.cast(Scope.ZIRModule).?; - const entry = zir_module.contents.module.findDecl(src_decl.name).?; - return self.resolveZirDeclHavingIndex(scope, src_decl, entry.index); -} - -fn resolveZirDeclHavingIndex(self: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { - const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); - const decl = self.decl_table.get(name_hash).?; - decl.src_index = src_index; - try self.ensureDeclAnalyzed(decl); - return decl; -} - -/// Declares a dependency on the decl. -fn resolveCompleteZirDecl(self: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { - const decl = try self.resolveZirDecl(scope, src_decl); - switch (decl.analysis) { - .unreferenced => unreachable, - .in_progress => unreachable, - .outdated => unreachable, - - .dependency_failure, - .sema_failure, - .sema_failure_retryable, - .codegen_failure, - .codegen_failure_retryable, - => return error.AnalysisFail, - - .complete => {}, - } - return decl; -} - -/// TODO Look into removing this function. The body is only needed for .zir files, not .zig files. -fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { - if (old_inst.analyzed_inst) |inst| return inst; - - // If this assert trips, the instruction that was referenced did not get properly - // analyzed before it was referenced. - const zir_module = scope.namespace().cast(Scope.ZIRModule).?; - const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: { - const decl_name = declval.positionals.name; - const entry = zir_module.contents.module.findDecl(decl_name) orelse - return self.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name}); - break :blk entry; - } else blk: { - // If this assert trips, the instruction that was referenced did not get - // properly analyzed by a previous instruction analysis before it was - // referenced by the current one. - break :blk zir_module.contents.module.findInstDecl(old_inst).?; - }; - const decl = try self.resolveCompleteZirDecl(scope, entry.decl); - const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); - // Note: it would be tempting here to store the result into old_inst.analyzed_inst field, - // but this would prevent the analyzeDeclRef from happening, which is needed to properly - // detect Decl dependencies and dependency failures on updates. - return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); -} - /// TODO split this into `requireRuntimeBlock` and `requireFunctionBlock` and audit callsites. -fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { +pub fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { return scope.cast(Scope.Block) orelse return self.fail(scope, src, "instruction illegal outside function body", .{}); } -fn resolveInstConst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { - const new_inst = try self.resolveInst(scope, old_inst); - const val = try self.resolveConstValue(scope, new_inst); - return TypedValue{ - .ty = new_inst.ty, - .val = val, - }; -} - -fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { +pub fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value { return (try self.resolveDefinedValue(scope, base)) orelse return self.fail(scope, base.src, "unable to resolve comptime value", .{}); } -fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { +pub fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { if (base.value()) |val| { if (val.isUndef()) { return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{}); @@ -1892,23 +1767,7 @@ fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value { return null; } -fn resolveConstString(self: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 { - const new_inst = try self.resolveInst(scope, old_inst); - const wanted_type = Type.initTag(.const_slice_u8); - const coerced_inst = try self.coerce(scope, wanted_type, new_inst); - const val = try self.resolveConstValue(scope, coerced_inst); - return val.toAllocatedBytes(scope.arena()); -} - -fn resolveType(self: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { - const new_inst = try self.resolveInst(scope, old_inst); - const wanted_type = Type.initTag(.@"type"); - const coerced_inst = try self.coerce(scope, wanted_type, new_inst); - const val = try self.resolveConstValue(scope, coerced_inst); - return val.toType(); -} - -fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const u8, exported_decl: *Decl) !void { +pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const u8, exported_decl: *Decl) !void { try self.ensureDeclAnalyzed(exported_decl); const typed_value = exported_decl.typed_value.most_recent.typed_value; switch (typed_value.ty.zigTypeTag()) { @@ -1980,7 +1839,7 @@ fn analyzeExport(self: *Module, scope: *Scope, src: usize, symbol_name: []const }; } -fn addNoOp( +pub fn addNoOp( self: *Module, block: *Scope.Block, src: usize, @@ -1999,7 +1858,7 @@ fn addNoOp( return &inst.base; } -fn addUnOp( +pub fn addUnOp( self: *Module, block: *Scope.Block, src: usize, @@ -2020,7 +1879,7 @@ fn addUnOp( return &inst.base; } -fn addBinOp( +pub fn addBinOp( self: *Module, block: *Scope.Block, src: usize, @@ -2043,7 +1902,7 @@ fn addBinOp( return &inst.base; } -fn addBr( +pub fn addBr( self: *Module, scope_block: *Scope.Block, src: usize, @@ -2064,7 +1923,7 @@ fn addBr( return &inst.base; } -fn addCondBr( +pub fn addCondBr( self: *Module, block: *Scope.Block, src: usize, @@ -2087,7 +1946,7 @@ fn addCondBr( return &inst.base; } -fn addCall( +pub fn addCall( self: *Module, block: *Scope.Block, src: usize, @@ -2109,138 +1968,7 @@ fn addCall( return &inst.base; } -pub fn addZIRInstSpecial( - self: *Module, - scope: *Scope, - src: usize, - comptime T: type, - positionals: std.meta.fieldInfo(T, "positionals").field_type, - kw_args: std.meta.fieldInfo(T, "kw_args").field_type, -) !*T { - const gen_zir = scope.getGenZIR(); - try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); - const inst = try gen_zir.arena.create(T); - inst.* = .{ - .base = .{ - .tag = T.base_tag, - .src = src, - }, - .positionals = positionals, - .kw_args = kw_args, - }; - gen_zir.instructions.appendAssumeCapacity(&inst.base); - return inst; -} - -pub fn addZIRNoOpT(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp { - const gen_zir = scope.getGenZIR(); - try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); - const inst = try gen_zir.arena.create(zir.Inst.NoOp); - inst.* = .{ - .base = .{ - .tag = tag, - .src = src, - }, - .positionals = .{}, - .kw_args = .{}, - }; - gen_zir.instructions.appendAssumeCapacity(&inst.base); - return inst; -} - -pub fn addZIRNoOp(self: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst { - const inst = try self.addZIRNoOpT(scope, src, tag); - return &inst.base; -} - -pub fn addZIRUnOp( - self: *Module, - scope: *Scope, - src: usize, - tag: zir.Inst.Tag, - operand: *zir.Inst, -) !*zir.Inst { - const gen_zir = scope.getGenZIR(); - try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); - const inst = try gen_zir.arena.create(zir.Inst.UnOp); - inst.* = .{ - .base = .{ - .tag = tag, - .src = src, - }, - .positionals = .{ - .operand = operand, - }, - .kw_args = .{}, - }; - gen_zir.instructions.appendAssumeCapacity(&inst.base); - return &inst.base; -} - -pub fn addZIRBinOp( - self: *Module, - scope: *Scope, - src: usize, - tag: zir.Inst.Tag, - lhs: *zir.Inst, - rhs: *zir.Inst, -) !*zir.Inst { - const gen_zir = scope.getGenZIR(); - try gen_zir.instructions.ensureCapacity(self.gpa, gen_zir.instructions.items.len + 1); - const inst = try gen_zir.arena.create(zir.Inst.BinOp); - inst.* = .{ - .base = .{ - .tag = tag, - .src = src, - }, - .positionals = .{ - .lhs = lhs, - .rhs = rhs, - }, - .kw_args = .{}, - }; - gen_zir.instructions.appendAssumeCapacity(&inst.base); - return &inst.base; -} - -pub fn addZIRInst( - self: *Module, - scope: *Scope, - src: usize, - comptime T: type, - positionals: std.meta.fieldInfo(T, "positionals").field_type, - kw_args: std.meta.fieldInfo(T, "kw_args").field_type, -) !*zir.Inst { - const inst_special = try self.addZIRInstSpecial(scope, src, T, positionals, kw_args); - return &inst_special.base; -} - -/// TODO The existence of this function is a workaround for a bug in stage1. -pub fn addZIRInstConst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { - const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; - return self.addZIRInst(scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); -} - -/// TODO The existence of this function is a workaround for a bug in stage1. -pub fn addZIRInstBlock(self: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { - const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; - return self.addZIRInstSpecial(scope, src, zir.Inst.Block, P{ .body = body }, .{}); -} - -fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T { - const inst = try block.arena.create(T); - inst.* = .{ - .base = .{ - .tag = T.base_tag, - .ty = ty, - .src = src, - }, - }; - try block.instructions.append(self.gpa, &inst.base); - return inst; -} - -fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { +pub fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst { const const_inst = try scope.arena().create(Inst.Constant); const_inst.* = .{ .base = .{ @@ -2253,42 +1981,42 @@ fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) return &const_inst.base; } -fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { +pub fn constType(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { return self.constInst(scope, src, .{ .ty = Type.initTag(.type), .val = try ty.toValue(scope.arena()), }); } -fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst { +pub fn constVoid(self: *Module, scope: *Scope, src: usize) !*Inst { return self.constInst(scope, src, .{ .ty = Type.initTag(.void), .val = Value.initTag(.the_one_possible_value), }); } -fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst { +pub fn constNoReturn(self: *Module, scope: *Scope, src: usize) !*Inst { return self.constInst(scope, src, .{ .ty = Type.initTag(.noreturn), .val = Value.initTag(.the_one_possible_value), }); } -fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { +pub fn constUndef(self: *Module, scope: *Scope, src: usize, ty: Type) !*Inst { return self.constInst(scope, src, .{ .ty = ty, .val = Value.initTag(.undef), }); } -fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst { +pub fn constBool(self: *Module, scope: *Scope, src: usize, v: bool) !*Inst { return self.constInst(scope, src, .{ .ty = Type.initTag(.bool), .val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)], }); } -fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst { +pub fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64) !*Inst { const int_payload = try scope.arena().create(Value.Payload.Int_u64); int_payload.* = .{ .int = int }; @@ -2298,7 +2026,7 @@ fn constIntUnsigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: u64 }); } -fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst { +pub fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) !*Inst { const int_payload = try scope.arena().create(Value.Payload.Int_i64); int_payload.* = .{ .int = int }; @@ -2308,7 +2036,7 @@ fn constIntSigned(self: *Module, scope: *Scope, src: usize, ty: Type, int: i64) }); } -fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst { +pub fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst { const val_payload = if (big_int.positive) blk: { if (big_int.to(u64)) |x| { return self.constIntUnsigned(scope, src, ty, x); @@ -2337,217 +2065,7 @@ fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigI }); } -fn analyzeConstInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { - const new_inst = try self.analyzeInst(scope, old_inst); - return TypedValue{ - .ty = new_inst.ty, - .val = try self.resolveConstValue(scope, new_inst), - }; -} - -fn analyzeInstConst(self: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst { - // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions - // after analysis. - const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena()); - return self.constInst(scope, const_inst.base.src, typed_value_copy); -} - -fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { - switch (old_inst.tag) { - .alloc => return self.analyzeInstAlloc(scope, old_inst.castTag(.alloc).?), - .alloc_inferred => return self.analyzeInstAllocInferred(scope, old_inst.castTag(.alloc_inferred).?), - .arg => return self.analyzeInstArg(scope, old_inst.castTag(.arg).?), - .bitcast_lvalue => return self.analyzeInstBitCastLValue(scope, old_inst.castTag(.bitcast_lvalue).?), - .bitcast_result_ptr => return self.analyzeInstBitCastResultPtr(scope, old_inst.castTag(.bitcast_result_ptr).?), - .block => return self.analyzeInstBlock(scope, old_inst.castTag(.block).?), - .@"break" => return self.analyzeInstBreak(scope, old_inst.castTag(.@"break").?), - .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.castTag(.breakpoint).?), - .breakvoid => return self.analyzeInstBreakVoid(scope, old_inst.castTag(.breakvoid).?), - .call => return self.analyzeInstCall(scope, old_inst.castTag(.call).?), - .coerce_result_block_ptr => return self.analyzeInstCoerceResultBlockPtr(scope, old_inst.castTag(.coerce_result_block_ptr).?), - .coerce_result_ptr => return self.analyzeInstCoerceResultPtr(scope, old_inst.castTag(.coerce_result_ptr).?), - .coerce_to_ptr_elem => return self.analyzeInstCoerceToPtrElem(scope, old_inst.castTag(.coerce_to_ptr_elem).?), - .compileerror => return self.analyzeInstCompileError(scope, old_inst.castTag(.compileerror).?), - .@"const" => return self.analyzeInstConst(scope, old_inst.castTag(.@"const").?), - .declref => return self.analyzeInstDeclRef(scope, old_inst.castTag(.declref).?), - .declref_str => return self.analyzeInstDeclRefStr(scope, old_inst.castTag(.declref_str).?), - .declval => return self.analyzeInstDeclVal(scope, old_inst.castTag(.declval).?), - .declval_in_module => return self.analyzeInstDeclValInModule(scope, old_inst.castTag(.declval_in_module).?), - .ensure_result_used => return self.analyzeInstEnsureResultUsed(scope, old_inst.castTag(.ensure_result_used).?), - .ensure_result_non_error => return self.analyzeInstEnsureResultNonError(scope, old_inst.castTag(.ensure_result_non_error).?), - .ref => return self.analyzeInstRef(scope, old_inst.castTag(.ref).?), - .ret_ptr => return self.analyzeInstRetPtr(scope, old_inst.castTag(.ret_ptr).?), - .ret_type => return self.analyzeInstRetType(scope, old_inst.castTag(.ret_type).?), - .store => return self.analyzeInstStore(scope, old_inst.castTag(.store).?), - .str => return self.analyzeInstStr(scope, old_inst.castTag(.str).?), - .int => { - const big_int = old_inst.castTag(.int).?.positionals.int; - return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); - }, - .inttype => return self.analyzeInstIntType(scope, old_inst.castTag(.inttype).?), - .param_type => return self.analyzeInstParamType(scope, old_inst.castTag(.param_type).?), - .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.castTag(.ptrtoint).?), - .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.castTag(.fieldptr).?), - .deref => return self.analyzeInstDeref(scope, old_inst.castTag(.deref).?), - .as => return self.analyzeInstAs(scope, old_inst.castTag(.as).?), - .@"asm" => return self.analyzeInstAsm(scope, old_inst.castTag(.@"asm").?), - .@"unreachable" => return self.analyzeInstUnreachable(scope, old_inst.castTag(.@"unreachable").?), - .unreach_nocheck => return self.analyzeInstUnreachNoChk(scope, old_inst.castTag(.unreach_nocheck).?), - .@"return" => return self.analyzeInstRet(scope, old_inst.castTag(.@"return").?), - .returnvoid => return self.analyzeInstRetVoid(scope, old_inst.castTag(.returnvoid).?), - .@"fn" => return self.analyzeInstFn(scope, old_inst.castTag(.@"fn").?), - .@"export" => return self.analyzeInstExport(scope, old_inst.castTag(.@"export").?), - .primitive => return self.analyzeInstPrimitive(scope, old_inst.castTag(.primitive).?), - .fntype => return self.analyzeInstFnType(scope, old_inst.castTag(.fntype).?), - .intcast => return self.analyzeInstIntCast(scope, old_inst.castTag(.intcast).?), - .bitcast => return self.analyzeInstBitCast(scope, old_inst.castTag(.bitcast).?), - .floatcast => return self.analyzeInstFloatCast(scope, old_inst.castTag(.floatcast).?), - .elemptr => return self.analyzeInstElemPtr(scope, old_inst.castTag(.elemptr).?), - .add => return self.analyzeInstArithmetic(scope, old_inst.castTag(.add).?), - .addwrap => return self.analyzeInstArithmetic(scope, old_inst.castTag(.addwrap).?), - .sub => return self.analyzeInstArithmetic(scope, old_inst.castTag(.sub).?), - .subwrap => return self.analyzeInstArithmetic(scope, old_inst.castTag(.subwrap).?), - .mul => return self.analyzeInstArithmetic(scope, old_inst.castTag(.mul).?), - .mulwrap => return self.analyzeInstArithmetic(scope, old_inst.castTag(.mulwrap).?), - .div => return self.analyzeInstArithmetic(scope, old_inst.castTag(.div).?), - .mod_rem => return self.analyzeInstArithmetic(scope, old_inst.castTag(.mod_rem).?), - .array_cat => return self.analyzeInstArrayCat(scope, old_inst.castTag(.array_cat).?), - .array_mul => return self.analyzeInstArrayMul(scope, old_inst.castTag(.array_mul).?), - .bitand => return self.analyzeInstBitwise(scope, old_inst.castTag(.bitand).?), - .bitor => return self.analyzeInstBitwise(scope, old_inst.castTag(.bitor).?), - .xor => return self.analyzeInstBitwise(scope, old_inst.castTag(.xor).?), - .shl => return self.analyzeInstShl(scope, old_inst.castTag(.shl).?), - .shr => return self.analyzeInstShr(scope, old_inst.castTag(.shr).?), - .cmp_lt => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_lt).?, .lt), - .cmp_lte => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_lte).?, .lte), - .cmp_eq => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_eq).?, .eq), - .cmp_gte => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_gte).?, .gte), - .cmp_gt => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_gt).?, .gt), - .cmp_neq => return self.analyzeInstCmp(scope, old_inst.castTag(.cmp_neq).?, .neq), - .condbr => return self.analyzeInstCondBr(scope, old_inst.castTag(.condbr).?), - .isnull => return self.analyzeInstIsNonNull(scope, old_inst.castTag(.isnull).?, true), - .isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.castTag(.isnonnull).?, false), - .boolnot => return self.analyzeInstBoolNot(scope, old_inst.castTag(.boolnot).?), - .typeof => return self.analyzeInstTypeOf(scope, old_inst.castTag(.typeof).?), - } -} - -fn analyzeInstCoerceResultBlockPtr( - self: *Module, - scope: *Scope, - inst: *zir.Inst.CoerceResultBlockPtr, -) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{}); -} - -fn analyzeInstBitCastLValue(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastLValue", .{}); -} - -fn analyzeInstBitCastResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{}); -} - -fn analyzeInstCoerceResultPtr(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{}); -} - -fn analyzeInstCoerceToPtrElem(self: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceToPtrElem", .{}); -} - -fn analyzeInstRetPtr(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{}); -} - -fn analyzeInstRef(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstRef", .{}); -} - -fn analyzeInstRetType(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - const b = try self.requireRuntimeBlock(scope, inst.base.src); - const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; - const ret_type = fn_ty.fnReturnType(); - return self.constType(scope, inst.base.src, ret_type); -} - -fn analyzeInstEnsureResultUsed(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - const operand = try self.resolveInst(scope, inst.positionals.operand); - switch (operand.ty.zigTypeTag()) { - .Void, .NoReturn => return self.constVoid(scope, operand.src), - else => return self.fail(scope, operand.src, "expression value is ignored", .{}), - } -} - -fn analyzeInstEnsureResultNonError(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - const operand = try self.resolveInst(scope, inst.positionals.operand); - switch (operand.ty.zigTypeTag()) { - .ErrorSet, .ErrorUnion => return self.fail(scope, operand.src, "error is discarded", .{}), - else => return self.constVoid(scope, operand.src), - } -} - -fn analyzeInstAlloc(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstAlloc", .{}); -} - -fn analyzeInstAllocInferred(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); -} - -fn analyzeInstStore(self: *Module, scope: *Scope, inst: *zir.Inst.Store) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstStore", .{}); -} - -fn analyzeInstParamType(self: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst { - const fn_inst = try self.resolveInst(scope, inst.positionals.func); - const arg_index = inst.positionals.arg_index; - - const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) { - .Fn => fn_inst.ty, - .BoundFn => { - return self.fail(scope, fn_inst.src, "TODO implement analyzeInstParamType for method call syntax", .{}); - }, - else => { - return self.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty}); - }, - }; - - // TODO support C-style var args - const param_count = fn_ty.fnParamLen(); - if (arg_index >= param_count) { - return self.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} arguments", .{ - arg_index, - fn_ty, - param_count, - }); - } - - // TODO support generic functions - const param_type = fn_ty.fnParamType(arg_index); - return self.constType(scope, inst.base.src, param_type); -} - -fn analyzeInstStr(self: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { - // The bytes references memory inside the ZIR module, which can get deallocated - // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. - var new_decl_arena = std.heap.ArenaAllocator.init(self.gpa); - const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); - - const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); - ty_payload.* = .{ .len = arena_bytes.len }; - - const bytes_payload = try scope.arena().create(Value.Payload.Bytes); - bytes_payload.* = .{ .data = arena_bytes }; - - const new_decl = try self.createAnonymousDecl(scope, &new_decl_arena, .{ - .ty = Type.initPayload(&ty_payload.base), - .val = Value.initPayload(&bytes_payload.base), - }); - return self.analyzeDeclRef(scope, str_inst.base.src, new_decl); -} - -fn createAnonymousDecl( +pub fn createAnonymousDecl( self: *Module, scope: *Scope, decl_arena: *std.heap.ArenaAllocator, @@ -2593,151 +2111,7 @@ pub fn lookupDeclName(self: *Module, scope: *Scope, ident_name: []const u8) ?*De return self.decl_table.get(name_hash); } -fn analyzeInstExport(self: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { - const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name); - const exported_decl = self.lookupDeclName(scope, export_inst.positionals.decl_name) orelse - return self.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name}); - try self.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl); - return self.constVoid(scope, export_inst.base.src); -} - -fn analyzeInstCompileError(self: *Module, scope: *Scope, inst: *zir.Inst.CompileError) InnerError!*Inst { - return self.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); -} - -fn analyzeInstArg(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - const b = try self.requireRuntimeBlock(scope, inst.base.src); - const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; - const param_index = b.instructions.items.len; - const param_count = fn_ty.fnParamLen(); - if (param_index >= param_count) { - return self.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{ - param_index, - param_count, - }); - } - const param_type = fn_ty.fnParamType(param_index); - return self.addNoOp(b, inst.base.src, param_type, .arg); -} - -fn analyzeInstBlock(self: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { - const parent_block = scope.cast(Scope.Block).?; - - // Reserve space for a Block instruction so that generated Break instructions can - // point to it, even if it doesn't end up getting used because the code ends up being - // comptime evaluated. - const block_inst = try parent_block.arena.create(Inst.Block); - block_inst.* = .{ - .base = .{ - .tag = Inst.Block.base_tag, - .ty = undefined, // Set after analysis. - .src = inst.base.src, - }, - .body = undefined, - }; - - var child_block: Scope.Block = .{ - .parent = parent_block, - .func = parent_block.func, - .decl = parent_block.decl, - .instructions = .{}, - .arena = parent_block.arena, - // TODO @as here is working around a miscompilation compiler bug :( - .label = @as(?Scope.Block.Label, Scope.Block.Label{ - .zir_block = inst, - .results = .{}, - .block_inst = block_inst, - }), - }; - const label = &child_block.label.?; - - defer child_block.instructions.deinit(self.gpa); - defer label.results.deinit(self.gpa); - - try self.analyzeBody(&child_block.base, inst.positionals.body); - - // Blocks must terminate with noreturn instruction. - assert(child_block.instructions.items.len != 0); - assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); - - // Need to set the type and emit the Block instruction. This allows machine code generation - // to emit a jump instruction to after the block when it encounters the break. - try parent_block.instructions.append(self.gpa, &block_inst.base); - block_inst.base.ty = try self.resolvePeerTypes(scope, label.results.items); - block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; - return &block_inst.base; -} - -fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint); -} - -fn analyzeInstBreak(self: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst { - const operand = try self.resolveInst(scope, inst.positionals.operand); - const block = inst.positionals.block; - return self.analyzeBreak(scope, inst.base.src, block, operand); -} - -fn analyzeInstBreakVoid(self: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst { - const block = inst.positionals.block; - const void_inst = try self.constVoid(scope, inst.base.src); - return self.analyzeBreak(scope, inst.base.src, block, void_inst); -} - -fn analyzeBreak( - self: *Module, - scope: *Scope, - src: usize, - zir_block: *zir.Inst.Block, - operand: *Inst, -) InnerError!*Inst { - var opt_block = scope.cast(Scope.Block); - while (opt_block) |block| { - if (block.label) |*label| { - if (label.zir_block == zir_block) { - try label.results.append(self.gpa, operand); - const b = try self.requireRuntimeBlock(scope, src); - return self.addBr(b, src, label.block_inst, operand); - } - } - opt_block = block.parent; - } else unreachable; -} - -fn analyzeInstDeclRefStr(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { - const decl_name = try self.resolveConstString(scope, inst.positionals.name); - return self.analyzeDeclRefByName(scope, inst.base.src, decl_name); -} - -fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { - return self.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name); -} - -fn analyzeDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { - const decl_name = inst.positionals.name; - const zir_module = scope.namespace().cast(Scope.ZIRModule).?; - const src_decl = zir_module.contents.module.findDecl(decl_name) orelse - return self.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); - - const decl = try self.resolveCompleteZirDecl(scope, src_decl.decl); - - return decl; -} - -fn analyzeInstDeclVal(self: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst { - const decl = try self.analyzeDeclVal(scope, inst); - const ptr = try self.analyzeDeclRef(scope, inst.base.src, decl); - return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); -} - -fn analyzeInstDeclValInModule(self: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { - const decl = inst.positionals.decl; - const ptr = try self.analyzeDeclRef(scope, inst.base.src, decl); - return self.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); -} - -fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { +pub fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst { const scope_decl = scope.decl().?; try self.declareDeclDependency(scope_decl, decl); self.ensureDeclAnalyzed(decl) catch |err| { @@ -2765,432 +2139,7 @@ fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerEr }); } -fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { - const decl = self.lookupDeclName(scope, decl_name) orelse - return self.fail(scope, src, "decl '{}' not found", .{decl_name}); - return self.analyzeDeclRef(scope, src, decl); -} - -fn analyzeInstCall(self: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { - const func = try self.resolveInst(scope, inst.positionals.func); - if (func.ty.zigTypeTag() != .Fn) - return self.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty}); - - const cc = func.ty.fnCallingConvention(); - if (cc == .Naked) { - // TODO add error note: declared here - return self.fail( - scope, - inst.positionals.func.src, - "unable to call function with naked calling convention", - .{}, - ); - } - const call_params_len = inst.positionals.args.len; - const fn_params_len = func.ty.fnParamLen(); - if (func.ty.fnIsVarArgs()) { - if (call_params_len < fn_params_len) { - // TODO add error note: declared here - return self.fail( - scope, - inst.positionals.func.src, - "expected at least {} arguments, found {}", - .{ fn_params_len, call_params_len }, - ); - } - return self.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{}); - } else if (fn_params_len != call_params_len) { - // TODO add error note: declared here - return self.fail( - scope, - inst.positionals.func.src, - "expected {} arguments, found {}", - .{ fn_params_len, call_params_len }, - ); - } - - if (inst.kw_args.modifier == .compile_time) { - return self.fail(scope, inst.base.src, "TODO implement comptime function calls", .{}); - } - if (inst.kw_args.modifier != .auto) { - return self.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.kw_args.modifier}); - } - - // TODO handle function calls of generic functions - - const fn_param_types = try self.gpa.alloc(Type, fn_params_len); - defer self.gpa.free(fn_param_types); - func.ty.fnParamTypes(fn_param_types); - - const casted_args = try scope.arena().alloc(*Inst, fn_params_len); - for (inst.positionals.args) |src_arg, i| { - const uncasted_arg = try self.resolveInst(scope, src_arg); - casted_args[i] = try self.coerce(scope, fn_param_types[i], uncasted_arg); - } - - const ret_type = func.ty.fnReturnType(); - - const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addCall(b, inst.base.src, ret_type, func, casted_args); -} - -fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { - const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type); - const fn_zir = blk: { - var fn_arena = std.heap.ArenaAllocator.init(self.gpa); - errdefer fn_arena.deinit(); - - const fn_zir = try scope.arena().create(Fn.ZIR); - fn_zir.* = .{ - .body = .{ - .instructions = fn_inst.positionals.body.instructions, - }, - .arena = fn_arena.state, - }; - break :blk fn_zir; - }; - const new_func = try scope.arena().create(Fn); - new_func.* = .{ - .analysis = .{ .queued = fn_zir }, - .owner_decl = scope.decl().?, - }; - const fn_payload = try scope.arena().create(Value.Payload.Function); - fn_payload.* = .{ .func = new_func }; - return self.constInst(scope, fn_inst.base.src, .{ - .ty = fn_type, - .val = Value.initPayload(&fn_payload.base), - }); -} - -fn analyzeInstIntType(self: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst { - return self.fail(scope, inttype.base.src, "TODO implement inttype", .{}); -} - -fn analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { - const return_type = try self.resolveType(scope, fntype.positionals.return_type); - - // Hot path for some common function types. - if (fntype.positionals.param_types.len == 0) { - if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Unspecified) { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); - } - - if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .Unspecified) { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args)); - } - - if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Naked) { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); - } - - if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .C) { - return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); - } - } - - const arena = scope.arena(); - const param_types = try arena.alloc(Type, fntype.positionals.param_types.len); - for (fntype.positionals.param_types) |param_type, i| { - param_types[i] = try self.resolveType(scope, param_type); - } - - const payload = try arena.create(Type.Payload.Function); - payload.* = .{ - .cc = fntype.kw_args.cc, - .return_type = return_type, - .param_types = param_types, - }; - return self.constType(scope, fntype.base.src, Type.initPayload(&payload.base)); -} - -fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst { - return self.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue()); -} - -fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst { - const dest_type = try self.resolveType(scope, as.positionals.lhs); - const new_inst = try self.resolveInst(scope, as.positionals.rhs); - return self.coerce(scope, dest_type, new_inst); -} - -fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst { - const ptr = try self.resolveInst(scope, ptrtoint.positionals.operand); - if (ptr.ty.zigTypeTag() != .Pointer) { - return self.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty}); - } - // TODO handle known-pointer-address - const b = try self.requireRuntimeBlock(scope, ptrtoint.base.src); - const ty = Type.initTag(.usize); - return self.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr); -} - -fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst { - const object_ptr = try self.resolveInst(scope, fieldptr.positionals.object_ptr); - const field_name = try self.resolveConstString(scope, fieldptr.positionals.field_name); - - const elem_ty = switch (object_ptr.ty.zigTypeTag()) { - .Pointer => object_ptr.ty.elemType(), - else => return self.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), - }; - switch (elem_ty.zigTypeTag()) { - .Array => { - if (mem.eql(u8, field_name, "len")) { - const len_payload = try scope.arena().create(Value.Payload.Int_u64); - len_payload.* = .{ .int = elem_ty.arrayLen() }; - - const ref_payload = try scope.arena().create(Value.Payload.RefVal); - ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; - - return self.constInst(scope, fieldptr.base.src, .{ - .ty = Type.initTag(.single_const_pointer_to_comptime_int), - .val = Value.initPayload(&ref_payload.base), - }); - } else { - return self.fail( - scope, - fieldptr.positionals.field_name.src, - "no member named '{}' in '{}'", - .{ field_name, elem_ty }, - ); - } - }, - else => return self.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), - } -} - -fn analyzeInstIntCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - const dest_type = try self.resolveType(scope, inst.positionals.lhs); - const operand = try self.resolveInst(scope, inst.positionals.rhs); - - const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { - .ComptimeInt => true, - .Int => false, - else => return self.fail( - scope, - inst.positionals.lhs.src, - "expected integer type, found '{}'", - .{ - dest_type, - }, - ), - }; - - switch (operand.ty.zigTypeTag()) { - .ComptimeInt, .Int => {}, - else => return self.fail( - scope, - inst.positionals.rhs.src, - "expected integer type, found '{}'", - .{operand.ty}, - ), - } - - if (operand.value() != null) { - return self.coerce(scope, dest_type, operand); - } else if (dest_is_comptime_int) { - return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{}); - } - - return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{}); -} - -fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - const dest_type = try self.resolveType(scope, inst.positionals.lhs); - const operand = try self.resolveInst(scope, inst.positionals.rhs); - return self.bitcast(scope, dest_type, operand); -} - -fn analyzeInstFloatCast(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - const dest_type = try self.resolveType(scope, inst.positionals.lhs); - const operand = try self.resolveInst(scope, inst.positionals.rhs); - - const dest_is_comptime_float = switch (dest_type.zigTypeTag()) { - .ComptimeFloat => true, - .Float => false, - else => return self.fail( - scope, - inst.positionals.lhs.src, - "expected float type, found '{}'", - .{ - dest_type, - }, - ), - }; - - switch (operand.ty.zigTypeTag()) { - .ComptimeFloat, .Float, .ComptimeInt => {}, - else => return self.fail( - scope, - inst.positionals.rhs.src, - "expected float type, found '{}'", - .{operand.ty}, - ), - } - - if (operand.value() != null) { - return self.coerce(scope, dest_type, operand); - } else if (dest_is_comptime_float) { - return self.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{}); - } - - return self.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{}); -} - -fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst { - const array_ptr = try self.resolveInst(scope, inst.positionals.array_ptr); - const uncasted_index = try self.resolveInst(scope, inst.positionals.index); - const elem_index = try self.coerce(scope, Type.initTag(.usize), uncasted_index); - - if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) { - if (array_ptr.value()) |array_ptr_val| { - if (elem_index.value()) |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(scope.arena(), @intCast(usize, index_u64)); - - const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); - type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() }; - - return self.constInst(scope, inst.base.src, .{ - .ty = Type.initPayload(&type_payload.base), - .val = elem_ptr, - }); - } - } - } - - return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); -} - -fn floatOpAllowed(tag: zir.Inst.Tag) bool { - // extend this swich as additional operators are implemented - return switch (tag) { - .add, .sub => true, - else => false, - }; -} - -fn analyzeInstShl(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{}); -} - -fn analyzeInstShr(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstShr", .{}); -} - -fn analyzeInstBitwise(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstBitwise", .{}); -} - -fn analyzeInstArrayCat(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstArrayCat", .{}); -} - -fn analyzeInstArrayMul(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - return self.fail(scope, inst.base.src, "TODO implement analyzeInstArrayMul", .{}); -} - -fn analyzeInstArithmetic(self: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { - const tracy = trace(@src()); - defer tracy.end(); - - const lhs = try self.resolveInst(scope, inst.positionals.lhs); - const rhs = try self.resolveInst(scope, inst.positionals.rhs); - - const instructions = &[_]*Inst{ lhs, rhs }; - const resolved_type = try self.resolvePeerTypes(scope, instructions); - const casted_lhs = try self.coerce(scope, resolved_type, lhs); - const casted_rhs = try self.coerce(scope, resolved_type, rhs); - - const scalar_type = if (resolved_type.zigTypeTag() == .Vector) - resolved_type.elemType() - else - resolved_type; - - const scalar_tag = scalar_type.zigTypeTag(); - - if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) { - if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { - return self.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{ - lhs.ty.arrayLen(), - rhs.ty.arrayLen(), - }); - } - return self.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBinOp", .{}); - } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) { - return self.fail(scope, inst.base.src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ - lhs.ty, - rhs.ty, - }); - } - - const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; - const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; - - if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) { - return self.fail(scope, inst.base.src, "invalid operands to binary expression: '{}' and '{}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) }); - } - - if (casted_lhs.value()) |lhs_val| { - if (casted_rhs.value()) |rhs_val| { - return self.analyzeInstComptimeOp(scope, scalar_type, inst, lhs_val, rhs_val); - } - } - - const b = try self.requireRuntimeBlock(scope, inst.base.src); - const ir_tag = switch (inst.base.tag) { - .add => Inst.Tag.add, - .sub => Inst.Tag.sub, - else => return self.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{}''", .{@tagName(inst.base.tag)}), - }; - - return self.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs); -} - -/// Analyzes operands that are known at comptime -fn analyzeInstComptimeOp(self: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst { - // incase rhs is 0, simply return lhs without doing any calculations - // TODO Once division is implemented we should throw an error when dividing by 0. - if (rhs_val.tag() == .zero or rhs_val.tag() == .the_one_possible_value) { - return self.constInst(scope, inst.base.src, .{ - .ty = res_type, - .val = lhs_val, - }); - } - const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt; - - const value = try switch (inst.base.tag) { - .add => blk: { - const val = if (is_int) - intAdd(scope.arena(), lhs_val, rhs_val) - else - self.floatAdd(scope, res_type, inst, lhs_val, rhs_val); - break :blk val; - }, - .sub => blk: { - const val = if (is_int) - intSub(scope.arena(), lhs_val, rhs_val) - else - self.floatSub(scope, res_type, inst, lhs_val, rhs_val); - break :blk val; - }, - else => return self.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{}'", .{@tagName(inst.base.tag)}), - }; - - return self.constInst(scope, inst.base.src, .{ - .ty = res_type, - .val = value, - }); -} - -fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *zir.Inst.UnOp) InnerError!*Inst { - const ptr = try self.resolveInst(scope, deref.positionals.operand); - return self.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.operand.src); -} - -fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst { +pub fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: usize) InnerError!*Inst { const elem_ty = switch (ptr.ty.zigTypeTag()) { .Pointer => ptr.ty.elemType(), else => return self.fail(scope, ptr_src, "expected pointer, found '{}'", .{ptr.ty}), @@ -3205,165 +2154,13 @@ fn analyzeDeref(self: *Module, scope: *Scope, src: usize, ptr: *Inst, ptr_src: u return self.fail(scope, src, "TODO implement runtime deref", .{}); } -fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst { - const return_type = try self.resolveType(scope, assembly.positionals.return_type); - const asm_source = try self.resolveConstString(scope, assembly.positionals.asm_source); - const output = if (assembly.kw_args.output) |o| try self.resolveConstString(scope, o) else null; - - const inputs = try scope.arena().alloc([]const u8, assembly.kw_args.inputs.len); - const clobbers = try scope.arena().alloc([]const u8, assembly.kw_args.clobbers.len); - const args = try scope.arena().alloc(*Inst, assembly.kw_args.args.len); - - for (inputs) |*elem, i| { - elem.* = try self.resolveConstString(scope, assembly.kw_args.inputs[i]); - } - for (clobbers) |*elem, i| { - elem.* = try self.resolveConstString(scope, assembly.kw_args.clobbers[i]); - } - for (args) |*elem, i| { - const arg = try self.resolveInst(scope, assembly.kw_args.args[i]); - elem.* = try self.coerce(scope, Type.initTag(.usize), arg); - } - - const b = try self.requireRuntimeBlock(scope, assembly.base.src); - const inst = try b.arena.create(Inst.Assembly); - inst.* = .{ - .base = .{ - .tag = .assembly, - .ty = return_type, - .src = assembly.base.src, - }, - .asm_source = asm_source, - .is_volatile = assembly.kw_args.@"volatile", - .output = output, - .inputs = inputs, - .clobbers = clobbers, - .args = args, - }; - try b.instructions.append(self.gpa, &inst.base); - return &inst.base; +pub fn analyzeDeclRefByName(self: *Module, scope: *Scope, src: usize, decl_name: []const u8) InnerError!*Inst { + const decl = self.lookupDeclName(scope, decl_name) orelse + return self.fail(scope, src, "decl '{}' not found", .{decl_name}); + return self.analyzeDeclRef(scope, src, decl); } -fn analyzeInstCmp( - self: *Module, - scope: *Scope, - inst: *zir.Inst.BinOp, - op: std.math.CompareOperator, -) InnerError!*Inst { - const lhs = try self.resolveInst(scope, inst.positionals.lhs); - const rhs = try self.resolveInst(scope, inst.positionals.rhs); - - const is_equality_cmp = switch (op) { - .eq, .neq => true, - else => false, - }; - const lhs_ty_tag = lhs.ty.zigTypeTag(); - const rhs_ty_tag = rhs.ty.zigTypeTag(); - if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { - // null == null, null != null - return self.constBool(scope, inst.base.src, op == .eq); - } else if (is_equality_cmp and - ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or - rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) - { - // comparing null with optionals - const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; - if (opt_operand.value()) |opt_val| { - const is_null = opt_val.isNull(); - return self.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null); - } - const b = try self.requireRuntimeBlock(scope, inst.base.src); - const inst_tag: Inst.Tag = switch (op) { - .eq => .isnull, - .neq => .isnonnull, - else => unreachable, - }; - return self.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand); - } else if (is_equality_cmp and - ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) - { - return self.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{}); - } else 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 self.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type}); - } else if (is_equality_cmp and - ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or - (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) - { - return self.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); - } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { - if (!is_equality_cmp) { - return self.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)}); - } - return self.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{}); - } else 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 - // numeric types. - return self.cmpNumeric(scope, inst.base.src, lhs, rhs, op); - } - return self.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{}); -} - -fn analyzeInstTypeOf(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - const operand = try self.resolveInst(scope, inst.positionals.operand); - return self.constType(scope, inst.base.src, operand.ty); -} - -fn analyzeInstBoolNot(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - const uncasted_operand = try self.resolveInst(scope, inst.positionals.operand); - const bool_type = Type.initTag(.bool); - const operand = try self.coerce(scope, bool_type, uncasted_operand); - if (try self.resolveDefinedValue(scope, operand)) |val| { - return self.constBool(scope, inst.base.src, !val.toBool()); - } - const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addUnOp(b, inst.base.src, bool_type, .not, operand); -} - -fn analyzeInstIsNonNull(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst { - const operand = try self.resolveInst(scope, inst.positionals.operand); - return self.analyzeIsNull(scope, inst.base.src, operand, invert_logic); -} - -fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst { - const uncasted_cond = try self.resolveInst(scope, inst.positionals.condition); - const cond = try self.coerce(scope, Type.initTag(.bool), uncasted_cond); - - if (try self.resolveDefinedValue(scope, cond)) |cond_val| { - const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; - try self.analyzeBody(scope, body.*); - return self.constVoid(scope, inst.base.src); - } - - const parent_block = try self.requireRuntimeBlock(scope, inst.base.src); - - var true_block: Scope.Block = .{ - .parent = parent_block, - .func = parent_block.func, - .decl = parent_block.decl, - .instructions = .{}, - .arena = parent_block.arena, - }; - defer true_block.instructions.deinit(self.gpa); - try self.analyzeBody(&true_block.base, inst.positionals.then_body); - - var false_block: Scope.Block = .{ - .parent = parent_block, - .func = parent_block.func, - .decl = parent_block.decl, - .instructions = .{}, - .arena = parent_block.arena, - }; - defer false_block.instructions.deinit(self.gpa); - try self.analyzeBody(&false_block.base, inst.positionals.else_body); - - const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }; - const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }; - return self.addCondBr(parent_block, inst.base.src, cond, then_body, else_body); -} - -fn wantSafety(self: *Module, scope: *Scope) bool { +pub fn wantSafety(self: *Module, scope: *Scope) bool { return switch (self.optimize_mode) { .Debug => true, .ReleaseSafe => true, @@ -3372,42 +2169,12 @@ fn wantSafety(self: *Module, scope: *Scope) bool { }; } -fn analyzeUnreach(self: *Module, scope: *Scope, src: usize) InnerError!*Inst { +pub fn analyzeUnreach(self: *Module, scope: *Scope, src: usize) InnerError!*Inst { const b = try self.requireRuntimeBlock(scope, src); return self.addNoOp(b, src, Type.initTag(.noreturn), .unreach); } -fn analyzeInstUnreachNoChk(self: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { - return self.analyzeUnreach(scope, unreach.base.src); -} - -fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { - const b = try self.requireRuntimeBlock(scope, unreach.base.src); - if (self.wantSafety(scope)) { - // TODO Once we have a panic function to call, call it here instead of this. - _ = try self.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint); - } - return self.analyzeUnreach(scope, unreach.base.src); -} - -fn analyzeInstRet(self: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { - const operand = try self.resolveInst(scope, inst.positionals.operand); - const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); -} - -fn analyzeInstRetVoid(self: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { - const b = try self.requireRuntimeBlock(scope, inst.base.src); - return self.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid); -} - -fn analyzeBody(self: *Module, scope: *Scope, body: zir.Module.Body) !void { - for (body.instructions) |src_inst| { - src_inst.analyzed_inst = try self.analyzeInst(scope, src_inst); - } -} - -fn analyzeIsNull( +pub fn analyzeIsNull( self: *Module, scope: *Scope, src: usize, @@ -3418,7 +2185,7 @@ fn analyzeIsNull( } /// Asserts that lhs and rhs types are both numeric. -fn cmpNumeric( +pub fn cmpNumeric( self: *Module, scope: *Scope, src: usize, @@ -3601,7 +2368,7 @@ fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type { } } -fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { +pub fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { if (instructions.len == 0) return Type.initTag(.noreturn); @@ -3641,7 +2408,7 @@ fn resolvePeerTypes(self: *Module, scope: *Scope, instructions: []*Inst) !Type { return prev_inst.ty; } -fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { +pub fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { // If the types are the same, we can return the operand. if (dest_type.eql(inst.ty)) return inst; @@ -3737,7 +2504,7 @@ fn coerce(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type }); } -fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { +pub fn bitcast(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst { if (inst.value()) |val| { // Keep the comptime Value representation; take the new type. return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val }); @@ -3884,7 +2651,7 @@ fn srcHashEql(a: std.zig.SrcHash, b: std.zig.SrcHash) bool { return @bitCast(u128, a) == @bitCast(u128, b); } -fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { +pub fn intAdd(allocator: *Allocator, 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; @@ -3912,7 +2679,7 @@ fn intAdd(allocator: *Allocator, lhs: Value, rhs: Value) !Value { return Value.initPayload(val_payload); } -fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { +pub fn intSub(allocator: *Allocator, 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; @@ -3940,7 +2707,7 @@ fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value { return Value.initPayload(val_payload); } -fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinOp, lhs: Value, rhs: Value) !Value { +pub fn floatAdd(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { var bit_count = switch (float_type.tag()) { .comptime_float => 128, else => float_type.floatBits(self.target()), @@ -3949,7 +2716,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO const allocator = scope.arena(); const val_payload = switch (bit_count) { 16 => { - return self.fail(scope, inst.base.src, "TODO Implement addition for soft floats", .{}); + return self.fail(scope, src, "TODO Implement addition for soft floats", .{}); }, 32 => blk: { const lhs_val = lhs.toFloat(f32); @@ -3966,7 +2733,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO break :blk &val_payload.base; }, 128 => blk: { - return self.fail(scope, inst.base.src, "TODO Implement addition for big floats", .{}); + return self.fail(scope, src, "TODO Implement addition for big floats", .{}); }, else => unreachable, }; @@ -3974,7 +2741,7 @@ fn floatAdd(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO return Value.initPayload(val_payload); } -fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinOp, lhs: Value, rhs: Value) !Value { +pub fn floatSub(self: *Module, scope: *Scope, float_type: Type, src: usize, lhs: Value, rhs: Value) !Value { var bit_count = switch (float_type.tag()) { .comptime_float => 128, else => float_type.floatBits(self.target()), @@ -3983,7 +2750,7 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO const allocator = scope.arena(); const val_payload = switch (bit_count) { 16 => { - return self.fail(scope, inst.base.src, "TODO Implement substraction for soft floats", .{}); + return self.fail(scope, src, "TODO Implement substraction for soft floats", .{}); }, 32 => blk: { const lhs_val = lhs.toFloat(f32); @@ -4000,7 +2767,7 @@ fn floatSub(self: *Module, scope: *Scope, float_type: Type, inst: *zir.Inst.BinO break :blk &val_payload.base; }, 128 => blk: { - return self.fail(scope, inst.base.src, "TODO Implement substraction for big floats", .{}); + return self.fail(scope, src, "TODO Implement substraction for big floats", .{}); }, else => unreachable, }; diff --git a/src-self-hosted/astgen.zig b/src-self-hosted/astgen.zig index 6a9c47f6e3..eeeb74a0d8 100644 --- a/src-self-hosted/astgen.zig +++ b/src-self-hosted/astgen.zig @@ -36,7 +36,7 @@ pub const ResultLoc = union(enum) { pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*zir.Inst { const type_src = scope.tree().token_locs[type_node.firstToken()].start; - const type_type = try mod.addZIRInstConst(scope, type_src, .{ + const type_type = try addZIRInstConst(mod, scope, type_src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.type_type), }); @@ -146,7 +146,7 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block else => { const possibly_unused_result = try expr(mod, scope, .none, statement); const src = scope.tree().token_locs[statement.firstToken()].start; - _ = try mod.addZIRUnOp(scope, src, .ensure_result_used, possibly_unused_result); + _ = try addZIRUnOp(mod, scope, src, .ensure_result_used, possibly_unused_result); }, } } @@ -177,7 +177,7 @@ fn varDecl( if (nodeMayNeedMemoryLocation(init_node)) { if (node.getTrailer("type_node")) |type_node| { const type_inst = try typeExpr(mod, scope, type_node); - const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst); + const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst); const result_loc: ResultLoc = .{ .ptr = alloc }; const init_inst = try expr(mod, scope, result_loc, init_node); const sub_scope = try block_arena.create(Scope.LocalVal); @@ -189,7 +189,7 @@ fn varDecl( }; return &sub_scope.base; } else { - const alloc = try mod.addZIRNoOpT(scope, name_src, .alloc_inferred); + const alloc = try addZIRNoOpT(mod, scope, name_src, .alloc_inferred); const result_loc: ResultLoc = .{ .inferred_ptr = alloc }; const init_inst = try expr(mod, scope, result_loc, init_node); const sub_scope = try block_arena.create(Scope.LocalVal); @@ -220,7 +220,7 @@ fn varDecl( .Keyword_var => { if (node.getTrailer("type_node")) |type_node| { const type_inst = try typeExpr(mod, scope, type_node); - const alloc = try mod.addZIRUnOp(scope, name_src, .alloc, type_inst); + const alloc = try addZIRUnOp(mod, scope, name_src, .alloc, type_inst); const result_loc: ResultLoc = .{ .ptr = alloc }; const init_inst = try expr(mod, scope, result_loc, init_node); const sub_scope = try block_arena.create(Scope.LocalPtr); @@ -232,7 +232,7 @@ fn varDecl( }; return &sub_scope.base; } else { - const alloc = try mod.addZIRNoOp(scope, name_src, .alloc_inferred); + const alloc = try addZIRNoOp(mod, scope, name_src, .alloc_inferred); const result_loc = .{ .inferred_ptr = alloc.castTag(.alloc_inferred).? }; const init_inst = try expr(mod, scope, result_loc, init_node); const sub_scope = try block_arena.create(Scope.LocalPtr); @@ -269,26 +269,26 @@ fn assignOp( op_inst_tag: zir.Inst.Tag, ) InnerError!void { const lhs_ptr = try expr(mod, scope, .lvalue, infix_node.lhs); - const lhs = try mod.addZIRUnOp(scope, lhs_ptr.src, .deref, lhs_ptr); - const lhs_type = try mod.addZIRUnOp(scope, lhs_ptr.src, .typeof, lhs); + const lhs = try addZIRUnOp(mod, scope, lhs_ptr.src, .deref, lhs_ptr); + const lhs_type = try addZIRUnOp(mod, scope, lhs_ptr.src, .typeof, lhs); const rhs = try expr(mod, scope, .{ .ty = lhs_type }, infix_node.rhs); const tree = scope.tree(); const src = tree.token_locs[infix_node.op_token].start; - const result = try mod.addZIRBinOp(scope, src, op_inst_tag, lhs, rhs); - _ = try mod.addZIRBinOp(scope, src, .store, lhs_ptr, result); + const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs); + _ = try addZIRBinOp(mod, scope, src, .store, lhs_ptr, result); } fn boolNot(mod: *Module, scope: *Scope, node: *ast.Node.SimplePrefixOp) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.op_token].start; - const bool_type = try mod.addZIRInstConst(scope, src, .{ + const bool_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.bool_type), }); const operand = try expr(mod, scope, .{ .ty = bool_type }, node.rhs); - return mod.addZIRUnOp(scope, src, .boolnot, operand); + return addZIRUnOp(mod, scope, src, .boolnot, operand); } /// Identifier token -> String (allocated in scope.arena()) @@ -317,7 +317,7 @@ pub fn identifierStringInst(mod: *Module, scope: *Scope, node: *ast.Node.OneToke const ident_name = try identifierTokenString(mod, scope, node.token); - return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{}); + return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = ident_name }, .{}); } fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError!*zir.Inst { @@ -328,15 +328,15 @@ fn field(mod: *Module, scope: *Scope, node: *ast.Node.SimpleInfixOp) InnerError! const lhs = try expr(mod, scope, .none, node.lhs); const field_name = try identifierStringInst(mod, scope, node.rhs.castTag(.Identifier).?); - const pointer = try mod.addZIRInst(scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}); - return mod.addZIRUnOp(scope, src, .deref, pointer); + const pointer = try addZIRInst(mod, scope, src, zir.Inst.FieldPtr, .{ .object_ptr = lhs, .field_name = field_name }, .{}); + return addZIRUnOp(mod, scope, src, .deref, pointer); } fn deref(mod: *Module, scope: *Scope, node: *ast.Node.SimpleSuffixOp) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[node.rtoken].start; const lhs = try expr(mod, scope, .none, node.lhs); - return mod.addZIRUnOp(scope, src, .deref, lhs); + return addZIRUnOp(mod, scope, src, .deref, lhs); } fn simpleBinOp( @@ -352,7 +352,7 @@ fn simpleBinOp( const lhs = try expr(mod, scope, .none, infix_node.lhs); const rhs = try expr(mod, scope, .none, infix_node.rhs); - const result = try mod.addZIRBinOp(scope, src, op_inst_tag, lhs, rhs); + const result = try addZIRBinOp(mod, scope, src, op_inst_tag, lhs, rhs); return rlWrap(mod, scope, rl, result); } @@ -375,19 +375,19 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn const tree = scope.tree(); const if_src = tree.token_locs[if_node.if_token].start; - const bool_type = try mod.addZIRInstConst(scope, if_src, .{ + const bool_type = try addZIRInstConst(mod, scope, if_src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.bool_type), }); const cond = try expr(mod, &block_scope.base, .{ .ty = bool_type }, if_node.condition); - const condbr = try mod.addZIRInstSpecial(&block_scope.base, if_src, zir.Inst.CondBr, .{ + const condbr = try addZIRInstSpecial(mod, &block_scope.base, if_src, zir.Inst.CondBr, .{ .condition = cond, .then_body = undefined, // populated below .else_body = undefined, // populated below }, .{}); - const block = try mod.addZIRInstBlock(scope, if_src, .{ + const block = try addZIRInstBlock(mod, scope, if_src, .{ .instructions = try block_scope.arena.dupe(*zir.Inst, block_scope.instructions.items), }); var then_scope: Scope.GenZIR = .{ @@ -410,7 +410,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn const then_result = try expr(mod, &then_scope.base, branch_rl, if_node.body); if (!then_result.tag.isNoReturn()) { const then_src = tree.token_locs[if_node.body.lastToken()].start; - _ = try mod.addZIRInst(&then_scope.base, then_src, zir.Inst.Break, .{ + _ = try addZIRInst(mod, &then_scope.base, then_src, zir.Inst.Break, .{ .block = block, .operand = then_result, }, .{}); @@ -431,7 +431,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body); if (!else_result.tag.isNoReturn()) { const else_src = tree.token_locs[else_node.body.lastToken()].start; - _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.Break, .{ + _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.Break, .{ .block = block, .operand = else_result, }, .{}); @@ -440,7 +440,7 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn // TODO Optimization opportunity: we can avoid an allocation and a memcpy here // by directly allocating the body for this one instruction. const else_src = tree.token_locs[if_node.lastToken()].start; - _ = try mod.addZIRInst(&else_scope.base, else_src, zir.Inst.BreakVoid, .{ + _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.BreakVoid, .{ .block = block, }, .{}); } @@ -456,16 +456,16 @@ fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerE const src = tree.token_locs[cfe.ltoken].start; if (cfe.getRHS()) |rhs_node| { if (nodeMayNeedMemoryLocation(rhs_node)) { - const ret_ptr = try mod.addZIRNoOp(scope, src, .ret_ptr); + const ret_ptr = try addZIRNoOp(mod, scope, src, .ret_ptr); const operand = try expr(mod, scope, .{ .ptr = ret_ptr }, rhs_node); - return mod.addZIRUnOp(scope, src, .@"return", operand); + return addZIRUnOp(mod, scope, src, .@"return", operand); } else { - const fn_ret_ty = try mod.addZIRNoOp(scope, src, .ret_type); + const fn_ret_ty = try addZIRNoOp(mod, scope, src, .ret_type); const operand = try expr(mod, scope, .{ .ty = fn_ret_ty }, rhs_node); - return mod.addZIRUnOp(scope, src, .@"return", operand); + return addZIRUnOp(mod, scope, src, .@"return", operand); } } else { - return mod.addZIRNoOp(scope, src, .returnvoid); + return addZIRNoOp(mod, scope, src, .returnvoid); } } @@ -481,7 +481,7 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError } if (getSimplePrimitiveValue(ident_name)) |typed_value| { - return mod.addZIRInstConst(scope, src, typed_value); + return addZIRInstConst(mod, scope, src, typed_value); } if (ident_name.len >= 2) integer: { @@ -505,13 +505,13 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError else => { const int_type_payload = try scope.arena().create(Value.Payload.IntType); int_type_payload.* = .{ .signed = is_signed, .bits = bit_count }; - return mod.addZIRInstConst(scope, src, .{ + return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.comptime_int), .val = Value.initPayload(&int_type_payload.base), }); }, }; - return mod.addZIRInstConst(scope, src, .{ + return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = val, }); @@ -532,7 +532,7 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError .local_ptr => { const local_ptr = s.cast(Scope.LocalPtr).?; if (mem.eql(u8, local_ptr.name, ident_name)) { - return try mod.addZIRUnOp(scope, src, .deref, local_ptr.ptr); + return try addZIRUnOp(mod, scope, src, .deref, local_ptr.ptr); } s = local_ptr.parent; }, @@ -542,7 +542,7 @@ fn identifier(mod: *Module, scope: *Scope, ident: *ast.Node.OneToken) InnerError } if (mod.lookupDeclName(scope, ident_name)) |decl| { - return try mod.addZIRInst(scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); + return try addZIRInst(mod, scope, src, zir.Inst.DeclValInModule, .{ .decl = decl }, .{}); } return mod.failNode(scope, &ident.base, "use of undeclared identifier '{}'", .{ident_name}); @@ -564,7 +564,7 @@ fn stringLiteral(mod: *Module, scope: *Scope, str_lit: *ast.Node.OneToken) Inner }; const src = tree.token_locs[str_lit.token].start; - return mod.addZIRInst(scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); + return addZIRInst(mod, scope, src, zir.Inst.Str, .{ .bytes = bytes }, .{}); } fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) InnerError!*zir.Inst { @@ -589,7 +589,7 @@ fn integerLiteral(mod: *Module, scope: *Scope, int_lit: *ast.Node.OneToken) Inne const int_payload = try arena.create(Value.Payload.Int_u64); int_payload.* = .{ .int = small_int }; const src = tree.token_locs[int_lit.token].start; - return mod.addZIRInstConst(scope, src, .{ + return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.comptime_int), .val = Value.initPayload(&int_payload.base), }); @@ -612,7 +612,7 @@ fn floatLiteral(mod: *Module, scope: *Scope, float_lit: *ast.Node.OneToken) Inne const float_payload = try arena.create(Value.Payload.Float_128); float_payload.* = .{ .val = val }; const src = tree.token_locs[float_lit.token].start; - return mod.addZIRInstConst(scope, src, .{ + return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.comptime_float), .val = Value.initPayload(&float_payload.base), }); @@ -622,7 +622,7 @@ fn undefLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerErro const arena = scope.arena(); const tree = scope.tree(); const src = tree.token_locs[node.token].start; - return mod.addZIRInstConst(scope, src, .{ + return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.@"undefined"), .val = Value.initTag(.undef), }); @@ -632,7 +632,7 @@ fn boolLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError const arena = scope.arena(); const tree = scope.tree(); const src = tree.token_locs[node.token].start; - return mod.addZIRInstConst(scope, src, .{ + return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.bool), .val = switch (tree.token_ids[node.token]) { .Keyword_true => Value.initTag(.bool_true), @@ -646,7 +646,7 @@ fn nullLiteral(mod: *Module, scope: *Scope, node: *ast.Node.OneToken) InnerError const arena = scope.arena(); const tree = scope.tree(); const src = tree.token_locs[node.token].start; - return mod.addZIRInstConst(scope, src, .{ + return addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.@"null"), .val = Value.initTag(.null_value), }); @@ -664,7 +664,7 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi const src = tree.token_locs[asm_node.asm_token].start; - const str_type = try mod.addZIRInstConst(scope, src, .{ + const str_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.const_slice_u8_type), }); @@ -676,11 +676,11 @@ fn assembly(mod: *Module, scope: *Scope, asm_node: *ast.Node.Asm) InnerError!*zi args[i] = try expr(mod, scope, .none, input.expr); } - const return_type = try mod.addZIRInstConst(scope, src, .{ + const return_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.void_type), }); - const asm_inst = try mod.addZIRInst(scope, src, zir.Inst.Asm, .{ + const asm_inst = try addZIRInst(mod, scope, src, zir.Inst.Asm, .{ .asm_source = try expr(mod, scope, str_type_rl, asm_node.template), .return_type = return_type, }, .{ @@ -710,14 +710,14 @@ fn simpleCast( try ensureBuiltinParamCount(mod, scope, call, 2); const tree = scope.tree(); const src = tree.token_locs[call.builtin_token].start; - const type_type = try mod.addZIRInstConst(scope, src, .{ + const type_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.type_type), }); const params = call.params(); const dest_type = try expr(mod, scope, .{ .ty = type_type }, params[0]); const rhs = try expr(mod, scope, .none, params[1]); - const result = try mod.addZIRBinOp(scope, src, inst_tag, dest_type, rhs); + const result = try addZIRBinOp(mod, scope, src, inst_tag, dest_type, rhs); return rlWrap(mod, scope, rl, result); } @@ -726,7 +726,7 @@ fn ptrToInt(mod: *Module, scope: *Scope, call: *ast.Node.BuiltinCall) InnerError const operand = try expr(mod, scope, .none, call.params()[0]); const tree = scope.tree(); const src = tree.token_locs[call.builtin_token].start; - return mod.addZIRUnOp(scope, src, .ptrtoint, operand); + return addZIRUnOp(mod, scope, src, .ptrtoint, operand); } fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) InnerError!*zir.Inst { @@ -739,19 +739,19 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I .none => return try expr(mod, scope, .{ .ty = dest_type }, params[1]), .discard => { const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); - _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); + _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); return result; }, .lvalue => { const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); - return mod.addZIRUnOp(scope, result.src, .ref, result); + return addZIRUnOp(mod, scope, result.src, .ref, result); }, .ty => |result_ty| { const result = try expr(mod, scope, .{ .ty = dest_type }, params[1]); - return mod.addZIRBinOp(scope, src, .as, result_ty, result); + return addZIRBinOp(mod, scope, src, .as, result_ty, result); }, .ptr => |result_ptr| { - const casted_result_ptr = try mod.addZIRBinOp(scope, src, .coerce_result_ptr, dest_type, result_ptr); + const casted_result_ptr = try addZIRBinOp(mod, scope, src, .coerce_result_ptr, dest_type, result_ptr); return expr(mod, scope, .{ .ptr = casted_result_ptr }, params[1]); }, .bitcasted_ptr => |bitcasted_ptr| { @@ -763,7 +763,7 @@ fn as(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCall) I return mod.failTok(scope, call.builtin_token, "TODO implement @as with inferred-type result location pointer", .{}); }, .block_ptr => |block_ptr| { - const casted_block_ptr = try mod.addZIRInst(scope, src, zir.Inst.CoerceResultBlockPtr, .{ + const casted_block_ptr = try addZIRInst(mod, scope, src, zir.Inst.CoerceResultBlockPtr, .{ .dest_type = dest_type, .block = block_ptr, }, .{}); @@ -776,7 +776,7 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa try ensureBuiltinParamCount(mod, scope, call, 2); const tree = scope.tree(); const src = tree.token_locs[call.builtin_token].start; - const type_type = try mod.addZIRInstConst(scope, src, .{ + const type_type = try addZIRInstConst(mod, scope, src, .{ .ty = Type.initTag(.type), .val = Value.initTag(.type_type), }); @@ -785,26 +785,26 @@ fn bitCast(mod: *Module, scope: *Scope, rl: ResultLoc, call: *ast.Node.BuiltinCa switch (rl) { .none => { const operand = try expr(mod, scope, .none, params[1]); - return mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand); + return addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); }, .discard => { const operand = try expr(mod, scope, .none, params[1]); - const result = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, operand); - _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); + const result = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, operand); + _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); return result; }, .lvalue => { const operand = try expr(mod, scope, .lvalue, params[1]); - const result = try mod.addZIRBinOp(scope, src, .bitcast_lvalue, dest_type, operand); + const result = try addZIRBinOp(mod, scope, src, .bitcast_lvalue, dest_type, operand); return result; }, .ty => |result_ty| { const result = try expr(mod, scope, .none, params[1]); - const bitcasted = try mod.addZIRBinOp(scope, src, .bitcast, dest_type, result); - return mod.addZIRBinOp(scope, src, .as, result_ty, bitcasted); + const bitcasted = try addZIRBinOp(mod, scope, src, .bitcast, dest_type, result); + return addZIRBinOp(mod, scope, src, .as, result_ty, bitcasted); }, .ptr => |result_ptr| { - const casted_result_ptr = try mod.addZIRUnOp(scope, src, .bitcast_result_ptr, result_ptr); + const casted_result_ptr = try addZIRUnOp(mod, scope, src, .bitcast_result_ptr, result_ptr); return expr(mod, scope, .{ .bitcasted_ptr = casted_result_ptr.castTag(.bitcast_result_ptr).? }, params[1]); }, .bitcasted_ptr => |bitcasted_ptr| { @@ -852,7 +852,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In const args = try scope.getGenZIR().arena.alloc(*zir.Inst, param_nodes.len); for (param_nodes) |param_node, i| { const param_src = tree.token_locs[param_node.firstToken()].start; - const param_type = try mod.addZIRInst(scope, param_src, zir.Inst.ParamType, .{ + const param_type = try addZIRInst(mod, scope, param_src, zir.Inst.ParamType, .{ .func = lhs, .arg_index = i, }, .{}); @@ -860,7 +860,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In } const src = tree.token_locs[node.lhs.firstToken()].start; - const result = try mod.addZIRInst(scope, src, zir.Inst.Call, .{ + const result = try addZIRInst(mod, scope, src, zir.Inst.Call, .{ .func = lhs, .args = args, }, .{}); @@ -871,7 +871,7 @@ fn callExpr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node.Call) In fn unreach(mod: *Module, scope: *Scope, unreach_node: *ast.Node.OneToken) InnerError!*zir.Inst { const tree = scope.tree(); const src = tree.token_locs[unreach_node.token].start; - return mod.addZIRNoOp(scope, src, .@"unreachable"); + return addZIRNoOp(mod, scope, src, .@"unreachable"); } fn getSimplePrimitiveValue(name: []const u8) ?TypedValue { @@ -1053,20 +1053,20 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr .none => return result, .discard => { // Emit a compile error for discarding error values. - _ = try mod.addZIRUnOp(scope, result.src, .ensure_result_non_error, result); + _ = try addZIRUnOp(mod, scope, result.src, .ensure_result_non_error, result); return result; }, .lvalue => { // We need a pointer but we have a value. - return mod.addZIRUnOp(scope, result.src, .ref, result); + return addZIRUnOp(mod, scope, result.src, .ref, result); }, - .ty => |ty_inst| return mod.addZIRBinOp(scope, result.src, .as, ty_inst, result), + .ty => |ty_inst| return addZIRBinOp(mod, scope, result.src, .as, ty_inst, result), .ptr => |ptr_inst| { - const casted_result = try mod.addZIRInst(scope, result.src, zir.Inst.CoerceToPtrElem, .{ + const casted_result = try addZIRInst(mod, scope, result.src, zir.Inst.CoerceToPtrElem, .{ .ptr = ptr_inst, .value = result, }, .{}); - _ = try mod.addZIRInst(scope, result.src, zir.Inst.Store, .{ + _ = try addZIRInst(mod, scope, result.src, zir.Inst.Store, .{ .ptr = ptr_inst, .value = casted_result, }, .{}); @@ -1083,3 +1083,121 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr }, } } + +pub fn addZIRInstSpecial( + mod: *Module, + scope: *Scope, + src: usize, + comptime T: type, + positionals: std.meta.fieldInfo(T, "positionals").field_type, + kw_args: std.meta.fieldInfo(T, "kw_args").field_type, +) !*T { + const gen_zir = scope.getGenZIR(); + try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); + const inst = try gen_zir.arena.create(T); + inst.* = .{ + .base = .{ + .tag = T.base_tag, + .src = src, + }, + .positionals = positionals, + .kw_args = kw_args, + }; + gen_zir.instructions.appendAssumeCapacity(&inst.base); + return inst; +} + +pub fn addZIRNoOpT(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst.NoOp { + const gen_zir = scope.getGenZIR(); + try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); + const inst = try gen_zir.arena.create(zir.Inst.NoOp); + inst.* = .{ + .base = .{ + .tag = tag, + .src = src, + }, + .positionals = .{}, + .kw_args = .{}, + }; + gen_zir.instructions.appendAssumeCapacity(&inst.base); + return inst; +} + +pub fn addZIRNoOp(mod: *Module, scope: *Scope, src: usize, tag: zir.Inst.Tag) !*zir.Inst { + const inst = try addZIRNoOpT(mod, scope, src, tag); + return &inst.base; +} + +pub fn addZIRUnOp( + mod: *Module, + scope: *Scope, + src: usize, + tag: zir.Inst.Tag, + operand: *zir.Inst, +) !*zir.Inst { + const gen_zir = scope.getGenZIR(); + try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); + const inst = try gen_zir.arena.create(zir.Inst.UnOp); + inst.* = .{ + .base = .{ + .tag = tag, + .src = src, + }, + .positionals = .{ + .operand = operand, + }, + .kw_args = .{}, + }; + gen_zir.instructions.appendAssumeCapacity(&inst.base); + return &inst.base; +} + +pub fn addZIRBinOp( + mod: *Module, + scope: *Scope, + src: usize, + tag: zir.Inst.Tag, + lhs: *zir.Inst, + rhs: *zir.Inst, +) !*zir.Inst { + const gen_zir = scope.getGenZIR(); + try gen_zir.instructions.ensureCapacity(mod.gpa, gen_zir.instructions.items.len + 1); + const inst = try gen_zir.arena.create(zir.Inst.BinOp); + inst.* = .{ + .base = .{ + .tag = tag, + .src = src, + }, + .positionals = .{ + .lhs = lhs, + .rhs = rhs, + }, + .kw_args = .{}, + }; + gen_zir.instructions.appendAssumeCapacity(&inst.base); + return &inst.base; +} + +pub fn addZIRInst( + mod: *Module, + scope: *Scope, + src: usize, + comptime T: type, + positionals: std.meta.fieldInfo(T, "positionals").field_type, + kw_args: std.meta.fieldInfo(T, "kw_args").field_type, +) !*zir.Inst { + const inst_special = try addZIRInstSpecial(mod, scope, src, T, positionals, kw_args); + return &inst_special.base; +} + +/// TODO The existence of this function is a workaround for a bug in stage1. +pub fn addZIRInstConst(mod: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*zir.Inst { + const P = std.meta.fieldInfo(zir.Inst.Const, "positionals").field_type; + return addZIRInst(mod, scope, src, zir.Inst.Const, P{ .typed_value = typed_value }, .{}); +} + +/// TODO The existence of this function is a workaround for a bug in stage1. +pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Block { + const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type; + return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{}); +} diff --git a/src-self-hosted/zir_sema.zig b/src-self-hosted/zir_sema.zig new file mode 100644 index 0000000000..877ad4bfa6 --- /dev/null +++ b/src-self-hosted/zir_sema.zig @@ -0,0 +1,1128 @@ +//! Semantic analysis of ZIR instructions. +//! This file operates on a `Module` instance, transforming untyped ZIR +//! instructions into semantically-analyzed IR instructions. It does type +//! checking, comptime control flow, and safety-check generation. This is the +//! the heart of the Zig compiler. +//! When deciding if something goes into this file or into Module, here is a +//! guiding principle: if it has to do with (untyped) ZIR instructions, it goes +//! here. If the analysis operates on typed IR instructions, it goes in Module. + +const std = @import("std"); +const mem = std.mem; +const Allocator = std.mem.Allocator; +const Value = @import("value.zig").Value; +const Type = @import("type.zig").Type; +const TypedValue = @import("TypedValue.zig"); +const assert = std.debug.assert; +const ir = @import("ir.zig"); +const zir = @import("zir.zig"); +const Module = @import("Module.zig"); +const Inst = ir.Inst; +const Body = ir.Body; +const trace = @import("tracy.zig").trace; +const Scope = Module.Scope; +const InnerError = Module.InnerError; +const Decl = Module.Decl; + +pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { + switch (old_inst.tag) { + .alloc => return analyzeInstAlloc(mod, scope, old_inst.castTag(.alloc).?), + .alloc_inferred => return analyzeInstAllocInferred(mod, scope, old_inst.castTag(.alloc_inferred).?), + .arg => return analyzeInstArg(mod, scope, old_inst.castTag(.arg).?), + .bitcast_lvalue => return analyzeInstBitCastLValue(mod, scope, old_inst.castTag(.bitcast_lvalue).?), + .bitcast_result_ptr => return analyzeInstBitCastResultPtr(mod, scope, old_inst.castTag(.bitcast_result_ptr).?), + .block => return analyzeInstBlock(mod, scope, old_inst.castTag(.block).?), + .@"break" => return analyzeInstBreak(mod, scope, old_inst.castTag(.@"break").?), + .breakpoint => return analyzeInstBreakpoint(mod, scope, old_inst.castTag(.breakpoint).?), + .breakvoid => return analyzeInstBreakVoid(mod, scope, old_inst.castTag(.breakvoid).?), + .call => return analyzeInstCall(mod, scope, old_inst.castTag(.call).?), + .coerce_result_block_ptr => return analyzeInstCoerceResultBlockPtr(mod, scope, old_inst.castTag(.coerce_result_block_ptr).?), + .coerce_result_ptr => return analyzeInstCoerceResultPtr(mod, scope, old_inst.castTag(.coerce_result_ptr).?), + .coerce_to_ptr_elem => return analyzeInstCoerceToPtrElem(mod, scope, old_inst.castTag(.coerce_to_ptr_elem).?), + .compileerror => return analyzeInstCompileError(mod, scope, old_inst.castTag(.compileerror).?), + .@"const" => return analyzeInstConst(mod, scope, old_inst.castTag(.@"const").?), + .declref => return analyzeInstDeclRef(mod, scope, old_inst.castTag(.declref).?), + .declref_str => return analyzeInstDeclRefStr(mod, scope, old_inst.castTag(.declref_str).?), + .declval => return analyzeInstDeclVal(mod, scope, old_inst.castTag(.declval).?), + .declval_in_module => return analyzeInstDeclValInModule(mod, scope, old_inst.castTag(.declval_in_module).?), + .ensure_result_used => return analyzeInstEnsureResultUsed(mod, scope, old_inst.castTag(.ensure_result_used).?), + .ensure_result_non_error => return analyzeInstEnsureResultNonError(mod, scope, old_inst.castTag(.ensure_result_non_error).?), + .ref => return analyzeInstRef(mod, scope, old_inst.castTag(.ref).?), + .ret_ptr => return analyzeInstRetPtr(mod, scope, old_inst.castTag(.ret_ptr).?), + .ret_type => return analyzeInstRetType(mod, scope, old_inst.castTag(.ret_type).?), + .store => return analyzeInstStore(mod, scope, old_inst.castTag(.store).?), + .str => return analyzeInstStr(mod, scope, old_inst.castTag(.str).?), + .int => { + const big_int = old_inst.castTag(.int).?.positionals.int; + return mod.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int); + }, + .inttype => return analyzeInstIntType(mod, scope, old_inst.castTag(.inttype).?), + .param_type => return analyzeInstParamType(mod, scope, old_inst.castTag(.param_type).?), + .ptrtoint => return analyzeInstPtrToInt(mod, scope, old_inst.castTag(.ptrtoint).?), + .fieldptr => return analyzeInstFieldPtr(mod, scope, old_inst.castTag(.fieldptr).?), + .deref => return analyzeInstDeref(mod, scope, old_inst.castTag(.deref).?), + .as => return analyzeInstAs(mod, scope, old_inst.castTag(.as).?), + .@"asm" => return analyzeInstAsm(mod, scope, old_inst.castTag(.@"asm").?), + .@"unreachable" => return analyzeInstUnreachable(mod, scope, old_inst.castTag(.@"unreachable").?), + .unreach_nocheck => return analyzeInstUnreachNoChk(mod, scope, old_inst.castTag(.unreach_nocheck).?), + .@"return" => return analyzeInstRet(mod, scope, old_inst.castTag(.@"return").?), + .returnvoid => return analyzeInstRetVoid(mod, scope, old_inst.castTag(.returnvoid).?), + .@"fn" => return analyzeInstFn(mod, scope, old_inst.castTag(.@"fn").?), + .@"export" => return analyzeInstExport(mod, scope, old_inst.castTag(.@"export").?), + .primitive => return analyzeInstPrimitive(mod, scope, old_inst.castTag(.primitive).?), + .fntype => return analyzeInstFnType(mod, scope, old_inst.castTag(.fntype).?), + .intcast => return analyzeInstIntCast(mod, scope, old_inst.castTag(.intcast).?), + .bitcast => return analyzeInstBitCast(mod, scope, old_inst.castTag(.bitcast).?), + .floatcast => return analyzeInstFloatCast(mod, scope, old_inst.castTag(.floatcast).?), + .elemptr => return analyzeInstElemPtr(mod, scope, old_inst.castTag(.elemptr).?), + .add => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.add).?), + .addwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.addwrap).?), + .sub => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.sub).?), + .subwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.subwrap).?), + .mul => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mul).?), + .mulwrap => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mulwrap).?), + .div => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.div).?), + .mod_rem => return analyzeInstArithmetic(mod, scope, old_inst.castTag(.mod_rem).?), + .array_cat => return analyzeInstArrayCat(mod, scope, old_inst.castTag(.array_cat).?), + .array_mul => return analyzeInstArrayMul(mod, scope, old_inst.castTag(.array_mul).?), + .bitand => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitand).?), + .bitor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.bitor).?), + .xor => return analyzeInstBitwise(mod, scope, old_inst.castTag(.xor).?), + .shl => return analyzeInstShl(mod, scope, old_inst.castTag(.shl).?), + .shr => return analyzeInstShr(mod, scope, old_inst.castTag(.shr).?), + .cmp_lt => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_lt).?, .lt), + .cmp_lte => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_lte).?, .lte), + .cmp_eq => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_eq).?, .eq), + .cmp_gte => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_gte).?, .gte), + .cmp_gt => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_gt).?, .gt), + .cmp_neq => return analyzeInstCmp(mod, scope, old_inst.castTag(.cmp_neq).?, .neq), + .condbr => return analyzeInstCondBr(mod, scope, old_inst.castTag(.condbr).?), + .isnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnull).?, true), + .isnonnull => return analyzeInstIsNonNull(mod, scope, old_inst.castTag(.isnonnull).?, false), + .boolnot => return analyzeInstBoolNot(mod, scope, old_inst.castTag(.boolnot).?), + .typeof => return analyzeInstTypeOf(mod, scope, old_inst.castTag(.typeof).?), + } +} + +pub fn analyzeBody(mod: *Module, scope: *Scope, body: zir.Module.Body) !void { + for (body.instructions) |src_inst| { + src_inst.analyzed_inst = try analyzeInst(mod, scope, src_inst); + } +} + +pub fn analyzeBodyValueAsType(mod: *Module, block_scope: *Scope.Block, body: zir.Module.Body) !Type { + try analyzeBody(mod, &block_scope.base, body); + for (block_scope.instructions.items) |inst| { + if (inst.castTag(.ret)) |ret| { + const val = try mod.resolveConstValue(&block_scope.base, ret.operand); + return val.toType(); + } else { + return mod.fail(&block_scope.base, inst.src, "unable to resolve comptime value", .{}); + } + } + unreachable; +} + +pub fn analyzeZirDecl(mod: *Module, decl: *Decl, src_decl: *zir.Decl) InnerError!bool { + var decl_scope: Scope.DeclAnalysis = .{ + .decl = decl, + .arena = std.heap.ArenaAllocator.init(mod.gpa), + }; + errdefer decl_scope.arena.deinit(); + + decl.analysis = .in_progress; + + const typed_value = try analyzeConstInst(mod, &decl_scope.base, src_decl.inst); + const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State); + + var prev_type_has_bits = false; + var type_changed = true; + + if (decl.typedValueManaged()) |tvm| { + prev_type_has_bits = tvm.typed_value.ty.hasCodeGenBits(); + type_changed = !tvm.typed_value.ty.eql(typed_value.ty); + + tvm.deinit(mod.gpa); + } + + arena_state.* = decl_scope.arena.state; + decl.typed_value = .{ + .most_recent = .{ + .typed_value = typed_value, + .arena = arena_state, + }, + }; + decl.analysis = .complete; + decl.generation = mod.generation; + if (typed_value.ty.hasCodeGenBits()) { + // We don't fully codegen the decl until later, but we do need to reserve a global + // offset table index for it. This allows us to codegen decls out of dependency order, + // increasing how many computations can be done in parallel. + try mod.bin_file.allocateDeclIndexes(decl); + try mod.work_queue.writeItem(.{ .codegen_decl = decl }); + } else if (prev_type_has_bits) { + mod.bin_file.freeDecl(decl); + } + + return type_changed; +} + +pub fn resolveZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { + const zir_module = mod.root_scope.cast(Scope.ZIRModule).?; + const entry = zir_module.contents.module.findDecl(src_decl.name).?; + return resolveZirDeclHavingIndex(mod, scope, src_decl, entry.index); +} + +fn resolveZirDeclHavingIndex(mod: *Module, scope: *Scope, src_decl: *zir.Decl, src_index: usize) InnerError!*Decl { + const name_hash = scope.namespace().fullyQualifiedNameHash(src_decl.name); + const decl = mod.decl_table.get(name_hash).?; + decl.src_index = src_index; + try mod.ensureDeclAnalyzed(decl); + return decl; +} + +/// Declares a dependency on the decl. +fn resolveCompleteZirDecl(mod: *Module, scope: *Scope, src_decl: *zir.Decl) InnerError!*Decl { + const decl = try resolveZirDecl(mod, scope, src_decl); + switch (decl.analysis) { + .unreferenced => unreachable, + .in_progress => unreachable, + .outdated => unreachable, + + .dependency_failure, + .sema_failure, + .sema_failure_retryable, + .codegen_failure, + .codegen_failure_retryable, + => return error.AnalysisFail, + + .complete => {}, + } + return decl; +} + +/// TODO Look into removing this function. The body is only needed for .zir files, not .zig files. +pub fn resolveInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst { + if (old_inst.analyzed_inst) |inst| return inst; + + // If this assert trips, the instruction that was referenced did not get properly + // analyzed before it was referenced. + const zir_module = scope.namespace().cast(Scope.ZIRModule).?; + const entry = if (old_inst.cast(zir.Inst.DeclVal)) |declval| blk: { + const decl_name = declval.positionals.name; + const entry = zir_module.contents.module.findDecl(decl_name) orelse + return mod.fail(scope, old_inst.src, "decl '{}' not found", .{decl_name}); + break :blk entry; + } else blk: { + // If this assert trips, the instruction that was referenced did not get + // properly analyzed by a previous instruction analysis before it was + // referenced by the current one. + break :blk zir_module.contents.module.findInstDecl(old_inst).?; + }; + const decl = try resolveCompleteZirDecl(mod, scope, entry.decl); + const decl_ref = try mod.analyzeDeclRef(scope, old_inst.src, decl); + // Note: it would be tempting here to store the result into old_inst.analyzed_inst field, + // but this would prevent the analyzeDeclRef from happening, which is needed to properly + // detect Decl dependencies and dependency failures on updates. + return mod.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src); +} + +fn resolveConstString(mod: *Module, scope: *Scope, old_inst: *zir.Inst) ![]u8 { + const new_inst = try resolveInst(mod, scope, old_inst); + const wanted_type = Type.initTag(.const_slice_u8); + const coerced_inst = try mod.coerce(scope, wanted_type, new_inst); + const val = try mod.resolveConstValue(scope, coerced_inst); + return val.toAllocatedBytes(scope.arena()); +} + +fn resolveType(mod: *Module, scope: *Scope, old_inst: *zir.Inst) !Type { + const new_inst = try resolveInst(mod, scope, old_inst); + const wanted_type = Type.initTag(.@"type"); + const coerced_inst = try mod.coerce(scope, wanted_type, new_inst); + const val = try mod.resolveConstValue(scope, coerced_inst); + return val.toType(); +} + +pub fn resolveInstConst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { + const new_inst = try resolveInst(mod, scope, old_inst); + const val = try mod.resolveConstValue(scope, new_inst); + return TypedValue{ + .ty = new_inst.ty, + .val = val, + }; +} + +fn analyzeInstConst(mod: *Module, scope: *Scope, const_inst: *zir.Inst.Const) InnerError!*Inst { + // Move the TypedValue from old memory to new memory. This allows freeing the ZIR instructions + // after analysis. + const typed_value_copy = try const_inst.positionals.typed_value.copy(scope.arena()); + return mod.constInst(scope, const_inst.base.src, typed_value_copy); +} + +fn analyzeConstInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!TypedValue { + const new_inst = try analyzeInst(mod, scope, old_inst); + return TypedValue{ + .ty = new_inst.ty, + .val = try mod.resolveConstValue(scope, new_inst), + }; +} + +fn analyzeInstCoerceResultBlockPtr( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.CoerceResultBlockPtr, +) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultBlockPtr", .{}); +} + +fn analyzeInstBitCastLValue(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastLValue", .{}); +} + +fn analyzeInstBitCastResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitCastResultPtr", .{}); +} + +fn analyzeInstCoerceResultPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceResultPtr", .{}); +} + +fn analyzeInstCoerceToPtrElem(mod: *Module, scope: *Scope, inst: *zir.Inst.CoerceToPtrElem) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstCoerceToPtrElem", .{}); +} + +fn analyzeInstRetPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstRetPtr", .{}); +} + +fn analyzeInstRef(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstRef", .{}); +} + +fn analyzeInstRetType(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; + const ret_type = fn_ty.fnReturnType(); + return mod.constType(scope, inst.base.src, ret_type); +} + +fn analyzeInstEnsureResultUsed(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try resolveInst(mod, scope, inst.positionals.operand); + switch (operand.ty.zigTypeTag()) { + .Void, .NoReturn => return mod.constVoid(scope, operand.src), + else => return mod.fail(scope, operand.src, "expression value is ignored", .{}), + } +} + +fn analyzeInstEnsureResultNonError(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try resolveInst(mod, scope, inst.positionals.operand); + switch (operand.ty.zigTypeTag()) { + .ErrorSet, .ErrorUnion => return mod.fail(scope, operand.src, "error is discarded", .{}), + else => return mod.constVoid(scope, operand.src), + } +} + +fn analyzeInstAlloc(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAlloc", .{}); +} + +fn analyzeInstAllocInferred(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstAllocInferred", .{}); +} + +fn analyzeInstStore(mod: *Module, scope: *Scope, inst: *zir.Inst.Store) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstStore", .{}); +} + +fn analyzeInstParamType(mod: *Module, scope: *Scope, inst: *zir.Inst.ParamType) InnerError!*Inst { + const fn_inst = try resolveInst(mod, scope, inst.positionals.func); + const arg_index = inst.positionals.arg_index; + + const fn_ty: Type = switch (fn_inst.ty.zigTypeTag()) { + .Fn => fn_inst.ty, + .BoundFn => { + return mod.fail(scope, fn_inst.src, "TODO implement analyzeInstParamType for method call syntax", .{}); + }, + else => { + return mod.fail(scope, fn_inst.src, "expected function, found '{}'", .{fn_inst.ty}); + }, + }; + + // TODO support C-style var args + const param_count = fn_ty.fnParamLen(); + if (arg_index >= param_count) { + return mod.fail(scope, inst.base.src, "arg index {} out of bounds; '{}' has {} arguments", .{ + arg_index, + fn_ty, + param_count, + }); + } + + // TODO support generic functions + const param_type = fn_ty.fnParamType(arg_index); + return mod.constType(scope, inst.base.src, param_type); +} + +fn analyzeInstStr(mod: *Module, scope: *Scope, str_inst: *zir.Inst.Str) InnerError!*Inst { + // The bytes references memory inside the ZIR module, which can get deallocated + // after semantic analysis is complete. We need the memory to be in the new anonymous Decl's arena. + var new_decl_arena = std.heap.ArenaAllocator.init(mod.gpa); + const arena_bytes = try new_decl_arena.allocator.dupe(u8, str_inst.positionals.bytes); + + const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0); + ty_payload.* = .{ .len = arena_bytes.len }; + + const bytes_payload = try scope.arena().create(Value.Payload.Bytes); + bytes_payload.* = .{ .data = arena_bytes }; + + const new_decl = try mod.createAnonymousDecl(scope, &new_decl_arena, .{ + .ty = Type.initPayload(&ty_payload.base), + .val = Value.initPayload(&bytes_payload.base), + }); + return mod.analyzeDeclRef(scope, str_inst.base.src, new_decl); +} + +fn analyzeInstExport(mod: *Module, scope: *Scope, export_inst: *zir.Inst.Export) InnerError!*Inst { + const symbol_name = try resolveConstString(mod, scope, export_inst.positionals.symbol_name); + const exported_decl = mod.lookupDeclName(scope, export_inst.positionals.decl_name) orelse + return mod.fail(scope, export_inst.base.src, "decl '{}' not found", .{export_inst.positionals.decl_name}); + try mod.analyzeExport(scope, export_inst.base.src, symbol_name, exported_decl); + return mod.constVoid(scope, export_inst.base.src); +} + +fn analyzeInstCompileError(mod: *Module, scope: *Scope, inst: *zir.Inst.CompileError) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "{}", .{inst.positionals.msg}); +} + +fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const fn_ty = b.func.?.owner_decl.typed_value.most_recent.typed_value.ty; + const param_index = b.instructions.items.len; + const param_count = fn_ty.fnParamLen(); + if (param_index >= param_count) { + return mod.fail(scope, inst.base.src, "parameter index {} outside list of length {}", .{ + param_index, + param_count, + }); + } + const param_type = fn_ty.fnParamType(param_index); + return mod.addNoOp(b, inst.base.src, param_type, .arg); +} + +fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst { + const parent_block = scope.cast(Scope.Block).?; + + // Reserve space for a Block instruction so that generated Break instructions can + // point to it, even if it doesn't end up getting used because the code ends up being + // comptime evaluated. + const block_inst = try parent_block.arena.create(Inst.Block); + block_inst.* = .{ + .base = .{ + .tag = Inst.Block.base_tag, + .ty = undefined, // Set after analysis. + .src = inst.base.src, + }, + .body = undefined, + }; + + var child_block: Scope.Block = .{ + .parent = parent_block, + .func = parent_block.func, + .decl = parent_block.decl, + .instructions = .{}, + .arena = parent_block.arena, + // TODO @as here is working around a miscompilation compiler bug :( + .label = @as(?Scope.Block.Label, Scope.Block.Label{ + .zir_block = inst, + .results = .{}, + .block_inst = block_inst, + }), + }; + const label = &child_block.label.?; + + defer child_block.instructions.deinit(mod.gpa); + defer label.results.deinit(mod.gpa); + + try analyzeBody(mod, &child_block.base, inst.positionals.body); + + // Blocks must terminate with noreturn instruction. + assert(child_block.instructions.items.len != 0); + assert(child_block.instructions.items[child_block.instructions.items.len - 1].ty.isNoReturn()); + + // Need to set the type and emit the Block instruction. This allows machine code generation + // to emit a jump instruction to after the block when it encounters the break. + try parent_block.instructions.append(mod.gpa, &block_inst.base); + block_inst.base.ty = try mod.resolvePeerTypes(scope, label.results.items); + block_inst.body = .{ .instructions = try parent_block.arena.dupe(*Inst, child_block.instructions.items) }; + return &block_inst.base; +} + +fn analyzeInstBreakpoint(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + return mod.addNoOp(b, inst.base.src, Type.initTag(.void), .breakpoint); +} + +fn analyzeInstBreak(mod: *Module, scope: *Scope, inst: *zir.Inst.Break) InnerError!*Inst { + const operand = try resolveInst(mod, scope, inst.positionals.operand); + const block = inst.positionals.block; + return analyzeBreak(mod, scope, inst.base.src, block, operand); +} + +fn analyzeInstBreakVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.BreakVoid) InnerError!*Inst { + const block = inst.positionals.block; + const void_inst = try mod.constVoid(scope, inst.base.src); + return analyzeBreak(mod, scope, inst.base.src, block, void_inst); +} + +fn analyzeInstDeclRefStr(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRefStr) InnerError!*Inst { + const decl_name = try resolveConstString(mod, scope, inst.positionals.name); + return mod.analyzeDeclRefByName(scope, inst.base.src, decl_name); +} + +fn analyzeInstDeclRef(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst { + return mod.analyzeDeclRefByName(scope, inst.base.src, inst.positionals.name); +} + +fn analyzeInstDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Inst { + const decl = try analyzeDeclVal(mod, scope, inst); + const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl); + return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); +} + +fn analyzeInstDeclValInModule(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclValInModule) InnerError!*Inst { + const decl = inst.positionals.decl; + const ptr = try mod.analyzeDeclRef(scope, inst.base.src, decl); + return mod.analyzeDeref(scope, inst.base.src, ptr, inst.base.src); +} + +fn analyzeInstCall(mod: *Module, scope: *Scope, inst: *zir.Inst.Call) InnerError!*Inst { + const func = try resolveInst(mod, scope, inst.positionals.func); + if (func.ty.zigTypeTag() != .Fn) + return mod.fail(scope, inst.positionals.func.src, "type '{}' not a function", .{func.ty}); + + const cc = func.ty.fnCallingConvention(); + if (cc == .Naked) { + // TODO add error note: declared here + return mod.fail( + scope, + inst.positionals.func.src, + "unable to call function with naked calling convention", + .{}, + ); + } + const call_params_len = inst.positionals.args.len; + const fn_params_len = func.ty.fnParamLen(); + if (func.ty.fnIsVarArgs()) { + if (call_params_len < fn_params_len) { + // TODO add error note: declared here + return mod.fail( + scope, + inst.positionals.func.src, + "expected at least {} arguments, found {}", + .{ fn_params_len, call_params_len }, + ); + } + return mod.fail(scope, inst.base.src, "TODO implement support for calling var args functions", .{}); + } else if (fn_params_len != call_params_len) { + // TODO add error note: declared here + return mod.fail( + scope, + inst.positionals.func.src, + "expected {} arguments, found {}", + .{ fn_params_len, call_params_len }, + ); + } + + if (inst.kw_args.modifier == .compile_time) { + return mod.fail(scope, inst.base.src, "TODO implement comptime function calls", .{}); + } + if (inst.kw_args.modifier != .auto) { + return mod.fail(scope, inst.base.src, "TODO implement call with modifier {}", .{inst.kw_args.modifier}); + } + + // TODO handle function calls of generic functions + + const fn_param_types = try mod.gpa.alloc(Type, fn_params_len); + defer mod.gpa.free(fn_param_types); + func.ty.fnParamTypes(fn_param_types); + + const casted_args = try scope.arena().alloc(*Inst, fn_params_len); + for (inst.positionals.args) |src_arg, i| { + const uncasted_arg = try resolveInst(mod, scope, src_arg); + casted_args[i] = try mod.coerce(scope, fn_param_types[i], uncasted_arg); + } + + const ret_type = func.ty.fnReturnType(); + + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + return mod.addCall(b, inst.base.src, ret_type, func, casted_args); +} + +fn analyzeInstFn(mod: *Module, scope: *Scope, fn_inst: *zir.Inst.Fn) InnerError!*Inst { + const fn_type = try resolveType(mod, scope, fn_inst.positionals.fn_type); + const fn_zir = blk: { + var fn_arena = std.heap.ArenaAllocator.init(mod.gpa); + errdefer fn_arena.deinit(); + + const fn_zir = try scope.arena().create(Module.Fn.ZIR); + fn_zir.* = .{ + .body = .{ + .instructions = fn_inst.positionals.body.instructions, + }, + .arena = fn_arena.state, + }; + break :blk fn_zir; + }; + const new_func = try scope.arena().create(Module.Fn); + new_func.* = .{ + .analysis = .{ .queued = fn_zir }, + .owner_decl = scope.decl().?, + }; + const fn_payload = try scope.arena().create(Value.Payload.Function); + fn_payload.* = .{ .func = new_func }; + return mod.constInst(scope, fn_inst.base.src, .{ + .ty = fn_type, + .val = Value.initPayload(&fn_payload.base), + }); +} + +fn analyzeInstIntType(mod: *Module, scope: *Scope, inttype: *zir.Inst.IntType) InnerError!*Inst { + return mod.fail(scope, inttype.base.src, "TODO implement inttype", .{}); +} + +fn analyzeInstFnType(mod: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst { + const return_type = try resolveType(mod, scope, fntype.positionals.return_type); + + // Hot path for some common function types. + if (fntype.positionals.param_types.len == 0) { + if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Unspecified) { + return mod.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args)); + } + + if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .Unspecified) { + return mod.constType(scope, fntype.base.src, Type.initTag(.fn_void_no_args)); + } + + if (return_type.zigTypeTag() == .NoReturn and fntype.kw_args.cc == .Naked) { + return mod.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); + } + + if (return_type.zigTypeTag() == .Void and fntype.kw_args.cc == .C) { + return mod.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); + } + } + + const arena = scope.arena(); + const param_types = try arena.alloc(Type, fntype.positionals.param_types.len); + for (fntype.positionals.param_types) |param_type, i| { + param_types[i] = try resolveType(mod, scope, param_type); + } + + const payload = try arena.create(Type.Payload.Function); + payload.* = .{ + .cc = fntype.kw_args.cc, + .return_type = return_type, + .param_types = param_types, + }; + return mod.constType(scope, fntype.base.src, Type.initPayload(&payload.base)); +} + +fn analyzeInstPrimitive(mod: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst { + return mod.constInst(scope, primitive.base.src, primitive.positionals.tag.toTypedValue()); +} + +fn analyzeInstAs(mod: *Module, scope: *Scope, as: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try resolveType(mod, scope, as.positionals.lhs); + const new_inst = try resolveInst(mod, scope, as.positionals.rhs); + return mod.coerce(scope, dest_type, new_inst); +} + +fn analyzeInstPtrToInt(mod: *Module, scope: *Scope, ptrtoint: *zir.Inst.UnOp) InnerError!*Inst { + const ptr = try resolveInst(mod, scope, ptrtoint.positionals.operand); + if (ptr.ty.zigTypeTag() != .Pointer) { + return mod.fail(scope, ptrtoint.positionals.operand.src, "expected pointer, found '{}'", .{ptr.ty}); + } + // TODO handle known-pointer-address + const b = try mod.requireRuntimeBlock(scope, ptrtoint.base.src); + const ty = Type.initTag(.usize); + return mod.addUnOp(b, ptrtoint.base.src, ty, .ptrtoint, ptr); +} + +fn analyzeInstFieldPtr(mod: *Module, scope: *Scope, fieldptr: *zir.Inst.FieldPtr) InnerError!*Inst { + const object_ptr = try resolveInst(mod, scope, fieldptr.positionals.object_ptr); + const field_name = try resolveConstString(mod, scope, fieldptr.positionals.field_name); + + const elem_ty = switch (object_ptr.ty.zigTypeTag()) { + .Pointer => object_ptr.ty.elemType(), + else => return mod.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}), + }; + switch (elem_ty.zigTypeTag()) { + .Array => { + if (mem.eql(u8, field_name, "len")) { + const len_payload = try scope.arena().create(Value.Payload.Int_u64); + len_payload.* = .{ .int = elem_ty.arrayLen() }; + + const ref_payload = try scope.arena().create(Value.Payload.RefVal); + ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) }; + + return mod.constInst(scope, fieldptr.base.src, .{ + .ty = Type.initTag(.single_const_pointer_to_comptime_int), + .val = Value.initPayload(&ref_payload.base), + }); + } else { + return mod.fail( + scope, + fieldptr.positionals.field_name.src, + "no member named '{}' in '{}'", + .{ field_name, elem_ty }, + ); + } + }, + else => return mod.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), + } +} + +fn analyzeInstIntCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try resolveType(mod, scope, inst.positionals.lhs); + const operand = try resolveInst(mod, scope, inst.positionals.rhs); + + const dest_is_comptime_int = switch (dest_type.zigTypeTag()) { + .ComptimeInt => true, + .Int => false, + else => return mod.fail( + scope, + inst.positionals.lhs.src, + "expected integer type, found '{}'", + .{ + dest_type, + }, + ), + }; + + switch (operand.ty.zigTypeTag()) { + .ComptimeInt, .Int => {}, + else => return mod.fail( + scope, + inst.positionals.rhs.src, + "expected integer type, found '{}'", + .{operand.ty}, + ), + } + + if (operand.value() != null) { + return mod.coerce(scope, dest_type, operand); + } else if (dest_is_comptime_int) { + return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_int'", .{}); + } + + return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten int", .{}); +} + +fn analyzeInstBitCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try resolveType(mod, scope, inst.positionals.lhs); + const operand = try resolveInst(mod, scope, inst.positionals.rhs); + return mod.bitcast(scope, dest_type, operand); +} + +fn analyzeInstFloatCast(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const dest_type = try resolveType(mod, scope, inst.positionals.lhs); + const operand = try resolveInst(mod, scope, inst.positionals.rhs); + + const dest_is_comptime_float = switch (dest_type.zigTypeTag()) { + .ComptimeFloat => true, + .Float => false, + else => return mod.fail( + scope, + inst.positionals.lhs.src, + "expected float type, found '{}'", + .{ + dest_type, + }, + ), + }; + + switch (operand.ty.zigTypeTag()) { + .ComptimeFloat, .Float, .ComptimeInt => {}, + else => return mod.fail( + scope, + inst.positionals.rhs.src, + "expected float type, found '{}'", + .{operand.ty}, + ), + } + + if (operand.value() != null) { + return mod.coerce(scope, dest_type, operand); + } else if (dest_is_comptime_float) { + return mod.fail(scope, inst.base.src, "unable to cast runtime value to 'comptime_float'", .{}); + } + + return mod.fail(scope, inst.base.src, "TODO implement analyze widen or shorten float", .{}); +} + +fn analyzeInstElemPtr(mod: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) InnerError!*Inst { + const array_ptr = try resolveInst(mod, scope, inst.positionals.array_ptr); + const uncasted_index = try resolveInst(mod, scope, inst.positionals.index); + const elem_index = try mod.coerce(scope, Type.initTag(.usize), uncasted_index); + + if (array_ptr.ty.isSinglePointer() and array_ptr.ty.elemType().zigTypeTag() == .Array) { + if (array_ptr.value()) |array_ptr_val| { + if (elem_index.value()) |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(scope.arena(), @intCast(usize, index_u64)); + + const type_payload = try scope.arena().create(Type.Payload.SingleConstPointer); + type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() }; + + return mod.constInst(scope, inst.base.src, .{ + .ty = Type.initPayload(&type_payload.base), + .val = elem_ptr, + }); + } + } + } + + return mod.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); +} + +fn analyzeInstShl(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShl", .{}); +} + +fn analyzeInstShr(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstShr", .{}); +} + +fn analyzeInstBitwise(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstBitwise", .{}); +} + +fn analyzeInstArrayCat(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstArrayCat", .{}); +} + +fn analyzeInstArrayMul(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + return mod.fail(scope, inst.base.src, "TODO implement analyzeInstArrayMul", .{}); +} + +fn analyzeInstArithmetic(mod: *Module, scope: *Scope, inst: *zir.Inst.BinOp) InnerError!*Inst { + const tracy = trace(@src()); + defer tracy.end(); + + const lhs = try resolveInst(mod, scope, inst.positionals.lhs); + const rhs = try resolveInst(mod, scope, inst.positionals.rhs); + + const instructions = &[_]*Inst{ lhs, rhs }; + const resolved_type = try mod.resolvePeerTypes(scope, instructions); + const casted_lhs = try mod.coerce(scope, resolved_type, lhs); + const casted_rhs = try mod.coerce(scope, resolved_type, rhs); + + const scalar_type = if (resolved_type.zigTypeTag() == .Vector) + resolved_type.elemType() + else + resolved_type; + + const scalar_tag = scalar_type.zigTypeTag(); + + if (lhs.ty.zigTypeTag() == .Vector and rhs.ty.zigTypeTag() == .Vector) { + if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) { + return mod.fail(scope, inst.base.src, "vector length mismatch: {} and {}", .{ + lhs.ty.arrayLen(), + rhs.ty.arrayLen(), + }); + } + return mod.fail(scope, inst.base.src, "TODO implement support for vectors in analyzeInstBinOp", .{}); + } else if (lhs.ty.zigTypeTag() == .Vector or rhs.ty.zigTypeTag() == .Vector) { + return mod.fail(scope, inst.base.src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{ + lhs.ty, + rhs.ty, + }); + } + + const is_int = scalar_tag == .Int or scalar_tag == .ComptimeInt; + const is_float = scalar_tag == .Float or scalar_tag == .ComptimeFloat; + + if (!is_int and !(is_float and floatOpAllowed(inst.base.tag))) { + return mod.fail(scope, inst.base.src, "invalid operands to binary expression: '{}' and '{}'", .{ @tagName(lhs.ty.zigTypeTag()), @tagName(rhs.ty.zigTypeTag()) }); + } + + if (casted_lhs.value()) |lhs_val| { + if (casted_rhs.value()) |rhs_val| { + return analyzeInstComptimeOp(mod, scope, scalar_type, inst, lhs_val, rhs_val); + } + } + + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const ir_tag = switch (inst.base.tag) { + .add => Inst.Tag.add, + .sub => Inst.Tag.sub, + else => return mod.fail(scope, inst.base.src, "TODO implement arithmetic for operand '{}''", .{@tagName(inst.base.tag)}), + }; + + return mod.addBinOp(b, inst.base.src, scalar_type, ir_tag, casted_lhs, casted_rhs); +} + +/// Analyzes operands that are known at comptime +fn analyzeInstComptimeOp(mod: *Module, scope: *Scope, res_type: Type, inst: *zir.Inst.BinOp, lhs_val: Value, rhs_val: Value) InnerError!*Inst { + // incase rhs is 0, simply return lhs without doing any calculations + // TODO Once division is implemented we should throw an error when dividing by 0. + if (rhs_val.tag() == .zero or rhs_val.tag() == .the_one_possible_value) { + return mod.constInst(scope, inst.base.src, .{ + .ty = res_type, + .val = lhs_val, + }); + } + const is_int = res_type.isInt() or res_type.zigTypeTag() == .ComptimeInt; + + const value = try switch (inst.base.tag) { + .add => blk: { + const val = if (is_int) + Module.intAdd(scope.arena(), lhs_val, rhs_val) + else + mod.floatAdd(scope, res_type, inst.base.src, lhs_val, rhs_val); + break :blk val; + }, + .sub => blk: { + const val = if (is_int) + Module.intSub(scope.arena(), lhs_val, rhs_val) + else + mod.floatSub(scope, res_type, inst.base.src, lhs_val, rhs_val); + break :blk val; + }, + else => return mod.fail(scope, inst.base.src, "TODO Implement arithmetic operand '{}'", .{@tagName(inst.base.tag)}), + }; + + return mod.constInst(scope, inst.base.src, .{ + .ty = res_type, + .val = value, + }); +} + +fn analyzeInstDeref(mod: *Module, scope: *Scope, deref: *zir.Inst.UnOp) InnerError!*Inst { + const ptr = try resolveInst(mod, scope, deref.positionals.operand); + return mod.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.operand.src); +} + +fn analyzeInstAsm(mod: *Module, scope: *Scope, assembly: *zir.Inst.Asm) InnerError!*Inst { + const return_type = try resolveType(mod, scope, assembly.positionals.return_type); + const asm_source = try resolveConstString(mod, scope, assembly.positionals.asm_source); + const output = if (assembly.kw_args.output) |o| try resolveConstString(mod, scope, o) else null; + + const inputs = try scope.arena().alloc([]const u8, assembly.kw_args.inputs.len); + const clobbers = try scope.arena().alloc([]const u8, assembly.kw_args.clobbers.len); + const args = try scope.arena().alloc(*Inst, assembly.kw_args.args.len); + + for (inputs) |*elem, i| { + elem.* = try resolveConstString(mod, scope, assembly.kw_args.inputs[i]); + } + for (clobbers) |*elem, i| { + elem.* = try resolveConstString(mod, scope, assembly.kw_args.clobbers[i]); + } + for (args) |*elem, i| { + const arg = try resolveInst(mod, scope, assembly.kw_args.args[i]); + elem.* = try mod.coerce(scope, Type.initTag(.usize), arg); + } + + const b = try mod.requireRuntimeBlock(scope, assembly.base.src); + const inst = try b.arena.create(Inst.Assembly); + inst.* = .{ + .base = .{ + .tag = .assembly, + .ty = return_type, + .src = assembly.base.src, + }, + .asm_source = asm_source, + .is_volatile = assembly.kw_args.@"volatile", + .output = output, + .inputs = inputs, + .clobbers = clobbers, + .args = args, + }; + try b.instructions.append(mod.gpa, &inst.base); + return &inst.base; +} + +fn analyzeInstCmp( + mod: *Module, + scope: *Scope, + inst: *zir.Inst.BinOp, + op: std.math.CompareOperator, +) InnerError!*Inst { + const lhs = try resolveInst(mod, scope, inst.positionals.lhs); + const rhs = try resolveInst(mod, scope, inst.positionals.rhs); + + const is_equality_cmp = switch (op) { + .eq, .neq => true, + else => false, + }; + const lhs_ty_tag = lhs.ty.zigTypeTag(); + const rhs_ty_tag = rhs.ty.zigTypeTag(); + if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { + // null == null, null != null + return mod.constBool(scope, inst.base.src, op == .eq); + } else if (is_equality_cmp and + ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or + rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) + { + // comparing null with optionals + const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; + if (opt_operand.value()) |opt_val| { + const is_null = opt_val.isNull(); + return mod.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null); + } + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + const inst_tag: Inst.Tag = switch (op) { + .eq => .isnull, + .neq => .isnonnull, + else => unreachable, + }; + return mod.addUnOp(b, inst.base.src, Type.initTag(.bool), inst_tag, opt_operand); + } else if (is_equality_cmp and + ((lhs_ty_tag == .Null and rhs.ty.isCPtr()) or (rhs_ty_tag == .Null and lhs.ty.isCPtr()))) + { + return mod.fail(scope, inst.base.src, "TODO implement C pointer cmp", .{}); + } else 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 mod.fail(scope, inst.base.src, "comparison of '{}' with null", .{non_null_type}); + } else if (is_equality_cmp and + ((lhs_ty_tag == .EnumLiteral and rhs_ty_tag == .Union) or + (rhs_ty_tag == .EnumLiteral and lhs_ty_tag == .Union))) + { + return mod.fail(scope, inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{}); + } else if (lhs_ty_tag == .ErrorSet and rhs_ty_tag == .ErrorSet) { + if (!is_equality_cmp) { + return mod.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)}); + } + return mod.fail(scope, inst.base.src, "TODO implement equality comparison between errors", .{}); + } else 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 + // numeric types. + return mod.cmpNumeric(scope, inst.base.src, lhs, rhs, op); + } + return mod.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{}); +} + +fn analyzeInstTypeOf(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try resolveInst(mod, scope, inst.positionals.operand); + return mod.constType(scope, inst.base.src, operand.ty); +} + +fn analyzeInstBoolNot(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const uncasted_operand = try resolveInst(mod, scope, inst.positionals.operand); + const bool_type = Type.initTag(.bool); + const operand = try mod.coerce(scope, bool_type, uncasted_operand); + if (try mod.resolveDefinedValue(scope, operand)) |val| { + return mod.constBool(scope, inst.base.src, !val.toBool()); + } + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + return mod.addUnOp(b, inst.base.src, bool_type, .not, operand); +} + +fn analyzeInstIsNonNull(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp, invert_logic: bool) InnerError!*Inst { + const operand = try resolveInst(mod, scope, inst.positionals.operand); + return mod.analyzeIsNull(scope, inst.base.src, operand, invert_logic); +} + +fn analyzeInstCondBr(mod: *Module, scope: *Scope, inst: *zir.Inst.CondBr) InnerError!*Inst { + const uncasted_cond = try resolveInst(mod, scope, inst.positionals.condition); + const cond = try mod.coerce(scope, Type.initTag(.bool), uncasted_cond); + + if (try mod.resolveDefinedValue(scope, cond)) |cond_val| { + const body = if (cond_val.toBool()) &inst.positionals.then_body else &inst.positionals.else_body; + try analyzeBody(mod, scope, body.*); + return mod.constVoid(scope, inst.base.src); + } + + const parent_block = try mod.requireRuntimeBlock(scope, inst.base.src); + + var true_block: Scope.Block = .{ + .parent = parent_block, + .func = parent_block.func, + .decl = parent_block.decl, + .instructions = .{}, + .arena = parent_block.arena, + }; + defer true_block.instructions.deinit(mod.gpa); + try analyzeBody(mod, &true_block.base, inst.positionals.then_body); + + var false_block: Scope.Block = .{ + .parent = parent_block, + .func = parent_block.func, + .decl = parent_block.decl, + .instructions = .{}, + .arena = parent_block.arena, + }; + defer false_block.instructions.deinit(mod.gpa); + try analyzeBody(mod, &false_block.base, inst.positionals.else_body); + + const then_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) }; + const else_body: ir.Body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) }; + return mod.addCondBr(parent_block, inst.base.src, cond, then_body, else_body); +} + +fn analyzeInstUnreachNoChk(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { + return mod.analyzeUnreach(scope, unreach.base.src); +} + +fn analyzeInstUnreachable(mod: *Module, scope: *Scope, unreach: *zir.Inst.NoOp) InnerError!*Inst { + const b = try mod.requireRuntimeBlock(scope, unreach.base.src); + if (mod.wantSafety(scope)) { + // TODO Once we have a panic function to call, call it here instead of this. + _ = try mod.addNoOp(b, unreach.base.src, Type.initTag(.void), .breakpoint); + } + return mod.analyzeUnreach(scope, unreach.base.src); +} + +fn analyzeInstRet(mod: *Module, scope: *Scope, inst: *zir.Inst.UnOp) InnerError!*Inst { + const operand = try resolveInst(mod, scope, inst.positionals.operand); + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + return mod.addUnOp(b, inst.base.src, Type.initTag(.noreturn), .ret, operand); +} + +fn analyzeInstRetVoid(mod: *Module, scope: *Scope, inst: *zir.Inst.NoOp) InnerError!*Inst { + const b = try mod.requireRuntimeBlock(scope, inst.base.src); + return mod.addNoOp(b, inst.base.src, Type.initTag(.noreturn), .retvoid); +} + +fn floatOpAllowed(tag: zir.Inst.Tag) bool { + // extend this swich as additional operators are implemented + return switch (tag) { + .add, .sub => true, + else => false, + }; +} + +fn analyzeBreak( + mod: *Module, + scope: *Scope, + src: usize, + zir_block: *zir.Inst.Block, + operand: *Inst, +) InnerError!*Inst { + var opt_block = scope.cast(Scope.Block); + while (opt_block) |block| { + if (block.label) |*label| { + if (label.zir_block == zir_block) { + try label.results.append(mod.gpa, operand); + const b = try mod.requireRuntimeBlock(scope, src); + return mod.addBr(b, src, label.block_inst, operand); + } + } + opt_block = block.parent; + } else unreachable; +} + +fn analyzeDeclVal(mod: *Module, scope: *Scope, inst: *zir.Inst.DeclVal) InnerError!*Decl { + const decl_name = inst.positionals.name; + const zir_module = scope.namespace().cast(Scope.ZIRModule).?; + const src_decl = zir_module.contents.module.findDecl(decl_name) orelse + return mod.fail(scope, inst.base.src, "use of undeclared identifier '{}'", .{decl_name}); + + const decl = try resolveCompleteZirDecl(mod, scope, src_decl.decl); + + return decl; +}