self-hosted: fix compile errors, except for codegen.zig

This commit is contained in:
Andrew Kelley 2020-05-13 20:06:01 -04:00
parent a3da584248
commit 080022f6c6
8 changed files with 525 additions and 264 deletions

View File

@ -269,13 +269,6 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
/// Bring-your-own allocator with every function call. /// Bring-your-own allocator with every function call.
/// Initialize directly and deinitialize with `deinit` or use `toOwnedSlice`. /// Initialize directly and deinitialize with `deinit` or use `toOwnedSlice`.
pub fn init() Self {
return .{
.items = &[_]T{},
.capacity = 0,
};
}
pub fn ArrayListUnmanaged(comptime T: type) type { pub fn ArrayListUnmanaged(comptime T: type) type {
return ArrayListAlignedUnmanaged(T, null); return ArrayListAlignedUnmanaged(T, null);
} }
@ -317,7 +310,7 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
/// The caller owns the returned memory. ArrayList becomes empty. /// The caller owns the returned memory. ArrayList becomes empty.
pub fn toOwnedSlice(self: *Self, allocator: *Allocator) Slice { pub fn toOwnedSlice(self: *Self, allocator: *Allocator) Slice {
const result = allocator.shrink(self.allocatedSlice(), self.items.len); const result = allocator.shrink(self.allocatedSlice(), self.items.len);
self.* = init(allocator); self.* = Self{};
return result; return result;
} }

View File

@ -279,6 +279,21 @@ pub const Allocator = struct {
const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1); const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1);
assert(shrink_result.len == 0); assert(shrink_result.len == 0);
} }
/// Copies `m` to newly allocated memory. Caller owns the memory.
pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
const new_buf = try allocator.alloc(T, m.len);
copy(T, new_buf, m);
return new_buf;
}
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
const new_buf = try allocator.alloc(T, m.len + 1);
copy(T, new_buf, m);
new_buf[m.len] = 0;
return new_buf[0..m.len :0];
}
}; };
/// Copy all of source into dest at position 0. /// Copy all of source into dest at position 0.
@ -762,19 +777,14 @@ pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
return true; return true;
} }
/// Copies `m` to newly allocated memory. Caller owns the memory. /// Deprecated, use `Allocator.dupe`.
pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T { pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
const new_buf = try allocator.alloc(T, m.len); return allocator.dupe(T, m);
copy(T, new_buf, m);
return new_buf;
} }
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. /// Deprecated, use `Allocator.dupeZ`.
pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T { pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
const new_buf = try allocator.alloc(T, m.len + 1); return allocator.dupeZ(T, m);
copy(T, new_buf, m);
new_buf[m.len] = 0;
return new_buf[0..m.len :0];
} }
/// Remove values from the beginning of a slice. /// Remove values from the beginning of a slice.

View File

