Commit f2feb4e47a
Changed files (7)
src-self-hosted
src-self-hosted/codegen.zig
@@ -6,6 +6,8 @@ const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
const TypedValue = @import("TypedValue.zig");
const link = @import("link.zig");
+const Module = @import("Module.zig");
+const ErrorMsg = Module.ErrorMsg;
const Target = std.Target;
const Allocator = mem.Allocator;
@@ -14,7 +16,7 @@ pub const Result = union(enum) {
appended: void,
/// The value is available externally, `code` is unused.
externally_managed: []const u8,
- fail: *ir.ErrorMsg,
+ fail: *Module.ErrorMsg,
};
pub fn generateSymbol(
@@ -77,7 +79,7 @@ pub fn generateSymbol(
}
}
return Result{
- .fail = try ir.ErrorMsg.create(
+ .fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for more kinds of arrays",
@@ -107,7 +109,7 @@ pub fn generateSymbol(
return Result{ .appended = {} };
}
return Result{
- .fail = try ir.ErrorMsg.create(
+ .fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for pointer {}",
@@ -123,7 +125,7 @@ pub fn generateSymbol(
return Result{ .appended = {} };
}
return Result{
- .fail = try ir.ErrorMsg.create(
+ .fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for int type '{}'",
@@ -133,7 +135,7 @@ pub fn generateSymbol(
},
else => |t| {
return Result{
- .fail = try ir.ErrorMsg.create(
+ .fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for type '{}'",
@@ -147,10 +149,10 @@ pub fn generateSymbol(
const Function = struct {
bin_file: *link.ElfFile,
target: *const std.Target,
- mod_fn: *const ir.Module.Fn,
+ mod_fn: *const Module.Fn,
code: *std.ArrayList(u8),
inst_table: std.AutoHashMap(*ir.Inst, MCValue),
- err_msg: ?*ir.ErrorMsg,
+ err_msg: ?*ErrorMsg,
const MCValue = union(enum) {
none,
@@ -570,7 +572,7 @@ const Function = struct {
fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } {
@setCold(true);
assert(self.err_msg == null);
- self.err_msg = try ir.ErrorMsg.create(self.code.allocator, src, format, args);
+ self.err_msg = try ErrorMsg.create(self.code.allocator, src, format, args);
return error.CodegenFail;
}
};
src-self-hosted/ir.zig
@@ -1,20 +1,9 @@
const std = @import("std");
-const mem = std.mem;
-const Allocator = std.mem.Allocator;
-const ArrayListUnmanaged = std.ArrayListUnmanaged;
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
-const TypedValue = @import("TypedValue.zig");
-const assert = std.debug.assert;
-const BigIntConst = std.math.big.int.Const;
-const BigIntMutable = std.math.big.int.Mutable;
-const Target = std.Target;
-const Package = @import("Package.zig");
-const link = @import("link.zig");
+const Module = @import("Module.zig");
-pub const text = @import("ir/text.zig");
-
-/// These are in-memory, analyzed instructions. See `text.Inst` for the representation
+/// These are in-memory, analyzed instructions. See `zir.Inst` for the representation
/// of instructions that correspond to the ZIR text format.
/// This struct owns the `Value` and `Type` memory. When the struct is deallocated,
/// so are the `Value` and `Type`. The value of a constant must be copied into
@@ -166,2006 +155,3 @@ pub const Inst = struct {
args: void,
};
};
-
-pub const Module = struct {
- /// General-purpose allocator.
- allocator: *Allocator,
- /// Module owns this resource.
- root_pkg: *Package,
- /// Module owns this resource.
- root_scope: *Scope.ZIRModule,
- /// Pointer to externally managed resource.
- bin_file: *link.ElfFile,
- /// It's rare for a decl to be exported, so we save memory by having a sparse map of
- /// Decl pointers to details about them being exported.
- /// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.
- decl_exports: std.AutoHashMap(*Decl, []*Export),
- /// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
- /// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
- /// is performing the export of another Decl.
- /// This table owns the Export memory.
- export_owners: std.AutoHashMap(*Decl, []*Export),
- /// Maps fully qualified namespaced names to the Decl struct for them.
- decl_table: std.AutoHashMap(Decl.Hash, *Decl),
-
- optimize_mode: std.builtin.Mode,
- link_error_flags: link.ElfFile.ErrorFlags = link.ElfFile.ErrorFlags{},
-
- work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
-
- /// We optimize memory usage for a compilation with no compile errors by storing the
- /// error messages and mapping outside of `Decl`.
- /// The ErrorMsg memory is owned by the decl, using Module's allocator.
- /// Note that a Decl can succeed but the Fn it represents can fail. In this case,
- /// a Decl can have a failed_decls entry but have analysis status of success.
- failed_decls: std.AutoHashMap(*Decl, *ErrorMsg),
- /// Using a map here for consistency with the other fields here.
- /// The ErrorMsg memory is owned by the `Scope.ZIRModule`, using Module's allocator.
- failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg),
- /// Using a map here for consistency with the other fields here.
- /// The ErrorMsg memory is owned by the `Export`, using Module's allocator.
- failed_exports: std.AutoHashMap(*Export, *ErrorMsg),
-
- pub const WorkItem = union(enum) {
- /// Write the machine code for a Decl to the output file.
- codegen_decl: *Decl,
- };
-
- pub const Export = struct {
- options: std.builtin.ExportOptions,
- /// Byte offset into the file that contains the export directive.
- src: usize,
- /// Represents the position of the export, if any, in the output file.
- link: link.ElfFile.Export,
- /// The Decl that performs the export. Note that this is *not* the Decl being exported.
- owner_decl: *Decl,
- status: enum {
- in_progress,
- failed,
- /// Indicates that the failure was due to a temporary issue, such as an I/O error
- /// when writing to the output file. Retrying the export may succeed.
- failed_retryable,
- complete,
- },
- };
-
- pub const Decl = struct {
- /// This name is relative to the containing namespace of the decl. It uses a null-termination
- /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed
- /// in symbol names, because executable file formats use null-terminated strings for symbol names.
- /// All Decls have names, even values that are not bound to a zig namespace. This is necessary for
- /// mapping them to an address in the output file.
- /// Memory owned by this decl, using Module's allocator.
- name: [*:0]const u8,
- /// The direct parent container of the Decl. This field will need to get more fleshed out when
- /// self-hosted supports proper struct types and Zig AST => ZIR.
- /// Reference to externally owned memory.
- scope: *Scope.ZIRModule,
- /// Byte offset into the source file that contains this declaration.
- /// This is the base offset that src offsets within this Decl are relative to.
- src: usize,
- /// The most recent value of the Decl after a successful semantic analysis.
- /// The tag for this union is determined by the tag value of the analysis field.
- typed_value: union {
- never_succeeded: void,
- most_recent: TypedValue.Managed,
- },
- /// Represents the "shallow" analysis status. For example, for decls that are functions,
- /// the function type is analyzed with this set to `in_progress`, however, the semantic
- /// analysis of the function body is performed with this value set to `success`. Functions
- /// have their own analysis status field.
- analysis: enum {
- initial_in_progress,
- /// This Decl might be OK but it depends on another one which did not successfully complete
- /// semantic analysis. This Decl never had a value computed.
- initial_dependency_failure,
- /// Semantic analysis failure. This Decl never had a value computed.
- /// There will be a corresponding ErrorMsg in Module.failed_decls.
- initial_sema_failure,
- /// In this case the `typed_value.most_recent` can still be accessed.
- /// There will be a corresponding ErrorMsg in Module.failed_decls.
- codegen_failure,
- /// In this case the `typed_value.most_recent` can still be accessed.
- /// There will be a corresponding ErrorMsg in Module.failed_decls.
- /// This indicates the failure was something like running out of disk space,
- /// and attempting codegen again may succeed.
- codegen_failure_retryable,
- /// This Decl might be OK but it depends on another one which did not successfully complete
- /// semantic analysis. There is a most recent value available.
- repeat_dependency_failure,
- /// Semantic anlaysis failure, but the `typed_value.most_recent` can be accessed.
- /// There will be a corresponding ErrorMsg in Module.failed_decls.
- repeat_sema_failure,
- /// Completed successfully before; the `typed_value.most_recent` can be accessed, and
- /// new semantic analysis is in progress.
- repeat_in_progress,
- /// Everything is done and updated.
- complete,
- },
-
- /// Represents the position of the code in the output file.
- /// This is populated regardless of semantic analysis and code generation.
- link: link.ElfFile.Decl = link.ElfFile.Decl.empty,
-
- /// The shallow set of other decls whose typed_value could possibly change if this Decl's
- /// typed_value is modified.
- /// TODO look into using a lightweight map/set data structure rather than a linear array.
- dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){},
-
- contents_hash: Hash,
-
- pub fn destroy(self: *Decl, allocator: *Allocator) void {
- allocator.free(mem.spanZ(self.name));
- if (self.typedValueManaged()) |tvm| {
- tvm.deinit(allocator);
- }
- allocator.destroy(self);
- }
-
- pub const Hash = [16]u8;
-
- /// If the name is small enough, it is used directly as the hash.
- /// If it is long, blake3 hash is computed.
- pub fn hashSimpleName(name: []const u8) Hash {
- var out: Hash = undefined;
- if (name.len <= Hash.len) {
- mem.copy(u8, &out, name);
- mem.set(u8, out[name.len..], 0);
- } else {
- std.crypto.Blake3.hash(name, &out);
- }
- return out;
- }
-
- /// Must generate unique bytes with no collisions with other decls.
- /// The point of hashing here is only to limit the number of bytes of
- /// the unique identifier to a fixed size (16 bytes).
- pub fn fullyQualifiedNameHash(self: Decl) Hash {
- // Right now we only have ZIRModule as the source. So this is simply the
- // relative name of the decl.
- return hashSimpleName(mem.spanZ(u8, self.name));
- }
-
- pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue {
- const tvm = self.typedValueManaged() orelse return error.AnalysisFail;
- return tvm.typed_value;
- }
-
- pub fn value(self: *Decl) error{AnalysisFail}!Value {
- return (try self.typedValue()).val;
- }
-
- pub fn dump(self: *Decl) void {
- const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src);
- std.debug.warn("{}:{}:{} name={} status={}", .{
- self.scope.sub_file_path,
- loc.line + 1,
- loc.column + 1,
- mem.spanZ(self.name),
- @tagName(self.analysis),
- });
- if (self.typedValueManaged()) |tvm| {
- std.debug.warn(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val });
- }
- std.debug.warn("\n", .{});
- }
-
- fn typedValueManaged(self: *Decl) ?*TypedValue.Managed {
- switch (self.analysis) {
- .initial_in_progress,
- .initial_dependency_failure,
- .initial_sema_failure,
- => return null,
- .codegen_failure,
- .codegen_failure_retryable,
- .repeat_dependency_failure,
- .repeat_sema_failure,
- .repeat_in_progress,
- .complete,
- => return &self.typed_value.most_recent,
- }
- }
- };
-
- /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
- pub const Fn = struct {
- /// This memory owned by the Decl's TypedValue.Managed arena allocator.
- fn_type: Type,
- analysis: union(enum) {
- /// The value is the source instruction.
- queued: *text.Inst.Fn,
- in_progress: *Analysis,
- /// There will be a corresponding ErrorMsg in Module.failed_decls
- sema_failure,
- /// This Fn might be OK but it depends on another Decl which did not successfully complete
- /// semantic analysis.
- dependency_failure,
- success: Body,
- },
-
- /// This memory is temporary and points to stack memory for the duration
- /// of Fn analysis.
- pub const Analysis = struct {
- inner_block: Scope.Block,
- /// TODO Performance optimization idea: instead of this inst_table,
- /// use a field in the text.Inst instead to track corresponding instructions
- inst_table: std.AutoHashMap(*text.Inst, *Inst),
- needed_inst_capacity: usize,
- };
- };
-
- pub const Scope = struct {
- tag: Tag,
-
- pub fn cast(base: *Scope, comptime T: type) ?*T {
- if (base.tag != T.base_tag)
- return null;
-
- return @fieldParentPtr(T, "base", base);
- }
-
- /// Asserts the scope has a parent which is a DeclAnalysis and
- /// returns the arena Allocator.
- pub fn arena(self: *Scope) *Allocator {
- switch (self.tag) {
- .block => return self.cast(Block).?.arena,
- .decl => return &self.cast(DeclAnalysis).?.arena.allocator,
- .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator,
- }
- }
-
- /// Asserts the scope has a parent which is a DeclAnalysis and
- /// returns the Decl.
- pub fn decl(self: *Scope) *Decl {
- switch (self.tag) {
- .block => return self.cast(Block).?.decl,
- .decl => return self.cast(DeclAnalysis).?.decl,
- .zir_module => unreachable,
- }
- }
-
- /// Asserts the scope has a parent which is a ZIRModule and
- /// returns it.
- pub fn namespace(self: *Scope) *ZIRModule {
- switch (self.tag) {
- .block => return self.cast(Block).?.decl.scope,
- .decl => return self.cast(DeclAnalysis).?.decl.scope,
- .zir_module => return self.cast(ZIRModule).?,
- }
- }
-
- pub fn dumpInst(self: *Scope, inst: *Inst) void {
- const zir_module = self.namespace();
- const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src);
- std.debug.warn("{}:{}:{}: {}: ty={}\n", .{
- zir_module.sub_file_path,
- loc.line + 1,
- loc.column + 1,
- @tagName(inst.tag),
- inst.ty,
- });
- }
-
- pub const Tag = enum {
- zir_module,
- block,
- decl,
- };
-
- pub const ZIRModule = struct {
- pub const base_tag: Tag = .zir_module;
- base: Scope = Scope{ .tag = base_tag },
- /// Relative to the owning package's root_src_dir.
- /// Reference to external memory, not owned by ZIRModule.
- sub_file_path: []const u8,
- source: union {
- unloaded: void,
- bytes: [:0]const u8,
- },
- contents: union {
- not_available: void,
- module: *text.Module,
- },
- status: enum {
- never_loaded,
- unloaded_success,
- unloaded_parse_failure,
- unloaded_sema_failure,
- loaded_parse_failure,
- loaded_sema_failure,
- loaded_success,
- },
-
- pub fn unload(self: *ZIRModule, allocator: *Allocator) void {
- switch (self.status) {
- .never_loaded,
- .unloaded_parse_failure,
- .unloaded_sema_failure,
- .unloaded_success,
- => {},
-
- .loaded_success => {
- allocator.free(self.source.bytes);
- self.contents.module.deinit(allocator);
- allocator.destroy(self.contents.module);
- self.status = .unloaded_success;
- },
- .loaded_sema_failure => {
- allocator.free(self.source.bytes);
- self.contents.module.deinit(allocator);
- allocator.destroy(self.contents.module);
- self.status = .unloaded_sema_failure;
- },
- .loaded_parse_failure => {
- allocator.free(self.source.bytes);
- self.status = .unloaded_parse_failure;
- },
- }
- }
-
- pub fn deinit(self: *ZIRModule, allocator: *Allocator) void {
- self.unload(allocator);
- self.* = undefined;
- }
-
- pub fn dumpSrc(self: *ZIRModule, src: usize) void {
- const loc = std.zig.findLineColumn(self.source.bytes, src);
- std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
- }
- };
-
- /// This is a temporary structure, references to it are valid only
- /// during semantic analysis of the block.
- pub const Block = struct {
- pub const base_tag: Tag = .block;
- base: Scope = Scope{ .tag = base_tag },
- func: *Fn,
- decl: *Decl,
- instructions: ArrayListUnmanaged(*Inst),
- /// Points to the arena allocator of DeclAnalysis
- arena: *Allocator,
- };
-
- /// This is a temporary structure, references to it are valid only
- /// during semantic analysis of the decl.
- pub const DeclAnalysis = struct {
- pub const base_tag: Tag = .decl;
- base: Scope = Scope{ .tag = base_tag },
- decl: *Decl,
- arena: std.heap.ArenaAllocator,
- };
- };
-
- pub const Body = struct {
- instructions: []*Inst,
- };
-
- pub const AllErrors = struct {
- arena: std.heap.ArenaAllocator.State,
- list: []const Message,
-
- pub const Message = struct {
- src_path: []const u8,
- line: usize,
- column: usize,
- byte_offset: usize,
- msg: []const u8,
- };
-
- pub fn deinit(self: *AllErrors, allocator: *Allocator) void {
- self.arena.promote(allocator).deinit();
- }
-
- fn add(
- arena: *std.heap.ArenaAllocator,
- errors: *std.ArrayList(Message),
- sub_file_path: []const u8,
- source: []const u8,
- simple_err_msg: ErrorMsg,
- ) !void {
- const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset);
- try errors.append(.{
- .src_path = try arena.allocator.dupe(u8, sub_file_path),
- .msg = try arena.allocator.dupe(u8, simple_err_msg.msg),
- .byte_offset = simple_err_msg.byte_offset,
- .line = loc.line,
- .column = loc.column,
- });
- }
- };
-
- pub fn deinit(self: *Module) void {
- const allocator = self.allocator;
- self.work_queue.deinit();
- {
- var it = self.decl_table.iterator();
- while (it.next()) |kv| {
- kv.value.destroy(allocator);
- }
- self.decl_table.deinit();
- }
- {
- var it = self.failed_decls.iterator();
- while (it.next()) |kv| {
- kv.value.destroy(allocator);
- }
- self.failed_decls.deinit();
- }
- {
- var it = self.failed_files.iterator();
- while (it.next()) |kv| {
- kv.value.destroy(allocator);
- }
- self.failed_files.deinit();
- }
- {
- var it = self.failed_exports.iterator();
- while (it.next()) |kv| {
- kv.value.destroy(allocator);
- }
- self.failed_exports.deinit();
- }
- {
- var it = self.decl_exports.iterator();
- while (it.next()) |kv| {
- const export_list = kv.value;
- allocator.free(export_list);
- }
- self.decl_exports.deinit();
- }
- {
- var it = self.export_owners.iterator();
- while (it.next()) |kv| {
- const export_list = kv.value;
- for (export_list) |exp| {
- allocator.destroy(exp);
- }
- allocator.free(export_list);
- }
- self.export_owners.deinit();
- }
- self.root_pkg.destroy();
- {
- self.root_scope.deinit(allocator);
- allocator.destroy(self.root_scope);
- }
- self.* = undefined;
- }
-
- pub fn target(self: Module) std.Target {
- return self.bin_file.options.target;
- }
-
- /// Detect changes to source files, perform semantic analysis, and update the output files.
- pub fn update(self: *Module) !void {
- // TODO Use the cache hash file system to detect which source files changed.
- // Here we simulate a full cache miss.
- // Analyze the root source file now.
- self.analyzeRoot(self.root_scope) catch |err| switch (err) {
- error.AnalysisFail => {
- assert(self.totalErrorCount() != 0);
- },
- else => |e| return e,
- };
-
- try self.performAllTheWork();
-
- // Unload all the source files from memory.
- self.root_scope.unload(self.allocator);
-
- try self.bin_file.flush();
- self.link_error_flags = self.bin_file.error_flags;
- }
-
- pub fn totalErrorCount(self: *Module) usize {
- return self.failed_decls.size +
- self.failed_files.size +
- self.failed_exports.size +
- @boolToInt(self.link_error_flags.no_entry_point_found);
- }
-
- pub fn getAllErrorsAlloc(self: *Module) !AllErrors {
- var arena = std.heap.ArenaAllocator.init(self.allocator);
- errdefer arena.deinit();
-
- var errors = std.ArrayList(AllErrors.Message).init(self.allocator);
- defer errors.deinit();
-
- {
- var it = self.failed_files.iterator();
- while (it.next()) |kv| {
- const scope = kv.key;
- const err_msg = kv.value;
- const source = scope.source.bytes;
- try AllErrors.add(&arena, &errors, scope.sub_file_path, source, err_msg.*);
- }
- }
- {
- var it = self.failed_decls.iterator();
- while (it.next()) |kv| {
- const decl = kv.key;
- const err_msg = kv.value;
- const source = decl.scope.source.bytes;
- try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*);
- }
- }
- {
- var it = self.failed_exports.iterator();
- while (it.next()) |kv| {
- const decl = kv.key.owner_decl;
- const err_msg = kv.value;
- const source = decl.scope.source.bytes;
- try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*);
- }
- }
-
- if (self.link_error_flags.no_entry_point_found) {
- try errors.append(.{
- .src_path = self.root_pkg.root_src_path,
- .line = 0,
- .column = 0,
- .byte_offset = 0,
- .msg = try std.fmt.allocPrint(&arena.allocator, "no entry point found", .{}),
- });
- }
-
- assert(errors.items.len == self.totalErrorCount());
-
- return AllErrors{
- .arena = arena.state,
- .list = try arena.allocator.dupe(AllErrors.Message, errors.items),
- };
- }
-
- const InnerError = error{ OutOfMemory, AnalysisFail };
-
- pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
- while (self.work_queue.readItem()) |work_item| switch (work_item) {
- .codegen_decl => |decl| switch (decl.analysis) {
- .initial_in_progress,
- .repeat_in_progress,
- => unreachable,
-
- .initial_sema_failure,
- .repeat_sema_failure,
- .codegen_failure,
- .initial_dependency_failure,
- .repeat_dependency_failure,
- => continue,
-
- .complete, .codegen_failure_retryable => {
- if (decl.typed_value.most_recent.typed_value.val.cast(Value.Payload.Function)) |payload| {
- switch (payload.func.analysis) {
- .queued => self.analyzeFnBody(decl, payload.func) catch |err| switch (err) {
- error.AnalysisFail => {
- if (payload.func.analysis == .queued) {
- payload.func.analysis = .dependency_failure;
- }
- continue;
- },
- else => |e| return e,
- },
- .in_progress => unreachable,
- .sema_failure, .dependency_failure => continue,
- .success => {},
- }
- }
-
- assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
-
- self.bin_file.updateDecl(self, decl) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.AnalysisFail => {
- decl.analysis = .repeat_dependency_failure;
- },
- else => {
- try self.failed_decls.ensureCapacity(self.failed_decls.size + 1);
- self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
- self.allocator,
- decl.src,
- "unable to codegen: {}",
- .{@errorName(err)},
- ));
- decl.analysis = .codegen_failure_retryable;
- },
- };
- },
- },
- };
- }
-
- fn getTextModule(self: *Module, root_scope: *Scope.ZIRModule) !*text.Module {
- switch (root_scope.status) {
- .never_loaded, .unloaded_success => {
- try self.failed_files.ensureCapacity(self.failed_files.size + 1);
-
- var keep_source = false;
- const source = try self.root_pkg.root_src_dir.readFileAllocOptions(
- self.allocator,
- self.root_pkg.root_src_path,
- std.math.maxInt(u32),
- 1,
- 0,
- );
- defer if (!keep_source) self.allocator.free(source);
-
- var keep_zir_module = false;
- const zir_module = try self.allocator.create(text.Module);
- defer if (!keep_zir_module) self.allocator.destroy(zir_module);
-
- zir_module.* = try text.parse(self.allocator, source);
- defer if (!keep_zir_module) zir_module.deinit(self.allocator);
-
- if (zir_module.error_msg) |src_err_msg| {
- self.failed_files.putAssumeCapacityNoClobber(
- root_scope,
- try ErrorMsg.create(self.allocator, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}),
- );
- root_scope.status = .loaded_parse_failure;
- root_scope.source = .{ .bytes = source };
- keep_source = true;
- return error.AnalysisFail;
- }
-
- root_scope.status = .loaded_success;
- root_scope.source = .{ .bytes = source };
- keep_source = true;
- root_scope.contents = .{ .module = zir_module };
- keep_zir_module = true;
-
- return zir_module;
- },
-
- .unloaded_parse_failure,
- .unloaded_sema_failure,
- .loaded_parse_failure,
- .loaded_sema_failure,
- => return error.AnalysisFail,
- .loaded_success => return root_scope.contents.module,
- }
- }
-
- fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {
- // TODO use the cache to identify, from the modified source files, the decls which have
- // changed based on the span of memory that represents the decl in the re-parsed source file.
- // Use the cached dependency graph to recursively determine the set of decls which need
- // regeneration.
- // Here we simulate adding a source file which was previously not part of the compilation,
- // which means scanning the decls looking for exports.
- // TODO also identify decls that need to be deleted.
- switch (root_scope.status) {
- .never_loaded => {
- const src_module = try self.getTextModule(root_scope);
-
- // Here we ensure enough queue capacity to store all the decls, so that later we can use
- // appendAssumeCapacity.
- try self.work_queue.ensureUnusedCapacity(src_module.decls.len);
-
- for (src_module.decls) |decl| {
- if (decl.cast(text.Inst.Export)) |export_inst| {
- _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty);
- }
- }
- },
-
- .unloaded_parse_failure,
- .unloaded_sema_failure,
- .loaded_parse_failure,
- .loaded_sema_failure,
- .loaded_success,
- .unloaded_success,
- => {
- const src_module = try self.getTextModule(root_scope);
-
- // Look for changed decls.
- for (src_module.decls) |src_decl| {
- const name_hash = Decl.hashSimpleName(src_decl.name);
- if (self.decl_table.get(name_hash)) |kv| {
- const decl = kv.value;
- const new_contents_hash = Decl.hashSimpleName(src_decl.contents);
- if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) {
- // TODO recursive dependency management
- std.debug.warn("noticed that '{}' changed\n", .{src_decl.name});
- self.decl_table.removeAssertDiscard(name_hash);
- const saved_link = decl.link;
- decl.destroy(self.allocator);
- if (self.export_owners.getValue(decl)) |exports| {
- @panic("TODO handle updating a decl that does an export");
- }
- const new_decl = self.resolveDecl(
- &root_scope.base,
- src_decl,
- saved_link,
- ) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.AnalysisFail => continue,
- };
- if (self.decl_exports.remove(decl)) |entry| {
- self.decl_exports.putAssumeCapacityNoClobber(new_decl, entry.value);
- }
- }
- } else if (src_decl.cast(text.Inst.Export)) |export_inst| {
- _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty);
- }
- }
- },
- }
- }
-
- fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
- // Use the Decl's arena for function memory.
- var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator);
- defer decl.typed_value.most_recent.arena.?.* = arena.state;
- var analysis: Fn.Analysis = .{
- .inner_block = .{
- .func = func,
- .decl = decl,
- .instructions = .{},
- .arena = &arena.allocator,
- },
- .needed_inst_capacity = 0,
- .inst_table = std.AutoHashMap(*text.Inst, *Inst).init(self.allocator),
- };
- defer analysis.inner_block.instructions.deinit(self.allocator);
- defer analysis.inst_table.deinit();
-
- const fn_inst = func.analysis.queued;
- func.analysis = .{ .in_progress = &analysis };
-
- try self.analyzeBody(&analysis.inner_block.base, fn_inst.positionals.body);
-
- func.analysis = .{
- .success = .{
- .instructions = try arena.allocator.dupe(*Inst, analysis.inner_block.instructions.items),
- },
- };
- }
-
- fn resolveDecl(
- self: *Module,
- scope: *Scope,
- old_inst: *text.Inst,
- bin_file_link: link.ElfFile.Decl,
- ) InnerError!*Decl {
- const hash = Decl.hashSimpleName(old_inst.name);
- if (self.decl_table.get(hash)) |kv| {
- return kv.value;
- } else {
- const new_decl = blk: {
- try self.decl_table.ensureCapacity(self.decl_table.size + 1);
- const new_decl = try self.allocator.create(Decl);
- errdefer self.allocator.destroy(new_decl);
- const name = try mem.dupeZ(self.allocator, u8, old_inst.name);
- errdefer self.allocator.free(name);
- new_decl.* = .{
- .name = name,
- .scope = scope.namespace(),
- .src = old_inst.src,
- .typed_value = .{ .never_succeeded = {} },
- .analysis = .initial_in_progress,
- .contents_hash = Decl.hashSimpleName(old_inst.contents),
- .link = bin_file_link,
- };
- self.decl_table.putAssumeCapacityNoClobber(hash, new_decl);
- break :blk new_decl;
- };
-
- var decl_scope: Scope.DeclAnalysis = .{
- .decl = new_decl,
- .arena = std.heap.ArenaAllocator.init(self.allocator),
- };
- errdefer decl_scope.arena.deinit();
-
- const typed_value = self.analyzeInstConst(&decl_scope.base, old_inst) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.AnalysisFail => {
- switch (new_decl.analysis) {
- .initial_in_progress => new_decl.analysis = .initial_dependency_failure,
- .repeat_in_progress => new_decl.analysis = .repeat_dependency_failure,
- else => {},
- }
- return error.AnalysisFail;
- },
- };
- const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State);
-
- const has_codegen_bits = typed_value.ty.hasCodeGenBits();
- if (has_codegen_bits) {
- // 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(new_decl);
- }
-
- arena_state.* = decl_scope.arena.state;
-
- new_decl.typed_value = .{
- .most_recent = .{
- .typed_value = typed_value,
- .arena = arena_state,
- },
- };
- new_decl.analysis = .complete;
- if (has_codegen_bits) {
- // We ensureCapacity when scanning for decls.
- self.work_queue.writeItemAssumeCapacity(.{ .codegen_decl = new_decl });
- }
- return new_decl;
- }
- }
-
- fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Decl {
- const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.Decl.empty);
- switch (decl.analysis) {
- .initial_in_progress => unreachable,
- .repeat_in_progress => unreachable,
- .initial_dependency_failure,
- .repeat_dependency_failure,
- .initial_sema_failure,
- .repeat_sema_failure,
- .codegen_failure,
- .codegen_failure_retryable,
- => return error.AnalysisFail,
-
- .complete => return decl,
- }
- }
-
- fn resolveInst(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Inst {
- if (scope.cast(Scope.Block)) |block| {
- if (block.func.analysis.in_progress.inst_table.get(old_inst)) |kv| {
- return kv.value;
- }
- }
-
- const decl = try self.resolveCompleteDecl(scope, old_inst);
- const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl);
- return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src);
- }
-
- 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: *text.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 {
- 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 {
- if (base.value()) |val| {
- if (val.isUndef()) {
- return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{});
- }
- return val;
- }
- return null;
- }
-
- fn resolveConstString(self: *Module, scope: *Scope, old_inst: *text.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: *text.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, export_inst: *text.Inst.Export) InnerError!void {
- try self.decl_exports.ensureCapacity(self.decl_exports.size + 1);
- try self.export_owners.ensureCapacity(self.export_owners.size + 1);
- const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name);
- const exported_decl = try self.resolveCompleteDecl(scope, export_inst.positionals.value);
- const typed_value = exported_decl.typed_value.most_recent.typed_value;
- switch (typed_value.ty.zigTypeTag()) {
- .Fn => {},
- else => return self.fail(
- scope,
- export_inst.positionals.value.src,
- "unable to export type '{}'",
- .{typed_value.ty},
- ),
- }
- const new_export = try self.allocator.create(Export);
- errdefer self.allocator.destroy(new_export);
-
- const owner_decl = scope.decl();
-
- new_export.* = .{
- .options = .{ .name = symbol_name },
- .src = export_inst.base.src,
- .link = .{},
- .owner_decl = owner_decl,
- .status = .in_progress,
- };
-
- // Add to export_owners table.
- const eo_gop = self.export_owners.getOrPut(owner_decl) catch unreachable;
- if (!eo_gop.found_existing) {
- eo_gop.kv.value = &[0]*Export{};
- }
- eo_gop.kv.value = try self.allocator.realloc(eo_gop.kv.value, eo_gop.kv.value.len + 1);
- eo_gop.kv.value[eo_gop.kv.value.len - 1] = new_export;
- errdefer eo_gop.kv.value = self.allocator.shrink(eo_gop.kv.value, eo_gop.kv.value.len - 1);
-
- // Add to exported_decl table.
- const de_gop = self.decl_exports.getOrPut(exported_decl) catch unreachable;
- if (!de_gop.found_existing) {
- de_gop.kv.value = &[0]*Export{};
- }
- de_gop.kv.value = try self.allocator.realloc(de_gop.kv.value, de_gop.kv.value.len + 1);
- de_gop.kv.value[de_gop.kv.value.len - 1] = new_export;
- errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1);
-
- self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- else => {
- try self.failed_exports.ensureCapacity(self.failed_exports.size + 1);
- self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create(
- self.allocator,
- export_inst.base.src,
- "unable to export: {}",
- .{@errorName(err)},
- ));
- new_export.status = .failed_retryable;
- },
- };
- }
-
- /// TODO should not need the cast on the last parameter at the callsites
- fn addNewInstArgs(
- self: *Module,
- block: *Scope.Block,
- src: usize,
- ty: Type,
- comptime T: type,
- args: Inst.Args(T),
- ) !*Inst {
- const inst = try self.addNewInst(block, src, ty, T);
- inst.args = args;
- return &inst.base;
- }
-
- 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,
- },
- .args = undefined,
- };
- try block.instructions.append(self.allocator, &inst.base);
- return inst;
- }
-
- fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst {
- const const_inst = try scope.arena().create(Inst.Constant);
- const_inst.* = .{
- .base = .{
- .tag = Inst.Constant.base_tag,
- .ty = typed_value.ty,
- .src = src,
- },
- .val = typed_value.val,
- };
- return &const_inst.base;
- }
-
- fn constStr(self: *Module, scope: *Scope, src: usize, str: []const u8) !*Inst {
- const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0);
- ty_payload.* = .{ .len = str.len };
-
- const bytes_payload = try scope.arena().create(Value.Payload.Bytes);
- bytes_payload.* = .{ .data = str };
-
- return self.constInst(scope, src, .{
- .ty = Type.initPayload(&ty_payload.base),
- .val = Value.initPayload(&bytes_payload.base),
- });
- }
-
- 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 {
- return self.constInst(scope, src, .{
- .ty = Type.initTag(.void),
- .val = Value.initTag(.the_one_possible_value),
- });
- }
-
- 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 {
- 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 {
- const int_payload = try scope.arena().create(Value.Payload.Int_u64);
- int_payload.* = .{ .int = int };
-
- return self.constInst(scope, src, .{
- .ty = ty,
- .val = Value.initPayload(&int_payload.base),
- });
- }
-
- 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 };
-
- return self.constInst(scope, src, .{
- .ty = ty,
- .val = Value.initPayload(&int_payload.base),
- });
- }
-
- 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);
- } else |err| switch (err) {
- error.NegativeIntoUnsigned => unreachable,
- error.TargetTooSmall => {}, // handled below
- }
- const big_int_payload = try scope.arena().create(Value.Payload.IntBigPositive);
- big_int_payload.* = .{ .limbs = big_int.limbs };
- break :blk &big_int_payload.base;
- } else blk: {
- if (big_int.to(i64)) |x| {
- return self.constIntSigned(scope, src, ty, x);
- } else |err| switch (err) {
- error.NegativeIntoUnsigned => unreachable,
- error.TargetTooSmall => {}, // handled below
- }
- const big_int_payload = try scope.arena().create(Value.Payload.IntBigNegative);
- big_int_payload.* = .{ .limbs = big_int.limbs };
- break :blk &big_int_payload.base;
- };
-
- return self.constInst(scope, src, .{
- .ty = ty,
- .val = Value.initPayload(val_payload),
- });
- }
-
- fn analyzeInstConst(self: *Module, scope: *Scope, old_inst: *text.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 analyzeInst(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Inst {
- switch (old_inst.tag) {
- .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(text.Inst.Breakpoint).?),
- .call => return self.analyzeInstCall(scope, old_inst.cast(text.Inst.Call).?),
- .declref => return self.analyzeInstDeclRef(scope, old_inst.cast(text.Inst.DeclRef).?),
- .str => {
- const bytes = old_inst.cast(text.Inst.Str).?.positionals.bytes;
- // The bytes references memory inside the ZIR text module, which can get deallocated
- // after semantic analysis is complete. We need the memory to be in the Decl's arena.
- const arena_bytes = try scope.arena().dupe(u8, bytes);
- return self.constStr(scope, old_inst.src, arena_bytes);
- },
- .int => {
- const big_int = old_inst.cast(text.Inst.Int).?.positionals.int;
- return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int);
- },
- .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.cast(text.Inst.PtrToInt).?),
- .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.cast(text.Inst.FieldPtr).?),
- .deref => return self.analyzeInstDeref(scope, old_inst.cast(text.Inst.Deref).?),
- .as => return self.analyzeInstAs(scope, old_inst.cast(text.Inst.As).?),
- .@"asm" => return self.analyzeInstAsm(scope, old_inst.cast(text.Inst.Asm).?),
- .@"unreachable" => return self.analyzeInstUnreachable(scope, old_inst.cast(text.Inst.Unreachable).?),
- .@"return" => return self.analyzeInstRet(scope, old_inst.cast(text.Inst.Return).?),
- .@"fn" => return self.analyzeInstFn(scope, old_inst.cast(text.Inst.Fn).?),
- .@"export" => {
- try self.analyzeExport(scope, old_inst.cast(text.Inst.Export).?);
- return self.constVoid(scope, old_inst.src);
- },
- .primitive => return self.analyzeInstPrimitive(scope, old_inst.cast(text.Inst.Primitive).?),
- .ref => return self.analyzeInstRef(scope, old_inst.cast(text.Inst.Ref).?),
- .fntype => return self.analyzeInstFnType(scope, old_inst.cast(text.Inst.FnType).?),
- .intcast => return self.analyzeInstIntCast(scope, old_inst.cast(text.Inst.IntCast).?),
- .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(text.Inst.BitCast).?),
- .elemptr => return self.analyzeInstElemPtr(scope, old_inst.cast(text.Inst.ElemPtr).?),
- .add => return self.analyzeInstAdd(scope, old_inst.cast(text.Inst.Add).?),
- .cmp => return self.analyzeInstCmp(scope, old_inst.cast(text.Inst.Cmp).?),
- .condbr => return self.analyzeInstCondBr(scope, old_inst.cast(text.Inst.CondBr).?),
- .isnull => return self.analyzeInstIsNull(scope, old_inst.cast(text.Inst.IsNull).?),
- .isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.cast(text.Inst.IsNonNull).?),
- }
- }
-
- fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *text.Inst.Breakpoint) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){});
- }
-
- fn analyzeInstRef(self: *Module, scope: *Scope, inst: *text.Inst.Ref) InnerError!*Inst {
- const decl = try self.resolveCompleteDecl(scope, inst.positionals.operand);
- return self.analyzeDeclRef(scope, inst.base.src, decl);
- }
-
- fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *text.Inst.DeclRef) InnerError!*Inst {
- const decl_name = try self.resolveConstString(scope, inst.positionals.name);
- // This will need to get more fleshed out when there are proper structs & namespaces.
- const zir_module = scope.namespace();
- for (zir_module.contents.module.decls) |src_decl| {
- if (mem.eql(u8, src_decl.name, decl_name)) {
- const decl = try self.resolveCompleteDecl(scope, src_decl);
- return self.analyzeDeclRef(scope, inst.base.src, decl);
- }
- }
- return self.fail(scope, inst.positionals.name.src, "use of undeclared identifier '{}'", .{decl_name});
- }
-
- fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
- const decl_tv = try decl.typedValue();
- const ty_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
- ty_payload.* = .{ .pointee_type = decl_tv.ty };
- const val_payload = try scope.arena().create(Value.Payload.DeclRef);
- val_payload.* = .{ .decl = decl };
- return self.constInst(scope, src, .{
- .ty = Type.initPayload(&ty_payload.base),
- .val = Value.initPayload(&val_payload.base),
- });
- }
-
- fn analyzeInstCall(self: *Module, scope: *Scope, inst: *text.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.allocator.alloc(Type, fn_params_len);
- defer self.allocator.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 b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Call, Inst.Args(Inst.Call){
- .func = func,
- .args = casted_args,
- });
- }
-
- fn analyzeInstFn(self: *Module, scope: *Scope, fn_inst: *text.Inst.Fn) InnerError!*Inst {
- const fn_type = try self.resolveType(scope, fn_inst.positionals.fn_type);
- const new_func = try scope.arena().create(Fn);
- new_func.* = .{
- .fn_type = fn_type,
- .analysis = .{ .queued = fn_inst },
- };
- 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 analyzeInstFnType(self: *Module, scope: *Scope, fntype: *text.Inst.FnType) InnerError!*Inst {
- const return_type = try self.resolveType(scope, fntype.positionals.return_type);
-
- if (return_type.zigTypeTag() == .NoReturn and
- fntype.positionals.param_types.len == 0 and
- fntype.kw_args.cc == .Unspecified)
- {
- return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args));
- }
-
- if (return_type.zigTypeTag() == .NoReturn and
- fntype.positionals.param_types.len == 0 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.positionals.param_types.len == 0 and
- fntype.kw_args.cc == .C)
- {
- return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args));
- }
-
- return self.fail(scope, fntype.base.src, "TODO implement fntype instruction more", .{});
- }
-
- fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *text.Inst.Primitive) InnerError!*Inst {
- return self.constType(scope, primitive.base.src, primitive.positionals.tag.toType());
- }
-
- fn analyzeInstAs(self: *Module, scope: *Scope, as: *text.Inst.As) InnerError!*Inst {
- const dest_type = try self.resolveType(scope, as.positionals.dest_type);
- const new_inst = try self.resolveInst(scope, as.positionals.value);
- return self.coerce(scope, dest_type, new_inst);
- }
-
- fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *text.Inst.PtrToInt) InnerError!*Inst {
- const ptr = try self.resolveInst(scope, ptrtoint.positionals.ptr);
- if (ptr.ty.zigTypeTag() != .Pointer) {
- return self.fail(scope, ptrtoint.positionals.ptr.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.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = ptr });
- }
-
- fn analyzeInstFieldPtr(self: *Module, scope: *Scope, fieldptr: *text.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, intcast: *text.Inst.IntCast) InnerError!*Inst {
- const dest_type = try self.resolveType(scope, intcast.positionals.dest_type);
- const new_inst = try self.resolveInst(scope, intcast.positionals.value);
-
- const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
- .ComptimeInt => true,
- .Int => false,
- else => return self.fail(
- scope,
- intcast.positionals.dest_type.src,
- "expected integer type, found '{}'",
- .{
- dest_type,
- },
- ),
- };
-
- switch (new_inst.ty.zigTypeTag()) {
- .ComptimeInt, .Int => {},
- else => return self.fail(
- scope,
- intcast.positionals.value.src,
- "expected integer type, found '{}'",
- .{new_inst.ty},
- ),
- }
-
- if (dest_is_comptime_int or new_inst.value() != null) {
- return self.coerce(scope, dest_type, new_inst);
- }
-
- return self.fail(scope, intcast.base.src, "TODO implement analyze widen or shorten int", .{});
- }
-
- fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *text.Inst.BitCast) InnerError!*Inst {
- const dest_type = try self.resolveType(scope, inst.positionals.dest_type);
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- return self.bitcast(scope, dest_type, operand);
- }
-
- fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *text.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 analyzeInstAdd(self: *Module, scope: *Scope, inst: *text.Inst.Add) InnerError!*Inst {
- const lhs = try self.resolveInst(scope, inst.positionals.lhs);
- const rhs = try self.resolveInst(scope, inst.positionals.rhs);
-
- if (lhs.ty.zigTypeTag() == .Int and rhs.ty.zigTypeTag() == .Int) {
- if (lhs.value()) |lhs_val| {
- if (rhs.value()) |rhs_val| {
- // TODO is this a performance issue? maybe we should try the operation without
- // resorting to BigInt first.
- var lhs_space: Value.BigIntSpace = undefined;
- var rhs_space: Value.BigIntSpace = undefined;
- const lhs_bigint = lhs_val.toBigInt(&lhs_space);
- const rhs_bigint = rhs_val.toBigInt(&rhs_space);
- const limbs = try scope.arena().alloc(
- std.math.big.Limb,
- std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
- );
- var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
- result_bigint.add(lhs_bigint, rhs_bigint);
- const result_limbs = result_bigint.limbs[0..result_bigint.len];
-
- if (!lhs.ty.eql(rhs.ty)) {
- return self.fail(scope, inst.base.src, "TODO implement peer type resolution", .{});
- }
-
- const val_payload = if (result_bigint.positive) blk: {
- const val_payload = try scope.arena().create(Value.Payload.IntBigPositive);
- val_payload.* = .{ .limbs = result_limbs };
- break :blk &val_payload.base;
- } else blk: {
- const val_payload = try scope.arena().create(Value.Payload.IntBigNegative);
- val_payload.* = .{ .limbs = result_limbs };
- break :blk &val_payload.base;
- };
-
- return self.constInst(scope, inst.base.src, .{
- .ty = lhs.ty,
- .val = Value.initPayload(val_payload),
- });
- }
- }
- }
-
- return self.fail(scope, inst.base.src, "TODO implement more analyze add", .{});
- }
-
- fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *text.Inst.Deref) InnerError!*Inst {
- const ptr = try self.resolveInst(scope, deref.positionals.ptr);
- return self.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.ptr.src);
- }
-
- 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}),
- };
- if (ptr.value()) |val| {
- return self.constInst(scope, src, .{
- .ty = elem_ty,
- .val = try val.pointerDeref(scope.arena()),
- });
- }
-
- return self.fail(scope, src, "TODO implement runtime deref", .{});
- }
-
- fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *text.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);
- return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){
- .asm_source = asm_source,
- .is_volatile = assembly.kw_args.@"volatile",
- .output = output,
- .inputs = inputs,
- .clobbers = clobbers,
- .args = args,
- });
- }
-
- fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *text.Inst.Cmp) InnerError!*Inst {
- const lhs = try self.resolveInst(scope, inst.positionals.lhs);
- const rhs = try self.resolveInst(scope, inst.positionals.rhs);
- const op = inst.positionals.op;
-
- 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);
- switch (op) {
- .eq => return self.addNewInstArgs(
- b,
- inst.base.src,
- Type.initTag(.bool),
- Inst.IsNull,
- Inst.Args(Inst.IsNull){ .operand = opt_operand },
- ),
- .neq => return self.addNewInstArgs(
- b,
- inst.base.src,
- Type.initTag(.bool),
- Inst.IsNonNull,
- Inst.Args(Inst.IsNonNull){ .operand = opt_operand },
- ),
- else => unreachable,
- }
- } 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 analyzeInstIsNull(self: *Module, scope: *Scope, inst: *text.Inst.IsNull) InnerError!*Inst {
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- return self.analyzeIsNull(scope, inst.base.src, operand, true);
- }
-
- fn analyzeInstIsNonNull(self: *Module, scope: *Scope, inst: *text.Inst.IsNonNull) InnerError!*Inst {
- const operand = try self.resolveInst(scope, inst.positionals.operand);
- return self.analyzeIsNull(scope, inst.base.src, operand, false);
- }
-
- fn analyzeInstCondBr(self: *Module, scope: *Scope, inst: *text.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.true_body else &inst.positionals.false_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 = .{
- .func = parent_block.func,
- .decl = parent_block.decl,
- .instructions = .{},
- .arena = parent_block.arena,
- };
- defer true_block.instructions.deinit(self.allocator);
- try self.analyzeBody(&true_block.base, inst.positionals.true_body);
-
- var false_block: Scope.Block = .{
- .func = parent_block.func,
- .decl = parent_block.decl,
- .instructions = .{},
- .arena = parent_block.arena,
- };
- defer false_block.instructions.deinit(self.allocator);
- try self.analyzeBody(&false_block.base, inst.positionals.false_body);
-
- return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){
- .condition = cond,
- .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) },
- .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) },
- });
- }
-
- fn wantSafety(self: *Module, scope: *Scope) bool {
- return switch (self.optimize_mode) {
- .Debug => true,
- .ReleaseSafe => true,
- .ReleaseFast => false,
- .ReleaseSmall => false,
- };
- }
-
- fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *text.Inst.Unreachable) 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.addNewInstArgs(b, unreach.base.src, Type.initTag(.void), Inst.Breakpoint, {});
- }
- return self.addNewInstArgs(b, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {});
- }
-
- fn analyzeInstRet(self: *Module, scope: *Scope, inst: *text.Inst.Return) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(scope, inst.base.src);
- return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, {});
- }
-
- fn analyzeBody(self: *Module, scope: *Scope, body: text.Module.Body) !void {
- if (scope.cast(Scope.Block)) |b| {
- const analysis = b.func.analysis.in_progress;
- analysis.needed_inst_capacity += body.instructions.len;
- try analysis.inst_table.ensureCapacity(analysis.needed_inst_capacity);
- for (body.instructions) |src_inst| {
- const new_inst = try self.analyzeInst(scope, src_inst);
- analysis.inst_table.putAssumeCapacityNoClobber(src_inst, new_inst);
- }
- } else {
- for (body.instructions) |src_inst| {
- _ = try self.analyzeInst(scope, src_inst);
- }
- }
- }
-
- fn analyzeIsNull(
- self: *Module,
- scope: *Scope,
- src: usize,
- operand: *Inst,
- invert_logic: bool,
- ) InnerError!*Inst {
- return self.fail(scope, src, "TODO implement analysis of isnull and isnotnull", .{});
- }
-
- /// Asserts that lhs and rhs types are both numeric.
- fn cmpNumeric(
- self: *Module,
- scope: *Scope,
- src: usize,
- lhs: *Inst,
- rhs: *Inst,
- op: std.math.CompareOperator,
- ) !*Inst {
- assert(lhs.ty.isNumeric());
- assert(rhs.ty.isNumeric());
-
- const lhs_ty_tag = lhs.ty.zigTypeTag();
- const rhs_ty_tag = rhs.ty.zigTypeTag();
-
- if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) {
- if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
- return self.fail(scope, src, "vector length mismatch: {} and {}", .{
- lhs.ty.arrayLen(),
- rhs.ty.arrayLen(),
- });
- }
- return self.fail(scope, src, "TODO implement support for vectors in cmpNumeric", .{});
- } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) {
- return self.fail(scope, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
- lhs.ty,
- rhs.ty,
- });
- }
-
- if (lhs.value()) |lhs_val| {
- if (rhs.value()) |rhs_val| {
- return self.constBool(scope, src, Value.compare(lhs_val, op, rhs_val));
- }
- }
-
- // TODO handle comparisons against lazy zero values
- // Some values can be compared against zero without being runtime known or without forcing
- // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to
- // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout
- // of this function if we don't need to.
-
- // It must be a runtime comparison.
- const b = try self.requireRuntimeBlock(scope, src);
- // For floats, emit a float comparison instruction.
- const lhs_is_float = switch (lhs_ty_tag) {
- .Float, .ComptimeFloat => true,
- else => false,
- };
- const rhs_is_float = switch (rhs_ty_tag) {
- .Float, .ComptimeFloat => true,
- else => false,
- };
- if (lhs_is_float and rhs_is_float) {
- // Implicit cast the smaller one to the larger one.
- const dest_type = x: {
- if (lhs_ty_tag == .ComptimeFloat) {
- break :x rhs.ty;
- } else if (rhs_ty_tag == .ComptimeFloat) {
- break :x lhs.ty;
- }
- if (lhs.ty.floatBits(self.target()) >= rhs.ty.floatBits(self.target())) {
- break :x lhs.ty;
- } else {
- break :x rhs.ty;
- }
- };
- const casted_lhs = try self.coerce(scope, dest_type, lhs);
- const casted_rhs = try self.coerce(scope, dest_type, rhs);
- return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){
- .lhs = casted_lhs,
- .rhs = casted_rhs,
- .op = op,
- });
- }
- // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
- // For mixed signed and unsigned integers, implicit cast both operands to a signed
- // integer with + 1 bit.
- // For mixed floats and integers, extract the integer part from the float, cast that to
- // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
- // add/subtract 1.
- const lhs_is_signed = if (lhs.value()) |lhs_val|
- lhs_val.compareWithZero(.lt)
- else
- (lhs.ty.isFloat() or lhs.ty.isSignedInt());
- const rhs_is_signed = if (rhs.value()) |rhs_val|
- rhs_val.compareWithZero(.lt)
- else
- (rhs.ty.isFloat() or rhs.ty.isSignedInt());
- const dest_int_is_signed = lhs_is_signed or rhs_is_signed;
-
- var dest_float_type: ?Type = null;
-
- var lhs_bits: usize = undefined;
- if (lhs.value()) |lhs_val| {
- if (lhs_val.isUndef())
- return self.constUndef(scope, src, Type.initTag(.bool));
- const is_unsigned = if (lhs_is_float) x: {
- var bigint_space: Value.BigIntSpace = undefined;
- var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.allocator);
- defer bigint.deinit();
- const zcmp = lhs_val.orderAgainstZero();
- if (lhs_val.floatHasFraction()) {
- switch (op) {
- .eq => return self.constBool(scope, src, false),
- .neq => return self.constBool(scope, src, true),
- else => {},
- }
- if (zcmp == .lt) {
- try bigint.addScalar(bigint.toConst(), -1);
- } else {
- try bigint.addScalar(bigint.toConst(), 1);
- }
- }
- lhs_bits = bigint.toConst().bitCountTwosComp();
- break :x (zcmp != .lt);
- } else x: {
- lhs_bits = lhs_val.intBitCountTwosComp();
- break :x (lhs_val.orderAgainstZero() != .lt);
- };
- lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
- } else if (lhs_is_float) {
- dest_float_type = lhs.ty;
- } else {
- const int_info = lhs.ty.intInfo(self.target());
- lhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed);
- }
-
- var rhs_bits: usize = undefined;
- if (rhs.value()) |rhs_val| {
- if (rhs_val.isUndef())
- return self.constUndef(scope, src, Type.initTag(.bool));
- const is_unsigned = if (rhs_is_float) x: {
- var bigint_space: Value.BigIntSpace = undefined;
- var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.allocator);
- defer bigint.deinit();
- const zcmp = rhs_val.orderAgainstZero();
- if (rhs_val.floatHasFraction()) {
- switch (op) {
- .eq => return self.constBool(scope, src, false),
- .neq => return self.constBool(scope, src, true),
- else => {},
- }
- if (zcmp == .lt) {
- try bigint.addScalar(bigint.toConst(), -1);
- } else {
- try bigint.addScalar(bigint.toConst(), 1);
- }
- }
- rhs_bits = bigint.toConst().bitCountTwosComp();
- break :x (zcmp != .lt);
- } else x: {
- rhs_bits = rhs_val.intBitCountTwosComp();
- break :x (rhs_val.orderAgainstZero() != .lt);
- };
- rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
- } else if (rhs_is_float) {
- dest_float_type = rhs.ty;
- } else {
- const int_info = rhs.ty.intInfo(self.target());
- rhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed);
- }
-
- const dest_type = if (dest_float_type) |ft| ft else blk: {
- const max_bits = std.math.max(lhs_bits, rhs_bits);
- const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) {
- error.Overflow => return self.fail(scope, src, "{} exceeds maximum integer bit count", .{max_bits}),
- };
- break :blk try self.makeIntType(scope, dest_int_is_signed, casted_bits);
- };
- const casted_lhs = try self.coerce(scope, dest_type, lhs);
- const casted_rhs = try self.coerce(scope, dest_type, lhs);
-
- return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){
- .lhs = casted_lhs,
- .rhs = casted_rhs,
- .op = op,
- });
- }
-
- fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
- if (signed) {
- const int_payload = try scope.arena().create(Type.Payload.IntSigned);
- int_payload.* = .{ .bits = bits };
- return Type.initPayload(&int_payload.base);
- } else {
- const int_payload = try scope.arena().create(Type.Payload.IntUnsigned);
- int_payload.* = .{ .bits = bits };
- return Type.initPayload(&int_payload.base);
- }
- }
-
- 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;
-
- const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty);
- if (in_memory_result == .ok) {
- return self.bitcast(scope, dest_type, inst);
- }
-
- // *[N]T to []T
- if (inst.ty.isSinglePointer() and dest_type.isSlice() and
- (!inst.ty.pointerIsConst() or dest_type.pointerIsConst()))
- {
- const array_type = inst.ty.elemType();
- const dst_elem_type = dest_type.elemType();
- if (array_type.zigTypeTag() == .Array and
- coerceInMemoryAllowed(dst_elem_type, array_type.elemType()) == .ok)
- {
- return self.coerceArrayPtrToSlice(scope, dest_type, inst);
- }
- }
-
- // comptime_int to fixed-width integer
- if (inst.ty.zigTypeTag() == .ComptimeInt and dest_type.zigTypeTag() == .Int) {
- // The representation is already correct; we only need to make sure it fits in the destination type.
- const val = inst.value().?; // comptime_int always has comptime known value
- if (!val.intFitsInType(dest_type, self.target())) {
- return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
- }
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
-
- // integer widening
- if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) {
- const src_info = inst.ty.intInfo(self.target());
- const dst_info = dest_type.intInfo(self.target());
- if (src_info.signed == dst_info.signed and dst_info.bits >= src_info.bits) {
- if (inst.value()) |val| {
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- } else {
- return self.fail(scope, inst.src, "TODO implement runtime integer widening", .{});
- }
- } else {
- return self.fail(scope, inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type });
- }
- }
-
- 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 {
- if (inst.value()) |val| {
- // Keep the comptime Value representation; take the new type.
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
- // TODO validate the type size and other compile errors
- const b = try self.requireRuntimeBlock(scope, inst.src);
- return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst });
- }
-
- fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
- if (inst.value()) |val| {
- // The comptime Value representation is compatible with both types.
- return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
- }
- return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{});
- }
-
- fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError {
- @setCold(true);
- try self.failed_decls.ensureCapacity(self.failed_decls.size + 1);
- try self.failed_files.ensureCapacity(self.failed_files.size + 1);
- const err_msg = try ErrorMsg.create(self.allocator, src, format, args);
- switch (scope.tag) {
- .decl => {
- const decl = scope.cast(Scope.DeclAnalysis).?.decl;
- switch (decl.analysis) {
- .initial_in_progress => decl.analysis = .initial_sema_failure,
- .repeat_in_progress => decl.analysis = .repeat_sema_failure,
- else => unreachable,
- }
- self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg);
- },
- .block => {
- const block = scope.cast(Scope.Block).?;
- block.func.analysis = .sema_failure;
- self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg);
- },
- .zir_module => {
- const zir_module = scope.cast(Scope.ZIRModule).?;
- zir_module.status = .loaded_sema_failure;
- self.failed_files.putAssumeCapacityNoClobber(zir_module, err_msg);
- },
- }
- return error.AnalysisFail;
- }
-
- const InMemoryCoercionResult = enum {
- ok,
- no_match,
- };
-
- fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult {
- if (dest_type.eql(src_type))
- return .ok;
-
- // TODO: implement more of this function
-
- return .no_match;
- }
-};
-
-pub const ErrorMsg = struct {
- byte_offset: usize,
- msg: []const u8,
-
- pub fn create(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg {
- const self = try allocator.create(ErrorMsg);
- errdefer allocator.destroy(self);
- self.* = try init(allocator, byte_offset, format, args);
- return self;
- }
-
- /// Assumes the ErrorMsg struct and msg were both allocated with allocator.
- pub fn destroy(self: *ErrorMsg, allocator: *Allocator) void {
- self.deinit(allocator);
- allocator.destroy(self);
- }
-
- pub fn init(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg {
- return ErrorMsg{
- .byte_offset = byte_offset,
- .msg = try std.fmt.allocPrint(allocator, format, args),
- };
- }
-
- pub fn deinit(self: *ErrorMsg, allocator: *Allocator) void {
- allocator.free(self.msg);
- self.* = undefined;
- }
-};
src-self-hosted/link.zig
@@ -3,6 +3,7 @@ const mem = std.mem;
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const ir = @import("ir.zig");
+const Module = @import("Module.zig");
const fs = std.fs;
const elf = std.elf;
const codegen = @import("codegen.zig");
@@ -45,8 +46,8 @@ pub fn writeFilePath(
allocator: *Allocator,
dir: fs.Dir,
sub_path: []const u8,
- module: ir.Module,
- errors: *std.ArrayList(ir.ErrorMsg),
+ module: Module,
+ errors: *std.ArrayList(Module.ErrorMsg),
) !void {
const options: Options = .{
.target = module.target,
@@ -755,7 +756,7 @@ pub const ElfFile = struct {
};
}
- pub fn allocateDeclIndexes(self: *ElfFile, decl: *ir.Module.Decl) !void {
+ pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void {
if (decl.link.local_sym_index != 0) return;
try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);
@@ -784,7 +785,7 @@ pub const ElfFile = struct {
};
}
- pub fn updateDecl(self: *ElfFile, module: *ir.Module, decl: *ir.Module.Decl) !void {
+ pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void {
var code_buffer = std.ArrayList(u8).init(self.allocator);
defer code_buffer.deinit();
@@ -878,16 +879,16 @@ pub const ElfFile = struct {
try self.file.pwriteAll(code, file_offset);
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
- const decl_exports = module.decl_exports.getValue(decl) orelse &[0]*ir.Module.Export{};
+ const decl_exports = module.decl_exports.getValue(decl) orelse &[0]*Module.Export{};
return self.updateDeclExports(module, decl, decl_exports);
}
/// Must be called only after a successful call to `updateDecl`.
pub fn updateDeclExports(
self: *ElfFile,
- module: *ir.Module,
- decl: *const ir.Module.Decl,
- exports: []const *ir.Module.Export,
+ module: *Module,
+ decl: *const Module.Decl,
+ exports: []const *Module.Export,
) !void {
try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len);
const typed_value = decl.typed_value.most_recent.typed_value;
@@ -900,7 +901,7 @@ pub const ElfFile = struct {
try module.failed_exports.ensureCapacity(module.failed_exports.size + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
- try ir.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
+ try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
);
continue;
}
@@ -918,7 +919,7 @@ pub const ElfFile = struct {
try module.failed_exports.ensureCapacity(module.failed_exports.size + 1);
module.failed_exports.putAssumeCapacityNoClobber(
exp,
- try ir.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
+ try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
);
continue;
},
src-self-hosted/main.zig
@@ -6,9 +6,10 @@ const process = std.process;
const Allocator = mem.Allocator;
const ArrayList = std.ArrayList;
const ast = std.zig.ast;
-const ir = @import("ir.zig");
+const Module = @import("Module.zig");
const link = @import("link.zig");
const Package = @import("Package.zig");
+const zir = @import("zir.zig");
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
@@ -438,7 +439,7 @@ fn buildOutputType(
const root_pkg = try Package.create(gpa, fs.cwd(), ".", src_path);
errdefer root_pkg.destroy();
- const root_scope = try gpa.create(ir.Module.Scope.ZIRModule);
+ const root_scope = try gpa.create(Module.Scope.ZIRModule);
errdefer gpa.destroy(root_scope);
root_scope.* = .{
.sub_file_path = root_pkg.root_src_path,
@@ -447,19 +448,19 @@ fn buildOutputType(
.status = .never_loaded,
};
- break :blk ir.Module{
+ break :blk Module{
.allocator = gpa,
.root_pkg = root_pkg,
.root_scope = root_scope,
.bin_file = &bin_file,
.optimize_mode = .Debug,
- .decl_table = std.AutoHashMap(ir.Module.Decl.Hash, *ir.Module.Decl).init(gpa),
- .decl_exports = std.AutoHashMap(*ir.Module.Decl, []*ir.Module.Export).init(gpa),
- .export_owners = std.AutoHashMap(*ir.Module.Decl, []*ir.Module.Export).init(gpa),
- .failed_decls = std.AutoHashMap(*ir.Module.Decl, *ir.ErrorMsg).init(gpa),
- .failed_files = std.AutoHashMap(*ir.Module.Scope.ZIRModule, *ir.ErrorMsg).init(gpa),
- .failed_exports = std.AutoHashMap(*ir.Module.Export, *ir.ErrorMsg).init(gpa),
- .work_queue = std.fifo.LinearFifo(ir.Module.WorkItem, .Dynamic).init(gpa),
+ .decl_table = std.AutoHashMap(Module.Decl.Hash, *Module.Decl).init(gpa),
+ .decl_exports = std.AutoHashMap(*Module.Decl, []*Module.Export).init(gpa),
+ .export_owners = std.AutoHashMap(*Module.Decl, []*Module.Export).init(gpa),
+ .failed_decls = std.AutoHashMap(*Module.Decl, *Module.ErrorMsg).init(gpa),
+ .failed_files = std.AutoHashMap(*Module.Scope.ZIRModule, *Module.ErrorMsg).init(gpa),
+ .failed_exports = std.AutoHashMap(*Module.Export, *Module.ErrorMsg).init(gpa),
+ .work_queue = std.fifo.LinearFifo(Module.WorkItem, .Dynamic).init(gpa),
};
};
defer module.deinit();
@@ -491,7 +492,7 @@ fn buildOutputType(
}
}
-fn updateModule(gpa: *Allocator, module: *ir.Module, zir_out_path: ?[]const u8) !void {
+fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !void {
try module.update();
var errors = try module.getAllErrorsAlloc();
@@ -509,7 +510,7 @@ fn updateModule(gpa: *Allocator, module: *ir.Module, zir_out_path: ?[]const u8)
}
if (zir_out_path) |zop| {
- var new_zir_module = try ir.text.emit_zir(gpa, module.*);
+ var new_zir_module = try zir.emit(gpa, module.*);
defer new_zir_module.deinit(gpa);
const baf = try io.BufferedAtomicFile.create(gpa, fs.cwd(), zop, .{});
src-self-hosted/Module.zig
@@ -0,0 +1,2018 @@
+const std = @import("std");
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+const ArrayListUnmanaged = std.ArrayListUnmanaged;
+const Value = @import("value.zig").Value;
+const Type = @import("type.zig").Type;
+const TypedValue = @import("TypedValue.zig");
+const assert = std.debug.assert;
+const BigIntConst = std.math.big.int.Const;
+const BigIntMutable = std.math.big.int.Mutable;
+const Target = std.Target;
+const Package = @import("Package.zig");
+const link = @import("link.zig");
+const ir = @import("ir.zig");
+const zir = @import("zir.zig");
+const Module = @This();
+const Inst = ir.Inst;
+
+/// General-purpose allocator.
+allocator: *Allocator,
+/// Module owns this resource.
+root_pkg: *Package,
+/// Module owns this resource.
+root_scope: *Scope.ZIRModule,
+/// Pointer to externally managed resource.
+bin_file: *link.ElfFile,
+/// It's rare for a decl to be exported, so we save memory by having a sparse map of
+/// Decl pointers to details about them being exported.
+/// The Export memory is owned by the `export_owners` table; the slice itself is owned by this table.
+decl_exports: std.AutoHashMap(*Decl, []*Export),
+/// This models the Decls that perform exports, so that `decl_exports` can be updated when a Decl
+/// is modified. Note that the key of this table is not the Decl being exported, but the Decl that
+/// is performing the export of another Decl.
+/// This table owns the Export memory.
+export_owners: std.AutoHashMap(*Decl, []*Export),
+/// Maps fully qualified namespaced names to the Decl struct for them.
+decl_table: std.AutoHashMap(Decl.Hash, *Decl),
+
+optimize_mode: std.builtin.Mode,
+link_error_flags: link.ElfFile.ErrorFlags = link.ElfFile.ErrorFlags{},
+
+work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
+
+/// We optimize memory usage for a compilation with no compile errors by storing the
+/// error messages and mapping outside of `Decl`.
+/// The ErrorMsg memory is owned by the decl, using Module's allocator.
+/// Note that a Decl can succeed but the Fn it represents can fail. In this case,
+/// a Decl can have a failed_decls entry but have analysis status of success.
+failed_decls: std.AutoHashMap(*Decl, *ErrorMsg),
+/// Using a map here for consistency with the other fields here.
+/// The ErrorMsg memory is owned by the `Scope.ZIRModule`, using Module's allocator.
+failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg),
+/// Using a map here for consistency with the other fields here.
+/// The ErrorMsg memory is owned by the `Export`, using Module's allocator.
+failed_exports: std.AutoHashMap(*Export, *ErrorMsg),
+
+pub const WorkItem = union(enum) {
+ /// Write the machine code for a Decl to the output file.
+ codegen_decl: *Decl,
+};
+
+pub const Export = struct {
+ options: std.builtin.ExportOptions,
+ /// Byte offset into the file that contains the export directive.
+ src: usize,
+ /// Represents the position of the export, if any, in the output file.
+ link: link.ElfFile.Export,
+ /// The Decl that performs the export. Note that this is *not* the Decl being exported.
+ owner_decl: *Decl,
+ status: enum {
+ in_progress,
+ failed,
+ /// Indicates that the failure was due to a temporary issue, such as an I/O error
+ /// when writing to the output file. Retrying the export may succeed.
+ failed_retryable,
+ complete,
+ },
+};
+
+pub const Decl = struct {
+ /// This name is relative to the containing namespace of the decl. It uses a null-termination
+ /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed
+ /// in symbol names, because executable file formats use null-terminated strings for symbol names.
+ /// All Decls have names, even values that are not bound to a zig namespace. This is necessary for
+ /// mapping them to an address in the output file.
+ /// Memory owned by this decl, using Module's allocator.
+ name: [*:0]const u8,
+ /// The direct parent container of the Decl. This field will need to get more fleshed out when
+ /// self-hosted supports proper struct types and Zig AST => ZIR.
+ /// Reference to externally owned memory.
+ scope: *Scope.ZIRModule,
+ /// Byte offset into the source file that contains this declaration.
+ /// This is the base offset that src offsets within this Decl are relative to.
+ src: usize,
+ /// The most recent value of the Decl after a successful semantic analysis.
+ /// The tag for this union is determined by the tag value of the analysis field.
+ typed_value: union {
+ never_succeeded: void,
+ most_recent: TypedValue.Managed,
+ },
+ /// Represents the "shallow" analysis status. For example, for decls that are functions,
+ /// the function type is analyzed with this set to `in_progress`, however, the semantic
+ /// analysis of the function body is performed with this value set to `success`. Functions
+ /// have their own analysis status field.
+ analysis: enum {
+ initial_in_progress,
+ /// This Decl might be OK but it depends on another one which did not successfully complete
+ /// semantic analysis. This Decl never had a value computed.
+ initial_dependency_failure,
+ /// Semantic analysis failure. This Decl never had a value computed.
+ /// There will be a corresponding ErrorMsg in Module.failed_decls.
+ initial_sema_failure,
+ /// In this case the `typed_value.most_recent` can still be accessed.
+ /// There will be a corresponding ErrorMsg in Module.failed_decls.
+ codegen_failure,
+ /// In this case the `typed_value.most_recent` can still be accessed.
+ /// There will be a corresponding ErrorMsg in Module.failed_decls.
+ /// This indicates the failure was something like running out of disk space,
+ /// and attempting codegen again may succeed.
+ codegen_failure_retryable,
+ /// This Decl might be OK but it depends on another one which did not successfully complete
+ /// semantic analysis. There is a most recent value available.
+ repeat_dependency_failure,
+ /// Semantic anlaysis failure, but the `typed_value.most_recent` can be accessed.
+ /// There will be a corresponding ErrorMsg in Module.failed_decls.
+ repeat_sema_failure,
+ /// Completed successfully before; the `typed_value.most_recent` can be accessed, and
+ /// new semantic analysis is in progress.
+ repeat_in_progress,
+ /// Everything is done and updated.
+ complete,
+ },
+
+ /// Represents the position of the code in the output file.
+ /// This is populated regardless of semantic analysis and code generation.
+ link: link.ElfFile.Decl = link.ElfFile.Decl.empty,
+
+ /// The shallow set of other decls whose typed_value could possibly change if this Decl's
+ /// typed_value is modified.
+ /// TODO look into using a lightweight map/set data structure rather than a linear array.
+ dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){},
+
+ contents_hash: Hash,
+
+ pub fn destroy(self: *Decl, allocator: *Allocator) void {
+ allocator.free(mem.spanZ(self.name));
+ if (self.typedValueManaged()) |tvm| {
+ tvm.deinit(allocator);
+ }
+ allocator.destroy(self);
+ }
+
+ pub const Hash = [16]u8;
+
+ /// If the name is small enough, it is used directly as the hash.
+ /// If it is long, blake3 hash is computed.
+ pub fn hashSimpleName(name: []const u8) Hash {
+ var out: Hash = undefined;
+ if (name.len <= Hash.len) {
+ mem.copy(u8, &out, name);
+ mem.set(u8, out[name.len..], 0);
+ } else {
+ std.crypto.Blake3.hash(name, &out);
+ }
+ return out;
+ }
+
+ /// Must generate unique bytes with no collisions with other decls.
+ /// The point of hashing here is only to limit the number of bytes of
+ /// the unique identifier to a fixed size (16 bytes).
+ pub fn fullyQualifiedNameHash(self: Decl) Hash {
+ // Right now we only have ZIRModule as the source. So this is simply the
+ // relative name of the decl.
+ return hashSimpleName(mem.spanZ(u8, self.name));
+ }
+
+ pub fn typedValue(self: *Decl) error{AnalysisFail}!TypedValue {
+ const tvm = self.typedValueManaged() orelse return error.AnalysisFail;
+ return tvm.typed_value;
+ }
+
+ pub fn value(self: *Decl) error{AnalysisFail}!Value {
+ return (try self.typedValue()).val;
+ }
+
+ pub fn dump(self: *Decl) void {
+ const loc = std.zig.findLineColumn(self.scope.source.bytes, self.src);
+ std.debug.warn("{}:{}:{} name={} status={}", .{
+ self.scope.sub_file_path,
+ loc.line + 1,
+ loc.column + 1,
+ mem.spanZ(self.name),
+ @tagName(self.analysis),
+ });
+ if (self.typedValueManaged()) |tvm| {
+ std.debug.warn(" ty={} val={}", .{ tvm.typed_value.ty, tvm.typed_value.val });
+ }
+ std.debug.warn("\n", .{});
+ }
+
+ fn typedValueManaged(self: *Decl) ?*TypedValue.Managed {
+ switch (self.analysis) {
+ .initial_in_progress,
+ .initial_dependency_failure,
+ .initial_sema_failure,
+ => return null,
+ .codegen_failure,
+ .codegen_failure_retryable,
+ .repeat_dependency_failure,
+ .repeat_sema_failure,
+ .repeat_in_progress,
+ .complete,
+ => return &self.typed_value.most_recent,
+ }
+ }
+};
+
+/// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
+pub const Fn = struct {
+ /// This memory owned by the Decl's TypedValue.Managed arena allocator.
+ fn_type: Type,
+ analysis: union(enum) {
+ /// The value is the source instruction.
+ queued: *zir.Inst.Fn,
+ in_progress: *Analysis,
+ /// There will be a corresponding ErrorMsg in Module.failed_decls
+ sema_failure,
+ /// This Fn might be OK but it depends on another Decl which did not successfully complete
+ /// semantic analysis.
+ dependency_failure,
+ success: Body,
+ },
+
+ /// This memory is temporary and points to stack memory for the duration
+ /// of Fn analysis.
+ pub const Analysis = struct {
+ inner_block: Scope.Block,
+ /// TODO Performance optimization idea: instead of this inst_table,
+ /// use a field in the zir.Inst instead to track corresponding instructions
+ inst_table: std.AutoHashMap(*zir.Inst, *Inst),
+ needed_inst_capacity: usize,
+ };
+};
+
+pub const Scope = struct {
+ tag: Tag,
+
+ pub fn cast(base: *Scope, comptime T: type) ?*T {
+ if (base.tag != T.base_tag)
+ return null;
+
+ return @fieldParentPtr(T, "base", base);
+ }
+
+ /// Asserts the scope has a parent which is a DeclAnalysis and
+ /// returns the arena Allocator.
+ pub fn arena(self: *Scope) *Allocator {
+ switch (self.tag) {
+ .block => return self.cast(Block).?.arena,
+ .decl => return &self.cast(DeclAnalysis).?.arena.allocator,
+ .zir_module => return &self.cast(ZIRModule).?.contents.module.arena.allocator,
+ }
+ }
+
+ /// Asserts the scope has a parent which is a DeclAnalysis and
+ /// returns the Decl.
+ pub fn decl(self: *Scope) *Decl {
+ switch (self.tag) {
+ .block => return self.cast(Block).?.decl,
+ .decl => return self.cast(DeclAnalysis).?.decl,
+ .zir_module => unreachable,
+ }
+ }
+
+ /// Asserts the scope has a parent which is a ZIRModule and
+ /// returns it.
+ pub fn namespace(self: *Scope) *ZIRModule {
+ switch (self.tag) {
+ .block => return self.cast(Block).?.decl.scope,
+ .decl => return self.cast(DeclAnalysis).?.decl.scope,
+ .zir_module => return self.cast(ZIRModule).?,
+ }
+ }
+
+ pub fn dumpInst(self: *Scope, inst: *Inst) void {
+ const zir_module = self.namespace();
+ const loc = std.zig.findLineColumn(zir_module.source.bytes, inst.src);
+ std.debug.warn("{}:{}:{}: {}: ty={}\n", .{
+ zir_module.sub_file_path,
+ loc.line + 1,
+ loc.column + 1,
+ @tagName(inst.tag),
+ inst.ty,
+ });
+ }
+
+ pub const Tag = enum {
+ zir_module,
+ block,
+ decl,
+ };
+
+ pub const ZIRModule = struct {
+ pub const base_tag: Tag = .zir_module;
+ base: Scope = Scope{ .tag = base_tag },
+ /// Relative to the owning package's root_src_dir.
+ /// Reference to external memory, not owned by ZIRModule.
+ sub_file_path: []const u8,
+ source: union {
+ unloaded: void,
+ bytes: [:0]const u8,
+ },
+ contents: union {
+ not_available: void,
+ module: *zir.Module,
+ },
+ status: enum {
+ never_loaded,
+ unloaded_success,
+ unloaded_parse_failure,
+ unloaded_sema_failure,
+ loaded_parse_failure,
+ loaded_sema_failure,
+ loaded_success,
+ },
+
+ pub fn unload(self: *ZIRModule, allocator: *Allocator) void {
+ switch (self.status) {
+ .never_loaded,
+ .unloaded_parse_failure,
+ .unloaded_sema_failure,
+ .unloaded_success,
+ => {},
+
+ .loaded_success => {
+ allocator.free(self.source.bytes);
+ self.contents.module.deinit(allocator);
+ allocator.destroy(self.contents.module);
+ self.status = .unloaded_success;
+ },
+ .loaded_sema_failure => {
+ allocator.free(self.source.bytes);
+ self.contents.module.deinit(allocator);
+ allocator.destroy(self.contents.module);
+ self.status = .unloaded_sema_failure;
+ },
+ .loaded_parse_failure => {
+ allocator.free(self.source.bytes);
+ self.status = .unloaded_parse_failure;
+ },
+ }
+ }
+
+ pub fn deinit(self: *ZIRModule, allocator: *Allocator) void {
+ self.unload(allocator);
+ self.* = undefined;
+ }
+
+ pub fn dumpSrc(self: *ZIRModule, src: usize) void {
+ const loc = std.zig.findLineColumn(self.source.bytes, src);
+ std.debug.warn("{}:{}:{}\n", .{ self.sub_file_path, loc.line + 1, loc.column + 1 });
+ }
+ };
+
+ /// This is a temporary structure, references to it are valid only
+ /// during semantic analysis of the block.
+ pub const Block = struct {
+ pub const base_tag: Tag = .block;
+ base: Scope = Scope{ .tag = base_tag },
+ func: *Fn,
+ decl: *Decl,
+ instructions: ArrayListUnmanaged(*Inst),
+ /// Points to the arena allocator of DeclAnalysis
+ arena: *Allocator,
+ };
+
+ /// This is a temporary structure, references to it are valid only
+ /// during semantic analysis of the decl.
+ pub const DeclAnalysis = struct {
+ pub const base_tag: Tag = .decl;
+ base: Scope = Scope{ .tag = base_tag },
+ decl: *Decl,
+ arena: std.heap.ArenaAllocator,
+ };
+};
+
+pub const Body = struct {
+ instructions: []*Inst,
+};
+
+pub const AllErrors = struct {
+ arena: std.heap.ArenaAllocator.State,
+ list: []const Message,
+
+ pub const Message = struct {
+ src_path: []const u8,
+ line: usize,
+ column: usize,
+ byte_offset: usize,
+ msg: []const u8,
+ };
+
+ pub fn deinit(self: *AllErrors, allocator: *Allocator) void {
+ self.arena.promote(allocator).deinit();
+ }
+
+ fn add(
+ arena: *std.heap.ArenaAllocator,
+ errors: *std.ArrayList(Message),
+ sub_file_path: []const u8,
+ source: []const u8,
+ simple_err_msg: ErrorMsg,
+ ) !void {
+ const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset);
+ try errors.append(.{
+ .src_path = try arena.allocator.dupe(u8, sub_file_path),
+ .msg = try arena.allocator.dupe(u8, simple_err_msg.msg),
+ .byte_offset = simple_err_msg.byte_offset,
+ .line = loc.line,
+ .column = loc.column,
+ });
+ }
+};
+
+pub fn deinit(self: *Module) void {
+ const allocator = self.allocator;
+ self.work_queue.deinit();
+ {
+ var it = self.decl_table.iterator();
+ while (it.next()) |kv| {
+ kv.value.destroy(allocator);
+ }
+ self.decl_table.deinit();
+ }
+ {
+ var it = self.failed_decls.iterator();
+ while (it.next()) |kv| {
+ kv.value.destroy(allocator);
+ }
+ self.failed_decls.deinit();
+ }
+ {
+ var it = self.failed_files.iterator();
+ while (it.next()) |kv| {
+ kv.value.destroy(allocator);
+ }
+ self.failed_files.deinit();
+ }
+ {
+ var it = self.failed_exports.iterator();
+ while (it.next()) |kv| {
+ kv.value.destroy(allocator);
+ }
+ self.failed_exports.deinit();
+ }
+ {
+ var it = self.decl_exports.iterator();
+ while (it.next()) |kv| {
+ const export_list = kv.value;
+ allocator.free(export_list);
+ }
+ self.decl_exports.deinit();
+ }
+ {
+ var it = self.export_owners.iterator();
+ while (it.next()) |kv| {
+ const export_list = kv.value;
+ for (export_list) |exp| {
+ allocator.destroy(exp);
+ }
+ allocator.free(export_list);
+ }
+ self.export_owners.deinit();
+ }
+ self.root_pkg.destroy();
+ {
+ self.root_scope.deinit(allocator);
+ allocator.destroy(self.root_scope);
+ }
+ self.* = undefined;
+}
+
+pub fn target(self: Module) std.Target {
+ return self.bin_file.options.target;
+}
+
+/// Detect changes to source files, perform semantic analysis, and update the output files.
+pub fn update(self: *Module) !void {
+ // TODO Use the cache hash file system to detect which source files changed.
+ // Here we simulate a full cache miss.
+ // Analyze the root source file now.
+ self.analyzeRoot(self.root_scope) catch |err| switch (err) {
+ error.AnalysisFail => {
+ assert(self.totalErrorCount() != 0);
+ },
+ else => |e| return e,
+ };
+
+ try self.performAllTheWork();
+
+ // Unload all the source files from memory.
+ self.root_scope.unload(self.allocator);
+
+ try self.bin_file.flush();
+ self.link_error_flags = self.bin_file.error_flags;
+}
+
+pub fn totalErrorCount(self: *Module) usize {
+ return self.failed_decls.size +
+ self.failed_files.size +
+ self.failed_exports.size +
+ @boolToInt(self.link_error_flags.no_entry_point_found);
+}
+
+pub fn getAllErrorsAlloc(self: *Module) !AllErrors {
+ var arena = std.heap.ArenaAllocator.init(self.allocator);
+ errdefer arena.deinit();
+
+ var errors = std.ArrayList(AllErrors.Message).init(self.allocator);
+ defer errors.deinit();
+
+ {
+ var it = self.failed_files.iterator();
+ while (it.next()) |kv| {
+ const scope = kv.key;
+ const err_msg = kv.value;
+ const source = scope.source.bytes;
+ try AllErrors.add(&arena, &errors, scope.sub_file_path, source, err_msg.*);
+ }
+ }
+ {
+ var it = self.failed_decls.iterator();
+ while (it.next()) |kv| {
+ const decl = kv.key;
+ const err_msg = kv.value;
+ const source = decl.scope.source.bytes;
+ try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*);
+ }
+ }
+ {
+ var it = self.failed_exports.iterator();
+ while (it.next()) |kv| {
+ const decl = kv.key.owner_decl;
+ const err_msg = kv.value;
+ const source = decl.scope.source.bytes;
+ try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*);
+ }
+ }
+
+ if (self.link_error_flags.no_entry_point_found) {
+ try errors.append(.{
+ .src_path = self.root_pkg.root_src_path,
+ .line = 0,
+ .column = 0,
+ .byte_offset = 0,
+ .msg = try std.fmt.allocPrint(&arena.allocator, "no entry point found", .{}),
+ });
+ }
+
+ assert(errors.items.len == self.totalErrorCount());
+
+ return AllErrors{
+ .arena = arena.state,
+ .list = try arena.allocator.dupe(AllErrors.Message, errors.items),
+ };
+}
+
+const InnerError = error{ OutOfMemory, AnalysisFail };
+
+pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
+ while (self.work_queue.readItem()) |work_item| switch (work_item) {
+ .codegen_decl => |decl| switch (decl.analysis) {
+ .initial_in_progress,
+ .repeat_in_progress,
+ => unreachable,
+
+ .initial_sema_failure,
+ .repeat_sema_failure,
+ .codegen_failure,
+ .initial_dependency_failure,
+ .repeat_dependency_failure,
+ => continue,
+
+ .complete, .codegen_failure_retryable => {
+ if (decl.typed_value.most_recent.typed_value.val.cast(Value.Payload.Function)) |payload| {
+ switch (payload.func.analysis) {
+ .queued => self.analyzeFnBody(decl, payload.func) catch |err| switch (err) {
+ error.AnalysisFail => {
+ if (payload.func.analysis == .queued) {
+ payload.func.analysis = .dependency_failure;
+ }
+ continue;
+ },
+ else => |e| return e,
+ },
+ .in_progress => unreachable,
+ .sema_failure, .dependency_failure => continue,
+ .success => {},
+ }
+ }
+
+ assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
+
+ self.bin_file.updateDecl(self, decl) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.AnalysisFail => {
+ decl.analysis = .repeat_dependency_failure;
+ },
+ else => {
+ try self.failed_decls.ensureCapacity(self.failed_decls.size + 1);
+ self.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
+ self.allocator,
+ decl.src,
+ "unable to codegen: {}",
+ .{@errorName(err)},
+ ));
+ decl.analysis = .codegen_failure_retryable;
+ },
+ };
+ },
+ },
+ };
+}
+
+fn getSrcModule(self: *Module, root_scope: *Scope.ZIRModule) !*zir.Module {
+ switch (root_scope.status) {
+ .never_loaded, .unloaded_success => {
+ try self.failed_files.ensureCapacity(self.failed_files.size + 1);
+
+ var keep_source = false;
+ const source = try self.root_pkg.root_src_dir.readFileAllocOptions(
+ self.allocator,
+ self.root_pkg.root_src_path,
+ std.math.maxInt(u32),
+ 1,
+ 0,
+ );
+ defer if (!keep_source) self.allocator.free(source);
+
+ var keep_zir_module = false;
+ const zir_module = try self.allocator.create(zir.Module);
+ defer if (!keep_zir_module) self.allocator.destroy(zir_module);
+
+ zir_module.* = try zir.parse(self.allocator, source);
+ defer if (!keep_zir_module) zir_module.deinit(self.allocator);
+
+ if (zir_module.error_msg) |src_err_msg| {
+ self.failed_files.putAssumeCapacityNoClobber(
+ root_scope,
+ try ErrorMsg.create(self.allocator, src_err_msg.byte_offset, "{}", .{src_err_msg.msg}),
+ );
+ root_scope.status = .loaded_parse_failure;
+ root_scope.source = .{ .bytes = source };
+ keep_source = true;
+ return error.AnalysisFail;
+ }
+
+ root_scope.status = .loaded_success;
+ root_scope.source = .{ .bytes = source };
+ keep_source = true;
+ root_scope.contents = .{ .module = zir_module };
+ keep_zir_module = true;
+
+ return zir_module;
+ },
+
+ .unloaded_parse_failure,
+ .unloaded_sema_failure,
+ .loaded_parse_failure,
+ .loaded_sema_failure,
+ => return error.AnalysisFail,
+ .loaded_success => return root_scope.contents.module,
+ }
+}
+
+fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {
+ // TODO use the cache to identify, from the modified source files, the decls which have
+ // changed based on the span of memory that represents the decl in the re-parsed source file.
+ // Use the cached dependency graph to recursively determine the set of decls which need
+ // regeneration.
+ // Here we simulate adding a source file which was previously not part of the compilation,
+ // which means scanning the decls looking for exports.
+ // TODO also identify decls that need to be deleted.
+ switch (root_scope.status) {
+ .never_loaded => {
+ const src_module = try self.getSrcModule(root_scope);
+
+ // Here we ensure enough queue capacity to store all the decls, so that later we can use
+ // appendAssumeCapacity.
+ try self.work_queue.ensureUnusedCapacity(src_module.decls.len);
+
+ for (src_module.decls) |decl| {
+ if (decl.cast(zir.Inst.Export)) |export_inst| {
+ _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty);
+ }
+ }
+ },
+
+ .unloaded_parse_failure,
+ .unloaded_sema_failure,
+ .loaded_parse_failure,
+ .loaded_sema_failure,
+ .loaded_success,
+ .unloaded_success,
+ => {
+ const src_module = try self.getSrcModule(root_scope);
+
+ // Look for changed decls.
+ for (src_module.decls) |src_decl| {
+ const name_hash = Decl.hashSimpleName(src_decl.name);
+ if (self.decl_table.get(name_hash)) |kv| {
+ const decl = kv.value;
+ const new_contents_hash = Decl.hashSimpleName(src_decl.contents);
+ if (!mem.eql(u8, &new_contents_hash, &decl.contents_hash)) {
+ // TODO recursive dependency management
+ std.debug.warn("noticed that '{}' changed\n", .{src_decl.name});
+ self.decl_table.removeAssertDiscard(name_hash);
+ const saved_link = decl.link;
+ decl.destroy(self.allocator);
+ if (self.export_owners.getValue(decl)) |exports| {
+ @panic("TODO handle updating a decl that does an export");
+ }
+ const new_decl = self.resolveDecl(
+ &root_scope.base,
+ src_decl,
+ saved_link,
+ ) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.AnalysisFail => continue,
+ };
+ if (self.decl_exports.remove(decl)) |entry| {
+ self.decl_exports.putAssumeCapacityNoClobber(new_decl, entry.value);
+ }
+ }
+ } else if (src_decl.cast(zir.Inst.Export)) |export_inst| {
+ _ = try self.resolveDecl(&root_scope.base, &export_inst.base, link.ElfFile.Decl.empty);
+ }
+ }
+ },
+ }
+}
+
+fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
+ // Use the Decl's arena for function memory.
+ var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator);
+ defer decl.typed_value.most_recent.arena.?.* = arena.state;
+ var analysis: Fn.Analysis = .{
+ .inner_block = .{
+ .func = func,
+ .decl = decl,
+ .instructions = .{},
+ .arena = &arena.allocator,
+ },
+ .needed_inst_capacity = 0,
+ .inst_table = std.AutoHashMap(*zir.Inst, *Inst).init(self.allocator),
+ };
+ defer analysis.inner_block.instructions.deinit(self.allocator);
+ defer analysis.inst_table.deinit();
+
+ const fn_inst = func.analysis.queued;
+ func.analysis = .{ .in_progress = &analysis };
+
+ try self.analyzeBody(&analysis.inner_block.base, fn_inst.positionals.body);
+
+ func.analysis = .{
+ .success = .{
+ .instructions = try arena.allocator.dupe(*Inst, analysis.inner_block.instructions.items),
+ },
+ };
+}
+
+fn resolveDecl(
+ self: *Module,
+ scope: *Scope,
+ old_inst: *zir.Inst,
+ bin_file_link: link.ElfFile.Decl,
+) InnerError!*Decl {
+ const hash = Decl.hashSimpleName(old_inst.name);
+ if (self.decl_table.get(hash)) |kv| {
+ return kv.value;
+ } else {
+ const new_decl = blk: {
+ try self.decl_table.ensureCapacity(self.decl_table.size + 1);
+ const new_decl = try self.allocator.create(Decl);
+ errdefer self.allocator.destroy(new_decl);
+ const name = try mem.dupeZ(self.allocator, u8, old_inst.name);
+ errdefer self.allocator.free(name);
+ new_decl.* = .{
+ .name = name,
+ .scope = scope.namespace(),
+ .src = old_inst.src,
+ .typed_value = .{ .never_succeeded = {} },
+ .analysis = .initial_in_progress,
+ .contents_hash = Decl.hashSimpleName(old_inst.contents),
+ .link = bin_file_link,
+ };
+ self.decl_table.putAssumeCapacityNoClobber(hash, new_decl);
+ break :blk new_decl;
+ };
+
+ var decl_scope: Scope.DeclAnalysis = .{
+ .decl = new_decl,
+ .arena = std.heap.ArenaAllocator.init(self.allocator),
+ };
+ errdefer decl_scope.arena.deinit();
+
+ const typed_value = self.analyzeInstConst(&decl_scope.base, old_inst) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.AnalysisFail => {
+ switch (new_decl.analysis) {
+ .initial_in_progress => new_decl.analysis = .initial_dependency_failure,
+ .repeat_in_progress => new_decl.analysis = .repeat_dependency_failure,
+ else => {},
+ }
+ return error.AnalysisFail;
+ },
+ };
+ const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State);
+
+ const has_codegen_bits = typed_value.ty.hasCodeGenBits();
+ if (has_codegen_bits) {
+ // 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(new_decl);
+ }
+
+ arena_state.* = decl_scope.arena.state;
+
+ new_decl.typed_value = .{
+ .most_recent = .{
+ .typed_value = typed_value,
+ .arena = arena_state,
+ },
+ };
+ new_decl.analysis = .complete;
+ if (has_codegen_bits) {
+ // We ensureCapacity when scanning for decls.
+ self.work_queue.writeItemAssumeCapacity(.{ .codegen_decl = new_decl });
+ }
+ return new_decl;
+ }
+}
+
+fn resolveCompleteDecl(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Decl {
+ const decl = try self.resolveDecl(scope, old_inst, link.ElfFile.Decl.empty);
+ switch (decl.analysis) {
+ .initial_in_progress => unreachable,
+ .repeat_in_progress => unreachable,
+ .initial_dependency_failure,
+ .repeat_dependency_failure,
+ .initial_sema_failure,
+ .repeat_sema_failure,
+ .codegen_failure,
+ .codegen_failure_retryable,
+ => return error.AnalysisFail,
+
+ .complete => return decl,
+ }
+}
+
+fn resolveInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
+ if (scope.cast(Scope.Block)) |block| {
+ if (block.func.analysis.in_progress.inst_table.get(old_inst)) |kv| {
+ return kv.value;
+ }
+ }
+
+ const decl = try self.resolveCompleteDecl(scope, old_inst);
+ const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl);
+ return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src);
+}
+
+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 {
+ 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 {
+ if (base.value()) |val| {
+ if (val.isUndef()) {
+ return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{});
+ }
+ return val;
+ }
+ 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, export_inst: *zir.Inst.Export) InnerError!void {
+ try self.decl_exports.ensureCapacity(self.decl_exports.size + 1);
+ try self.export_owners.ensureCapacity(self.export_owners.size + 1);
+ const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name);
+ const exported_decl = try self.resolveCompleteDecl(scope, export_inst.positionals.value);
+ const typed_value = exported_decl.typed_value.most_recent.typed_value;
+ switch (typed_value.ty.zigTypeTag()) {
+ .Fn => {},
+ else => return self.fail(
+ scope,
+ export_inst.positionals.value.src,
+ "unable to export type '{}'",
+ .{typed_value.ty},
+ ),
+ }
+ const new_export = try self.allocator.create(Export);
+ errdefer self.allocator.destroy(new_export);
+
+ const owner_decl = scope.decl();
+
+ new_export.* = .{
+ .options = .{ .name = symbol_name },
+ .src = export_inst.base.src,
+ .link = .{},
+ .owner_decl = owner_decl,
+ .status = .in_progress,
+ };
+
+ // Add to export_owners table.
+ const eo_gop = self.export_owners.getOrPut(owner_decl) catch unreachable;
+ if (!eo_gop.found_existing) {
+ eo_gop.kv.value = &[0]*Export{};
+ }
+ eo_gop.kv.value = try self.allocator.realloc(eo_gop.kv.value, eo_gop.kv.value.len + 1);
+ eo_gop.kv.value[eo_gop.kv.value.len - 1] = new_export;
+ errdefer eo_gop.kv.value = self.allocator.shrink(eo_gop.kv.value, eo_gop.kv.value.len - 1);
+
+ // Add to exported_decl table.
+ const de_gop = self.decl_exports.getOrPut(exported_decl) catch unreachable;
+ if (!de_gop.found_existing) {
+ de_gop.kv.value = &[0]*Export{};
+ }
+ de_gop.kv.value = try self.allocator.realloc(de_gop.kv.value, de_gop.kv.value.len + 1);
+ de_gop.kv.value[de_gop.kv.value.len - 1] = new_export;
+ errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1);
+
+ self.bin_file.updateDeclExports(self, exported_decl, de_gop.kv.value) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ else => {
+ try self.failed_exports.ensureCapacity(self.failed_exports.size + 1);
+ self.failed_exports.putAssumeCapacityNoClobber(new_export, try ErrorMsg.create(
+ self.allocator,
+ export_inst.base.src,
+ "unable to export: {}",
+ .{@errorName(err)},
+ ));
+ new_export.status = .failed_retryable;
+ },
+ };
+}
+
+/// TODO should not need the cast on the last parameter at the callsites
+fn addNewInstArgs(
+ self: *Module,
+ block: *Scope.Block,
+ src: usize,
+ ty: Type,
+ comptime T: type,
+ args: Inst.Args(T),
+) !*Inst {
+ const inst = try self.addNewInst(block, src, ty, T);
+ inst.args = args;
+ return &inst.base;
+}
+
+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,
+ },
+ .args = undefined,
+ };
+ try block.instructions.append(self.allocator, &inst.base);
+ return inst;
+}
+
+fn constInst(self: *Module, scope: *Scope, src: usize, typed_value: TypedValue) !*Inst {
+ const const_inst = try scope.arena().create(Inst.Constant);
+ const_inst.* = .{
+ .base = .{
+ .tag = Inst.Constant.base_tag,
+ .ty = typed_value.ty,
+ .src = src,
+ },
+ .val = typed_value.val,
+ };
+ return &const_inst.base;
+}
+
+fn constStr(self: *Module, scope: *Scope, src: usize, str: []const u8) !*Inst {
+ const ty_payload = try scope.arena().create(Type.Payload.Array_u8_Sentinel0);
+ ty_payload.* = .{ .len = str.len };
+
+ const bytes_payload = try scope.arena().create(Value.Payload.Bytes);
+ bytes_payload.* = .{ .data = str };
+
+ return self.constInst(scope, src, .{
+ .ty = Type.initPayload(&ty_payload.base),
+ .val = Value.initPayload(&bytes_payload.base),
+ });
+}
+
+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 {
+ return self.constInst(scope, src, .{
+ .ty = Type.initTag(.void),
+ .val = Value.initTag(.the_one_possible_value),
+ });
+}
+
+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 {
+ 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 {
+ const int_payload = try scope.arena().create(Value.Payload.Int_u64);
+ int_payload.* = .{ .int = int };
+
+ return self.constInst(scope, src, .{
+ .ty = ty,
+ .val = Value.initPayload(&int_payload.base),
+ });
+}
+
+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 };
+
+ return self.constInst(scope, src, .{
+ .ty = ty,
+ .val = Value.initPayload(&int_payload.base),
+ });
+}
+
+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);
+ } else |err| switch (err) {
+ error.NegativeIntoUnsigned => unreachable,
+ error.TargetTooSmall => {}, // handled below
+ }
+ const big_int_payload = try scope.arena().create(Value.Payload.IntBigPositive);
+ big_int_payload.* = .{ .limbs = big_int.limbs };
+ break :blk &big_int_payload.base;
+ } else blk: {
+ if (big_int.to(i64)) |x| {
+ return self.constIntSigned(scope, src, ty, x);
+ } else |err| switch (err) {
+ error.NegativeIntoUnsigned => unreachable,
+ error.TargetTooSmall => {}, // handled below
+ }
+ const big_int_payload = try scope.arena().create(Value.Payload.IntBigNegative);
+ big_int_payload.* = .{ .limbs = big_int.limbs };
+ break :blk &big_int_payload.base;
+ };
+
+ return self.constInst(scope, src, .{
+ .ty = ty,
+ .val = Value.initPayload(val_payload),
+ });
+}
+
+fn analyzeInstConst(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 analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*Inst {
+ switch (old_inst.tag) {
+ .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(zir.Inst.Breakpoint).?),
+ .call => return self.analyzeInstCall(scope, old_inst.cast(zir.Inst.Call).?),
+ .declref => return self.analyzeInstDeclRef(scope, old_inst.cast(zir.Inst.DeclRef).?),
+ .str => {
+ const bytes = old_inst.cast(zir.Inst.Str).?.positionals.bytes;
+ // 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 Decl's arena.
+ const arena_bytes = try scope.arena().dupe(u8, bytes);
+ return self.constStr(scope, old_inst.src, arena_bytes);
+ },
+ .int => {
+ const big_int = old_inst.cast(zir.Inst.Int).?.positionals.int;
+ return self.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int);
+ },
+ .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.cast(zir.Inst.PtrToInt).?),
+ .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.cast(zir.Inst.FieldPtr).?),
+ .deref => return self.analyzeInstDeref(scope, old_inst.cast(zir.Inst.Deref).?),
+ .as => return self.analyzeInstAs(scope, old_inst.cast(zir.Inst.As).?),
+ .@"asm" => return self.analyzeInstAsm(scope, old_inst.cast(zir.Inst.Asm).?),
+ .@"unreachable" => return self.analyzeInstUnreachable(scope, old_inst.cast(zir.Inst.Unreachable).?),
+ .@"return" => return self.analyzeInstRet(scope, old_inst.cast(zir.Inst.Return).?),
+ .@"fn" => return self.analyzeInstFn(scope, old_inst.cast(zir.Inst.Fn).?),
+ .@"export" => {
+ try self.analyzeExport(scope, old_inst.cast(zir.Inst.Export).?);
+ return self.constVoid(scope, old_inst.src);
+ },
+ .primitive => return self.analyzeInstPrimitive(scope, old_inst.cast(zir.Inst.Primitive).?),
+ .ref => return self.analyzeInstRef(scope, old_inst.cast(zir.Inst.Ref).?),
+ .fntype => return self.analyzeInstFnType(scope, old_inst.cast(zir.Inst.FnType).?),
+ .intcast => return self.analyzeInstIntCast(scope, old_inst.cast(zir.Inst.IntCast).?),
+ .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(zir.Inst.BitCast).?),
+ .elemptr => return self.analyzeInstElemPtr(scope, old_inst.cast(zir.Inst.ElemPtr).?),
+ .add => return self.analyzeInstAdd(scope, old_inst.cast(zir.Inst.Add).?),
+ .cmp => return self.analyzeInstCmp(scope, old_inst.cast(zir.Inst.Cmp).?),
+ .condbr => return self.analyzeInstCondBr(scope, old_inst.cast(zir.Inst.CondBr).?),
+ .isnull => return self.analyzeInstIsNull(scope, old_inst.cast(zir.Inst.IsNull).?),
+ .isnonnull => return self.analyzeInstIsNonNull(scope, old_inst.cast(zir.Inst.IsNonNull).?),
+ }
+}
+
+fn analyzeInstBreakpoint(self: *Module, scope: *Scope, inst: *zir.Inst.Breakpoint) InnerError!*Inst {
+ const b = try self.requireRuntimeBlock(scope, inst.base.src);
+ return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){});
+}
+
+fn analyzeInstRef(self: *Module, scope: *Scope, inst: *zir.Inst.Ref) InnerError!*Inst {
+ const decl = try self.resolveCompleteDecl(scope, inst.positionals.operand);
+ return self.analyzeDeclRef(scope, inst.base.src, decl);
+}
+
+fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *zir.Inst.DeclRef) InnerError!*Inst {
+ const decl_name = try self.resolveConstString(scope, inst.positionals.name);
+ // This will need to get more fleshed out when there are proper structs & namespaces.
+ const zir_module = scope.namespace();
+ for (zir_module.contents.module.decls) |src_decl| {
+ if (mem.eql(u8, src_decl.name, decl_name)) {
+ const decl = try self.resolveCompleteDecl(scope, src_decl);
+ return self.analyzeDeclRef(scope, inst.base.src, decl);
+ }
+ }
+ return self.fail(scope, inst.positionals.name.src, "use of undeclared identifier '{}'", .{decl_name});
+}
+
+fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
+ const decl_tv = try decl.typedValue();
+ const ty_payload = try scope.arena().create(Type.Payload.SingleConstPointer);
+ ty_payload.* = .{ .pointee_type = decl_tv.ty };
+ const val_payload = try scope.arena().create(Value.Payload.DeclRef);
+ val_payload.* = .{ .decl = decl };
+ return self.constInst(scope, src, .{
+ .ty = Type.initPayload(&ty_payload.base),
+ .val = Value.initPayload(&val_payload.base),
+ });
+}
+
+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.allocator.alloc(Type, fn_params_len);
+ defer self.allocator.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 b = try self.requireRuntimeBlock(scope, inst.base.src);
+ return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Call, Inst.Args(Inst.Call){
+ .func = func,
+ .args = 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 new_func = try scope.arena().create(Fn);
+ new_func.* = .{
+ .fn_type = fn_type,
+ .analysis = .{ .queued = fn_inst },
+ };
+ 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 analyzeInstFnType(self: *Module, scope: *Scope, fntype: *zir.Inst.FnType) InnerError!*Inst {
+ const return_type = try self.resolveType(scope, fntype.positionals.return_type);
+
+ if (return_type.zigTypeTag() == .NoReturn and
+ fntype.positionals.param_types.len == 0 and
+ fntype.kw_args.cc == .Unspecified)
+ {
+ return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args));
+ }
+
+ if (return_type.zigTypeTag() == .NoReturn and
+ fntype.positionals.param_types.len == 0 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.positionals.param_types.len == 0 and
+ fntype.kw_args.cc == .C)
+ {
+ return self.constType(scope, fntype.base.src, Type.initTag(.fn_ccc_void_no_args));
+ }
+
+ return self.fail(scope, fntype.base.src, "TODO implement fntype instruction more", .{});
+}
+
+fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *zir.Inst.Primitive) InnerError!*Inst {
+ return self.constType(scope, primitive.base.src, primitive.positionals.tag.toType());
+}
+
+fn analyzeInstAs(self: *Module, scope: *Scope, as: *zir.Inst.As) InnerError!*Inst {
+ const dest_type = try self.resolveType(scope, as.positionals.dest_type);
+ const new_inst = try self.resolveInst(scope, as.positionals.value);
+ return self.coerce(scope, dest_type, new_inst);
+}
+
+fn analyzeInstPtrToInt(self: *Module, scope: *Scope, ptrtoint: *zir.Inst.PtrToInt) InnerError!*Inst {
+ const ptr = try self.resolveInst(scope, ptrtoint.positionals.ptr);
+ if (ptr.ty.zigTypeTag() != .Pointer) {
+ return self.fail(scope, ptrtoint.positionals.ptr.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.addNewInstArgs(b, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = 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, intcast: *zir.Inst.IntCast) InnerError!*Inst {
+ const dest_type = try self.resolveType(scope, intcast.positionals.dest_type);
+ const new_inst = try self.resolveInst(scope, intcast.positionals.value);
+
+ const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
+ .ComptimeInt => true,
+ .Int => false,
+ else => return self.fail(
+ scope,
+ intcast.positionals.dest_type.src,
+ "expected integer type, found '{}'",
+ .{
+ dest_type,
+ },
+ ),
+ };
+
+ switch (new_inst.ty.zigTypeTag()) {
+ .ComptimeInt, .Int => {},
+ else => return self.fail(
+ scope,
+ intcast.positionals.value.src,
+ "expected integer type, found '{}'",
+ .{new_inst.ty},
+ ),
+ }
+
+ if (dest_is_comptime_int or new_inst.value() != null) {
+ return self.coerce(scope, dest_type, new_inst);
+ }
+
+ return self.fail(scope, intcast.base.src, "TODO implement analyze widen or shorten int", .{});
+}
+
+fn analyzeInstBitCast(self: *Module, scope: *Scope, inst: *zir.Inst.BitCast) InnerError!*Inst {
+ const dest_type = try self.resolveType(scope, inst.positionals.dest_type);
+ const operand = try self.resolveInst(scope, inst.positionals.operand);
+ return self.bitcast(scope, dest_type, operand);
+}
+
+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 analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError!*Inst {
+ const lhs = try self.resolveInst(scope, inst.positionals.lhs);
+ const rhs = try self.resolveInst(scope, inst.positionals.rhs);
+
+ if (lhs.ty.zigTypeTag() == .Int and rhs.ty.zigTypeTag() == .Int) {
+ if (lhs.value()) |lhs_val| {
+ if (rhs.value()) |rhs_val| {
+ // TODO is this a performance issue? maybe we should try the operation without
+ // resorting to BigInt first.
+ var lhs_space: Value.BigIntSpace = undefined;
+ var rhs_space: Value.BigIntSpace = undefined;
+ const lhs_bigint = lhs_val.toBigInt(&lhs_space);
+ const rhs_bigint = rhs_val.toBigInt(&rhs_space);
+ const limbs = try scope.arena().alloc(
+ std.math.big.Limb,
+ std.math.max(lhs_bigint.limbs.len, rhs_bigint.limbs.len) + 1,
+ );
+ var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
+ result_bigint.add(lhs_bigint, rhs_bigint);
+ const result_limbs = result_bigint.limbs[0..result_bigint.len];
+
+ if (!lhs.ty.eql(rhs.ty)) {
+ return self.fail(scope, inst.base.src, "TODO implement peer type resolution", .{});
+ }
+
+ const val_payload = if (result_bigint.positive) blk: {
+ const val_payload = try scope.arena().create(Value.Payload.IntBigPositive);
+ val_payload.* = .{ .limbs = result_limbs };
+ break :blk &val_payload.base;
+ } else blk: {
+ const val_payload = try scope.arena().create(Value.Payload.IntBigNegative);
+ val_payload.* = .{ .limbs = result_limbs };
+ break :blk &val_payload.base;
+ };
+
+ return self.constInst(scope, inst.base.src, .{
+ .ty = lhs.ty,
+ .val = Value.initPayload(val_payload),
+ });
+ }
+ }
+ }
+
+ return self.fail(scope, inst.base.src, "TODO implement more analyze add", .{});
+}
+
+fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *zir.Inst.Deref) InnerError!*Inst {
+ const ptr = try self.resolveInst(scope, deref.positionals.ptr);
+ return self.analyzeDeref(scope, deref.base.src, ptr, deref.positionals.ptr.src);
+}
+
+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}),
+ };
+ if (ptr.value()) |val| {
+ return self.constInst(scope, src, .{
+ .ty = elem_ty,
+ .val = try val.pointerDeref(scope.arena()),
+ });
+ }
+
+ 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);
+ return self.addNewInstArgs(b, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){
+ .asm_source = asm_source,
+ .is_volatile = assembly.kw_args.@"volatile",
+ .output = output,
+ .inputs = inputs,
+ .clobbers = clobbers,
+ .args = args,
+ });
+}
+
+fn analyzeInstCmp(self: *Module, scope: *Scope, inst: *zir.Inst.Cmp) InnerError!*Inst {
+ const lhs = try self.resolveInst(scope, inst.positionals.lhs);
+ const rhs = try self.resolveInst(scope, inst.positionals.rhs);
+ const op = inst.positionals.op;
+
+ 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);
+ switch (op) {
+ .eq => return self.addNewInstArgs(
+ b,
+ inst.base.src,
+ Type.initTag(.bool),
+ Inst.IsNull,
+ Inst.Args(Inst.IsNull){ .operand = opt_operand },
+ ),
+ .neq => return self.addNewInstArgs(
+ b,
+ inst.base.src,
+ Type.initTag(.bool),
+ Inst.IsNonNull,
+ Inst.Args(Inst.IsNonNull){ .operand = opt_operand },
+ ),
+ else => unreachable,
+ }
+ } 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 analyzeInstIsNull(self: *Module, scope: *Scope, inst: *zir.Inst.IsNull) InnerError!*Inst {
+ const operand = try self.resolveInst(scope, inst.positionals.operand);
+ return self.analyzeIsNull(scope, inst.base.src, operand, true);
+}
+
+fn analyzeInstIsNonNull(self: *Module, scope: *Scope, inst: *zir.Inst.IsNonNull) InnerError!*Inst {
+ const operand = try self.resolveInst(scope, inst.positionals.operand);
+ return self.analyzeIsNull(scope, inst.base.src, operand, false);
+}
+
+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.true_body else &inst.positionals.false_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 = .{
+ .func = parent_block.func,
+ .decl = parent_block.decl,
+ .instructions = .{},
+ .arena = parent_block.arena,
+ };
+ defer true_block.instructions.deinit(self.allocator);
+ try self.analyzeBody(&true_block.base, inst.positionals.true_body);
+
+ var false_block: Scope.Block = .{
+ .func = parent_block.func,
+ .decl = parent_block.decl,
+ .instructions = .{},
+ .arena = parent_block.arena,
+ };
+ defer false_block.instructions.deinit(self.allocator);
+ try self.analyzeBody(&false_block.base, inst.positionals.false_body);
+
+ return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){
+ .condition = cond,
+ .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) },
+ .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) },
+ });
+}
+
+fn wantSafety(self: *Module, scope: *Scope) bool {
+ return switch (self.optimize_mode) {
+ .Debug => true,
+ .ReleaseSafe => true,
+ .ReleaseFast => false,
+ .ReleaseSmall => false,
+ };
+}
+
+fn analyzeInstUnreachable(self: *Module, scope: *Scope, unreach: *zir.Inst.Unreachable) 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.addNewInstArgs(b, unreach.base.src, Type.initTag(.void), Inst.Breakpoint, {});
+ }
+ return self.addNewInstArgs(b, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {});
+}
+
+fn analyzeInstRet(self: *Module, scope: *Scope, inst: *zir.Inst.Return) InnerError!*Inst {
+ const b = try self.requireRuntimeBlock(scope, inst.base.src);
+ return self.addNewInstArgs(b, inst.base.src, Type.initTag(.noreturn), Inst.Ret, {});
+}
+
+fn analyzeBody(self: *Module, scope: *Scope, body: zir.Module.Body) !void {
+ if (scope.cast(Scope.Block)) |b| {
+ const analysis = b.func.analysis.in_progress;
+ analysis.needed_inst_capacity += body.instructions.len;
+ try analysis.inst_table.ensureCapacity(analysis.needed_inst_capacity);
+ for (body.instructions) |src_inst| {
+ const new_inst = try self.analyzeInst(scope, src_inst);
+ analysis.inst_table.putAssumeCapacityNoClobber(src_inst, new_inst);
+ }
+ } else {
+ for (body.instructions) |src_inst| {
+ _ = try self.analyzeInst(scope, src_inst);
+ }
+ }
+}
+
+fn analyzeIsNull(
+ self: *Module,
+ scope: *Scope,
+ src: usize,
+ operand: *Inst,
+ invert_logic: bool,
+) InnerError!*Inst {
+ return self.fail(scope, src, "TODO implement analysis of isnull and isnotnull", .{});
+}
+
+/// Asserts that lhs and rhs types are both numeric.
+fn cmpNumeric(
+ self: *Module,
+ scope: *Scope,
+ src: usize,
+ lhs: *Inst,
+ rhs: *Inst,
+ op: std.math.CompareOperator,
+) !*Inst {
+ assert(lhs.ty.isNumeric());
+ assert(rhs.ty.isNumeric());
+
+ const lhs_ty_tag = lhs.ty.zigTypeTag();
+ const rhs_ty_tag = rhs.ty.zigTypeTag();
+
+ if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) {
+ if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
+ return self.fail(scope, src, "vector length mismatch: {} and {}", .{
+ lhs.ty.arrayLen(),
+ rhs.ty.arrayLen(),
+ });
+ }
+ return self.fail(scope, src, "TODO implement support for vectors in cmpNumeric", .{});
+ } else if (lhs_ty_tag == .Vector or rhs_ty_tag == .Vector) {
+ return self.fail(scope, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
+ lhs.ty,
+ rhs.ty,
+ });
+ }
+
+ if (lhs.value()) |lhs_val| {
+ if (rhs.value()) |rhs_val| {
+ return self.constBool(scope, src, Value.compare(lhs_val, op, rhs_val));
+ }
+ }
+
+ // TODO handle comparisons against lazy zero values
+ // Some values can be compared against zero without being runtime known or without forcing
+ // a full resolution of their value, for example `@sizeOf(@Frame(function))` is known to
+ // always be nonzero, and we benefit from not forcing the full evaluation and stack frame layout
+ // of this function if we don't need to.
+
+ // It must be a runtime comparison.
+ const b = try self.requireRuntimeBlock(scope, src);
+ // For floats, emit a float comparison instruction.
+ const lhs_is_float = switch (lhs_ty_tag) {
+ .Float, .ComptimeFloat => true,
+ else => false,
+ };
+ const rhs_is_float = switch (rhs_ty_tag) {
+ .Float, .ComptimeFloat => true,
+ else => false,
+ };
+ if (lhs_is_float and rhs_is_float) {
+ // Implicit cast the smaller one to the larger one.
+ const dest_type = x: {
+ if (lhs_ty_tag == .ComptimeFloat) {
+ break :x rhs.ty;
+ } else if (rhs_ty_tag == .ComptimeFloat) {
+ break :x lhs.ty;
+ }
+ if (lhs.ty.floatBits(self.target()) >= rhs.ty.floatBits(self.target())) {
+ break :x lhs.ty;
+ } else {
+ break :x rhs.ty;
+ }
+ };
+ const casted_lhs = try self.coerce(scope, dest_type, lhs);
+ const casted_rhs = try self.coerce(scope, dest_type, rhs);
+ return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){
+ .lhs = casted_lhs,
+ .rhs = casted_rhs,
+ .op = op,
+ });
+ }
+ // For mixed unsigned integer sizes, implicit cast both operands to the larger integer.
+ // For mixed signed and unsigned integers, implicit cast both operands to a signed
+ // integer with + 1 bit.
+ // For mixed floats and integers, extract the integer part from the float, cast that to
+ // a signed integer with mantissa bits + 1, and if there was any non-integral part of the float,
+ // add/subtract 1.
+ const lhs_is_signed = if (lhs.value()) |lhs_val|
+ lhs_val.compareWithZero(.lt)
+ else
+ (lhs.ty.isFloat() or lhs.ty.isSignedInt());
+ const rhs_is_signed = if (rhs.value()) |rhs_val|
+ rhs_val.compareWithZero(.lt)
+ else
+ (rhs.ty.isFloat() or rhs.ty.isSignedInt());
+ const dest_int_is_signed = lhs_is_signed or rhs_is_signed;
+
+ var dest_float_type: ?Type = null;
+
+ var lhs_bits: usize = undefined;
+ if (lhs.value()) |lhs_val| {
+ if (lhs_val.isUndef())
+ return self.constUndef(scope, src, Type.initTag(.bool));
+ const is_unsigned = if (lhs_is_float) x: {
+ var bigint_space: Value.BigIntSpace = undefined;
+ var bigint = try lhs_val.toBigInt(&bigint_space).toManaged(self.allocator);
+ defer bigint.deinit();
+ const zcmp = lhs_val.orderAgainstZero();
+ if (lhs_val.floatHasFraction()) {
+ switch (op) {
+ .eq => return self.constBool(scope, src, false),
+ .neq => return self.constBool(scope, src, true),
+ else => {},
+ }
+ if (zcmp == .lt) {
+ try bigint.addScalar(bigint.toConst(), -1);
+ } else {
+ try bigint.addScalar(bigint.toConst(), 1);
+ }
+ }
+ lhs_bits = bigint.toConst().bitCountTwosComp();
+ break :x (zcmp != .lt);
+ } else x: {
+ lhs_bits = lhs_val.intBitCountTwosComp();
+ break :x (lhs_val.orderAgainstZero() != .lt);
+ };
+ lhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
+ } else if (lhs_is_float) {
+ dest_float_type = lhs.ty;
+ } else {
+ const int_info = lhs.ty.intInfo(self.target());
+ lhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed);
+ }
+
+ var rhs_bits: usize = undefined;
+ if (rhs.value()) |rhs_val| {
+ if (rhs_val.isUndef())
+ return self.constUndef(scope, src, Type.initTag(.bool));
+ const is_unsigned = if (rhs_is_float) x: {
+ var bigint_space: Value.BigIntSpace = undefined;
+ var bigint = try rhs_val.toBigInt(&bigint_space).toManaged(self.allocator);
+ defer bigint.deinit();
+ const zcmp = rhs_val.orderAgainstZero();
+ if (rhs_val.floatHasFraction()) {
+ switch (op) {
+ .eq => return self.constBool(scope, src, false),
+ .neq => return self.constBool(scope, src, true),
+ else => {},
+ }
+ if (zcmp == .lt) {
+ try bigint.addScalar(bigint.toConst(), -1);
+ } else {
+ try bigint.addScalar(bigint.toConst(), 1);
+ }
+ }
+ rhs_bits = bigint.toConst().bitCountTwosComp();
+ break :x (zcmp != .lt);
+ } else x: {
+ rhs_bits = rhs_val.intBitCountTwosComp();
+ break :x (rhs_val.orderAgainstZero() != .lt);
+ };
+ rhs_bits += @boolToInt(is_unsigned and dest_int_is_signed);
+ } else if (rhs_is_float) {
+ dest_float_type = rhs.ty;
+ } else {
+ const int_info = rhs.ty.intInfo(self.target());
+ rhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed);
+ }
+
+ const dest_type = if (dest_float_type) |ft| ft else blk: {
+ const max_bits = std.math.max(lhs_bits, rhs_bits);
+ const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) {
+ error.Overflow => return self.fail(scope, src, "{} exceeds maximum integer bit count", .{max_bits}),
+ };
+ break :blk try self.makeIntType(scope, dest_int_is_signed, casted_bits);
+ };
+ const casted_lhs = try self.coerce(scope, dest_type, lhs);
+ const casted_rhs = try self.coerce(scope, dest_type, lhs);
+
+ return self.addNewInstArgs(b, src, dest_type, Inst.Cmp, Inst.Args(Inst.Cmp){
+ .lhs = casted_lhs,
+ .rhs = casted_rhs,
+ .op = op,
+ });
+}
+
+fn makeIntType(self: *Module, scope: *Scope, signed: bool, bits: u16) !Type {
+ if (signed) {
+ const int_payload = try scope.arena().create(Type.Payload.IntSigned);
+ int_payload.* = .{ .bits = bits };
+ return Type.initPayload(&int_payload.base);
+ } else {
+ const int_payload = try scope.arena().create(Type.Payload.IntUnsigned);
+ int_payload.* = .{ .bits = bits };
+ return Type.initPayload(&int_payload.base);
+ }
+}
+
+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;
+
+ const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty);
+ if (in_memory_result == .ok) {
+ return self.bitcast(scope, dest_type, inst);
+ }
+
+ // *[N]T to []T
+ if (inst.ty.isSinglePointer() and dest_type.isSlice() and
+ (!inst.ty.pointerIsConst() or dest_type.pointerIsConst()))
+ {
+ const array_type = inst.ty.elemType();
+ const dst_elem_type = dest_type.elemType();
+ if (array_type.zigTypeTag() == .Array and
+ coerceInMemoryAllowed(dst_elem_type, array_type.elemType()) == .ok)
+ {
+ return self.coerceArrayPtrToSlice(scope, dest_type, inst);
+ }
+ }
+
+ // comptime_int to fixed-width integer
+ if (inst.ty.zigTypeTag() == .ComptimeInt and dest_type.zigTypeTag() == .Int) {
+ // The representation is already correct; we only need to make sure it fits in the destination type.
+ const val = inst.value().?; // comptime_int always has comptime known value
+ if (!val.intFitsInType(dest_type, self.target())) {
+ return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
+ }
+ return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
+ }
+
+ // integer widening
+ if (inst.ty.zigTypeTag() == .Int and dest_type.zigTypeTag() == .Int) {
+ const src_info = inst.ty.intInfo(self.target());
+ const dst_info = dest_type.intInfo(self.target());
+ if (src_info.signed == dst_info.signed and dst_info.bits >= src_info.bits) {
+ if (inst.value()) |val| {
+ return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
+ } else {
+ return self.fail(scope, inst.src, "TODO implement runtime integer widening", .{});
+ }
+ } else {
+ return self.fail(scope, inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type });
+ }
+ }
+
+ 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 {
+ if (inst.value()) |val| {
+ // Keep the comptime Value representation; take the new type.
+ return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
+ }
+ // TODO validate the type size and other compile errors
+ const b = try self.requireRuntimeBlock(scope, inst.src);
+ return self.addNewInstArgs(b, inst.src, dest_type, Inst.BitCast, Inst.Args(Inst.BitCast){ .operand = inst });
+}
+
+fn coerceArrayPtrToSlice(self: *Module, scope: *Scope, dest_type: Type, inst: *Inst) !*Inst {
+ if (inst.value()) |val| {
+ // The comptime Value representation is compatible with both types.
+ return self.constInst(scope, inst.src, .{ .ty = dest_type, .val = val });
+ }
+ return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{});
+}
+
+fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError {
+ @setCold(true);
+ try self.failed_decls.ensureCapacity(self.failed_decls.size + 1);
+ try self.failed_files.ensureCapacity(self.failed_files.size + 1);
+ const err_msg = try ErrorMsg.create(self.allocator, src, format, args);
+ switch (scope.tag) {
+ .decl => {
+ const decl = scope.cast(Scope.DeclAnalysis).?.decl;
+ switch (decl.analysis) {
+ .initial_in_progress => decl.analysis = .initial_sema_failure,
+ .repeat_in_progress => decl.analysis = .repeat_sema_failure,
+ else => unreachable,
+ }
+ self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg);
+ },
+ .block => {
+ const block = scope.cast(Scope.Block).?;
+ block.func.analysis = .sema_failure;
+ self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg);
+ },
+ .zir_module => {
+ const zir_module = scope.cast(Scope.ZIRModule).?;
+ zir_module.status = .loaded_sema_failure;
+ self.failed_files.putAssumeCapacityNoClobber(zir_module, err_msg);
+ },
+ }
+ return error.AnalysisFail;
+}
+
+const InMemoryCoercionResult = enum {
+ ok,
+ no_match,
+};
+
+fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult {
+ if (dest_type.eql(src_type))
+ return .ok;
+
+ // TODO: implement more of this function
+
+ return .no_match;
+}
+
+pub const ErrorMsg = struct {
+ byte_offset: usize,
+ msg: []const u8,
+
+ pub fn create(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !*ErrorMsg {
+ const self = try allocator.create(ErrorMsg);
+ errdefer allocator.destroy(self);
+ self.* = try init(allocator, byte_offset, format, args);
+ return self;
+ }
+
+ /// Assumes the ErrorMsg struct and msg were both allocated with allocator.
+ pub fn destroy(self: *ErrorMsg, allocator: *Allocator) void {
+ self.deinit(allocator);
+ allocator.destroy(self);
+ }
+
+ pub fn init(allocator: *Allocator, byte_offset: usize, comptime format: []const u8, args: var) !ErrorMsg {
+ return ErrorMsg{
+ .byte_offset = byte_offset,
+ .msg = try std.fmt.allocPrint(allocator, format, args),
+ };
+ }
+
+ pub fn deinit(self: *ErrorMsg, allocator: *Allocator) void {
+ allocator.free(self.msg);
+ self.* = undefined;
+ }
+};
src-self-hosted/value.zig
@@ -6,7 +6,7 @@ const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
const Allocator = std.mem.Allocator;
-const ir = @import("ir.zig");
+const Module = @import("Module.zig");
/// This is the raw data, with no bookkeeping, no memory awareness,
/// no de-duplication, and no type system awareness.
@@ -904,7 +904,7 @@ pub const Value = extern union {
pub const Function = struct {
base: Payload = Payload{ .tag = .function },
- func: *ir.Module.Fn,
+ func: *Module.Fn,
};
pub const ArraySentinel0_u8_Type = struct {
@@ -926,7 +926,7 @@ pub const Value = extern union {
/// Represents a pointer to a decl, not the value of the decl.
pub const DeclRef = struct {
base: Payload = Payload{ .tag = .decl_ref },
- decl: *ir.Module.Decl,
+ decl: *Module.Decl,
};
pub const ElemPtr = struct {
src-self-hosted/ir/text.zig → src-self-hosted/zir.zig
@@ -6,10 +6,11 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
-const Type = @import("../type.zig").Type;
-const Value = @import("../value.zig").Value;
-const TypedValue = @import("../TypedValue.zig");
-const ir = @import("../ir.zig");
+const Type = @import("type.zig").Type;
+const Value = @import("value.zig").Value;
+const TypedValue = @import("TypedValue.zig");
+const ir = @import("ir.zig");
+const IrModule = @import("Module.zig");
/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
/// in-memory, analyzed instructions with types and values.
@@ -990,7 +991,7 @@ const Parser = struct {
}
};
-pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module {
+pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
var ctx: EmitZIR = .{
.allocator = allocator,
.decls = .{},
@@ -1013,7 +1014,7 @@ pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module {
const EmitZIR = struct {
allocator: *Allocator,
arena: std.heap.ArenaAllocator,
- old_module: *const ir.Module,
+ old_module: *const IrModule,
decls: std.ArrayListUnmanaged(*Inst),
decl_table: std.AutoHashMap(*ir.Inst, *Inst),
@@ -1171,7 +1172,7 @@ const EmitZIR = struct {
fn emitBody(
self: *EmitZIR,
- body: ir.Module.Body,
+ body: IrModule.Body,
inst_table: *std.AutoHashMap(*ir.Inst, *Inst),
instructions: *std.ArrayList(*Inst),
) Allocator.Error!void {