@ -16,7 +16,7 @@ pub const Managed = struct {
/// If this is `null` then there is no memory management needed. /// If this is `null` then there is no memory management needed.
arena: ?*std.heap.ArenaAllocator.State = null, arena: ?*std.heap.ArenaAllocator.State = null,
pub fn deinit(self: *ManagedTypedValue, allocator: *Allocator) void { pub fn deinit(self: *Managed, allocator: *Allocator) void {
if (self.arena) |a| a.promote(allocator).deinit(); if (self.arena) |a| a.promote(allocator).deinit();
self.* = undefined; self.* = undefined;
} }

View File

@ -4,10 +4,11 @@ const assert = std.debug.assert;
const ir = @import("ir.zig"); const ir = @import("ir.zig");
const Type = @import("type.zig").Type; const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value; const Value = @import("value.zig").Value;
const TypedValue = @import("TypedValue.zig");
const Target = std.Target; const Target = std.Target;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.ArrayList(u8)) !?*ir.ErrorMsg { pub fn generateSymbol(typed_value: TypedValue, module: ir.Module, code: *std.ArrayList(u8)) !?*ir.ErrorMsg {
switch (typed_value.ty.zigTypeTag()) { switch (typed_value.ty.zigTypeTag()) {
.Fn => { .Fn => {
const module_fn = typed_value.val.cast(Value.Payload.Function).?.func; const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;

View File

@ -196,11 +196,9 @@ pub const Module = struct {
/// We optimize memory usage for a compilation with no compile errors by storing the /// We optimize memory usage for a compilation with no compile errors by storing the
/// error messages and mapping outside of `Decl`. /// error messages and mapping outside of `Decl`.
/// The ErrorMsg memory is owned by the decl, using Module's allocator. /// 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), failed_decls: std.AutoHashMap(*Decl, *ErrorMsg),
/// We optimize memory usage for a compilation with no compile errors by storing the
/// error messages and mapping outside of `Fn`.
/// The ErrorMsg memory is owned by the `Fn`, using Module's allocator.
failed_fns: std.AutoHashMap(*Fn, *ErrorMsg),
/// Using a map here for consistency with the other fields here. /// Using a map here for consistency with the other fields here.
/// The ErrorMsg memory is owned by the `Scope.ZIRModule`, using Module's allocator. /// The ErrorMsg memory is owned by the `Scope.ZIRModule`, using Module's allocator.
failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg), failed_files: std.AutoHashMap(*Scope.ZIRModule, *ErrorMsg),
@ -221,7 +219,14 @@ pub const Module = struct {
link: link.ElfFile.Export, link: link.ElfFile.Export,
/// The Decl that performs the export. Note that this is *not* the Decl being exported. /// The Decl that performs the export. Note that this is *not* the Decl being exported.
owner_decl: *Decl, owner_decl: *Decl,
status: enum { in_progress, failed, complete }, 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 { pub const Decl = struct {
@ -260,6 +265,11 @@ pub const Module = struct {
/// In this case the `typed_value.most_recent` can still be accessed. /// In this case the `typed_value.most_recent` can still be accessed.
/// There will be a corresponding ErrorMsg in Module.failed_decls. /// There will be a corresponding ErrorMsg in Module.failed_decls.
codegen_failure, 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 /// 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. /// semantic analysis. There is a most recent value available.
repeat_dependency_failure, repeat_dependency_failure,
@ -280,40 +290,63 @@ pub const Module = struct {
/// The shallow set of other decls whose typed_value could possibly change if this Decl's /// The shallow set of other decls whose typed_value could possibly change if this Decl's
/// typed_value is modified. /// typed_value is modified.
/// TODO look into using a lightweight map/set data structure rather than a linear array. /// TODO look into using a lightweight map/set data structure rather than a linear array.
dependants: ArrayListUnmanaged(*Decl) = .{}, dependants: ArrayListUnmanaged(*Decl) = ArrayListUnmanaged(*Decl){},
pub fn typedValue(self: Decl) ?TypedValue {
switch (self.analysis) {
.initial_in_progress,
.initial_dependency_failure,
.initial_sema_failure,
=> return null,
.codegen_failure,
.repeat_dependency_failure,
.repeat_sema_failure,
.repeat_in_progress,
.complete,
=> return self.typed_value.most_recent,
}
}
pub fn destroy(self: *Decl, allocator: *Allocator) void { pub fn destroy(self: *Decl, allocator: *Allocator) void {
allocator.free(mem.spanZ(u8, self.name)); allocator.free(mem.spanZ(self.name));
if (self.typedValue()) |tv| tv.deinit(allocator); if (self.typedValueManaged()) |tvm| {
tvm.deinit(allocator);
}
allocator.destroy(self); allocator.destroy(self);
} }
pub const Hash = [16]u8; 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. /// 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 point of hashing here is only to limit the number of bytes of
/// the unique identifier to a fixed size (16 bytes). /// the unique identifier to a fixed size (16 bytes).
pub fn fullyQualifiedNameHash(self: Decl) Hash { pub fn fullyQualifiedNameHash(self: Decl) Hash {
// Right now we only have ZIRModule as the source. So this is simply the // Right now we only have ZIRModule as the source. So this is simply the
// relative name of the decl. // relative name of the decl.
var out: Hash = undefined; return hashSimpleName(mem.spanZ(u8, self.name));
std.crypto.Blake3.hash(mem.spanZ(u8, self.name), &out); }
return out;
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;
}
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,
}
} }
}; };
@ -325,22 +358,19 @@ pub const Module = struct {
/// The value is the source instruction. /// The value is the source instruction.
queued: *text.Inst.Fn, queued: *text.Inst.Fn,
in_progress: *Analysis, in_progress: *Analysis,
/// There will be a corresponding ErrorMsg in Module.failed_fns /// There will be a corresponding ErrorMsg in Module.failed_decls
failure, failure,
success: Body, success: Body,
}, },
/// The direct container of the Fn. This field will need to get more fleshed out when
/// self-hosted supports proper struct types and Zig AST => ZIR.
scope: *Scope.ZIRModule,
/// This memory is temporary and points to stack memory for the duration /// This memory is temporary and points to stack memory for the duration
/// of Fn analysis. /// of Fn analysis.
pub const Analysis = struct { pub const Analysis = struct {
inner_block: Scope.Block, inner_block: Scope.Block,
/// null value means a semantic analysis error happened. /// TODO Performance optimization idea: instead of this inst_table,
inst_table: std.AutoHashMap(*text.Inst, ?*Inst), /// use a field in the text.Inst instead to track corresponding instructions
/// Owns the memory for instructions inst_table: std.AutoHashMap(*text.Inst, *Inst),
arena: std.heap.ArenaAllocator, needed_inst_capacity: usize,
}; };
}; };
@ -374,6 +404,16 @@ pub const Module = struct {
} }
} }
/// 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 const Tag = enum { pub const Tag = enum {
zir_module, zir_module,
block, block,
@ -407,11 +447,11 @@ pub const Module = struct {
.unloaded_parse_failure, .unloaded_parse_failure,
=> {}, => {},
.loaded_success => { .loaded_success => {
allocator.free(contents.source); allocator.free(self.source.bytes);
self.contents.module.deinit(allocator); self.contents.module.deinit(allocator);
}, },
.loaded_parse_failure => { .loaded_parse_failure => {
allocator.free(contents.source); allocator.free(self.source.bytes);
}, },
} }
self.* = undefined; self.* = undefined;
@ -469,8 +509,8 @@ pub const Module = struct {
) !void { ) !void {
const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset); const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset);
try errors.append(.{ try errors.append(.{
.src_path = try mem.dupe(u8, &arena.allocator, sub_file_path), .src_path = try arena.allocator.dupe(u8, sub_file_path),
.msg = try mem.dupe(u8, &arena.allocator, simple_err_msg.msg), .msg = try arena.allocator.dupe(u8, simple_err_msg.msg),
.byte_offset = simple_err_msg.byte_offset, .byte_offset = simple_err_msg.byte_offset,
.line = loc.line, .line = loc.line,
.column = loc.column, .column = loc.column,
@ -480,7 +520,7 @@ pub const Module = struct {
pub fn deinit(self: *Module) void { pub fn deinit(self: *Module) void {
const allocator = self.allocator; const allocator = self.allocator;
allocator.free(self.errors); self.work_stack.deinit(allocator);
{ {
var it = self.decl_table.iterator(); var it = self.decl_table.iterator();
while (it.next()) |kv| { while (it.next()) |kv| {
@ -488,8 +528,44 @@ pub const Module = struct {
} }
self.decl_table.deinit(); 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();
}
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.failed_exports.deinit();
}
self.root_pkg.destroy(); self.root_pkg.destroy();
self.root_scope.deinit(); {
self.root_scope.deinit(allocator);
allocator.destroy(self.root_scope);
}
self.* = undefined; self.* = undefined;
} }
@ -504,19 +580,20 @@ pub const Module = struct {
// Analyze the root source file now. // Analyze the root source file now.
self.analyzeRoot(self.root_scope) catch |err| switch (err) { self.analyzeRoot(self.root_scope) catch |err| switch (err) {
error.AnalysisFail => { error.AnalysisFail => {
assert(self.failed_files.size != 0); assert(self.totalErrorCount() != 0);
}, },
else => |e| return e, else => |e| return e,
}; };
try self.performAllTheWork();
try self.bin_file.flush(); try self.bin_file.flush();
self.link_error_flags = self.bin_file.error_flags; self.link_error_flags = self.bin_file.error_flags;
} }
pub fn totalErrorCount(self: *Module) usize { pub fn totalErrorCount(self: *Module) usize {
return self.failed_decls.size + return self.failed_decls.size +
self.failed_fns.size + self.failed_files.size +
self.failed_decls.size +
self.failed_exports.size + self.failed_exports.size +
@boolToInt(self.link_error_flags.no_entry_point_found); @boolToInt(self.link_error_flags.no_entry_point_found);
} }
@ -533,17 +610,8 @@ pub const Module = struct {
while (it.next()) |kv| { while (it.next()) |kv| {
const scope = kv.key; const scope = kv.key;
const err_msg = kv.value; const err_msg = kv.value;
const source = scope.parse_failure.source; const source = scope.source.bytes;
AllErrors.add(&arena, &errors, scope.sub_file_path, source, err_msg); try AllErrors.add(&arena, &errors, scope.sub_file_path, source, err_msg.*);
}
}
{
var it = self.failed_fns.iterator();
while (it.next()) |kv| {
const func = kv.key;
const err_msg = kv.value;
const source = func.scope.success.source;
AllErrors.add(&arena, &errors, func.scope.sub_file_path, source, err_msg);
} }
} }
{ {
@ -551,8 +619,8 @@ pub const Module = struct {
while (it.next()) |kv| { while (it.next()) |kv| {
const decl = kv.key; const decl = kv.key;
const err_msg = kv.value; const err_msg = kv.value;
const source = decl.scope.success.source; const source = decl.scope.source.bytes;
AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg); try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*);
} }
} }
{ {
@ -560,14 +628,14 @@ pub const Module = struct {
while (it.next()) |kv| { while (it.next()) |kv| {
const decl = kv.key.owner_decl; const decl = kv.key.owner_decl;
const err_msg = kv.value; const err_msg = kv.value;
const source = decl.scope.success.source; const source = decl.scope.source.bytes;
try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg); try AllErrors.add(&arena, &errors, decl.scope.sub_file_path, source, err_msg.*);
} }
} }
if (self.link_error_flags.no_entry_point_found) { if (self.link_error_flags.no_entry_point_found) {
try errors.append(.{ try errors.append(.{
.src_path = self.module.root_src_path, .src_path = self.root_pkg.root_src_path,
.line = 0, .line = 0,
.column = 0, .column = 0,
.byte_offset = 0, .byte_offset = 0,
@ -579,12 +647,56 @@ pub const Module = struct {
return AllErrors{ return AllErrors{
.arena = arena.state, .arena = arena.state,
.list = try mem.dupe(&arena.allocator, AllErrors.Message, errors.items), .list = try arena.allocator.dupe(AllErrors.Message, errors.items),
}; };
} }
const InnerError = error{ OutOfMemory, AnalysisFail }; const InnerError = error{ OutOfMemory, AnalysisFail };
pub fn performAllTheWork(self: *Module) error{OutOfMemory}!void {
while (self.work_stack.popOrNull()) |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 => continue,
else => |e| return e,
},
.in_progress => unreachable,
.failure => continue,
.success => {},
}
}
self.bin_file.updateDecl(self, decl) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
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 analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void { fn analyzeRoot(self: *Module, root_scope: *Scope.ZIRModule) !void {
// TODO use the cache to identify, from the modified source files, the decls which have // 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. // changed based on the span of memory that represents the decl in the re-parsed source file.
@ -650,56 +762,39 @@ pub const Module = struct {
try analyzeExport(self, &root_scope.base, export_inst); try analyzeExport(self, &root_scope.base, export_inst);
} }
} }
while (self.work_stack.pop()) |work_item| switch (work_item) {
.codegen_decl => |decl| switch (decl.analysis) {
.success => {
if (decl.typed_value.most_recent.typed_value.val.cast(Value.Function)) |payload| {
switch (payload.func.analysis) {
.queued => self.analyzeFnBody(decl, payload.func) catch |err| switch (err) {
error.AnalysisFail => {
assert(func_payload.func.analysis == .failure);
continue;
},
else => |e| return e,
},
.in_progress => unreachable,
.failure => continue,
.success => {},
}
}
try self.bin_file.updateDecl(self, decl);
},
},
};
} }
fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void { fn analyzeFnBody(self: *Module, decl: *Decl, func: *Fn) !void {
// Use the Decl's arena for function memory. // Use the Decl's arena for function memory.
var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator); var arena = decl.typed_value.most_recent.arena.?.promote(self.allocator);
defer decl.typed_value.most_recent.arena.?.* = arena.state; defer decl.typed_value.most_recent.arena.?.* = arena.state;
var analysis: Analysis = .{ var analysis: Fn.Analysis = .{
.inner_block = .{ .inner_block = .{
.func = func, .func = func,
.decl = decl, .decl = decl,
.instructions = .{}, .instructions = .{},
.arena = &arena.allocator, .arena = &arena.allocator,
}, },
.inst_table = std.AutoHashMap(*text.Inst, ?*Inst).init(self.allocator), .needed_inst_capacity = 0,
.inst_table = std.AutoHashMap(*text.Inst, *Inst).init(self.allocator),
}; };
defer analysis.inner_block.instructions.deinit(); defer analysis.inner_block.instructions.deinit(self.allocator);
defer analysis.inst_table.deinit(); defer analysis.inst_table.deinit();
const fn_inst = func.analysis.queued; const fn_inst = func.analysis.queued;
func.analysis = .{ .in_progress = &analysis }; func.analysis = .{ .in_progress = &analysis };
try self.analyzeBody(&analysis.inner_block, fn_inst.positionals.body); try self.analyzeBody(&analysis.inner_block.base, fn_inst.positionals.body);
func.analysis = .{ .success = .{ .instructions = analysis.inner_block.instructions.toOwnedSlice() } }; func.analysis = .{
.success = .{
.instructions = try arena.allocator.dupe(*Inst, analysis.inner_block.instructions.items),
},
};
} }
fn resolveDecl(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Decl { fn resolveDecl(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Decl {
const hash = old_inst.fullyQualifiedNameHash(); const hash = Decl.hashSimpleName(old_inst.name);
if (self.decl_table.get(hash)) |kv| { if (self.decl_table.get(hash)) |kv| {
return kv.value; return kv.value;
} else { } else {
@ -711,7 +806,7 @@ pub const Module = struct {
errdefer self.allocator.free(name); errdefer self.allocator.free(name);
new_decl.* = .{ new_decl.* = .{
.name = name, .name = name,
.scope = scope.findZIRModule(), .scope = scope.namespace(),
.src = old_inst.src, .src = old_inst.src,
.typed_value = .{ .never_succeeded = {} }, .typed_value = .{ .never_succeeded = {} },
.analysis = .initial_in_progress, .analysis = .initial_in_progress,
@ -726,12 +821,11 @@ pub const Module = struct {
}; };
errdefer decl_scope.arena.deinit(); errdefer decl_scope.arena.deinit();
const arena_state = try self.allocator.create(std.heap.ArenaAllocator.State); const arena_state = try decl_scope.arena.allocator.create(std.heap.ArenaAllocator.State);
errdefer self.allocator.destroy(arena_state);
const typed_value = try self.analyzeInstConst(&decl_scope.base, old_inst); const typed_value = try self.analyzeInstConst(&decl_scope.base, old_inst);
arena_state.* = decl_scope.arena; arena_state.* = decl_scope.arena.state;
new_decl.typed_value = .{ new_decl.typed_value = .{
.most_recent = .{ .most_recent = .{
@ -741,7 +835,7 @@ pub const Module = struct {
}; };
new_decl.analysis = .complete; new_decl.analysis = .complete;
// We ensureCapacity when scanning for decls. // We ensureCapacity when scanning for decls.
self.work_stack.appendAssumeCapacity(self.allocator, .{ .codegen_decl = new_decl }); self.work_stack.appendAssumeCapacity(.{ .codegen_decl = new_decl });
return new_decl; return new_decl;
} }
} }
@ -756,6 +850,7 @@ pub const Module = struct {
.initial_sema_failure, .initial_sema_failure,
.repeat_sema_failure, .repeat_sema_failure,
.codegen_failure, .codegen_failure,
.codegen_failure_retryable,
=> return error.AnalysisFail, => return error.AnalysisFail,
.complete => return decl, .complete => return decl,
@ -764,14 +859,14 @@ pub const Module = struct {
fn resolveInst(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Inst { fn resolveInst(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Inst {
if (scope.cast(Scope.Block)) |block| { if (scope.cast(Scope.Block)) |block| {
if (block.func.inst_table.get(old_inst)) |kv| { if (block.func.analysis.in_progress.inst_table.get(old_inst)) |kv| {
return kv.value.ptr orelse return error.AnalysisFail; return kv.value;
} }
} }
const decl = try self.resolveCompleteDecl(scope, old_inst); const decl = try self.resolveCompleteDecl(scope, old_inst);
const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl); const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl);
return self.analyzeDeref(scope, old_inst.src, decl_ref); return self.analyzeDeref(scope, old_inst.src, decl_ref, old_inst.src);
} }
fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block { fn requireRuntimeBlock(self: *Module, scope: *Scope, src: usize) !*Scope.Block {
@ -819,7 +914,7 @@ pub const Module = struct {
return val.toType(); return val.toType();
} }
fn analyzeExport(self: *Module, scope: *Scope, export_inst: *text.Inst.Export) !void { 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.decl_exports.ensureCapacity(self.decl_exports.size + 1);
try self.export_owners.ensureCapacity(self.export_owners.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 symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name);
@ -840,7 +935,7 @@ pub const Module = struct {
const owner_decl = scope.decl(); const owner_decl = scope.decl();
new_export.* = .{ new_export.* = .{
.options = .{ .data = .{ .name = symbol_name } }, .options = .{ .name = symbol_name },
.src = export_inst.base.src, .src = export_inst.base.src,
.link = .{}, .link = .{},
.owner_decl = owner_decl, .owner_decl = owner_decl,
@ -865,7 +960,19 @@ pub const Module = struct {
de_gop.kv.value[de_gop.kv.value.len - 1] = new_export; 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); errdefer de_gop.kv.value = self.allocator.shrink(de_gop.kv.value, de_gop.kv.value.len - 1);
try self.bin_file.updateDeclExports(self, decl, de_gop.kv.value); 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 /// TODO should not need the cast on the last parameter at the callsites
@ -976,7 +1083,7 @@ pub const Module = struct {
fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst { fn constIntBig(self: *Module, scope: *Scope, src: usize, ty: Type, big_int: BigIntConst) !*Inst {
const val_payload = if (big_int.positive) blk: { const val_payload = if (big_int.positive) blk: {
if (big_int.to(u64)) |x| { if (big_int.to(u64)) |x| {
return self.constIntUnsigned(src, ty, x); return self.constIntUnsigned(scope, src, ty, x);
} else |err| switch (err) { } else |err| switch (err) {
error.NegativeIntoUnsigned => unreachable, error.NegativeIntoUnsigned => unreachable,
error.TargetTooSmall => {}, // handled below error.TargetTooSmall => {}, // handled below
@ -986,7 +1093,7 @@ pub const Module = struct {
break :blk &big_int_payload.base; break :blk &big_int_payload.base;
} else blk: { } else blk: {
if (big_int.to(i64)) |x| { if (big_int.to(i64)) |x| {
return self.constIntSigned(src, ty, x); return self.constIntSigned(scope, src, ty, x);
} else |err| switch (err) { } else |err| switch (err) {
error.NegativeIntoUnsigned => unreachable, error.NegativeIntoUnsigned => unreachable,
error.TargetTooSmall => {}, // handled below error.TargetTooSmall => {}, // handled below
@ -1014,15 +1121,17 @@ pub const Module = struct {
switch (old_inst.tag) { switch (old_inst.tag) {
.breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(text.Inst.Breakpoint).?), .breakpoint => return self.analyzeInstBreakpoint(scope, old_inst.cast(text.Inst.Breakpoint).?),
.call => return self.analyzeInstCall(scope, old_inst.cast(text.Inst.Call).?), .call => return self.analyzeInstCall(scope, old_inst.cast(text.Inst.Call).?),
.declref => return self.analyzeInstDeclRef(scope, old_inst.cast(text.Inst.DeclRef).?),
.str => { .str => {
// We can use this reference because Inst.Const's Value is arena-allocated.
// The value would get copied to a MemoryCell before the `text.Inst.Str` lifetime ends.
const bytes = old_inst.cast(text.Inst.Str).?.positionals.bytes; const bytes = old_inst.cast(text.Inst.Str).?.positionals.bytes;
return self.constStr(old_inst.src, 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 => { .int => {
const big_int = old_inst.cast(text.Inst.Int).?.positionals.int; const big_int = old_inst.cast(text.Inst.Int).?.positionals.int;
return self.constIntBig(old_inst.src, Type.initTag(.comptime_int), big_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).?), .ptrtoint => return self.analyzeInstPtrToInt(scope, old_inst.cast(text.Inst.PtrToInt).?),
.fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.cast(text.Inst.FieldPtr).?), .fieldptr => return self.analyzeInstFieldPtr(scope, old_inst.cast(text.Inst.FieldPtr).?),
@ -1036,7 +1145,7 @@ pub const Module = struct {
try self.analyzeExport(scope, old_inst.cast(text.Inst.Export).?); try self.analyzeExport(scope, old_inst.cast(text.Inst.Export).?);
return self.constVoid(scope, old_inst.src); return self.constVoid(scope, old_inst.src);
}, },
.primitive => return self.analyzeInstPrimitive(old_inst.cast(text.Inst.Primitive).?), .primitive => return self.analyzeInstPrimitive(scope, old_inst.cast(text.Inst.Primitive).?),
.fntype => return self.analyzeInstFnType(scope, old_inst.cast(text.Inst.FnType).?), .fntype => return self.analyzeInstFnType(scope, old_inst.cast(text.Inst.FnType).?),
.intcast => return self.analyzeInstIntCast(scope, old_inst.cast(text.Inst.IntCast).?), .intcast => return self.analyzeInstIntCast(scope, old_inst.cast(text.Inst.IntCast).?),
.bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(text.Inst.BitCast).?), .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(text.Inst.BitCast).?),
@ -1054,6 +1163,14 @@ pub const Module = struct {
return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){}); return self.addNewInstArgs(b, inst.base.src, Type.initTag(.void), Inst.Breakpoint, Inst.Args(Inst.Breakpoint){});
} }
fn analyzeInstDeclRef(self: *Module, scope: *Scope, inst: *text.Inst.DeclRef) InnerError!*Inst {
return self.fail(scope, inst.base.src, "TODO implement analyzeInstDeclFef", .{});
}
fn analyzeDeclRef(self: *Module, scope: *Scope, src: usize, decl: *Decl) InnerError!*Inst {
return self.fail(scope, src, "TODO implement analyzeDeclRef", .{});
}
fn analyzeInstCall(self: *Module, scope: *Scope, inst: *text.Inst.Call) InnerError!*Inst { fn analyzeInstCall(self: *Module, scope: *Scope, inst: *text.Inst.Call) InnerError!*Inst {
const func = try self.resolveInst(scope, inst.positionals.func); const func = try self.resolveInst(scope, inst.positionals.func);
if (func.ty.zigTypeTag() != .Fn) if (func.ty.zigTypeTag() != .Fn)
@ -1123,8 +1240,7 @@ pub const Module = struct {
const new_func = try scope.arena().create(Fn); const new_func = try scope.arena().create(Fn);
new_func.* = .{ new_func.* = .{
.fn_type = fn_type, .fn_type = fn_type,
.analysis = .{ .queued = fn_inst.positionals.body }, .analysis = .{ .queued = fn_inst },
.scope = scope.namespace(),
}; };
const fn_payload = try scope.arena().create(Value.Payload.Function); const fn_payload = try scope.arena().create(Value.Payload.Function);
fn_payload.* = .{ .func = new_func }; fn_payload.* = .{ .func = new_func };
@ -1141,28 +1257,28 @@ pub const Module = struct {
fntype.positionals.param_types.len == 0 and fntype.positionals.param_types.len == 0 and
fntype.kw_args.cc == .Unspecified) fntype.kw_args.cc == .Unspecified)
{ {
return self.constType(fntype.base.src, Type.initTag(.fn_noreturn_no_args)); return self.constType(scope, fntype.base.src, Type.initTag(.fn_noreturn_no_args));
} }
if (return_type.zigTypeTag() == .NoReturn and if (return_type.zigTypeTag() == .NoReturn and
fntype.positionals.param_types.len == 0 and fntype.positionals.param_types.len == 0 and
fntype.kw_args.cc == .Naked) fntype.kw_args.cc == .Naked)
{ {
return self.constType(fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args)); return self.constType(scope, fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args));
} }
if (return_type.zigTypeTag() == .Void and if (return_type.zigTypeTag() == .Void and
fntype.positionals.param_types.len == 0 and fntype.positionals.param_types.len == 0 and
fntype.kw_args.cc == .C) fntype.kw_args.cc == .C)
{ {
return self.constType(fntype.base.src, Type.initTag(.fn_ccc_void_no_args)); 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", .{}); return self.fail(scope, fntype.base.src, "TODO implement fntype instruction more", .{});
} }
fn analyzeInstPrimitive(self: *Module, primitive: *text.Inst.Primitive) InnerError!*Inst { fn analyzeInstPrimitive(self: *Module, scope: *Scope, primitive: *text.Inst.Primitive) InnerError!*Inst {
return self.constType(primitive.base.src, primitive.positionals.tag.toType()); return self.constType(scope, primitive.base.src, primitive.positionals.tag.toType());
} }
fn analyzeInstAs(self: *Module, scope: *Scope, as: *text.Inst.As) InnerError!*Inst { fn analyzeInstAs(self: *Module, scope: *Scope, as: *text.Inst.As) InnerError!*Inst {
@ -1332,18 +1448,22 @@ pub const Module = struct {
fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *text.Inst.Deref) InnerError!*Inst { fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *text.Inst.Deref) InnerError!*Inst {
const ptr = try self.resolveInst(scope, deref.positionals.ptr); 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()) { const elem_ty = switch (ptr.ty.zigTypeTag()) {
.Pointer => ptr.ty.elemType(), .Pointer => ptr.ty.elemType(),
else => return self.fail(scope, deref.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}), else => return self.fail(scope, ptr_src, "expected pointer, found '{}'", .{ptr.ty}),
}; };
if (ptr.value()) |val| { if (ptr.value()) |val| {
return self.constInst(scope, deref.base.src, .{ return self.constInst(scope, src, .{
.ty = elem_ty, .ty = elem_ty,
.val = val.pointerDeref(), .val = try val.pointerDeref(scope.arena()),
}); });
} }
return self.fail(scope, deref.base.src, "TODO implement runtime deref", .{}); return self.fail(scope, src, "TODO implement runtime deref", .{});
} }
fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *text.Inst.Asm) InnerError!*Inst { fn analyzeInstAsm(self: *Module, scope: *Scope, assembly: *text.Inst.Asm) InnerError!*Inst {
@ -1390,7 +1510,7 @@ pub const Module = struct {
const rhs_ty_tag = rhs.ty.zigTypeTag(); const rhs_ty_tag = rhs.ty.zigTypeTag();
if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) { if (is_equality_cmp and lhs_ty_tag == .Null and rhs_ty_tag == .Null) {
// null == null, null != null // null == null, null != null
return self.constBool(inst.base.src, op == .eq); return self.constBool(scope, inst.base.src, op == .eq);
} else if (is_equality_cmp and } else if (is_equality_cmp and
((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or ((lhs_ty_tag == .Null and rhs_ty_tag == .Optional) or
rhs_ty_tag == .Null and lhs_ty_tag == .Optional)) rhs_ty_tag == .Null and lhs_ty_tag == .Optional))
@ -1399,7 +1519,7 @@ pub const Module = struct {
const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs; const opt_operand = if (lhs_ty_tag == .Optional) lhs else rhs;
if (opt_operand.value()) |opt_val| { if (opt_operand.value()) |opt_val| {
const is_null = opt_val.isNull(); const is_null = opt_val.isNull();
return self.constBool(inst.base.src, if (op == .eq) is_null else !is_null); return self.constBool(scope, inst.base.src, if (op == .eq) is_null else !is_null);
} }
const b = try self.requireRuntimeBlock(scope, inst.base.src); const b = try self.requireRuntimeBlock(scope, inst.base.src);
switch (op) { switch (op) {
@ -1468,32 +1588,27 @@ pub const Module = struct {
const parent_block = try self.requireRuntimeBlock(scope, inst.base.src); const parent_block = try self.requireRuntimeBlock(scope, inst.base.src);
var true_block: Scope.Block = .{ var true_block: Scope.Block = .{
.base = .{ .parent = scope },
.func = parent_block.func, .func = parent_block.func,
.decl = parent_block.decl,
.instructions = .{}, .instructions = .{},
.arena = parent_block.arena,
}; };
defer true_block.instructions.deinit(); defer true_block.instructions.deinit(self.allocator);
try self.analyzeBody(&true_block.base, inst.positionals.true_body); try self.analyzeBody(&true_block.base, inst.positionals.true_body);
var false_block: Scope.Block = .{ var false_block: Scope.Block = .{
.base = .{ .parent = scope },
.func = parent_block.func, .func = parent_block.func,
.decl = parent_block.decl,
.instructions = .{}, .instructions = .{},
.arena = parent_block.arena,
}; };
defer false_block.instructions.deinit(); defer false_block.instructions.deinit(self.allocator);
try self.analyzeBody(&false_block.base, inst.positionals.false_body); try self.analyzeBody(&false_block.base, inst.positionals.false_body);
// Copy the instruction pointers to the arena memory
const true_instructions = try scope.arena().alloc(*Inst, true_block.instructions.items.len);
const false_instructions = try scope.arena().alloc(*Inst, false_block.instructions.items.len);
mem.copy(*Inst, true_instructions, true_block.instructions.items);
mem.copy(*Inst, false_instructions, false_block.instructions.items);
return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){ return self.addNewInstArgs(parent_block, inst.base.src, Type.initTag(.void), Inst.CondBr, Inst.Args(Inst.CondBr){
.condition = cond, .condition = cond,
.true_body = .{ .instructions = true_instructions }, .true_body = .{ .instructions = try scope.arena().dupe(*Inst, true_block.instructions.items) },
.false_body = .{ .instructions = false_instructions }, .false_body = .{ .instructions = try scope.arena().dupe(*Inst, false_block.instructions.items) },
}); });
} }
@ -1521,15 +1636,18 @@ pub const Module = struct {
} }
fn analyzeBody(self: *Module, scope: *Scope, body: text.Module.Body) !void { fn analyzeBody(self: *Module, scope: *Scope, body: text.Module.Body) !void {
for (body.instructions) |src_inst| { if (scope.cast(Scope.Block)) |b| {
const new_inst = self.analyzeInst(scope, src_inst) catch |err| { const analysis = b.func.analysis.in_progress;
if (scope.cast(Scope.Block)) |b| { analysis.needed_inst_capacity += body.instructions.len;
self.fns.items[b.func.fn_index].analysis_status = .failure; try analysis.inst_table.ensureCapacity(analysis.needed_inst_capacity);
try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = null }); for (body.instructions) |src_inst| {
} const new_inst = try self.analyzeInst(scope, src_inst);
return err; analysis.inst_table.putAssumeCapacityNoClobber(src_inst, new_inst);
}; }
if (scope.cast(Scope.Block)) |b| try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst }); } else {
for (body.instructions) |src_inst| {
_ = try self.analyzeInst(scope, src_inst);
}
} }
} }
@ -1575,7 +1693,7 @@ pub const Module = struct {
if (lhs.value()) |lhs_val| { if (lhs.value()) |lhs_val| {
if (rhs.value()) |rhs_val| { if (rhs.value()) |rhs_val| {
return self.constBool(src, Value.compare(lhs_val, op, rhs_val)); return self.constBool(scope, src, Value.compare(lhs_val, op, rhs_val));
} }
} }
@ -1647,8 +1765,8 @@ pub const Module = struct {
const zcmp = lhs_val.orderAgainstZero(); const zcmp = lhs_val.orderAgainstZero();
if (lhs_val.floatHasFraction()) { if (lhs_val.floatHasFraction()) {
switch (op) { switch (op) {
.eq => return self.constBool(src, false), .eq => return self.constBool(scope, src, false),
.neq => return self.constBool(src, true), .neq => return self.constBool(scope, src, true),
else => {}, else => {},
} }
if (zcmp == .lt) { if (zcmp == .lt) {
@ -1682,8 +1800,8 @@ pub const Module = struct {
const zcmp = rhs_val.orderAgainstZero(); const zcmp = rhs_val.orderAgainstZero();
if (rhs_val.floatHasFraction()) { if (rhs_val.floatHasFraction()) {
switch (op) { switch (op) {
.eq => return self.constBool(src, false), .eq => return self.constBool(scope, src, false),
.neq => return self.constBool(src, true), .neq => return self.constBool(scope, src, true),
else => {}, else => {},
} }
if (zcmp == .lt) { if (zcmp == .lt) {
@ -1711,7 +1829,7 @@ pub const Module = struct {
const casted_bits = std.math.cast(u16, max_bits) catch |err| switch (err) { 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}), error.Overflow => return self.fail(scope, src, "{} exceeds maximum integer bit count", .{max_bits}),
}; };
break :blk try self.makeIntType(dest_int_is_signed, casted_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_lhs = try self.coerce(scope, dest_type, lhs);
const casted_rhs = try self.coerce(scope, dest_type, lhs); const casted_rhs = try self.coerce(scope, dest_type, lhs);
@ -1807,7 +1925,6 @@ pub const Module = struct {
fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError { fn fail(self: *Module, scope: *Scope, src: usize, comptime format: []const u8, args: var) InnerError {
@setCold(true); @setCold(true);
try self.failed_decls.ensureCapacity(self.failed_decls.size + 1); try self.failed_decls.ensureCapacity(self.failed_decls.size + 1);
try self.failed_fns.ensureCapacity(self.failed_fns.size + 1);
const err_msg = try ErrorMsg.create(self.allocator, src, format, args); const err_msg = try ErrorMsg.create(self.allocator, src, format, args);
switch (scope.tag) { switch (scope.tag) {
.decl => { .decl => {
@ -1820,10 +1937,11 @@ pub const Module = struct {
self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg); self.failed_decls.putAssumeCapacityNoClobber(decl, err_msg);
}, },
.block => { .block => {
const func = scope.cast(Scope.Block).?.func; const block = scope.cast(Scope.Block).?;
func.analysis = .failure; block.func.analysis = .failure;
self.failed_fns.putAssumeCapacityNoClobber(func, err_msg); self.failed_decls.putAssumeCapacityNoClobber(block.decl, err_msg);
}, },
.zir_module => unreachable,
} }
return error.AnalysisFail; return error.AnalysisFail;
} }
@ -1868,7 +1986,7 @@ pub const ErrorMsg = struct {
} }
pub fn deinit(self: *ErrorMsg, allocator: *Allocator) void { pub fn deinit(self: *ErrorMsg, allocator: *Allocator) void {
allocator.free(err_msg.msg); allocator.free(self.msg);
self.* = undefined; self.* = undefined;
} }
}; };
@ -1920,7 +2038,6 @@ pub fn main() anyerror!void {
.decl_exports = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator), .decl_exports = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator),
.export_owners = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator), .export_owners = std.AutoHashMap(*Module.Decl, []*Module.Export).init(allocator),
.failed_decls = std.AutoHashMap(*Module.Decl, *ErrorMsg).init(allocator), .failed_decls = std.AutoHashMap(*Module.Decl, *ErrorMsg).init(allocator),
.failed_fns = std.AutoHashMap(*Module.Fn, *ErrorMsg).init(allocator),
.failed_files = std.AutoHashMap(*Module.Scope.ZIRModule, *ErrorMsg).init(allocator), .failed_files = std.AutoHashMap(*Module.Scope.ZIRModule, *ErrorMsg).init(allocator),
.failed_exports = std.AutoHashMap(*Module.Export, *ErrorMsg).init(allocator), .failed_exports = std.AutoHashMap(*Module.Export, *ErrorMsg).init(allocator),
}; };
@ -1929,8 +2046,8 @@ pub fn main() anyerror!void {
try module.update(); try module.update();
const errors = try module.getAllErrorsAlloc(); var errors = try module.getAllErrorsAlloc();
defer errors.deinit(); defer errors.deinit(allocator);
if (errors.list.len != 0) { if (errors.list.len != 0) {
for (errors.list) |full_err_msg| { for (errors.list) |full_err_msg| {
@ -1954,6 +2071,3 @@ pub fn main() anyerror!void {
try bos.flush(); try bos.flush();
} }
} }
// Performance optimization ideas:
// * when analyzing use a field in the Inst instead of HashMap to track corresponding instructions

View File

@ -8,6 +8,7 @@ const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable; const BigIntMutable = std.math.big.int.Mutable;
const Type = @import("../type.zig").Type; const Type = @import("../type.zig").Type;
const Value = @import("../value.zig").Value; const Value = @import("../value.zig").Value;
const TypedValue = @import("../TypedValue.zig");
const ir = @import("../ir.zig"); const ir = @import("../ir.zig");
/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for /// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
@ -462,6 +463,7 @@ pub const Module = struct {
switch (decl.tag) { switch (decl.tag) {
.breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table), .breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table),
.call => return self.writeInstToStreamGeneric(stream, .call, decl, inst_table), .call => return self.writeInstToStreamGeneric(stream, .call, decl, inst_table),
.declref => return self.writeInstToStreamGeneric(stream, .declref, decl, inst_table),
.str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table), .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table),
.int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table), .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table),
.ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table), .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table),
@ -576,6 +578,7 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
.source = source, .source = source,
.global_name_map = &global_name_map, .global_name_map = &global_name_map,
.decls = .{}, .decls = .{},
.unnamed_index = 0,
}; };
errdefer parser.arena.deinit(); errdefer parser.arena.deinit();
@ -601,6 +604,7 @@ const Parser = struct {
decls: std.ArrayListUnmanaged(*Inst), decls: std.ArrayListUnmanaged(*Inst),
global_name_map: *std.StringHashMap(usize), global_name_map: *std.StringHashMap(usize),
error_msg: ?ErrorMsg = null, error_msg: ?ErrorMsg = null,
unnamed_index: usize,
const Body = struct { const Body = struct {
instructions: std.ArrayList(*Inst), instructions: std.ArrayList(*Inst),
@ -626,12 +630,12 @@ const Parser = struct {
skipSpace(self); skipSpace(self);
try requireEatBytes(self, "="); try requireEatBytes(self, "=");
skipSpace(self); skipSpace(self);
const inst = try parseInstruction(self, &body_context); const inst = try parseInstruction(self, &body_context, ident[1..]);
const ident_index = body_context.instructions.items.len; const ident_index = body_context.instructions.items.len;
if (try body_context.name_map.put(ident, ident_index)) |_| { if (try body_context.name_map.put(ident, ident_index)) |_| {
return self.fail("redefinition of identifier '{}'", .{ident}); return self.fail("redefinition of identifier '{}'", .{ident});
} }
try body_context.instructions.append(self.allocator, inst); try body_context.instructions.append(inst);
continue; continue;
}, },
' ', '\n' => continue, ' ', '\n' => continue,
@ -712,7 +716,7 @@ const Parser = struct {
skipSpace(self); skipSpace(self);
try requireEatBytes(self, "="); try requireEatBytes(self, "=");
skipSpace(self); skipSpace(self);
const inst = try parseInstruction(self, null); const inst = try parseInstruction(self, null, ident[1..]);
const ident_index = self.decls.items.len; const ident_index = self.decls.items.len;
if (try self.global_name_map.put(ident, ident_index)) |_| { if (try self.global_name_map.put(ident, ident_index)) |_| {
return self.fail("redefinition of identifier '{}'", .{ident}); return self.fail("redefinition of identifier '{}'", .{ident});
@ -781,12 +785,12 @@ const Parser = struct {
return error.ParseFailure; return error.ParseFailure;
} }
fn parseInstruction(self: *Parser, body_ctx: ?*Body) InnerError!*Inst { fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Inst {
const fn_name = try skipToAndOver(self, '('); const fn_name = try skipToAndOver(self, '(');
inline for (@typeInfo(Inst.Tag).Enum.fields) |field| { inline for (@typeInfo(Inst.Tag).Enum.fields) |field| {
if (mem.eql(u8, field.name, fn_name)) { if (mem.eql(u8, field.name, fn_name)) {
const tag = @field(Inst.Tag, field.name); const tag = @field(Inst.Tag, field.name);
return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx); return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx, name);
} }
} }
return self.fail("unknown instruction '{}'", .{fn_name}); return self.fail("unknown instruction '{}'", .{fn_name});
@ -797,9 +801,11 @@ const Parser = struct {
comptime fn_name: []const u8, comptime fn_name: []const u8,
comptime InstType: type, comptime InstType: type,
body_ctx: ?*Body, body_ctx: ?*Body,
) !*Inst { inst_name: []const u8,
) InnerError!*Inst {
const inst_specific = try self.arena.allocator.create(InstType); const inst_specific = try self.arena.allocator.create(InstType);
inst_specific.base = .{ inst_specific.base = .{
.name = inst_name,
.src = self.i, .src = self.i,
.tag = InstType.base_tag, .tag = InstType.base_tag,
}; };
@ -885,7 +891,7 @@ const Parser = struct {
var instructions = std.ArrayList(*Inst).init(&self.arena.allocator); var instructions = std.ArrayList(*Inst).init(&self.arena.allocator);
while (true) { while (true) {
skipSpace(self); skipSpace(self);
try instructions.append(self.allocator, try parseParameterInst(self, body_ctx)); try instructions.append(try parseParameterInst(self, body_ctx));
skipSpace(self); skipSpace(self);
if (!eatByte(self, ',')) break; if (!eatByte(self, ',')) break;
} }
@ -930,13 +936,21 @@ const Parser = struct {
} else { } else {
const name = try self.arena.allocator.create(Inst.Str); const name = try self.arena.allocator.create(Inst.Str);
name.* = .{ name.* = .{
.base = .{ .src = src, .tag = Inst.Str.base_tag }, .base = .{
.name = try self.generateName(),
.src = src,
.tag = Inst.Str.base_tag,
},
.positionals = .{ .bytes = ident }, .positionals = .{ .bytes = ident },
.kw_args = .{}, .kw_args = .{},
}; };
const declref = try self.arena.allocator.create(Inst.DeclRef); const declref = try self.arena.allocator.create(Inst.DeclRef);
declref.* = .{ declref.* = .{
.base = .{ .src = src, .tag = Inst.DeclRef.base_tag }, .base = .{
.name = try self.generateName(),
.src = src,
.tag = Inst.DeclRef.base_tag,
},
.positionals = .{ .name = &name.base }, .positionals = .{ .name = &name.base },
.kw_args = .{}, .kw_args = .{},
}; };
@ -949,25 +963,31 @@ const Parser = struct {
return self.decls.items[kv.value]; return self.decls.items[kv.value];
} }
} }
fn generateName(self: *Parser) ![]u8 {
const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.unnamed_index});
self.unnamed_index += 1;
return result;
}
}; };
pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module { pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module {
var ctx: EmitZIR = .{ var ctx: EmitZIR = .{
.allocator = allocator, .allocator = allocator,
.decls = std.ArrayList(*Inst).init(allocator), .decls = .{},
.decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator), .decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator),
.arena = std.heap.ArenaAllocator.init(allocator), .arena = std.heap.ArenaAllocator.init(allocator),
.old_module = &old_module, .old_module = &old_module,
}; };
defer ctx.decls.deinit(); defer ctx.decls.deinit(allocator);
defer ctx.decl_table.deinit(); defer ctx.decl_table.deinit();
errdefer ctx.arena.deinit(); errdefer ctx.arena.deinit();
try ctx.emit(); try ctx.emit();
return Module{ return Module{
.decls = ctx.decls.toOwnedSlice(), .decls = ctx.decls.toOwnedSlice(allocator),
.arena = ctx.arena, .arena = ctx.arena.state,
}; };
} }
@ -975,23 +995,32 @@ const EmitZIR = struct {
allocator: *Allocator, allocator: *Allocator,
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
old_module: *const ir.Module, old_module: *const ir.Module,
decls: std.ArrayList(*Inst), decls: std.ArrayListUnmanaged(*Inst),
decl_table: std.AutoHashMap(*ir.Inst, *Inst), decl_table: std.AutoHashMap(*ir.Inst, *Inst),
fn emit(self: *EmitZIR) !void { fn emit(self: *EmitZIR) !void {
for (self.old_module.exports) |module_export| { var it = self.old_module.decl_exports.iterator();
const export_value = try self.emitTypedValue(module_export.src, module_export.typed_value); while (it.next()) |kv| {
const symbol_name = try self.emitStringLiteral(module_export.src, module_export.name); const decl = kv.key;
const export_inst = try self.arena.allocator.create(Inst.Export); const exports = kv.value;
export_inst.* = .{ const export_value = try self.emitTypedValue(decl.src, decl.typed_value.most_recent.typed_value);
.base = .{ .src = module_export.src, .tag = Inst.Export.base_tag }, for (exports) |module_export| {
.positionals = .{ const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name);
.symbol_name = symbol_name, const export_inst = try self.arena.allocator.create(Inst.Export);
.value = export_value, export_inst.* = .{
}, .base = .{
.kw_args = .{}, .name = try self.autoName(),
}; .src = module_export.src,
try self.decls.append(self.allocator, &export_inst.base); .tag = Inst.Export.base_tag,
},
.positionals = .{
.symbol_name = symbol_name,
.value = export_value,
},
.kw_args = .{},
};
try self.decls.append(self.allocator, &export_inst.base);
}
} }
} }
@ -1012,7 +1041,11 @@ const EmitZIR = struct {
const big_int_space = try self.arena.allocator.create(Value.BigIntSpace); const big_int_space = try self.arena.allocator.create(Value.BigIntSpace);
const int_inst = try self.arena.allocator.create(Inst.Int); const int_inst = try self.arena.allocator.create(Inst.Int);
int_inst.* = .{ int_inst.* = .{
.base = .{ .src = src, .tag = Inst.Int.base_tag }, .base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Int.base_tag,
},
.positionals = .{ .positionals = .{
.int = val.toBigInt(big_int_space), .int = val.toBigInt(big_int_space),
}, },
@ -1022,7 +1055,7 @@ const EmitZIR = struct {
return &int_inst.base; return &int_inst.base;
} }
fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: ir.TypedValue) Allocator.Error!*Inst { fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Inst {
switch (typed_value.ty.zigTypeTag()) { switch (typed_value.ty.zigTypeTag()) {
.Pointer => { .Pointer => {
const ptr_elem_type = typed_value.ty.elemType(); const ptr_elem_type = typed_value.ty.elemType();
@ -1044,7 +1077,11 @@ const EmitZIR = struct {
.Int => { .Int => {
const as_inst = try self.arena.allocator.create(Inst.As); const as_inst = try self.arena.allocator.create(Inst.As);
as_inst.* = .{ as_inst.* = .{
.base = .{ .src = src, .tag = Inst.As.base_tag }, .base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.As.base_tag,
},
.positionals = .{ .positionals = .{
.dest_type = try self.emitType(src, typed_value.ty), .dest_type = try self.emitType(src, typed_value.ty),
.value = try self.emitComptimeIntVal(src, typed_value.val), .value = try self.emitComptimeIntVal(src, typed_value.val),
@ -1060,8 +1097,7 @@ const EmitZIR = struct {
return self.emitType(src, ty); return self.emitType(src, ty);
}, },
.Fn => { .Fn => {
const index = typed_value.val.cast(Value.Payload.Function).?.index; const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
const module_fn = self.old_module.fns[index];
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator); var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
defer inst_table.deinit(); defer inst_table.deinit();
@ -1069,7 +1105,7 @@ const EmitZIR = struct {
var instructions = std.ArrayList(*Inst).init(self.allocator); var instructions = std.ArrayList(*Inst).init(self.allocator);
defer instructions.deinit(); defer instructions.deinit();
try self.emitBody(module_fn.body, &inst_table, &instructions); try self.emitBody(module_fn.analysis.success, &inst_table, &instructions);
const fn_type = try self.emitType(src, module_fn.fn_type); const fn_type = try self.emitType(src, module_fn.fn_type);
@ -1078,7 +1114,11 @@ const EmitZIR = struct {
const fn_inst = try self.arena.allocator.create(Inst.Fn); const fn_inst = try self.arena.allocator.create(Inst.Fn);
fn_inst.* = .{ fn_inst.* = .{
.base = .{ .src = src, .tag = Inst.Fn.base_tag }, .base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Fn.base_tag,
},
.positionals = .{ .positionals = .{
.fn_type = fn_type, .fn_type = fn_type,
.body = .{ .instructions = arena_instrs }, .body = .{ .instructions = arena_instrs },
@ -1095,7 +1135,11 @@ const EmitZIR = struct {
fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst { fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst {
const new_inst = try self.arena.allocator.create(T); const new_inst = try self.arena.allocator.create(T);
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = src, .tag = T.base_tag }, .base = .{
.name = try self.autoName(),
.src = src,
.tag = T.base_tag,
},
.positionals = .{}, .positionals = .{},
.kw_args = .{}, .kw_args = .{},
}; };
@ -1120,7 +1164,11 @@ const EmitZIR = struct {
elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]); elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]);
} }
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.Call.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.Call.base_tag,
},
.positionals = .{ .positionals = .{
.func = try self.resolveInst(inst_table, old_inst.args.func), .func = try self.resolveInst(inst_table, old_inst.args.func),
.args = args, .args = args,
@ -1152,7 +1200,11 @@ const EmitZIR = struct {
} }
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.Asm.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.Asm.base_tag,
},
.positionals = .{ .positionals = .{
.asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source), .asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source),
.return_type = try self.emitType(inst.src, inst.ty), .return_type = try self.emitType(inst.src, inst.ty),
@ -1174,7 +1226,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.PtrToInt).?; const old_inst = inst.cast(ir.Inst.PtrToInt).?;
const new_inst = try self.arena.allocator.create(Inst.PtrToInt); const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.PtrToInt.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.PtrToInt.base_tag,
},
.positionals = .{ .positionals = .{
.ptr = try self.resolveInst(inst_table, old_inst.args.ptr), .ptr = try self.resolveInst(inst_table, old_inst.args.ptr),
}, },
@ -1186,7 +1242,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.BitCast).?; const old_inst = inst.cast(ir.Inst.BitCast).?;
const new_inst = try self.arena.allocator.create(Inst.BitCast); const new_inst = try self.arena.allocator.create(Inst.BitCast);
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.BitCast.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.BitCast.base_tag,
},
.positionals = .{ .positionals = .{
.dest_type = try self.emitType(inst.src, inst.ty), .dest_type = try self.emitType(inst.src, inst.ty),
.operand = try self.resolveInst(inst_table, old_inst.args.operand), .operand = try self.resolveInst(inst_table, old_inst.args.operand),
@ -1199,7 +1259,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.Cmp).?; const old_inst = inst.cast(ir.Inst.Cmp).?;
const new_inst = try self.arena.allocator.create(Inst.Cmp); const new_inst = try self.arena.allocator.create(Inst.Cmp);
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.Cmp.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.Cmp.base_tag,
},
.positionals = .{ .positionals = .{
.lhs = try self.resolveInst(inst_table, old_inst.args.lhs), .lhs = try self.resolveInst(inst_table, old_inst.args.lhs),
.rhs = try self.resolveInst(inst_table, old_inst.args.rhs), .rhs = try self.resolveInst(inst_table, old_inst.args.rhs),
@ -1223,7 +1287,11 @@ const EmitZIR = struct {
const new_inst = try self.arena.allocator.create(Inst.CondBr); const new_inst = try self.arena.allocator.create(Inst.CondBr);
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.CondBr.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.CondBr.base_tag,
},
.positionals = .{ .positionals = .{
.condition = try self.resolveInst(inst_table, old_inst.args.condition), .condition = try self.resolveInst(inst_table, old_inst.args.condition),
.true_body = .{ .instructions = true_body.toOwnedSlice() }, .true_body = .{ .instructions = true_body.toOwnedSlice() },
@ -1237,7 +1305,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.IsNull).?; const old_inst = inst.cast(ir.Inst.IsNull).?;
const new_inst = try self.arena.allocator.create(Inst.IsNull); const new_inst = try self.arena.allocator.create(Inst.IsNull);
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.IsNull.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.IsNull.base_tag,
},
.positionals = .{ .positionals = .{
.operand = try self.resolveInst(inst_table, old_inst.args.operand), .operand = try self.resolveInst(inst_table, old_inst.args.operand),
}, },
@ -1249,7 +1321,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.IsNonNull).?; const old_inst = inst.cast(ir.Inst.IsNonNull).?;
const new_inst = try self.arena.allocator.create(Inst.IsNonNull); const new_inst = try self.arena.allocator.create(Inst.IsNonNull);
new_inst.* = .{ new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.IsNonNull.base_tag }, .base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.IsNonNull.base_tag,
},
.positionals = .{ .positionals = .{
.operand = try self.resolveInst(inst_table, old_inst.args.operand), .operand = try self.resolveInst(inst_table, old_inst.args.operand),
}, },
@ -1258,7 +1334,7 @@ const EmitZIR = struct {
break :blk &new_inst.base; break :blk &new_inst.base;
}, },
}; };
try instructions.append(self.allocator, new_inst); try instructions.append(new_inst);
try inst_table.putNoClobber(inst, new_inst); try inst_table.putNoClobber(inst, new_inst);
} }
} }
@ -1301,7 +1377,11 @@ const EmitZIR = struct {
const fntype_inst = try self.arena.allocator.create(Inst.FnType); const fntype_inst = try self.arena.allocator.create(Inst.FnType);
fntype_inst.* = .{ fntype_inst.* = .{
.base = .{ .src = src, .tag = Inst.FnType.base_tag }, .base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.FnType.base_tag,
},
.positionals = .{ .positionals = .{
.param_types = emitted_params, .param_types = emitted_params,
.return_type = try self.emitType(src, ty.fnReturnType()), .return_type = try self.emitType(src, ty.fnReturnType()),
@ -1318,10 +1398,18 @@ const EmitZIR = struct {
} }
} }
fn autoName(self: *EmitZIR) ![]u8 {
return std.fmt.allocPrint(&self.arena.allocator, "{}", .{self.decls.items.len});
}
fn emitPrimitiveType(self: *EmitZIR, src: usize, tag: Inst.Primitive.BuiltinType) !*Inst { fn emitPrimitiveType(self: *EmitZIR, src: usize, tag: Inst.Primitive.BuiltinType) !*Inst {
const primitive_inst = try self.arena.allocator.create(Inst.Primitive); const primitive_inst = try self.arena.allocator.create(Inst.Primitive);
primitive_inst.* = .{ primitive_inst.* = .{
.base = .{ .src = src, .tag = Inst.Primitive.base_tag }, .base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Primitive.base_tag,
},
.positionals = .{ .positionals = .{
.tag = tag, .tag = tag,
}, },
@ -1334,7 +1422,11 @@ const EmitZIR = struct {
fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst { fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst {
const str_inst = try self.arena.allocator.create(Inst.Str); const str_inst = try self.arena.allocator.create(Inst.Str);
str_inst.* = .{ str_inst.* = .{
.base = .{ .src = src, .tag = Inst.Str.base_tag }, .base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Str.base_tag,
},
.positionals = .{ .positionals = .{
.bytes = str, .bytes = str,
}, },

View File

@ -153,7 +153,7 @@ pub const ElfFile = struct {
}; };
pub const Export = struct { pub const Export = struct {
sym_index: usize, sym_index: ?usize = null,
}; };
pub fn deinit(self: *ElfFile) void { pub fn deinit(self: *ElfFile) void {
@ -249,6 +249,11 @@ pub const ElfFile = struct {
return @intCast(u32, result); return @intCast(u32, result);
} }
fn getString(self: *ElfFile, str_off: u32) []const u8 {
assert(str_off < self.shstrtab.items.len);
return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off));
}
fn updateString(self: *ElfFile, old_str_off: u32, new_name: []const u8) !u32 { fn updateString(self: *ElfFile, old_str_off: u32, new_name: []const u8) !u32 {
const existing_name = self.getString(old_str_off); const existing_name = self.getString(old_str_off);
if (mem.eql(u8, existing_name, new_name)) { if (mem.eql(u8, existing_name, new_name)) {
@ -418,6 +423,14 @@ pub const ElfFile = struct {
const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian(); const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
if (self.phdr_table_dirty) { if (self.phdr_table_dirty) {
const phsize: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Phdr),
.p64 => @sizeOf(elf.Elf64_Phdr),
};
const phalign: u16 = switch (self.ptr_width) {
.p32 => @alignOf(elf.Elf32_Phdr),
.p64 => @alignOf(elf.Elf64_Phdr),
};
const allocated_size = self.allocatedSize(self.phdr_table_offset.?); const allocated_size = self.allocatedSize(self.phdr_table_offset.?);
const needed_size = self.program_headers.items.len * phsize; const needed_size = self.program_headers.items.len * phsize;
@ -426,11 +439,10 @@ pub const ElfFile = struct {
self.phdr_table_offset = self.findFreeSpace(needed_size, phalign); self.phdr_table_offset = self.findFreeSpace(needed_size, phalign);
} }
const allocator = self.program_headers.allocator;
switch (self.ptr_width) { switch (self.ptr_width) {
.p32 => { .p32 => {
const buf = try allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len); const buf = try self.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len);
defer allocator.free(buf); defer self.allocator.free(buf);
for (buf) |*phdr, i| { for (buf) |*phdr, i| {
phdr.* = progHeaderTo32(self.program_headers.items[i]); phdr.* = progHeaderTo32(self.program_headers.items[i]);
@ -441,8 +453,8 @@ pub const ElfFile = struct {
try self.file.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?); try self.file.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
}, },
.p64 => { .p64 => {
const buf = try allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len); const buf = try self.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len);
defer allocator.free(buf); defer self.allocator.free(buf);
for (buf) |*phdr, i| { for (buf) |*phdr, i| {
phdr.* = self.program_headers.items[i]; phdr.* = self.program_headers.items[i];
@ -478,12 +490,20 @@ pub const ElfFile = struct {
} }
} }
if (self.shdr_table_dirty) { if (self.shdr_table_dirty) {
const shsize: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Shdr),
.p64 => @sizeOf(elf.Elf64_Shdr),
};
const shalign: u16 = switch (self.ptr_width) {
.p32 => @alignOf(elf.Elf32_Shdr),
.p64 => @alignOf(elf.Elf64_Shdr),
};
const allocated_size = self.allocatedSize(self.shdr_table_offset.?); const allocated_size = self.allocatedSize(self.shdr_table_offset.?);
const needed_size = self.sections.items.len * phsize; const needed_size = self.sections.items.len * shsize;
if (needed_size > allocated_size) { if (needed_size > allocated_size) {
self.shdr_table_offset = null; // free the space self.shdr_table_offset = null; // free the space
self.shdr_table_offset = self.findFreeSpace(needed_size, phalign); self.shdr_table_offset = self.findFreeSpace(needed_size, shalign);
} }
switch (self.ptr_width) { switch (self.ptr_width) {
@ -719,7 +739,7 @@ pub const ElfFile = struct {
defer code.deinit(); defer code.deinit();
const typed_value = decl.typed_value.most_recent.typed_value; const typed_value = decl.typed_value.most_recent.typed_value;
const err_msg = try codegen.generateSymbol(typed_value, module, &code); const err_msg = try codegen.generateSymbol(typed_value, module.*, &code);
if (err_msg != null) |em| { if (err_msg != null) |em| {
decl.analysis = .codegen_failure; decl.analysis = .codegen_failure;
_ = try module.failed_decls.put(decl, em); _ = try module.failed_decls.put(decl, em);
@ -751,15 +771,15 @@ pub const ElfFile = struct {
try self.writeSymbol(decl.link.local_sym_index); try self.writeSymbol(decl.link.local_sym_index);
break :blk file_offset; break :blk file_offset;
} else { } else {
try self.symbols.ensureCapacity(self.symbols.items.len + 1); try self.symbols.ensureCapacity(self.allocator, self.symbols.items.len + 1);
try self.offset_table.ensureCapacity(self.offset_table.items.len + 1); try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
const decl_name = mem.spanZ(u8, decl.name); const decl_name = mem.spanZ(u8, decl.name);
const name_str_index = try self.makeString(decl_name); const name_str_index = try self.makeString(decl_name);
const new_block = try self.allocateTextBlock(code_size); const new_block = try self.allocateTextBlock(code_size);
const local_sym_index = self.symbols.items.len; const local_sym_index = self.symbols.items.len;
const offset_table_index = self.offset_table.items.len; const offset_table_index = self.offset_table.items.len;
self.symbols.appendAssumeCapacity(self.allocator, .{ self.symbols.appendAssumeCapacity(.{
.st_name = name_str_index, .st_name = name_str_index,
.st_info = (elf.STB_LOCAL << 4) | stt_bits, .st_info = (elf.STB_LOCAL << 4) | stt_bits,
.st_other = 0, .st_other = 0,
@ -767,9 +787,9 @@ pub const ElfFile = struct {
.st_value = new_block.vaddr, .st_value = new_block.vaddr,
.st_size = code_size, .st_size = code_size,
}); });
errdefer self.symbols.shrink(self.symbols.items.len - 1); errdefer self.symbols.shrink(self.allocator, self.symbols.items.len - 1);
self.offset_table.appendAssumeCapacity(self.allocator, new_block.vaddr); self.offset_table.appendAssumeCapacity(new_block.vaddr);
errdefer self.offset_table.shrink(self.offset_table.items.len - 1); errdefer self.offset_table.shrink(self.allocator, self.offset_table.items.len - 1);
try self.writeSymbol(local_sym_index); try self.writeSymbol(local_sym_index);
try self.writeOffsetTableEntry(offset_table_index); try self.writeOffsetTableEntry(offset_table_index);
@ -796,11 +816,12 @@ pub const ElfFile = struct {
self: *ElfFile, self: *ElfFile,
module: *ir.Module, module: *ir.Module,
decl: *const ir.Module.Decl, decl: *const ir.Module.Decl,
exports: []const *const Export, exports: []const *ir.Module.Export,
) !void { ) !void {
try self.symbols.ensureCapacity(self.symbols.items.len + exports.len); try self.symbols.ensureCapacity(self.allocator, self.symbols.items.len + exports.len);
const typed_value = decl.typed_value.most_recent.typed_value; const typed_value = decl.typed_value.most_recent.typed_value;
const decl_sym = self.symbols.items[decl.link.local_sym_index.?]; assert(decl.link.local_sym_index != 0);
const decl_sym = self.symbols.items[decl.link.local_sym_index];
for (exports) |exp| { for (exports) |exp| {
if (exp.options.section) |section_name| { if (exp.options.section) |section_name| {
@ -808,15 +829,16 @@ pub const ElfFile = struct {
try module.failed_exports.ensureCapacity(module.failed_exports.size + 1); try module.failed_exports.ensureCapacity(module.failed_exports.size + 1);
module.failed_exports.putAssumeCapacityNoClobber( module.failed_exports.putAssumeCapacityNoClobber(
exp, exp,
try ir.ErrorMsg.create(0, "Unimplemented: ExportOptions.section", .{}), try ir.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
); );
continue;
} }
} }
const stb_bits = switch (exp.options.linkage) { const stb_bits: u8 = switch (exp.options.linkage) {
.Internal => elf.STB_LOCAL, .Internal => elf.STB_LOCAL,
.Strong => blk: { .Strong => blk: {
if (mem.eql(u8, exp.options.name, "_start")) { if (mem.eql(u8, exp.options.name, "_start")) {
self.entry_addr = decl_symbol.vaddr; self.entry_addr = decl_sym.st_value;
} }
break :blk elf.STB_GLOBAL; break :blk elf.STB_GLOBAL;
}, },
@ -825,8 +847,9 @@ pub const ElfFile = struct {
try module.failed_exports.ensureCapacity(module.failed_exports.size + 1); try module.failed_exports.ensureCapacity(module.failed_exports.size + 1);
module.failed_exports.putAssumeCapacityNoClobber( module.failed_exports.putAssumeCapacityNoClobber(
exp, exp,
try ir.ErrorMsg.create(0, "Unimplemented: GlobalLinkage.LinkOnce", .{}), try ir.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
); );
continue;
}, },
}; };
const stt_bits: u8 = @truncate(u4, decl_sym.st_info); const stt_bits: u8 = @truncate(u4, decl_sym.st_info);
@ -844,15 +867,15 @@ pub const ElfFile = struct {
} else { } else {
const name = try self.makeString(exp.options.name); const name = try self.makeString(exp.options.name);
const i = self.symbols.items.len; const i = self.symbols.items.len;
self.symbols.appendAssumeCapacity(self.allocator, .{ self.symbols.appendAssumeCapacity(.{
.st_name = sn.name, .st_name = name,
.st_info = (stb_bits << 4) | stt_bits, .st_info = (stb_bits << 4) | stt_bits,
.st_other = 0, .st_other = 0,
.st_shndx = self.text_section_index.?, .st_shndx = self.text_section_index.?,
.st_value = decl_sym.st_value, .st_value = decl_sym.st_value,
.st_size = decl_sym.st_size, .st_size = decl_sym.st_size,
}); });
errdefer self.symbols.shrink(self.symbols.items.len - 1); errdefer self.symbols.shrink(self.allocator, self.symbols.items.len - 1);
try self.writeSymbol(i); try self.writeSymbol(i);
self.symbol_count_dirty = true; self.symbol_count_dirty = true;
@ -946,10 +969,15 @@ pub const ElfFile = struct {
} }
fn writeSymbol(self: *ElfFile, index: usize) !void { fn writeSymbol(self: *ElfFile, index: usize) !void {
assert(index != 0);
const syms_sect = &self.sections.items[self.symtab_section_index.?]; const syms_sect = &self.sections.items[self.symtab_section_index.?];
// Make sure we are not pointlessly writing symbol data that will have to get relocated // Make sure we are not pointlessly writing symbol data that will have to get relocated
// due to running out of space. // due to running out of space.
if (self.symbol_count_dirty) { if (self.symbol_count_dirty) {
const sym_size: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Sym),
.p64 => @sizeOf(elf.Elf64_Sym),
};
const allocated_size = self.allocatedSize(syms_sect.sh_offset); const allocated_size = self.allocatedSize(syms_sect.sh_offset);
const needed_size = self.symbols.items.len * sym_size; const needed_size = self.symbols.items.len * sym_size;
if (needed_size > allocated_size) { if (needed_size > allocated_size) {
@ -990,11 +1018,15 @@ pub const ElfFile = struct {
} }
fn writeAllSymbols(self: *ElfFile) !void { fn writeAllSymbols(self: *ElfFile) !void {
const small_ptr = self.ptr_width == .p32;
const syms_sect = &self.sections.items[self.symtab_section_index.?]; const syms_sect = &self.sections.items[self.symtab_section_index.?];
const sym_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym); const sym_align: u16 = switch (self.ptr_width) {
const sym_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym); .p32 => @alignOf(elf.Elf32_Sym),
.p64 => @alignOf(elf.Elf64_Sym),
};
const sym_size: u64 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Sym),
.p64 => @sizeOf(elf.Elf64_Sym),
};
const allocated_size = self.allocatedSize(syms_sect.sh_offset); const allocated_size = self.allocatedSize(syms_sect.sh_offset);
const needed_size = self.symbols.items.len * sym_size; const needed_size = self.symbols.items.len * sym_size;
if (needed_size > allocated_size) { if (needed_size > allocated_size) {

View File

@ -67,6 +67,7 @@ pub const Value = extern union {
int_big_positive, int_big_positive,
int_big_negative, int_big_negative,
function, function,
ref_val,
decl_ref, decl_ref,
elem_ptr, elem_ptr,
bytes, bytes,
@ -158,6 +159,11 @@ pub const Value = extern union {
.int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}), .int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
.int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}), .int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
.function => return out_stream.writeAll("(function)"), .function => return out_stream.writeAll("(function)"),
.ref_val => {
const ref_val = val.cast(Payload.RefVal).?;
try out_stream.writeAll("&const ");
val = ref_val.val;
},
.decl_ref => return out_stream.writeAll("(decl ref)"), .decl_ref => return out_stream.writeAll("(decl ref)"),
.elem_ptr => { .elem_ptr => {
const elem_ptr = val.cast(Payload.ElemPtr).?; const elem_ptr = val.cast(Payload.ElemPtr).?;
@ -229,6 +235,7 @@ pub const Value = extern union {
.int_big_positive, .int_big_positive,
.int_big_negative, .int_big_negative,
.function, .function,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -276,6 +283,7 @@ pub const Value = extern union {
.bool_false, .bool_false,
.null_value, .null_value,
.function, .function,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -333,6 +341,7 @@ pub const Value = extern union {
.bool_false, .bool_false,
.null_value, .null_value,
.function, .function,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -391,6 +400,7 @@ pub const Value = extern union {
.bool_false, .bool_false,
.null_value, .null_value,
.function, .function,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -454,6 +464,7 @@ pub const Value = extern union {
.bool_false, .bool_false,
.null_value, .null_value,
.function, .function,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -546,6 +557,7 @@ pub const Value = extern union {
.bool_false, .bool_false,
.null_value, .null_value,
.function, .function,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -600,6 +612,7 @@ pub const Value = extern union {
.bool_false, .bool_false,
.null_value, .null_value,
.function, .function,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -655,7 +668,8 @@ pub const Value = extern union {
} }
/// Asserts the value is a pointer and dereferences it. /// Asserts the value is a pointer and dereferences it.
pub fn pointerDeref(self: Value, module: *ir.Module) !Value { /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
return switch (self.tag()) { return switch (self.tag()) {
.ty, .ty,
.u8_type, .u8_type,
@ -704,21 +718,19 @@ pub const Value = extern union {
=> unreachable, => unreachable,
.the_one_possible_value => Value.initTag(.the_one_possible_value), .the_one_possible_value => Value.initTag(.the_one_possible_value),
.decl_ref => { .ref_val => self.cast(Payload.RefVal).?.val,
const index = self.cast(Payload.DeclRef).?.index; .decl_ref => self.cast(Payload.DeclRef).?.decl.value(),
return module.getDeclValue(index);
},
.elem_ptr => { .elem_ptr => {
const elem_ptr = self.cast(ElemPtr).?; const elem_ptr = self.cast(Payload.ElemPtr).?;
const array_val = try elem_ptr.array_ptr.pointerDeref(module); const array_val = try elem_ptr.array_ptr.pointerDeref(allocator);
return self.elemValue(array_val, elem_ptr.index); return array_val.elemValue(allocator, elem_ptr.index);
}, },
}; };
} }
/// Asserts the value is a single-item pointer to an array, or an array, /// Asserts the value is a single-item pointer to an array, or an array,
/// or an unknown-length pointer, and returns the element value at the index. /// or an unknown-length pointer, and returns the element value at the index.
pub fn elemValue(self: Value, index: usize) Value { pub fn elemValue(self: Value, allocator: *Allocator, index: usize) error{OutOfMemory}!Value {
switch (self.tag()) { switch (self.tag()) {
.ty, .ty,
.u8_type, .u8_type,
@ -764,6 +776,7 @@ pub const Value = extern union {
.int_big_negative, .int_big_negative,
.undef, .undef,
.elem_ptr, .elem_ptr,
.ref_val,
.decl_ref, .decl_ref,
=> unreachable, => unreachable,
@ -838,6 +851,7 @@ pub const Value = extern union {
.int_i64, .int_i64,
.int_big_positive, .int_big_positive,
.int_big_negative, .int_big_negative,
.ref_val,
.decl_ref, .decl_ref,
.elem_ptr, .elem_ptr,
.bytes, .bytes,
@ -896,11 +910,16 @@ pub const Value = extern union {
elem_type: *Type, elem_type: *Type,
}; };
/// Represents a pointer to another immutable value.
pub const RefVal = struct {
base: Payload = Payload{ .tag = .ref_val },
val: Value,
};
/// Represents a pointer to a decl, not the value of the decl. /// Represents a pointer to a decl, not the value of the decl.
pub const DeclRef = struct { pub const DeclRef = struct {
base: Payload = Payload{ .tag = .decl_ref }, base: Payload = Payload{ .tag = .decl_ref },
/// Index into the Module's decls list decl: *ir.Module.Decl,
index: usize,
}; };
pub const ElemPtr = struct { pub const ElemPtr = struct {