Commit a32d3a85d2
Changed files (21)
lib/std/heap/arena_allocator.zig
@@ -0,0 +1,102 @@
+const std = @import("../std.zig");
+const assert = std.debug.assert;
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+
+/// This allocator takes an existing allocator, wraps it, and provides an interface
+/// where you can allocate without freeing, and then free it all together.
+pub const ArenaAllocator = struct {
+ allocator: Allocator,
+
+ child_allocator: *Allocator,
+ state: State,
+
+ /// Inner state of ArenaAllocator. Can be stored rather than the entire ArenaAllocator
+ /// as a memory-saving optimization.
+ pub const State = struct {
+ buffer_list: std.SinglyLinkedList([]u8) = @as(std.SinglyLinkedList([]u8), .{}),
+ end_index: usize = 0,
+
+ pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator {
+ return .{
+ .allocator = Allocator{
+ .reallocFn = realloc,
+ .shrinkFn = shrink,
+ },
+ .child_allocator = child_allocator,
+ .state = self,
+ };
+ }
+ };
+
+ const BufNode = std.SinglyLinkedList([]u8).Node;
+
+ pub fn init(child_allocator: *Allocator) ArenaAllocator {
+ return (State{}).promote(child_allocator);
+ }
+
+ pub fn deinit(self: ArenaAllocator) void {
+ var it = self.state.buffer_list.first;
+ while (it) |node| {
+ // this has to occur before the free because the free frees node
+ const next_it = node.next;
+ self.child_allocator.free(node.data);
+ it = next_it;
+ }
+ }
+
+ fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) !*BufNode {
+ const actual_min_size = minimum_size + @sizeOf(BufNode);
+ var len = prev_len;
+ while (true) {
+ len += len / 2;
+ len += mem.page_size - @rem(len, mem.page_size);
+ if (len >= actual_min_size) break;
+ }
+ const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len);
+ const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]);
+ const buf_node = &buf_node_slice[0];
+ buf_node.* = BufNode{
+ .data = buf,
+ .next = null,
+ };
+ self.state.buffer_list.prepend(buf_node);
+ self.state.end_index = 0;
+ return buf_node;
+ }
+
+ fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 {
+ const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator);
+
+ var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment);
+ while (true) {
+ const cur_buf = cur_node.data[@sizeOf(BufNode)..];
+ const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index;
+ const adjusted_addr = mem.alignForward(addr, alignment);
+ const adjusted_index = self.state.end_index + (adjusted_addr - addr);
+ const new_end_index = adjusted_index + n;
+ if (new_end_index > cur_buf.len) {
+ cur_node = try self.createNode(cur_buf.len, n + alignment);
+ continue;
+ }
+ const result = cur_buf[adjusted_index..new_end_index];
+ self.state.end_index = new_end_index;
+ return result;
+ }
+ }
+
+ fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
+ if (new_size <= old_mem.len and new_align <= new_size) {
+ // We can't do anything with the memory, so tell the client to keep it.
+ return error.OutOfMemory;
+ } else {
+ const result = try alloc(allocator, new_size, new_align);
+ @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len));
+ return result;
+ }
+ }
+
+ fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
+ return old_mem[0..new_size];
+ }
+};
lib/std/array_list.zig
@@ -8,13 +8,13 @@ const Allocator = mem.Allocator;
/// A contiguous, growable list of items in memory.
/// This is a wrapper around an array of T values. Initialize with `init`.
pub fn ArrayList(comptime T: type) type {
- return AlignedArrayList(T, null);
+ return ArrayListAligned(T, null);
}
-pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type {
+pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
if (alignment) |a| {
if (a == @alignOf(T)) {
- return AlignedArrayList(T, null);
+ return ArrayListAligned(T, null);
}
}
return struct {
@@ -76,6 +76,10 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type {
};
}
+ pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) {
+ return .{ .items = self.items, .capacity = self.capacity };
+ }
+
/// The caller owns the returned memory. ArrayList becomes empty.
pub fn toOwnedSlice(self: *Self) Slice {
const allocator = self.allocator;
@@ -84,8 +88,8 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type {
return result;
}
- /// Insert `item` at index `n`. Moves `list[n .. list.len]`
- /// to make room.
+ /// Insert `item` at index `n` by moving `list[n .. list.len]` to make room.
+ /// This operation is O(N).
pub fn insert(self: *Self, n: usize, item: T) !void {
try self.ensureCapacity(self.items.len + 1);
self.items.len += 1;
@@ -94,8 +98,7 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type {
self.items[n] = item;
}
- /// Insert slice `items` at index `i`. Moves
- /// `list[i .. list.len]` to make room.
+ /// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
/// This operation is O(N).
pub fn insertSlice(self: *Self, i: usize, items: SliceConst) !void {
try self.ensureCapacity(self.items.len + items.len);
@@ -259,6 +262,232 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type {
};
}
+/// Bring-your-own allocator with every function call.
+/// 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 {
+ return ArrayListAlignedUnmanaged(T, null);
+}
+
+pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) type {
+ if (alignment) |a| {
+ if (a == @alignOf(T)) {
+ return ArrayListAlignedUnmanaged(T, null);
+ }
+ }
+ return struct {
+ const Self = @This();
+
+ /// Content of the ArrayList.
+ items: Slice = &[_]T{},
+ capacity: usize = 0,
+
+ pub const Slice = if (alignment) |a| ([]align(a) T) else []T;
+ pub const SliceConst = if (alignment) |a| ([]align(a) const T) else []const T;
+
+ /// Initialize with capacity to hold at least num elements.
+ /// Deinitialize with `deinit` or use `toOwnedSlice`.
+ pub fn initCapacity(allocator: *Allocator, num: usize) !Self {
+ var self = Self.init(allocator);
+ try self.ensureCapacity(allocator, num);
+ return self;
+ }
+
+ /// Release all allocated memory.
+ pub fn deinit(self: *Self, allocator: *Allocator) void {
+ allocator.free(self.allocatedSlice());
+ self.* = undefined;
+ }
+
+ pub fn toManaged(self: *Self, allocator: *Allocator) ArrayListAligned(T, alignment) {
+ return .{ .items = self.items, .capacity = self.capacity, .allocator = allocator };
+ }
+
+ /// The caller owns the returned memory. ArrayList becomes empty.
+ pub fn toOwnedSlice(self: *Self, allocator: *Allocator) Slice {
+ const result = allocator.shrink(self.allocatedSlice(), self.items.len);
+ self.* = init(allocator);
+ return result;
+ }
+
+ /// Insert `item` at index `n`. Moves `list[n .. list.len]`
+ /// to make room.
+ pub fn insert(self: *Self, allocator: *Allocator, n: usize, item: T) !void {
+ try self.ensureCapacity(allocator, self.items.len + 1);
+ self.items.len += 1;
+
+ mem.copyBackwards(T, self.items[n + 1 .. self.items.len], self.items[n .. self.items.len - 1]);
+ self.items[n] = item;
+ }
+
+ /// Insert slice `items` at index `i`. Moves
+ /// `list[i .. list.len]` to make room.
+ /// This operation is O(N).
+ pub fn insertSlice(self: *Self, allocator: *Allocator, i: usize, items: SliceConst) !void {
+ try self.ensureCapacity(allocator, self.items.len + items.len);
+ self.items.len += items.len;
+
+ mem.copyBackwards(T, self.items[i + items.len .. self.items.len], self.items[i .. self.items.len - items.len]);
+ mem.copy(T, self.items[i .. i + items.len], items);
+ }
+
+ /// Extend the list by 1 element. Allocates more memory as necessary.
+ pub fn append(self: *Self, allocator: *Allocator, item: T) !void {
+ const new_item_ptr = try self.addOne(allocator);
+ new_item_ptr.* = item;
+ }
+
+ /// Extend the list by 1 element, but asserting `self.capacity`
+ /// is sufficient to hold an additional item.
+ pub fn appendAssumeCapacity(self: *Self, item: T) void {
+ const new_item_ptr = self.addOneAssumeCapacity();
+ new_item_ptr.* = item;
+ }
+
+ /// Remove the element at index `i` from the list and return its value.
+ /// Asserts the array has at least one item.
+ /// This operation is O(N).
+ pub fn orderedRemove(self: *Self, i: usize) T {
+ const newlen = self.items.len - 1;
+ if (newlen == i) return self.pop();
+
+ const old_item = self.items[i];
+ for (self.items[i..newlen]) |*b, j| b.* = self.items[i + 1 + j];
+ self.items[newlen] = undefined;
+ self.items.len = newlen;
+ return old_item;
+ }
+
+ /// Removes the element at the specified index and returns it.
+ /// The empty slot is filled from the end of the list.
+ /// This operation is O(1).
+ pub fn swapRemove(self: *Self, i: usize) T {
+ if (self.items.len - 1 == i) return self.pop();
+
+ const old_item = self.items[i];
+ self.items[i] = self.pop();
+ return old_item;
+ }
+
+ /// Append the slice of items to the list. Allocates more
+ /// memory as necessary.
+ pub fn appendSlice(self: *Self, allocator: *Allocator, items: SliceConst) !void {
+ const oldlen = self.items.len;
+ const newlen = self.items.len + items.len;
+
+ try self.ensureCapacity(allocator, newlen);
+ self.items.len = newlen;
+ mem.copy(T, self.items[oldlen..], items);
+ }
+
+ /// Same as `append` except it returns the number of bytes written, which is always the same
+ /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API.
+ /// This function may be called only when `T` is `u8`.
+ fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize {
+ try self.appendSlice(allocator, m);
+ return m.len;
+ }
+
+ /// Append a value to the list `n` times.
+ /// Allocates more memory as necessary.
+ pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void {
+ const old_len = self.items.len;
+ try self.resize(self.items.len + n);
+ mem.set(T, self.items[old_len..self.items.len], value);
+ }
+
+ /// Adjust the list's length to `new_len`.
+ /// Does not initialize added items if any.
+ pub fn resize(self: *Self, allocator: *Allocator, new_len: usize) !void {
+ try self.ensureCapacity(allocator, new_len);
+ self.items.len = new_len;
+ }
+
+ /// Reduce allocated capacity to `new_len`.
+ /// Invalidates element pointers.
+ pub fn shrink(self: *Self, allocator: *Allocator, new_len: usize) void {
+ assert(new_len <= self.items.len);
+
+ self.items = allocator.realloc(self.allocatedSlice(), new_len) catch |e| switch (e) {
+ error.OutOfMemory => { // no problem, capacity is still correct then.
+ self.items.len = new_len;
+ return;
+ },
+ };
+ self.capacity = new_len;
+ }
+
+ pub fn ensureCapacity(self: *Self, allocator: *Allocator, new_capacity: usize) !void {
+ var better_capacity = self.capacity;
+ if (better_capacity >= new_capacity) return;
+
+ while (true) {
+ better_capacity += better_capacity / 2 + 8;
+ if (better_capacity >= new_capacity) break;
+ }
+
+ const new_memory = try allocator.realloc(self.allocatedSlice(), better_capacity);
+ self.items.ptr = new_memory.ptr;
+ self.capacity = new_memory.len;
+ }
+
+ /// Increases the array's length to match the full capacity that is already allocated.
+ /// The new elements have `undefined` values.
+ /// This operation does not invalidate any element pointers.
+ pub fn expandToCapacity(self: *Self) void {
+ self.items.len = self.capacity;
+ }
+
+ /// Increase length by 1, returning pointer to the new item.
+ /// The returned pointer becomes invalid when the list is resized.
+ pub fn addOne(self: *Self, allocator: *Allocator) !*T {
+ const newlen = self.items.len + 1;
+ try self.ensureCapacity(allocator, newlen);
+ return self.addOneAssumeCapacity();
+ }
+
+ /// Increase length by 1, returning pointer to the new item.
+ /// Asserts that there is already space for the new item without allocating more.
+ /// The returned pointer becomes invalid when the list is resized.
+ /// This operation does not invalidate any element pointers.
+ pub fn addOneAssumeCapacity(self: *Self) *T {
+ assert(self.items.len < self.capacity);
+
+ self.items.len += 1;
+ return &self.items[self.items.len - 1];
+ }
+
+ /// Remove and return the last element from the list.
+ /// Asserts the list has at least one item.
+ /// This operation does not invalidate any element pointers.
+ pub fn pop(self: *Self) T {
+ const val = self.items[self.items.len - 1];
+ self.items.len -= 1;
+ return val;
+ }
+
+ /// Remove and return the last element from the list.
+ /// If the list is empty, returns `null`.
+ /// This operation does not invalidate any element pointers.
+ pub fn popOrNull(self: *Self) ?T {
+ if (self.items.len == 0) return null;
+ return self.pop();
+ }
+
+ /// For a nicer API, `items.len` is the length, not the capacity.
+ /// This requires "unsafe" slicing.
+ fn allocatedSlice(self: Self) Slice {
+ return self.items.ptr[0..self.capacity];
+ }
+ };
+}
+
test "std.ArrayList.init" {
var list = ArrayList(i32).init(testing.allocator);
defer list.deinit();
lib/std/heap.zig
@@ -11,6 +11,7 @@ const maxInt = std.math.maxInt;
pub const LoggingAllocator = @import("heap/logging_allocator.zig").LoggingAllocator;
pub const loggingAllocator = @import("heap/logging_allocator.zig").loggingAllocator;
+pub const ArenaAllocator = @import("heap/arena_allocator.zig").ArenaAllocator;
const Allocator = mem.Allocator;
@@ -510,95 +511,6 @@ pub const HeapAllocator = switch (builtin.os.tag) {
else => @compileError("Unsupported OS"),
};
-/// This allocator takes an existing allocator, wraps it, and provides an interface
-/// where you can allocate without freeing, and then free it all together.
-pub const ArenaAllocator = struct {
- allocator: Allocator,
-
- child_allocator: *Allocator,
- buffer_list: std.SinglyLinkedList([]u8),
- end_index: usize,
-
- const BufNode = std.SinglyLinkedList([]u8).Node;
-
- pub fn init(child_allocator: *Allocator) ArenaAllocator {
- return ArenaAllocator{
- .allocator = Allocator{
- .reallocFn = realloc,
- .shrinkFn = shrink,
- },
- .child_allocator = child_allocator,
- .buffer_list = std.SinglyLinkedList([]u8).init(),
- .end_index = 0,
- };
- }
-
- pub fn deinit(self: ArenaAllocator) void {
- var it = self.buffer_list.first;
- while (it) |node| {
- // this has to occur before the free because the free frees node
- const next_it = node.next;
- self.child_allocator.free(node.data);
- it = next_it;
- }
- }
-
- fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) !*BufNode {
- const actual_min_size = minimum_size + @sizeOf(BufNode);
- var len = prev_len;
- while (true) {
- len += len / 2;
- len += mem.page_size - @rem(len, mem.page_size);
- if (len >= actual_min_size) break;
- }
- const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len);
- const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]);
- const buf_node = &buf_node_slice[0];
- buf_node.* = BufNode{
- .data = buf,
- .next = null,
- };
- self.buffer_list.prepend(buf_node);
- self.end_index = 0;
- return buf_node;
- }
-
- fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 {
- const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator);
-
- var cur_node = if (self.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment);
- while (true) {
- const cur_buf = cur_node.data[@sizeOf(BufNode)..];
- const addr = @ptrToInt(cur_buf.ptr) + self.end_index;
- const adjusted_addr = mem.alignForward(addr, alignment);
- const adjusted_index = self.end_index + (adjusted_addr - addr);
- const new_end_index = adjusted_index + n;
- if (new_end_index > cur_buf.len) {
- cur_node = try self.createNode(cur_buf.len, n + alignment);
- continue;
- }
- const result = cur_buf[adjusted_index..new_end_index];
- self.end_index = new_end_index;
- return result;
- }
- }
-
- fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
- if (new_size <= old_mem.len and new_align <= new_size) {
- // We can't do anything with the memory, so tell the client to keep it.
- return error.OutOfMemory;
- } else {
- const result = try alloc(allocator, new_size, new_align);
- @memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len));
- return result;
- }
- }
-
- fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
- return old_mem[0..new_size];
- }
-};
-
pub const FixedBufferAllocator = struct {
allocator: Allocator,
end_index: usize,
lib/std/linked_list.zig
@@ -49,7 +49,7 @@ pub fn SinglyLinkedList(comptime T: type) type {
}
};
- first: ?*Node,
+ first: ?*Node = null,
/// Initialize a linked list.
///
lib/std/std.zig
@@ -1,6 +1,8 @@
-pub const AlignedArrayList = @import("array_list.zig").AlignedArrayList;
pub const ArrayList = @import("array_list.zig").ArrayList;
+pub const ArrayListAligned = @import("array_list.zig").ArrayListAligned;
+pub const ArrayListAlignedUnmanaged = @import("array_list.zig").ArrayListAlignedUnmanaged;
pub const ArrayListSentineled = @import("array_list_sentineled.zig").ArrayListSentineled;
+pub const ArrayListUnmanaged = @import("array_list.zig").ArrayListUnmanaged;
pub const AutoHashMap = @import("hash_map.zig").AutoHashMap;
pub const BloomFilter = @import("bloom_filter.zig").BloomFilter;
pub const BufMap = @import("buf_map.zig").BufMap;
src-self-hosted/ir/text.zig
@@ -16,10 +16,16 @@ pub const Inst = struct {
tag: Tag,
/// Byte offset into the source.
src: usize,
+ name: []const u8,
/// These names are used directly as the instruction names in the text format.
pub const Tag = enum {
breakpoint,
+ call,
+ /// Represents a reference to a global decl by name.
+ /// Canonicalized ZIR will not have any of these. The
+ /// syntax `@foo` is equivalent to `declref("foo")`.
+ declref,
str,
int,
ptrtoint,
@@ -46,6 +52,8 @@ pub const Inst = struct {
pub fn TagToType(tag: Tag) type {
return switch (tag) {
.breakpoint => Breakpoint,
+ .call => Call,
+ .declref => DeclRef,
.str => Str,
.int => Int,
.ptrtoint => PtrToInt,
@@ -85,6 +93,29 @@ pub const Inst = struct {
kw_args: struct {},
};
+ pub const Call = struct {
+ pub const base_tag = Tag.call;
+ base: Inst,
+
+ positionals: struct {
+ func: *Inst,
+ args: []*Inst,
+ },
+ kw_args: struct {
+ modifier: std.builtin.CallOptions.Modifier = .auto,
+ },
+ };
+
+ pub const DeclRef = struct {
+ pub const base_tag = Tag.declref;
+ base: Inst,
+
+ positionals: struct {
+ name: *Inst,
+ },
+ kw_args: struct {},
+ };
+
pub const Str = struct {
pub const base_tag = Tag.str;
base: Inst,
@@ -212,55 +243,55 @@ pub const Inst = struct {
kw_args: struct {},
pub const BuiltinType = enum {
- @"isize",
- @"usize",
- @"c_short",
- @"c_ushort",
- @"c_int",
- @"c_uint",
- @"c_long",
- @"c_ulong",
- @"c_longlong",
- @"c_ulonglong",
- @"c_longdouble",
- @"c_void",
- @"f16",
- @"f32",
- @"f64",
- @"f128",
- @"bool",
- @"void",
- @"noreturn",
- @"type",
- @"anyerror",
- @"comptime_int",
- @"comptime_float",
+ isize,
+ usize,
+ c_short,
+ c_ushort,
+ c_int,
+ c_uint,
+ c_long,
+ c_ulong,
+ c_longlong,
+ c_ulonglong,
+ c_longdouble,
+ c_void,
+ f16,
+ f32,
+ f64,
+ f128,
+ bool,
+ void,
+ noreturn,
+ type,
+ anyerror,
+ comptime_int,
+ comptime_float,
fn toType(self: BuiltinType) Type {
return switch (self) {
- .@"isize" => Type.initTag(.@"isize"),
- .@"usize" => Type.initTag(.@"usize"),
- .@"c_short" => Type.initTag(.@"c_short"),
- .@"c_ushort" => Type.initTag(.@"c_ushort"),
- .@"c_int" => Type.initTag(.@"c_int"),
- .@"c_uint" => Type.initTag(.@"c_uint"),
- .@"c_long" => Type.initTag(.@"c_long"),
- .@"c_ulong" => Type.initTag(.@"c_ulong"),
- .@"c_longlong" => Type.initTag(.@"c_longlong"),
- .@"c_ulonglong" => Type.initTag(.@"c_ulonglong"),
- .@"c_longdouble" => Type.initTag(.@"c_longdouble"),
- .@"c_void" => Type.initTag(.@"c_void"),
- .@"f16" => Type.initTag(.@"f16"),
- .@"f32" => Type.initTag(.@"f32"),
- .@"f64" => Type.initTag(.@"f64"),
- .@"f128" => Type.initTag(.@"f128"),
- .@"bool" => Type.initTag(.@"bool"),
- .@"void" => Type.initTag(.@"void"),
- .@"noreturn" => Type.initTag(.@"noreturn"),
- .@"type" => Type.initTag(.@"type"),
- .@"anyerror" => Type.initTag(.@"anyerror"),
- .@"comptime_int" => Type.initTag(.@"comptime_int"),
- .@"comptime_float" => Type.initTag(.@"comptime_float"),
+ .isize => Type.initTag(.isize),
+ .usize => Type.initTag(.usize),
+ .c_short => Type.initTag(.c_short),
+ .c_ushort => Type.initTag(.c_ushort),
+ .c_int => Type.initTag(.c_int),
+ .c_uint => Type.initTag(.c_uint),
+ .c_long => Type.initTag(.c_long),
+ .c_ulong => Type.initTag(.c_ulong),
+ .c_longlong => Type.initTag(.c_longlong),
+ .c_ulonglong => Type.initTag(.c_ulonglong),
+ .c_longdouble => Type.initTag(.c_longdouble),
+ .c_void => Type.initTag(.c_void),
+ .f16 => Type.initTag(.f16),
+ .f32 => Type.initTag(.f32),
+ .f64 => Type.initTag(.f64),
+ .f128 => Type.initTag(.f128),
+ .bool => Type.initTag(.bool),
+ .void => Type.initTag(.void),
+ .noreturn => Type.initTag(.noreturn),
+ .type => Type.initTag(.type),
+ .anyerror => Type.initTag(.anyerror),
+ .comptime_int => Type.initTag(.comptime_int),
+ .comptime_float => Type.initTag(.comptime_float),
};
}
};
@@ -376,7 +407,7 @@ pub const ErrorMsg = struct {
pub const Module = struct {
decls: []*Inst,
errors: []ErrorMsg,
- arena: std.heap.ArenaAllocator,
+ arena: std.heap.ArenaAllocator.State,
pub const Body = struct {
instructions: []*Inst,
@@ -385,7 +416,7 @@ pub const Module = struct {
pub fn deinit(self: *Module, allocator: *Allocator) void {
allocator.free(self.decls);
allocator.free(self.errors);
- self.arena.deinit();
+ self.arena.promote(allocator).deinit();
self.* = undefined;
}
@@ -431,6 +462,7 @@ pub const Module = struct {
// TODO I tried implementing this with an inline for loop and hit a compiler bug
switch (decl.tag) {
.breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table),
+ .call => return self.writeInstToStreamGeneric(stream, .call, decl, inst_table),
.str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table),
.int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table),
.ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table),
@@ -543,9 +575,9 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
.arena = std.heap.ArenaAllocator.init(allocator),
.i = 0,
.source = source,
- .decls = std.ArrayList(*Inst).init(allocator),
- .errors = std.ArrayList(ErrorMsg).init(allocator),
.global_name_map = &global_name_map,
+ .errors = .{},
+ .decls = .{},
};
errdefer parser.arena.deinit();
@@ -555,10 +587,11 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
},
else => |e| return e,
};
+
return Module{
- .decls = parser.decls.toOwnedSlice(),
- .errors = parser.errors.toOwnedSlice(),
- .arena = parser.arena,
+ .decls = parser.decls.toOwnedSlice(allocator),
+ .errors = parser.errors.toOwnedSlice(allocator),
+ .arena = parser.arena.state,
};
}
@@ -567,8 +600,8 @@ const Parser = struct {
arena: std.heap.ArenaAllocator,
i: usize,
source: [:0]const u8,
- errors: std.ArrayList(ErrorMsg),
- decls: std.ArrayList(*Inst),
+ errors: std.ArrayListUnmanaged(ErrorMsg),
+ decls: std.ArrayListUnmanaged(*Inst),
global_name_map: *std.StringHashMap(usize),
const Body = struct {
@@ -893,8 +926,25 @@ const Parser = struct {
const ident = self.source[name_start..self.i];
const kv = map.get(ident) orelse {
const bad_name = self.source[name_start - 1 .. self.i];
- self.i = name_start - 1;
- return self.fail("unrecognized identifier: {}", .{bad_name});
+ const src = name_start - 1;
+ if (local_ref) {
+ self.i = src;
+ return self.fail("unrecognized identifier: {}", .{bad_name});
+ } else {
+ const name = try self.arena.allocator.create(Inst.Str);
+ name.* = .{
+ .base = .{ .src = src, .tag = Inst.Str.base_tag },
+ .positionals = .{ .bytes = ident },
+ .kw_args = .{},
+ };
+ const declref = try self.arena.allocator.create(Inst.DeclRef);
+ declref.* = .{
+ .base = .{ .src = src, .tag = Inst.DeclRef.base_tag },
+ .positionals = .{ .name = &name.base },
+ .kw_args = .{},
+ };
+ return &declref.base;
+ }
};
if (local_ref) {
return body_ctx.?.instructions.items[kv.value];
@@ -1065,6 +1115,24 @@ const EmitZIR = struct {
for (body.instructions) |inst| {
const new_inst = switch (inst.tag) {
.breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint),
+ .call => blk: {
+ const old_inst = inst.cast(ir.Inst.Call).?;
+ const new_inst = try self.arena.allocator.create(Inst.Call);
+
+ const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
+ for (args) |*elem, i| {
+ elem.* = try self.resolveInst(inst_table, old_inst.args.args[i]);
+ }
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.Call.base_tag },
+ .positionals = .{
+ .func = try self.resolveInst(inst_table, old_inst.args.func),
+ .args = args,
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
.unreach => try self.emitTrivial(inst.src, Inst.Unreachable),
.ret => try self.emitTrivial(inst.src, Inst.Return),
.constant => unreachable, // excluded from function bodies
src-self-hosted/c.zig
@@ -1,7 +0,0 @@
-pub usingnamespace @cImport({
- @cDefine("__STDC_CONSTANT_MACROS", "");
- @cDefine("__STDC_LIMIT_MACROS", "");
- @cInclude("inttypes.h");
- @cInclude("config.h");
- @cInclude("zig_llvm.h");
-});
src-self-hosted/codegen.zig
@@ -6,38 +6,24 @@ const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
const Target = std.Target;
-pub const ErrorMsg = struct {
- byte_offset: usize,
- msg: []const u8,
-};
-
-pub const Symbol = struct {
- errors: []ErrorMsg,
-
- pub fn deinit(self: *Symbol, allocator: *mem.Allocator) void {
- for (self.errors) |err| {
- allocator.free(err.msg);
- }
- allocator.free(self.errors);
- self.* = undefined;
- }
-};
-
-pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.ArrayList(u8)) !Symbol {
+pub fn generateSymbol(
+ typed_value: ir.TypedValue,
+ module: ir.Module,
+ code: *std.ArrayList(u8),
+ errors: *std.ArrayList(ir.ErrorMsg),
+) !void {
switch (typed_value.ty.zigTypeTag()) {
.Fn => {
- const index = typed_value.val.cast(Value.Payload.Function).?.index;
- const module_fn = module.fns[index];
+ const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
var function = Function{
.module = &module,
- .mod_fn = &module_fn,
+ .mod_fn = module_fn,
.code = code,
.inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator),
- .errors = std.ArrayList(ErrorMsg).init(code.allocator),
+ .errors = errors,
};
defer function.inst_table.deinit();
- defer function.errors.deinit();
for (module_fn.body.instructions) |inst| {
const new_inst = function.genFuncInst(inst) catch |err| switch (err) {
@@ -52,7 +38,7 @@ pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.
return Symbol{ .errors = function.errors.toOwnedSlice() };
},
- else => @panic("TODO implement generateSymbol for non-function types"),
+ else => @panic("TODO implement generateSymbol for non-function decls"),
}
}
@@ -61,7 +47,7 @@ const Function = struct {
mod_fn: *const ir.Module.Fn,
code: *std.ArrayList(u8),
inst_table: std.AutoHashMap(*ir.Inst, MCValue),
- errors: std.ArrayList(ErrorMsg),
+ errors: *std.ArrayList(ir.ErrorMsg),
const MCValue = union(enum) {
none,
@@ -78,6 +64,7 @@ const Function = struct {
fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue {
switch (inst.tag) {
.breakpoint => return self.genBreakpoint(inst.src),
+ .call => return self.genCall(inst.cast(ir.Inst.Call).?),
.unreach => return MCValue{ .unreach = {} },
.constant => unreachable, // excluded from function bodies
.assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
@@ -101,6 +88,13 @@ const Function = struct {
return .unreach;
}
+ fn genCall(self: *Function, inst: *ir.Inst.Call) !MCValue {
+ switch (self.module.target.cpu.arch) {
+ else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.module.target.cpu.arch}),
+ }
+ return .unreach;
+ }
+
fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue {
switch (self.module.target.cpu.arch) {
.i386, .x86_64 => {
@@ -140,6 +134,7 @@ const Function = struct {
fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void {
switch (self.module.target.cpu.arch) {
.i386, .x86_64 => {
+ // TODO x86 treats the operands as signed
if (amount <= std.math.maxInt(u8)) {
try self.code.resize(self.code.items.len + 2);
self.code.items[self.code.items.len - 2] = 0xeb;
@@ -433,14 +428,11 @@ const Function = struct {
fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } {
@setCold(true);
- const msg = try std.fmt.allocPrint(self.errors.allocator, format, args);
- {
- errdefer self.errors.allocator.free(msg);
- (try self.errors.addOne()).* = .{
- .byte_offset = src,
- .msg = msg,
- };
- }
+ try self.errors.ensureCapacity(self.errors.items.len + 1);
+ self.errors.appendAssumeCapacity(.{
+ .byte_offset = src,
+ .msg = try std.fmt.allocPrint(self.errors.allocator, format, args),
+ });
return error.CodegenFail;
}
};
src-self-hosted/compilation.zig
@@ -19,7 +19,6 @@ const AtomicOrder = builtin.AtomicOrder;
const Scope = @import("scope.zig").Scope;
const Decl = @import("decl.zig").Decl;
const ir = @import("ir.zig");
-const Visib = @import("visib.zig").Visib;
const Value = @import("value.zig").Value;
const Type = Value.Type;
const Span = errmsg.Span;
@@ -30,7 +29,11 @@ const link = @import("link.zig").link;
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
const CInt = @import("c_int.zig").CInt;
const fs = std.fs;
-const util = @import("util.zig");
+
+pub const Visib = enum {
+ Private,
+ Pub,
+};
const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
@@ -45,7 +48,7 @@ pub const ZigCompiler = struct {
native_libc: event.Future(LibCInstallation),
- var lazy_init_targets = std.once(util.initializeAllTargets);
+ var lazy_init_targets = std.once(initializeAllTargets);
pub fn init(allocator: *Allocator) !ZigCompiler {
lazy_init_targets.call();
@@ -119,6 +122,8 @@ pub const LlvmHandle = struct {
};
pub const Compilation = struct {
+ pub const FnLinkSet = std.TailQueue(?*Value.Fn);
+
zig_compiler: *ZigCompiler,
name: ArrayListSentineled(u8, 0),
llvm_triple: ArrayListSentineled(u8, 0),
@@ -152,8 +157,6 @@ pub const Compilation = struct {
/// it uses an optional pointer so that tombstone removals are possible
fn_link_set: event.Locked(FnLinkSet) = event.Locked(FnLinkSet).init(FnLinkSet.init()),
- pub const FnLinkSet = std.TailQueue(?*Value.Fn);
-
link_libs_list: ArrayList(*LinkLib),
libc_link_lib: ?*LinkLib = null,
@@ -361,8 +364,7 @@ pub const Compilation = struct {
return comp;
} else if (await frame) |_| unreachable else |err| return err;
}
-
- async fn createAsync(
+ fn createAsync(
out_comp: *?*Compilation,
zig_compiler: *ZigCompiler,
name: []const u8,
@@ -372,7 +374,7 @@ pub const Compilation = struct {
build_mode: builtin.Mode,
is_static: bool,
zig_lib_dir: []const u8,
- ) !void {
+ ) callconv(.Async) !void {
const allocator = zig_compiler.allocator;
// TODO merge this line with stage2.zig crossTargetToTarget
@@ -442,8 +444,8 @@ pub const Compilation = struct {
}
comp.name = try ArrayListSentineled(u8, 0).init(comp.arena(), name);
- comp.llvm_triple = try util.getLLVMTriple(comp.arena(), target);
- comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple);
+ comp.llvm_triple = try getLLVMTriple(comp.arena(), target);
+ comp.llvm_target = try llvmTargetFromTriple(comp.llvm_triple);
comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });
const opt_level = switch (build_mode) {
@@ -726,8 +728,7 @@ pub const Compilation = struct {
fn start(self: *Compilation) void {
self.main_loop_future.resolve();
}
-
- async fn mainLoop(self: *Compilation) void {
+ fn mainLoop(self: *Compilation) callconv(.Async) void {
// wait until start() is called
_ = self.main_loop_future.get();
@@ -790,8 +791,7 @@ pub const Compilation = struct {
build_result = group.wait();
}
}
-
- async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void {
+ fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) callconv(.Async) BuildError!void {
const tree_scope = blk: {
const source_code = fs.cwd().readFileAlloc(
self.gpa(),
@@ -964,15 +964,14 @@ pub const Compilation = struct {
try link(self);
}
}
-
/// caller takes ownership of resulting Code
- async fn genAndAnalyzeCode(
+ fn genAndAnalyzeCode(
comp: *Compilation,
tree_scope: *Scope.AstTree,
scope: *Scope,
node: *ast.Node,
expected_type: ?*Type,
- ) !*ir.Code {
+ ) callconv(.Async) !*ir.Code {
const unanalyzed_code = try ir.gen(
comp,
node,
@@ -1000,13 +999,12 @@ pub const Compilation = struct {
return analyzed_code;
}
-
- async fn addCompTimeBlock(
+ fn addCompTimeBlock(
comp: *Compilation,
tree_scope: *Scope.AstTree,
scope: *Scope,
comptime_node: *ast.Node.Comptime,
- ) BuildError!void {
+ ) callconv(.Async) BuildError!void {
const void_type = Type.Void.get(comp);
defer void_type.base.base.deref(comp);
@@ -1024,12 +1022,11 @@ pub const Compilation = struct {
};
analyzed_code.destroy(comp.gpa());
}
-
- async fn addTopLevelDecl(
+ fn addTopLevelDecl(
self: *Compilation,
decl: *Decl,
locked_table: *Decl.Table,
- ) BuildError!void {
+ ) callconv(.Async) BuildError!void {
const is_export = decl.isExported(decl.tree_scope.tree);
if (is_export) {
@@ -1065,11 +1062,10 @@ pub const Compilation = struct {
try self.prelink_group.call(addCompileErrorAsync, .{ self, msg });
}
-
- async fn addCompileErrorAsync(
+ fn addCompileErrorAsync(
self: *Compilation,
msg: *Msg,
- ) BuildError!void {
+ ) callconv(.Async) BuildError!void {
errdefer msg.destroy();
const compile_errors = self.compile_errors.acquire();
@@ -1077,8 +1073,7 @@ pub const Compilation = struct {
try compile_errors.value.append(msg);
}
-
- async fn verifyUniqueSymbol(self: *Compilation, decl: *Decl) BuildError!void {
+ fn verifyUniqueSymbol(self: *Compilation, decl: *Decl) callconv(.Async) BuildError!void {
const exported_symbol_names = self.exported_symbol_names.acquire();
defer exported_symbol_names.release();
@@ -1129,8 +1124,7 @@ pub const Compilation = struct {
}
return link_lib;
}
-
- async fn startFindingNativeLibC(self: *Compilation) void {
+ fn startFindingNativeLibC(self: *Compilation) callconv(.Async) void {
event.Loop.startCpuBoundOperation();
// we don't care if it fails, we're just trying to kick off the future resolution
_ = self.zig_compiler.getNativeLibC() catch return;
@@ -1234,7 +1228,7 @@ pub const Compilation = struct {
}
/// This declaration has been blessed as going into the final code generation.
- pub async fn resolveDecl(comp: *Compilation, decl: *Decl) BuildError!void {
+ pub fn resolveDecl(comp: *Compilation, decl: *Decl) callconv(.Async) BuildError!void {
if (decl.resolution.start()) |ptr| return ptr.*;
decl.resolution.data = try generateDecl(comp, decl);
@@ -1335,8 +1329,7 @@ fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
try comp.prelink_group.call(codegen.renderToLlvm, .{ comp, fn_val, analyzed_code });
try comp.prelink_group.call(addFnToLinkSet, .{ comp, fn_val });
}
-
-async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.BuildError!void {
+fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) callconv(.Async) Compilation.BuildError!void {
fn_val.base.ref();
defer fn_val.base.deref(comp);
@@ -1432,3 +1425,33 @@ fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void {
fn_decl.value = .{ .FnProto = fn_proto_val };
symbol_name_consumed = true;
}
+
+pub fn llvmTargetFromTriple(triple: [:0]const u8) !*llvm.Target {
+ var result: *llvm.Target = undefined;
+ var err_msg: [*:0]u8 = undefined;
+ if (llvm.GetTargetFromTriple(triple, &result, &err_msg) != 0) {
+ std.debug.warn("triple: {s} error: {s}\n", .{ triple, err_msg });
+ return error.UnsupportedTarget;
+ }
+ return result;
+}
+
+pub fn initializeAllTargets() void {
+ llvm.InitializeAllTargets();
+ llvm.InitializeAllTargetInfos();
+ llvm.InitializeAllTargetMCs();
+ llvm.InitializeAllAsmPrinters();
+ llvm.InitializeAllAsmParsers();
+}
+
+pub fn getLLVMTriple(allocator: *std.mem.Allocator, target: std.Target) ![:0]u8 {
+ var result = try std.ArrayListSentineled(u8, 0).initSize(allocator, 0);
+ defer result.deinit();
+
+ try result.outStream().print(
+ "{}-unknown-{}-{}",
+ .{ @tagName(target.cpu.arch), @tagName(target.os.tag), @tagName(target.abi) },
+ );
+
+ return result.toOwnedSlice();
+}
src-self-hosted/decl.zig
@@ -1,102 +0,0 @@
-const std = @import("std");
-const Allocator = mem.Allocator;
-const mem = std.mem;
-const ast = std.zig.ast;
-const Visib = @import("visib.zig").Visib;
-const event = std.event;
-const Value = @import("value.zig").Value;
-const Token = std.zig.Token;
-const errmsg = @import("errmsg.zig");
-const Scope = @import("scope.zig").Scope;
-const Compilation = @import("compilation.zig").Compilation;
-
-pub const Decl = struct {
- id: Id,
- name: []const u8,
- visib: Visib,
- resolution: event.Future(Compilation.BuildError!void),
- parent_scope: *Scope,
-
- // TODO when we destroy the decl, deref the tree scope
- tree_scope: *Scope.AstTree,
-
- pub const Table = std.StringHashMap(*Decl);
-
- pub fn cast(base: *Decl, comptime T: type) ?*T {
- if (base.id != @field(Id, @typeName(T))) return null;
- return @fieldParentPtr(T, "base", base);
- }
-
- pub fn isExported(base: *const Decl, tree: *ast.Tree) bool {
- switch (base.id) {
- .Fn => {
- const fn_decl = @fieldParentPtr(Fn, "base", base);
- return fn_decl.isExported(tree);
- },
- else => return false,
- }
- }
-
- pub fn getSpan(base: *const Decl) errmsg.Span {
- switch (base.id) {
- .Fn => {
- const fn_decl = @fieldParentPtr(Fn, "base", base);
- const fn_proto = fn_decl.fn_proto;
- const start = fn_proto.fn_token;
- const end = fn_proto.name_token orelse start;
- return errmsg.Span{
- .first = start,
- .last = end + 1,
- };
- },
- else => @panic("TODO"),
- }
- }
-
- pub fn findRootScope(base: *const Decl) *Scope.Root {
- return base.parent_scope.findRoot();
- }
-
- pub const Id = enum {
- Var,
- Fn,
- CompTime,
- };
-
- pub const Var = struct {
- base: Decl,
- };
-
- pub const Fn = struct {
- base: Decl,
- value: union(enum) {
- Unresolved,
- Fn: *Value.Fn,
- FnProto: *Value.FnProto,
- },
- fn_proto: *ast.Node.FnProto,
-
- pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 {
- return if (self.fn_proto.extern_export_inline_token) |tok_index| x: {
- const token = tree.tokens.at(tok_index);
- break :x switch (token.id) {
- .Extern => tree.tokenSlicePtr(token),
- else => null,
- };
- } else null;
- }
-
- pub fn isExported(self: Fn, tree: *ast.Tree) bool {
- if (self.fn_proto.extern_export_inline_token) |tok_index| {
- const token = tree.tokens.at(tok_index);
- return token.id == .Keyword_export;
- } else {
- return false;
- }
- }
- };
-
- pub const CompTime = struct {
- base: Decl,
- };
-};
src-self-hosted/ir.zig
@@ -1,12 +1,16 @@
const std = @import("std");
const mem = std.mem;
const Allocator = std.mem.Allocator;
+const ArrayListUnmanaged = std.ArrayListUnmanaged;
+const LinkedList = std.TailQueue;
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
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");
pub const text = @import("ir/text.zig");
@@ -25,6 +29,7 @@ pub const Inst = struct {
assembly,
bitcast,
breakpoint,
+ call,
cmp,
condbr,
constant,
@@ -84,6 +89,15 @@ pub const Inst = struct {
args: void,
};
+ pub const Call = struct {
+ pub const base_tag = Tag.call;
+ base: Inst,
+ args: struct {
+ func: *Inst,
+ args: []const *Inst,
+ },
+ };
+
pub const Cmp = struct {
pub const base_tag = Tag.cmp;
@@ -158,170 +172,416 @@ pub const TypedValue = struct {
val: Value,
};
+fn swapRemoveElem(allocator: *Allocator, comptime T: type, item: T, list: *ArrayListUnmanaged(T)) void {
+ var i: usize = 0;
+ while (i < list.items.len) {
+ if (list.items[i] == item) {
+ list.swapRemove(allocator, i);
+ continue;
+ }
+ i += 1;
+ }
+}
+
pub const Module = struct {
- exports: []Export,
- errors: []ErrorMsg,
- arena: std.heap.ArenaAllocator,
- fns: []Fn,
- target: Target,
- link_mode: std.builtin.LinkMode,
- output_mode: std.builtin.OutputMode,
- object_format: std.Target.ObjectFormat,
+ /// 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,
+ failed_decls: ArrayListUnmanaged(*Decl) = .{},
+ failed_fns: ArrayListUnmanaged(*Fn) = .{},
+ failed_files: ArrayListUnmanaged(*Scope.ZIRModule) = .{},
+ decl_table: std.AutoHashMap(Decl.Hash, *Decl),
optimize_mode: std.builtin.Mode,
-
- pub const Export = struct {
- name: []const u8,
- typed_value: TypedValue,
+ link_error_flags: link.ElfFile.ErrorFlags = .{},
+
+ pub const Decl = struct {
+ /// Contains the memory for `typed_value` and this `Decl` itself.
+ /// If the Decl is a function, also contains that memory.
+ /// If the decl has any export nodes, also contains that memory.
+ /// TODO look into using a more memory efficient arena that will cost less bytes per decl.
+ /// This one has a minimum allocation of 4096 bytes.
+ arena: std.heap.ArenaAllocator.State,
+ /// 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.
+ name: [*:0]const u8,
+ /// It's rare for a decl to be exported, and it's even rarer for a decl to be mapped to more
+ /// than one export, so we use a linked list to save memory.
+ export_node: ?*LinkedList(std.builtin.ExportOptions).Node = null,
+ /// 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,
+ /// 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: union(enum) {
+ in_progress,
+ failure: ErrorMsg,
+ success: TypedValue,
+ },
+ /// The direct container of the Decl. This field will need to get more fleshed out when
+ /// self-hosted supports proper struct types and Zig AST => ZIR.
+ scope: *Scope.ZIRModule,
+
+ pub fn destroy(self: *Decl, allocator: *Allocator) void {
+ var arena = self.arena.promote(allocator);
+ arena.deinit();
+ }
+
+ pub const Hash = [16]u8;
+
+ /// 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.
+ var out: Hash = undefined;
+ std.crypto.Blake3.hash(mem.spanZ(u8, self.name), &out);
+ return out;
+ }
};
+ /// Memory is managed by the arena of the owning Decl.
pub const Fn = struct {
- analysis_status: enum { in_progress, failure, success },
- body: Body,
fn_type: Type,
+ analysis: union(enum) {
+ in_progress: *Analysis,
+ failure: ErrorMsg,
+ 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 managed by the general purpose allocator.
+ pub const Analysis = struct {
+ inner_block: Scope.Block,
+ /// null value means a semantic analysis error happened.
+ inst_table: std.AutoHashMap(*text.Inst, ?*Inst),
+ };
+ };
+
+ 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);
+ }
+
+ 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,
+ contents: union(enum) {
+ unloaded,
+ parse_failure: ParseFailure,
+ success: Contents,
+ },
+ pub const ParseFailure = struct {
+ source: [:0]const u8,
+ errors: []ErrorMsg,
+
+ pub fn deinit(self: *ParseFailure, allocator: *Allocator) void {
+ allocator.free(self.errors);
+ allocator.free(source);
+ }
+ };
+ pub const Contents = struct {
+ source: [:0]const u8,
+ module: *text.Module,
+ };
+
+ pub fn deinit(self: *ZIRModule, allocator: *Allocator) void {
+ switch (self.contents) {
+ .unloaded => {},
+ .parse_failure => |pf| pd.deinit(allocator),
+ .success => |contents| {
+ allocator.free(contents.source);
+ contents.src_zir_module.deinit(allocator);
+ },
+ }
+ self.* = undefined;
+ }
+
+ pub fn loadContents(self: *ZIRModule, allocator: *Allocator) !*Contents {
+ if (self.contents) |contents| return contents;
+
+ const max_size = std.math.maxInt(u32);
+ const source = try self.root_pkg_dir.readFileAllocOptions(allocator, self.root_src_path, max_size, 1, 0);
+ errdefer allocator.free(source);
+
+ var errors = std.ArrayList(ErrorMsg).init(allocator);
+ defer errors.deinit();
+
+ var src_zir_module = try text.parse(allocator, source, &errors);
+ errdefer src_zir_module.deinit(allocator);
+
+ switch (self.contents) {
+ .parse_failure => |pf| pf.deinit(allocator),
+ .unloaded => {},
+ .success => unreachable,
+ }
+
+ if (errors.items.len != 0) {
+ self.contents = .{ .parse_failure = errors.toOwnedSlice() };
+ return error.ParseFailure;
+ }
+ self.contents = .{
+ .success = .{
+ .source = source,
+ .module = src_zir_module,
+ },
+ };
+ return &self.contents.success;
+ }
+ };
+
+ /// 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,
+ instructions: ArrayListUnmanaged(*Inst),
+ };
+
+ /// 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,
+ };
};
pub const Body = struct {
instructions: []*Inst,
};
- pub fn deinit(self: *Module, allocator: *Allocator) void {
- allocator.free(self.exports);
+ 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 mem.dupe(u8, &arena.allocator, sub_file_path),
+ .msg = try mem.dupe(u8, &arena.allocator, 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;
allocator.free(self.errors);
- for (self.fns) |f| {
- allocator.free(f.body.instructions);
+ {
+ var it = self.decl_table.iterator();
+ while (it.next()) |kv| {
+ kv.value.destroy(allocator);
+ }
+ self.decl_table.deinit();
}
- allocator.free(self.fns);
- self.arena.deinit();
+ self.root_pkg.destroy();
+ self.root_scope.deinit();
self.* = undefined;
}
-};
-pub const ErrorMsg = struct {
- byte_offset: usize,
- msg: []const u8,
-};
+ pub fn target(self: Module) std.Target {
+ return self.bin_file.options.target;
+ }
-pub const AnalyzeOptions = struct {
- target: Target,
- output_mode: std.builtin.OutputMode,
- link_mode: std.builtin.LinkMode,
- object_format: ?std.Target.ObjectFormat = null,
- optimize_mode: std.builtin.Mode,
-};
+ /// 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,
+ };
-pub fn analyze(allocator: *Allocator, old_module: text.Module, options: AnalyzeOptions) !Module {
- var ctx = Analyze{
- .allocator = allocator,
- .arena = std.heap.ArenaAllocator.init(allocator),
- .old_module = &old_module,
- .errors = std.ArrayList(ErrorMsg).init(allocator),
- .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator),
- .exports = std.ArrayList(Module.Export).init(allocator),
- .fns = std.ArrayList(Module.Fn).init(allocator),
- .target = options.target,
- .optimize_mode = options.optimize_mode,
- .link_mode = options.link_mode,
- .output_mode = options.output_mode,
- };
- defer ctx.errors.deinit();
- defer ctx.decl_table.deinit();
- defer ctx.exports.deinit();
- defer ctx.fns.deinit();
- errdefer ctx.arena.deinit();
-
- ctx.analyzeRoot() catch |err| switch (err) {
- error.AnalysisFail => {
- assert(ctx.errors.items.len != 0);
- },
- else => |e| return e,
- };
- return Module{
- .exports = ctx.exports.toOwnedSlice(),
- .errors = ctx.errors.toOwnedSlice(),
- .fns = ctx.fns.toOwnedSlice(),
- .arena = ctx.arena,
- .target = ctx.target,
- .link_mode = ctx.link_mode,
- .output_mode = ctx.output_mode,
- .object_format = options.object_format orelse ctx.target.getObjectFormat(),
- .optimize_mode = ctx.optimize_mode,
- };
-}
+ try self.bin_file.flush();
+ self.link_error_flags = self.bin_file.error_flags;
+ }
-const Analyze = struct {
- allocator: *Allocator,
- arena: std.heap.ArenaAllocator,
- old_module: *const text.Module,
- errors: std.ArrayList(ErrorMsg),
- decl_table: std.AutoHashMap(*text.Inst, NewDecl),
- exports: std.ArrayList(Module.Export),
- fns: std.ArrayList(Module.Fn),
- target: Target,
- link_mode: std.builtin.LinkMode,
- optimize_mode: std.builtin.Mode,
- output_mode: std.builtin.OutputMode,
+ pub fn totalErrorCount(self: *Module) usize {
+ return self.failed_decls.items.len +
+ self.failed_fns.items.len +
+ self.failed_decls.items.len +
+ @boolToInt(self.link_error_flags.no_entry_point_found);
+ }
- const NewDecl = struct {
- /// null means a semantic analysis error happened
- ptr: ?*Inst,
- };
+ pub fn getAllErrorsAlloc(self: *Module) !AllErrors {
+ var arena = std.heap.ArenaAllocator.init(self.allocator);
+ errdefer arena.deinit();
- const NewInst = struct {
- /// null means a semantic analysis error happened
- ptr: ?*Inst,
- };
+ var errors = std.ArrayList(AllErrors.Message).init(self.allocator);
+ defer errors.deinit();
- const Fn = struct {
- /// Index into Module fns array
- fn_index: usize,
- inner_block: Block,
- inst_table: std.AutoHashMap(*text.Inst, NewInst),
- };
+ for (self.failed_files.items) |scope| {
+ const source = scope.parse_failure.source;
+ for (scope.parse_failure.errors) |parse_error| {
+ AllErrors.add(&arena, &errors, scope.sub_file_path, source, parse_error);
+ }
+ }
- const Block = struct {
- func: *Fn,
- instructions: std.ArrayList(*Inst),
- };
+ for (self.failed_fns.items) |func| {
+ const source = func.scope.success.source;
+ for (func.analysis.failure) |err_msg| {
+ AllErrors.add(&arena, &errors, func.scope.sub_file_path, source, err_msg);
+ }
+ }
+
+ for (self.failed_decls.items) |decl| {
+ const source = decl.scope.success.source;
+ for (decl.analysis.failure) |err_msg| {
+ 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.module.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 mem.dupe(&arena.allocator, AllErrors.Message, errors.items),
+ };
+ }
const InnerError = error{ OutOfMemory, AnalysisFail };
- fn analyzeRoot(self: *Analyze) !void {
- for (self.old_module.decls) |decl| {
+ 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.
+ const contents = blk: {
+ // Clear parse errors.
+ swapRemoveElem(self.allocator, *Scope.ZIRModule, root_scope, self.failed_files);
+ try self.failed_files.ensureCapacity(self.allocator, self.failed_files.items.len + 1);
+ break :blk root_scope.loadContents(self.allocator) catch |err| switch (err) {
+ error.ParseFailure => {
+ self.failed_files.appendAssumeCapacity(root_scope);
+ return error.AnalysisFail;
+ },
+ else => |e| return e,
+ };
+ };
+ for (contents.module.decls) |decl| {
if (decl.cast(text.Inst.Export)) |export_inst| {
- try analyzeExport(self, null, export_inst);
+ try analyzeExport(self, &root_scope.base, export_inst);
}
}
}
- fn resolveInst(self: *Analyze, opt_block: ?*Block, old_inst: *text.Inst) InnerError!*Inst {
- if (opt_block) |block| {
+ fn resolveDecl(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Decl {
+ const hash = old_inst.fullyQualifiedNameHash();
+ if (self.decl_table.get(hash)) |kv| {
+ return kv.value;
+ } else {
+ const new_decl = blk: {
+ var decl_arena = std.heap.ArenaAllocator.init(self.allocator);
+ errdefer decl_arena.deinit();
+ const new_decl = try decl_arena.allocator.create(Decl);
+ const name = try mem.dupeZ(&decl_arena.allocator, u8, old_inst.name);
+ new_decl.* = .{
+ .arena = decl_arena.state,
+ .name = name,
+ .src = old_inst.src,
+ .analysis = .in_progress,
+ .scope = scope.findZIRModule(),
+ };
+ try self.decl_table.putNoClobber(hash, new_decl);
+ break :blk new_decl;
+ };
+
+ var decl_scope: Scope.DeclAnalysis = .{ .decl = new_decl };
+ const typed_value = self.analyzeInstConst(&decl_scope.base, old_inst) catch |err| switch (err) {
+ error.AnalysisFail => return error.AnalysisFail,
+ else => |e| return e,
+ };
+ new_decl.analysis = .{ .success = typed_value };
+ if (try self.bin_file.updateDecl(self.*, typed_value, new_decl.export_node, hash)) |err_msg| {
+ new_decl.analysis = .{ .success = typed_value };
+ } else |err| {
+ return err;
+ }
+ return new_decl;
+ }
+ }
+
+ fn resolveInst(self: *Module, scope: *Scope, old_inst: *text.Inst) InnerError!*Inst {
+ if (scope.cast(Scope.Block)) |block| {
if (block.func.inst_table.get(old_inst)) |kv| {
return kv.value.ptr orelse return error.AnalysisFail;
}
}
- if (self.decl_table.get(old_inst)) |kv| {
- return kv.value.ptr orelse return error.AnalysisFail;
- } else {
- const new_inst = self.analyzeInst(null, old_inst) catch |err| switch (err) {
- error.AnalysisFail => {
- try self.decl_table.putNoClobber(old_inst, .{ .ptr = null });
- return error.AnalysisFail;
- },
- else => |e| return e,
- };
- try self.decl_table.putNoClobber(old_inst, .{ .ptr = new_inst });
- return new_inst;
- }
+ const decl = try self.resolveDecl(scope, old_inst);
+ const decl_ref = try self.analyzeDeclRef(scope, old_inst.src, decl);
+ return self.analyzeDeref(scope, old_inst.src, decl_ref);
}
- fn requireRuntimeBlock(self: *Analyze, block: ?*Block, src: usize) !*Block {
- return block orelse return self.fail(src, "instruction illegal outside function body", .{});
+ 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: *Analyze, block: ?*Block, old_inst: *text.Inst) InnerError!TypedValue {
- const new_inst = try self.resolveInst(block, old_inst);
+ 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(new_inst);
return TypedValue{
.ty = new_inst.ty,
@@ -329,60 +589,67 @@ const Analyze = struct {
};
}
- fn resolveConstValue(self: *Analyze, base: *Inst) !Value {
+ fn resolveConstValue(self: *Module, scope: *Scope, base: *Inst) !Value {
return (try self.resolveDefinedValue(base)) orelse
- return self.fail(base.src, "unable to resolve comptime value", .{});
+ return self.fail(scope, base.src, "unable to resolve comptime value", .{});
}
- fn resolveDefinedValue(self: *Analyze, base: *Inst) !?Value {
+ fn resolveDefinedValue(self: *Module, scope: *Scope, base: *Inst) !?Value {
if (base.value()) |val| {
if (val.isUndef()) {
- return self.fail(base.src, "use of undefined value here causes undefined behavior", .{});
+ return self.fail(scope, base.src, "use of undefined value here causes undefined behavior", .{});
}
return val;
}
return null;
}
- fn resolveConstString(self: *Analyze, block: ?*Block, old_inst: *text.Inst) ![]u8 {
- const new_inst = try self.resolveInst(block, old_inst);
+ 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(block, wanted_type, new_inst);
+ const coerced_inst = try self.coerce(scope, wanted_type, new_inst);
const val = try self.resolveConstValue(coerced_inst);
return val.toAllocatedBytes(&self.arena.allocator);
}
- fn resolveType(self: *Analyze, block: ?*Block, old_inst: *text.Inst) !Type {
- const new_inst = try self.resolveInst(block, old_inst);
+ 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(block, wanted_type, new_inst);
+ const coerced_inst = try self.coerce(scope, wanted_type, new_inst);
const val = try self.resolveConstValue(coerced_inst);
return val.toType();
}
- fn analyzeExport(self: *Analyze, block: ?*Block, export_inst: *text.Inst.Export) !void {
- const symbol_name = try self.resolveConstString(block, export_inst.positionals.symbol_name);
- const typed_value = try self.resolveInstConst(block, export_inst.positionals.value);
-
- switch (typed_value.ty.zigTypeTag()) {
- .Fn => {},
- else => return self.fail(
- export_inst.positionals.value.src,
- "unable to export type '{}'",
- .{typed_value.ty},
- ),
+ fn analyzeExport(self: *Module, scope: *Scope, export_inst: *text.Inst.Export) !void {
+ const symbol_name = try self.resolveConstString(scope, export_inst.positionals.symbol_name);
+ const decl = try self.resolveDecl(scope, export_inst.positionals.value);
+
+ switch (decl.analysis) {
+ .in_progress => unreachable,
+ .failure => return error.AnalysisFail,
+ .success => |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},
+ ),
+ },
}
- try self.exports.append(.{
- .name = symbol_name,
- .typed_value = typed_value,
- .src = export_inst.base.src,
- });
+ const Node = LinkedList(std.builtin.ExportOptions).Node;
+ export_node = try decl.arena.promote(self.allocator).allocator.create(Node);
+ export_node.* = .{ .data = .{ .name = symbol_name } };
+ decl.export_node = export_node;
+
+ // TODO Avoid double update in the case of exporting a decl that we just created.
+ self.bin_file.updateDeclExports();
}
/// TODO should not need the cast on the last parameter at the callsites
fn addNewInstArgs(
- self: *Analyze,
- block: *Block,
+ self: *Module,
+ block: *Scope.Block,
src: usize,
ty: Type,
comptime T: type,
@@ -393,7 +660,7 @@ const Analyze = struct {
return &inst.base;
}
- fn addNewInst(self: *Analyze, block: *Block, src: usize, ty: Type, comptime T: type) !*T {
+ fn addNewInst(self: *Module, block: *Scope.Block, src: usize, ty: Type, comptime T: type) !*T {
const inst = try self.arena.allocator.create(T);
inst.* = .{
.base = .{
@@ -403,11 +670,11 @@ const Analyze = struct {
},
.args = undefined,
};
- try block.instructions.append(&inst.base);
+ try block.instructions.append(self.allocator, &inst.base);
return inst;
}
- fn constInst(self: *Analyze, src: usize, typed_value: TypedValue) !*Inst {
+ fn constInst(self: *Module, src: usize, typed_value: TypedValue) !*Inst {
const const_inst = try self.arena.allocator.create(Inst.Constant);
const_inst.* = .{
.base = .{
@@ -420,7 +687,7 @@ const Analyze = struct {
return &const_inst.base;
}
- fn constStr(self: *Analyze, src: usize, str: []const u8) !*Inst {
+ fn constStr(self: *Module, src: usize, str: []const u8) !*Inst {
const array_payload = try self.arena.allocator.create(Type.Payload.Array_u8_Sentinel0);
array_payload.* = .{ .len = str.len };
@@ -436,35 +703,35 @@ const Analyze = struct {
});
}
- fn constType(self: *Analyze, src: usize, ty: Type) !*Inst {
+ fn constType(self: *Module, src: usize, ty: Type) !*Inst {
return self.constInst(src, .{
.ty = Type.initTag(.type),
.val = try ty.toValue(&self.arena.allocator),
});
}
- fn constVoid(self: *Analyze, src: usize) !*Inst {
+ fn constVoid(self: *Module, src: usize) !*Inst {
return self.constInst(src, .{
.ty = Type.initTag(.void),
.val = Value.initTag(.the_one_possible_value),
});
}
- fn constUndef(self: *Analyze, src: usize, ty: Type) !*Inst {
+ fn constUndef(self: *Module, src: usize, ty: Type) !*Inst {
return self.constInst(src, .{
.ty = ty,
.val = Value.initTag(.undef),
});
}
- fn constBool(self: *Analyze, src: usize, v: bool) !*Inst {
+ fn constBool(self: *Module, src: usize, v: bool) !*Inst {
return self.constInst(src, .{
.ty = Type.initTag(.bool),
.val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)],
});
}
- fn constIntUnsigned(self: *Analyze, src: usize, ty: Type, int: u64) !*Inst {
+ fn constIntUnsigned(self: *Module, src: usize, ty: Type, int: u64) !*Inst {
const int_payload = try self.arena.allocator.create(Value.Payload.Int_u64);
int_payload.* = .{ .int = int };
@@ -474,7 +741,7 @@ const Analyze = struct {
});
}
- fn constIntSigned(self: *Analyze, src: usize, ty: Type, int: i64) !*Inst {
+ fn constIntSigned(self: *Module, src: usize, ty: Type, int: i64) !*Inst {
const int_payload = try self.arena.allocator.create(Value.Payload.Int_i64);
int_payload.* = .{ .int = int };
@@ -484,7 +751,7 @@ const Analyze = struct {
});
}
- fn constIntBig(self: *Analyze, src: usize, ty: Type, big_int: BigIntConst) !*Inst {
+ fn constIntBig(self: *Module, 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(src, ty, x);
@@ -513,9 +780,18 @@ const Analyze = struct {
});
}
- fn analyzeInst(self: *Analyze, block: ?*Block, old_inst: *text.Inst) InnerError!*Inst {
+ 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(block, 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).?),
.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.
@@ -526,53 +802,118 @@ const Analyze = struct {
const big_int = old_inst.cast(text.Inst.Int).?.positionals.int;
return self.constIntBig(old_inst.src, Type.initTag(.comptime_int), big_int);
},
- .ptrtoint => return self.analyzeInstPtrToInt(block, old_inst.cast(text.Inst.PtrToInt).?),
- .fieldptr => return self.analyzeInstFieldPtr(block, old_inst.cast(text.Inst.FieldPtr).?),
- .deref => return self.analyzeInstDeref(block, old_inst.cast(text.Inst.Deref).?),
- .as => return self.analyzeInstAs(block, old_inst.cast(text.Inst.As).?),
- .@"asm" => return self.analyzeInstAsm(block, old_inst.cast(text.Inst.Asm).?),
- .@"unreachable" => return self.analyzeInstUnreachable(block, old_inst.cast(text.Inst.Unreachable).?),
- .@"return" => return self.analyzeInstRet(block, old_inst.cast(text.Inst.Return).?),
- .@"fn" => return self.analyzeInstFn(block, old_inst.cast(text.Inst.Fn).?),
+ .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).?),
+ // TODO postpone function analysis until later
+ .@"fn" => return self.analyzeInstFn(scope, old_inst.cast(text.Inst.Fn).?),
.@"export" => {
- try self.analyzeExport(block, old_inst.cast(text.Inst.Export).?);
+ try self.analyzeExport(scope, old_inst.cast(text.Inst.Export).?);
return self.constVoid(old_inst.src);
},
.primitive => return self.analyzeInstPrimitive(old_inst.cast(text.Inst.Primitive).?),
- .fntype => return self.analyzeInstFnType(block, old_inst.cast(text.Inst.FnType).?),
- .intcast => return self.analyzeInstIntCast(block, old_inst.cast(text.Inst.IntCast).?),
- .bitcast => return self.analyzeInstBitCast(block, old_inst.cast(text.Inst.BitCast).?),
- .elemptr => return self.analyzeInstElemPtr(block, old_inst.cast(text.Inst.ElemPtr).?),
- .add => return self.analyzeInstAdd(block, old_inst.cast(text.Inst.Add).?),
- .cmp => return self.analyzeInstCmp(block, old_inst.cast(text.Inst.Cmp).?),
- .condbr => return self.analyzeInstCondBr(block, old_inst.cast(text.Inst.CondBr).?),
- .isnull => return self.analyzeInstIsNull(block, old_inst.cast(text.Inst.IsNull).?),
- .isnonnull => return self.analyzeInstIsNonNull(block, old_inst.cast(text.Inst.IsNonNull).?),
+ .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: *Analyze, block: ?*Block, inst: *text.Inst.Breakpoint) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(block, inst.base.src);
+ 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 analyzeInstFn(self: *Analyze, block: ?*Block, fn_inst: *text.Inst.Fn) InnerError!*Inst {
- const fn_type = try self.resolveType(block, fn_inst.positionals.fn_type);
+ 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 self.arena.allocator.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);
var new_func: Fn = .{
.fn_index = self.fns.items.len,
.inner_block = .{
.func = undefined,
- .instructions = std.ArrayList(*Inst).init(self.allocator),
+ .instructions = .{},
},
- .inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator),
+ .inst_table = std.AutoHashMap(*text.Inst, ?*Inst).init(self.allocator),
};
new_func.inner_block.func = &new_func;
defer new_func.inner_block.instructions.deinit();
defer new_func.inst_table.deinit();
// Don't hang on to a reference to this when analyzing body instructions, since the memory
// could become invalid.
- (try self.fns.addOne()).* = .{
+ (try self.fns.addOne(self.allocator)).* = .{
.analysis_status = .in_progress,
.fn_type = fn_type,
.body = undefined,
@@ -593,8 +934,15 @@ const Analyze = struct {
});
}
- fn analyzeInstFnType(self: *Analyze, block: ?*Block, fntype: *text.Inst.FnType) InnerError!*Inst {
- const return_type = try self.resolveType(block, fntype.positionals.return_type);
+ 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(fntype.base.src, Type.initTag(.fn_noreturn_no_args));
+ }
if (return_type.zigTypeTag() == .NoReturn and
fntype.positionals.param_types.len == 0 and
@@ -610,37 +958,37 @@ const Analyze = struct {
return self.constType(fntype.base.src, Type.initTag(.fn_ccc_void_no_args));
}
- return self.fail(fntype.base.src, "TODO implement fntype instruction more", .{});
+ return self.fail(scope, fntype.base.src, "TODO implement fntype instruction more", .{});
}
- fn analyzeInstPrimitive(self: *Analyze, primitive: *text.Inst.Primitive) InnerError!*Inst {
+ fn analyzeInstPrimitive(self: *Module, primitive: *text.Inst.Primitive) InnerError!*Inst {
return self.constType(primitive.base.src, primitive.positionals.tag.toType());
}
- fn analyzeInstAs(self: *Analyze, block: ?*Block, as: *text.Inst.As) InnerError!*Inst {
- const dest_type = try self.resolveType(block, as.positionals.dest_type);
- const new_inst = try self.resolveInst(block, as.positionals.value);
- return self.coerce(block, dest_type, new_inst);
+ 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: *Analyze, block: ?*Block, ptrtoint: *text.Inst.PtrToInt) InnerError!*Inst {
- const ptr = try self.resolveInst(block, ptrtoint.positionals.ptr);
+ 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(ptrtoint.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty});
+ return self.fail(scope, ptrtoint.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty});
}
// TODO handle known-pointer-address
- const b = try self.requireRuntimeBlock(block, ptrtoint.base.src);
+ 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: *Analyze, block: ?*Block, fieldptr: *text.Inst.FieldPtr) InnerError!*Inst {
- const object_ptr = try self.resolveInst(block, fieldptr.positionals.object_ptr);
- const field_name = try self.resolveConstString(block, fieldptr.positionals.field_name);
+ 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(fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
+ else => return self.fail(scope, fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
};
switch (elem_ty.zigTypeTag()) {
.Array => {
@@ -657,24 +1005,26 @@ const Analyze = struct {
});
} else {
return self.fail(
+ scope,
fieldptr.positionals.field_name.src,
"no member named '{}' in '{}'",
.{ field_name, elem_ty },
);
}
},
- else => return self.fail(fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}),
+ else => return self.fail(scope, fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}),
}
}
- fn analyzeInstIntCast(self: *Analyze, block: ?*Block, intcast: *text.Inst.IntCast) InnerError!*Inst {
- const dest_type = try self.resolveType(block, intcast.positionals.dest_type);
- const new_inst = try self.resolveInst(block, intcast.positionals.value);
+ 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 '{}'",
.{
@@ -686,6 +1036,7 @@ const Analyze = struct {
switch (new_inst.ty.zigTypeTag()) {
.ComptimeInt, .Int => {},
else => return self.fail(
+ scope,
intcast.positionals.value.src,
"expected integer type, found '{}'",
.{new_inst.ty},
@@ -693,22 +1044,22 @@ const Analyze = struct {
}
if (dest_is_comptime_int or new_inst.value() != null) {
- return self.coerce(block, dest_type, new_inst);
+ return self.coerce(scope, dest_type, new_inst);
}
- return self.fail(intcast.base.src, "TODO implement analyze widen or shorten int", .{});
+ return self.fail(scope, intcast.base.src, "TODO implement analyze widen or shorten int", .{});
}
- fn analyzeInstBitCast(self: *Analyze, block: ?*Block, inst: *text.Inst.BitCast) InnerError!*Inst {
- const dest_type = try self.resolveType(block, inst.positionals.dest_type);
- const operand = try self.resolveInst(block, inst.positionals.operand);
- return self.bitcast(block, dest_type, operand);
+ 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: *Analyze, block: ?*Block, inst: *text.Inst.ElemPtr) InnerError!*Inst {
- const array_ptr = try self.resolveInst(block, inst.positionals.array_ptr);
- const uncasted_index = try self.resolveInst(block, inst.positionals.index);
- const elem_index = try self.coerce(block, Type.initTag(.usize), uncasted_index);
+ 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| {
@@ -717,28 +1068,25 @@ const Analyze = struct {
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_val = try array_ptr_val.elemValueAt(&self.arena.allocator, @intCast(usize, index_u64));
-
- const ref_payload = try self.arena.allocator.create(Value.Payload.RefVal);
- ref_payload.* = .{ .val = elem_val };
+ const elem_ptr = try array_ptr_val.elemPtr(&self.arena.allocator, @intCast(usize, index_u64));
const type_payload = try self.arena.allocator.create(Type.Payload.SingleConstPointer);
type_payload.* = .{ .pointee_type = array_ptr.ty.elemType().elemType() };
return self.constInst(inst.base.src, .{
.ty = Type.initPayload(&type_payload.base),
- .val = Value.initPayload(&ref_payload.base),
+ .val = elem_ptr,
});
}
}
}
- return self.fail(inst.base.src, "TODO implement more analyze elemptr", .{});
+ return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{});
}
- fn analyzeInstAdd(self: *Analyze, block: ?*Block, inst: *text.Inst.Add) InnerError!*Inst {
- const lhs = try self.resolveInst(block, inst.positionals.lhs);
- const rhs = try self.resolveInst(block, inst.positionals.rhs);
+ 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| {
@@ -758,7 +1106,7 @@ const Analyze = struct {
const result_limbs = result_bigint.limbs[0..result_bigint.len];
if (!lhs.ty.eql(rhs.ty)) {
- return self.fail(inst.base.src, "TODO implement peer type resolution", .{});
+ return self.fail(scope, inst.base.src, "TODO implement peer type resolution", .{});
}
const val_payload = if (result_bigint.positive) blk: {
@@ -779,14 +1127,14 @@ const Analyze = struct {
}
}
- return self.fail(inst.base.src, "TODO implement more analyze add", .{});
+ return self.fail(scope, inst.base.src, "TODO implement more analyze add", .{});
}
- fn analyzeInstDeref(self: *Analyze, block: ?*Block, deref: *text.Inst.Deref) InnerError!*Inst {
- const ptr = try self.resolveInst(block, deref.positionals.ptr);
+ fn analyzeInstDeref(self: *Module, scope: *Scope, deref: *text.Inst.Deref) InnerError!*Inst {
+ const ptr = try self.resolveInst(scope, deref.positionals.ptr);
const elem_ty = switch (ptr.ty.zigTypeTag()) {
.Pointer => ptr.ty.elemType(),
- else => return self.fail(deref.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}),
+ else => return self.fail(scope, deref.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}),
};
if (ptr.value()) |val| {
return self.constInst(deref.base.src, .{
@@ -795,30 +1143,30 @@ const Analyze = struct {
});
}
- return self.fail(deref.base.src, "TODO implement runtime deref", .{});
+ return self.fail(scope, deref.base.src, "TODO implement runtime deref", .{});
}
- fn analyzeInstAsm(self: *Analyze, block: ?*Block, assembly: *text.Inst.Asm) InnerError!*Inst {
- const return_type = try self.resolveType(block, assembly.positionals.return_type);
- const asm_source = try self.resolveConstString(block, assembly.positionals.asm_source);
- const output = if (assembly.kw_args.output) |o| try self.resolveConstString(block, o) else null;
+ 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 self.arena.allocator.alloc([]const u8, assembly.kw_args.inputs.len);
const clobbers = try self.arena.allocator.alloc([]const u8, assembly.kw_args.clobbers.len);
const args = try self.arena.allocator.alloc(*Inst, assembly.kw_args.args.len);
for (inputs) |*elem, i| {
- elem.* = try self.resolveConstString(block, assembly.kw_args.inputs[i]);
+ elem.* = try self.resolveConstString(scope, assembly.kw_args.inputs[i]);
}
for (clobbers) |*elem, i| {
- elem.* = try self.resolveConstString(block, assembly.kw_args.clobbers[i]);
+ elem.* = try self.resolveConstString(scope, assembly.kw_args.clobbers[i]);
}
for (args) |*elem, i| {
- const arg = try self.resolveInst(block, assembly.kw_args.args[i]);
- elem.* = try self.coerce(block, Type.initTag(.usize), arg);
+ 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(block, assembly.base.src);
+ 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",
@@ -829,9 +1177,9 @@ const Analyze = struct {
});
}
- fn analyzeInstCmp(self: *Analyze, block: ?*Block, inst: *text.Inst.Cmp) InnerError!*Inst {
- const lhs = try self.resolveInst(block, inst.positionals.lhs);
- const rhs = try self.resolveInst(block, inst.positionals.rhs);
+ 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) {
@@ -853,7 +1201,7 @@ const Analyze = struct {
const is_null = opt_val.isNull();
return self.constBool(inst.base.src, if (op == .eq) is_null else !is_null);
}
- const b = try self.requireRuntimeBlock(block, inst.base.src);
+ const b = try self.requireRuntimeBlock(scope, inst.base.src);
switch (op) {
.eq => return self.addNewInstArgs(
b,
@@ -874,64 +1222,64 @@ const Analyze = struct {
} 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(inst.base.src, "TODO implement C pointer cmp", .{});
+ 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(inst.base.src, "comparison of '{}' with null", .{non_null_type});
+ 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(inst.base.src, "TODO implement equality comparison between a union's tag value and an enum literal", .{});
+ 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(inst.base.src, "{} operator not allowed for errors", .{@tagName(op)});
+ return self.fail(scope, inst.base.src, "{} operator not allowed for errors", .{@tagName(op)});
}
- return self.fail(inst.base.src, "TODO implement equality comparison between errors", .{});
+ 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(block, inst.base.src, lhs, rhs, op);
+ return self.cmpNumeric(scope, inst.base.src, lhs, rhs, op);
}
- return self.fail(inst.base.src, "TODO implement more cmp analysis", .{});
+ return self.fail(scope, inst.base.src, "TODO implement more cmp analysis", .{});
}
- fn analyzeInstIsNull(self: *Analyze, block: ?*Block, inst: *text.Inst.IsNull) InnerError!*Inst {
- const operand = try self.resolveInst(block, inst.positionals.operand);
- return self.analyzeIsNull(block, inst.base.src, operand, true);
+ 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: *Analyze, block: ?*Block, inst: *text.Inst.IsNonNull) InnerError!*Inst {
- const operand = try self.resolveInst(block, inst.positionals.operand);
- return self.analyzeIsNull(block, inst.base.src, operand, false);
+ 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: *Analyze, block: ?*Block, inst: *text.Inst.CondBr) InnerError!*Inst {
- const uncasted_cond = try self.resolveInst(block, inst.positionals.condition);
- const cond = try self.coerce(block, Type.initTag(.bool), uncasted_cond);
+ 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(cond)) |cond_val| {
const body = if (cond_val.toBool()) &inst.positionals.true_body else &inst.positionals.false_body;
- try self.analyzeBody(block, body.*);
+ try self.analyzeBody(scope, body.*);
return self.constVoid(inst.base.src);
}
- const parent_block = try self.requireRuntimeBlock(block, inst.base.src);
+ const parent_block = try self.requireRuntimeBlock(scope, inst.base.src);
- var true_block: Block = .{
+ var true_block: Scope.Block = .{
.func = parent_block.func,
- .instructions = std.ArrayList(*Inst).init(self.allocator),
+ .instructions = .{},
};
defer true_block.instructions.deinit();
- try self.analyzeBody(&true_block, inst.positionals.true_body);
+ try self.analyzeBody(&true_block.base, inst.positionals.true_body);
- var false_block: Block = .{
+ var false_block: Scope.Block = .{
.func = parent_block.func,
- .instructions = std.ArrayList(*Inst).init(self.allocator),
+ .instructions = .{},
};
defer false_block.instructions.deinit();
- try self.analyzeBody(&false_block, 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 self.arena.allocator.alloc(*Inst, true_block.instructions.items.len);
@@ -947,7 +1295,7 @@ const Analyze = struct {
});
}
- fn wantSafety(self: *Analyze, block: ?*Block) bool {
+ fn wantSafety(self: *Module, scope: *Scope) bool {
return switch (self.optimize_mode) {
.Debug => true,
.ReleaseSafe => true,
@@ -956,47 +1304,47 @@ const Analyze = struct {
};
}
- fn analyzeInstUnreachable(self: *Analyze, block: ?*Block, unreach: *text.Inst.Unreachable) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(block, unreach.base.src);
- if (self.wantSafety(block)) {
+ 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: *Analyze, block: ?*Block, inst: *text.Inst.Return) InnerError!*Inst {
- const b = try self.requireRuntimeBlock(block, inst.base.src);
+ 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: *Analyze, block: ?*Block, body: text.Module.Body) !void {
+ fn analyzeBody(self: *Module, scope: *Scope, body: text.Module.Body) !void {
for (body.instructions) |src_inst| {
- const new_inst = self.analyzeInst(block, src_inst) catch |err| {
- if (block) |b| {
+ const new_inst = self.analyzeInst(scope, src_inst) catch |err| {
+ if (scope.cast(Scope.Block)) |b| {
self.fns.items[b.func.fn_index].analysis_status = .failure;
try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = null });
}
return err;
};
- if (block) |b| try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst });
+ if (scope.cast(Scope.Block)) |b| try b.func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst });
}
}
fn analyzeIsNull(
- self: *Analyze,
- block: ?*Block,
+ self: *Module,
+ scope: *Scope,
src: usize,
operand: *Inst,
invert_logic: bool,
) InnerError!*Inst {
- return self.fail(src, "TODO implement analysis of isnull and isnotnull", .{});
+ return self.fail(scope, src, "TODO implement analysis of isnull and isnotnull", .{});
}
/// Asserts that lhs and rhs types are both numeric.
fn cmpNumeric(
- self: *Analyze,
- block: ?*Block,
+ self: *Module,
+ scope: *Scope,
src: usize,
lhs: *Inst,
rhs: *Inst,
@@ -1010,14 +1358,14 @@ const Analyze = struct {
if (lhs_ty_tag == .Vector and rhs_ty_tag == .Vector) {
if (lhs.ty.arrayLen() != rhs.ty.arrayLen()) {
- return self.fail(src, "vector length mismatch: {} and {}", .{
+ return self.fail(scope, src, "vector length mismatch: {} and {}", .{
lhs.ty.arrayLen(),
rhs.ty.arrayLen(),
});
}
- return self.fail(src, "TODO implement support for vectors in cmpNumeric", .{});
+ 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(src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
+ return self.fail(scope, src, "mixed scalar and vector operands to comparison operator: '{}' and '{}'", .{
lhs.ty,
rhs.ty,
});
@@ -1036,7 +1384,7 @@ const Analyze = struct {
// of this function if we don't need to.
// It must be a runtime comparison.
- const b = try self.requireRuntimeBlock(block, src);
+ 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,
@@ -1054,14 +1402,14 @@ const Analyze = struct {
} else if (rhs_ty_tag == .ComptimeFloat) {
break :x lhs.ty;
}
- if (lhs.ty.floatBits(self.target) >= rhs.ty.floatBits(self.target)) {
+ 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(block, dest_type, lhs);
- const casted_rhs = try self.coerce(block, dest_type, rhs);
+ 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,
@@ -1117,7 +1465,7 @@ const Analyze = struct {
} else if (lhs_is_float) {
dest_float_type = lhs.ty;
} else {
- const int_info = lhs.ty.intInfo(self.target);
+ const int_info = lhs.ty.intInfo(self.target());
lhs_bits = int_info.bits + @boolToInt(!int_info.signed and dest_int_is_signed);
}
@@ -1152,19 +1500,19 @@ const Analyze = struct {
} else if (rhs_is_float) {
dest_float_type = rhs.ty;
} else {
- const int_info = rhs.ty.intInfo(self.target);
+ 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(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);
};
- const casted_lhs = try self.coerce(block, dest_type, lhs);
- const casted_rhs = try self.coerce(block, dest_type, lhs);
+ 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,
@@ -1173,7 +1521,7 @@ const Analyze = struct {
});
}
- fn makeIntType(self: *Analyze, signed: bool, bits: u16) !Type {
+ fn makeIntType(self: *Module, signed: bool, bits: u16) !Type {
if (signed) {
const int_payload = try self.arena.allocator.create(Type.Payload.IntSigned);
int_payload.* = .{ .bits = bits };
@@ -1185,14 +1533,14 @@ const Analyze = struct {
}
}
- fn coerce(self: *Analyze, block: ?*Block, dest_type: Type, inst: *Inst) !*Inst {
+ 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(block, dest_type, inst);
+ return self.bitcast(scope, dest_type, inst);
}
// *[N]T to []T
@@ -1212,55 +1560,61 @@ const Analyze = struct {
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(inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
+ if (!val.intFitsInType(dest_type, self.target())) {
+ return self.fail(scope, inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
}
return self.constInst(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);
+ 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(inst.src, .{ .ty = dest_type, .val = val });
} else {
- return self.fail(inst.src, "TODO implement runtime integer widening", .{});
+ return self.fail(scope, inst.src, "TODO implement runtime integer widening", .{});
}
} else {
- return self.fail(inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type });
+ return self.fail(scope, inst.src, "TODO implement more int widening {} to {}", .{ inst.ty, dest_type });
}
}
- return self.fail(inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type });
+ return self.fail(scope, inst.src, "TODO implement type coercion from {} to {}", .{ inst.ty, dest_type });
}
- fn bitcast(self: *Analyze, block: ?*Block, dest_type: Type, inst: *Inst) !*Inst {
+ 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(inst.src, .{ .ty = dest_type, .val = val });
}
// TODO validate the type size and other compile errors
- const b = try self.requireRuntimeBlock(block, inst.src);
+ 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: *Analyze, dest_type: Type, inst: *Inst) !*Inst {
+ fn coerceArrayPtrToSlice(self: *Module, dest_type: Type, inst: *Inst) !*Inst {
if (inst.value()) |val| {
// The comptime Value representation is compatible with both types.
return self.constInst(inst.src, .{ .ty = dest_type, .val = val });
}
- return self.fail(inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{});
+ return self.fail(scope, inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{});
}
- fn fail(self: *Analyze, 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);
- const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args);
- (try self.errors.addOne()).* = .{
+ const err_msg = ErrorMsg{
.byte_offset = src,
- .msg = msg,
+ .msg = try std.fmt.allocPrint(self.allocator, format, args),
};
+ if (scope.cast(Scope.Block)) |block| {
+ block.func.analysis = .{ .failure = err_msg };
+ } else if (scope.cast(Scope.Decl)) |scope_decl| {
+ scope_decl.decl.analysis = .{ .failure = err_msg };
+ } else {
+ unreachable;
+ }
return error.AnalysisFail;
}
@@ -1279,6 +1633,11 @@ const Analyze = struct {
}
};
+pub const ErrorMsg = struct {
+ byte_offset: usize,
+ msg: []const u8,
+};
+
pub fn main() anyerror!void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
@@ -1288,63 +1647,68 @@ pub fn main() anyerror!void {
defer std.process.argsFree(allocator, args);
const src_path = args[1];
+ const bin_path = args[2];
const debug_error_trace = true;
-
- const source = try std.fs.cwd().readFileAllocOptions(allocator, src_path, std.math.maxInt(u32), 1, 0);
- defer allocator.free(source);
-
- var zir_module = try text.parse(allocator, source);
- defer zir_module.deinit(allocator);
-
- if (zir_module.errors.len != 0) {
- for (zir_module.errors) |err_msg| {
- const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
- std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
- }
- if (debug_error_trace) return error.ParseFailure;
- std.process.exit(1);
- }
+ const output_zir = true;
const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
- var analyzed_module = try analyze(allocator, zir_module, .{
+ var bin_file = try link.openBinFilePath(allocator, std.fs.cwd(), bin_path, .{
.target = native_info.target,
- .output_mode = .Obj,
+ .output_mode = .Exe,
.link_mode = .Static,
- .optimize_mode = .Debug,
+ .object_format = options.object_format orelse native_info.target.getObjectFormat(),
});
- defer analyzed_module.deinit(allocator);
+ defer bin_file.deinit(allocator);
+
+ var module = blk: {
+ const root_pkg = try Package.create(allocator, std.fs.cwd(), ".", src_path);
+ errdefer root_pkg.destroy();
+
+ const root_scope = try allocator.create(Module.Scope.ZIRModule);
+ errdefer allocator.destroy(root_scope);
+ root_scope.* = .{
+ .sub_file_path = root_pkg.root_src_path,
+ .contents = .unloaded,
+ };
- if (analyzed_module.errors.len != 0) {
- for (analyzed_module.errors) |err_msg| {
- const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
- std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
+ break :blk Module{
+ .allocator = allocator,
+ .root_pkg = root_pkg,
+ .root_scope = root_scope,
+ .bin_file = &bin_file,
+ .optimize_mode = .Debug,
+ .decl_table = std.AutoHashMap(Decl.Hash, *Decl).init(allocator),
+ };
+ };
+ defer module.deinit();
+
+ try module.update();
+
+ const errors = try module.getAllErrorsAlloc();
+ defer errors.deinit();
+
+ if (errors.list.len != 0) {
+ for (errors.list) |full_err_msg| {
+ std.debug.warn("{}:{}:{}: error: {}\n", .{
+ full_err_msg.src_path,
+ full_err_msg.line + 1,
+ full_err_msg.column + 1,
+ full_err_msg.msg,
+ });
}
if (debug_error_trace) return error.AnalysisFail;
std.process.exit(1);
}
- const output_zir = true;
if (output_zir) {
- var new_zir_module = try text.emit_zir(allocator, analyzed_module);
+ var new_zir_module = try text.emit_zir(allocator, module);
defer new_zir_module.deinit(allocator);
var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream());
try new_zir_module.writeToStream(allocator, bos.outStream());
try bos.flush();
}
-
- const link = @import("link.zig");
- var result = try link.updateFilePath(allocator, analyzed_module, std.fs.cwd(), "zir.o");
- defer result.deinit(allocator);
- if (result.errors.len != 0) {
- for (result.errors) |err_msg| {
- const loc = std.zig.findLineColumn(source, err_msg.byte_offset);
- std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
- }
- if (debug_error_trace) return error.LinkFailure;
- std.process.exit(1);
- }
}
// Performance optimization ideas:
src-self-hosted/libc_installation.zig
@@ -1,6 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
-const util = @import("util.zig");
const Target = std.Target;
const fs = std.fs;
const Allocator = std.mem.Allocator;
src-self-hosted/link.zig
@@ -9,50 +9,65 @@ const codegen = @import("codegen.zig");
const default_entry_addr = 0x8000000;
-pub const ErrorMsg = struct {
- byte_offset: usize,
- msg: []const u8,
-};
-
-pub const Result = struct {
- errors: []ErrorMsg,
-
- pub fn deinit(self: *Result, allocator: *mem.Allocator) void {
- for (self.errors) |err| {
- allocator.free(err.msg);
- }
- allocator.free(self.errors);
- self.* = undefined;
- }
+pub const Options = struct {
+ target: std.Target,
+ output_mode: std.builtin.OutputMode,
+ link_mode: std.builtin.LinkMode,
+ object_format: std.builtin.ObjectFormat,
+ /// Used for calculating how much space to reserve for symbols in case the binary file
+ /// does not already have a symbol table.
+ symbol_count_hint: u64 = 32,
+ /// Used for calculating how much space to reserve for executable program code in case
+ /// the binary file deos not already have such a section.
+ program_code_size_hint: u64 = 256 * 1024,
};
/// Attempts incremental linking, if the file already exists.
/// If incremental linking fails, falls back to truncating the file and rewriting it.
/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
/// This operation is not atomic.
-pub fn updateFilePath(
+pub fn openBinFilePath(
allocator: *Allocator,
- module: ir.Module,
dir: fs.Dir,
sub_path: []const u8,
-) !Result {
- const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(module) });
+ options: Options,
+) !ElfFile {
+ const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) });
defer file.close();
- return updateFile(allocator, module, file);
+ return openBinFile(allocator, file, options);
}
/// Atomically overwrites the old file, if present.
pub fn writeFilePath(
allocator: *Allocator,
- module: ir.Module,
dir: fs.Dir,
sub_path: []const u8,
-) !Result {
- const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(module) });
+ module: ir.Module,
+ errors: *std.ArrayList(ir.ErrorMsg),
+) !void {
+ const options: Options = .{
+ .target = module.target,
+ .output_mode = module.output_mode,
+ .link_mode = module.link_mode,
+ .object_format = module.object_format,
+ .symbol_count_hint = module.decls.items.len,
+ };
+ const af = try dir.atomicFile(sub_path, .{ .mode = determineMode(options) });
defer af.deinit();
- const result = try writeFile(allocator, module, af.file);
+ const elf_file = try createElfFile(allocator, af.file, options);
+ for (module.decls.items) |decl| {
+ try elf_file.updateDecl(module, decl, errors);
+ }
+ try elf_file.flush();
+ if (elf_file.error_flags.no_entry_point_found) {
+ try errors.ensureCapacity(errors.items.len + 1);
+ errors.appendAssumeCapacity(.{
+ .byte_offset = 0,
+ .msg = try std.fmt.allocPrint(errors.allocator, "no entry point found", .{}),
+ });
+ }
try af.finish();
return result;
}
@@ -62,49 +77,65 @@ pub fn writeFilePath(
/// Returns an error if `file` is not already open with +read +write +seek abilities.
/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
/// This operation is not atomic.
-pub fn updateFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
- return updateFileInner(allocator, module, file) catch |err| switch (err) {
+pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile {
+ return openBinFileInner(allocator, file, options) catch |err| switch (err) {
error.IncrFailed => {
- return writeFile(allocator, module, file);
+ return createElfFile(allocator, file, options);
},
else => |e| return e,
};
}
-const Update = struct {
+pub const ElfFile = struct {
+ allocator: *Allocator,
file: fs.File,
- module: *const ir.Module,
+ options: Options,
+ ptr_width: enum { p32, p64 },
/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
/// Same order as in the file.
- sections: std.ArrayList(elf.Elf64_Shdr),
- shdr_table_offset: ?u64,
+ sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = .{},
+ shdr_table_offset: ?u64 = null,
/// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
/// Same order as in the file.
- program_headers: std.ArrayList(elf.Elf64_Phdr),
- phdr_table_offset: ?u64,
+ program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = .{},
+ phdr_table_offset: ?u64 = null,
/// The index into the program headers of a PT_LOAD program header with Read and Execute flags
- phdr_load_re_index: ?u16,
- entry_addr: ?u64,
+ phdr_load_re_index: ?u16 = null,
+ entry_addr: ?u64 = null,
- shstrtab: std.ArrayList(u8),
- shstrtab_index: ?u16,
+ shstrtab: std.ArrayListUnmanaged(u8) = .{},
+ shstrtab_index: ?u16 = null,
- text_section_index: ?u16,
- symtab_section_index: ?u16,
+ text_section_index: ?u16 = null,
+ symtab_section_index: ?u16 = null,
/// The same order as in the file
- symbols: std.ArrayList(elf.Elf64_Sym),
+ symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = .{},
- errors: std.ArrayList(ErrorMsg),
+ /// Same order as in the file.
+ offset_table: std.ArrayListUnmanaged(aoeu) = .{},
+
+ /// This means the entire read-only executable program code needs to be rewritten.
+ phdr_load_re_dirty: bool = false,
+ phdr_table_dirty: bool = false,
+ shdr_table_dirty: bool = false,
+ shstrtab_dirty: bool = false,
+ symtab_dirty: bool = false,
+
+ error_flags: ErrorFlags = ErrorFlags{},
- fn deinit(self: *Update) void {
- self.sections.deinit();
- self.program_headers.deinit();
- self.shstrtab.deinit();
- self.symbols.deinit();
- self.errors.deinit();
+ pub const ErrorFlags = struct {
+ no_entry_point_found: bool = false,
+ };
+
+ pub fn deinit(self: *ElfFile) void {
+ self.sections.deinit(self.allocator);
+ self.program_headers.deinit(self.allocator);
+ self.shstrtab.deinit(self.allocator);
+ self.symbols.deinit(self.allocator);
+ self.offset_table.deinit(self.allocator);
}
// `expand_num / expand_den` is the factor of padding when allocation
@@ -112,8 +143,8 @@ const Update = struct {
const alloc_den = 3;
/// Returns end pos of collision, if any.
- fn detectAllocCollision(self: *Update, start: u64, size: u64) ?u64 {
- const small_ptr = self.module.target.cpu.arch.ptrBitWidth() == 32;
+ fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 {
+ const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;
const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
if (start < ehdr_size)
return ehdr_size;
@@ -157,7 +188,7 @@ const Update = struct {
return null;
}
- fn allocatedSize(self: *Update, start: u64) u64 {
+ fn allocatedSize(self: *ElfFile, start: u64) u64 {
var min_pos: u64 = std.math.maxInt(u64);
if (self.shdr_table_offset) |off| {
if (off > start and off < min_pos) min_pos = off;
@@ -176,7 +207,7 @@ const Update = struct {
return min_pos - start;
}
- fn findFreeSpace(self: *Update, object_size: u64, min_alignment: u16) u64 {
+ fn findFreeSpace(self: *ElfFile, object_size: u64, min_alignment: u16) u64 {
var start: u64 = 0;
while (self.detectAllocCollision(start, object_size)) |item_end| {
start = mem.alignForwardGeneric(u64, item_end, min_alignment);
@@ -184,33 +215,21 @@ const Update = struct {
return start;
}
- fn makeString(self: *Update, bytes: []const u8) !u32 {
+ fn makeString(self: *ElfFile, bytes: []const u8) !u32 {
const result = self.shstrtab.items.len;
try self.shstrtab.appendSlice(bytes);
try self.shstrtab.append(0);
return @intCast(u32, result);
}
- fn perform(self: *Update) !void {
- const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) {
- 32 => .p32,
- 64 => .p64,
- else => return error.UnsupportedArchitecture,
- };
- const small_ptr = switch (ptr_width) {
+ pub fn populateMissingMetadata(self: *ElfFile) !void {
+ const small_ptr = switch (self.ptr_width) {
.p32 => true,
.p64 => false,
};
- // This means the entire read-only executable program code needs to be rewritten.
- var phdr_load_re_dirty = false;
- var phdr_table_dirty = false;
- var shdr_table_dirty = false;
- var shstrtab_dirty = false;
- var symtab_dirty = false;
-
if (self.phdr_load_re_index == null) {
self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len);
- const file_size = 256 * 1024;
+ const file_size = self.options.program_code_size_hint;
const p_align = 0x1000;
const off = self.findFreeSpace(file_size, p_align);
//std.debug.warn("found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
@@ -225,24 +244,8 @@ const Update = struct {
.p_flags = elf.PF_X | elf.PF_R,
});
self.entry_addr = null;
- phdr_load_re_dirty = true;
- phdr_table_dirty = true;
- }
- if (self.sections.items.len == 0) {
- // There must always be a null section in index 0
- try self.sections.append(.{
- .sh_name = 0,
- .sh_type = elf.SHT_NULL,
- .sh_flags = 0,
- .sh_addr = 0,
- .sh_offset = 0,
- .sh_size = 0,
- .sh_link = 0,
- .sh_info = 0,
- .sh_addralign = 0,
- .sh_entsize = 0,
- });
- shdr_table_dirty = true;
+ self.phdr_load_re_dirty = true;
+ self.phdr_table_dirty = true;
}
if (self.shstrtab_index == null) {
self.shstrtab_index = @intCast(u16, self.sections.items.len);
@@ -262,8 +265,8 @@ const Update = struct {
.sh_addralign = 1,
.sh_entsize = 0,
});
- shstrtab_dirty = true;
- shdr_table_dirty = true;
+ self.shstrtab_dirty = true;
+ self.shdr_table_dirty = true;
}
if (self.text_section_index == null) {
self.text_section_index = @intCast(u16, self.sections.items.len);
@@ -281,13 +284,13 @@ const Update = struct {
.sh_addralign = phdr.p_align,
.sh_entsize = 0,
});
- shdr_table_dirty = true;
+ self.shdr_table_dirty = true;
}
if (self.symtab_section_index == null) {
self.symtab_section_index = @intCast(u16, self.sections.items.len);
const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);
- const file_size = self.module.exports.len * each_size;
+ const file_size = self.options.symbol_count_hint * each_size;
const off = self.findFreeSpace(file_size, min_align);
//std.debug.warn("found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
@@ -300,12 +303,12 @@ const Update = struct {
.sh_size = file_size,
// The section header index of the associated string table.
.sh_link = self.shstrtab_index.?,
- .sh_info = @intCast(u32, self.module.exports.len),
+ .sh_info = @intCast(u32, self.symbols.items.len),
.sh_addralign = min_align,
.sh_entsize = each_size,
});
- symtab_dirty = true;
- shdr_table_dirty = true;
+ self.symtab_dirty = true;
+ self.shdr_table_dirty = true;
}
const shsize: u64 = switch (ptr_width) {
.p32 => @sizeOf(elf.Elf32_Shdr),
@@ -317,7 +320,7 @@ const Update = struct {
};
if (self.shdr_table_offset == null) {
self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign);
- shdr_table_dirty = true;
+ self.shdr_table_dirty = true;
}
const phsize: u64 = switch (ptr_width) {
.p32 => @sizeOf(elf.Elf32_Phdr),
@@ -329,13 +332,15 @@ const Update = struct {
};
if (self.phdr_table_offset == null) {
self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign);
- phdr_table_dirty = true;
+ self.phdr_table_dirty = true;
}
- const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ }
- try self.writeCodeAndSymbols(phdr_table_dirty, shdr_table_dirty);
+ /// Commit pending changes and write headers.
+ pub fn flush(self: *ElfFile) !void {
+ const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
- if (phdr_table_dirty) {
+ if (self.phdr_table_dirty) {
const allocated_size = self.allocatedSize(self.phdr_table_offset.?);
const needed_size = self.program_headers.items.len * phsize;
@@ -345,7 +350,7 @@ const Update = struct {
}
const allocator = self.program_headers.allocator;
- switch (ptr_width) {
+ switch (self.ptr_width) {
.p32 => {
const buf = try allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len);
defer allocator.free(buf);
@@ -371,11 +376,12 @@ const Update = struct {
try self.file.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
},
}
+ self.phdr_table_offset = false;
}
{
const shstrtab_sect = &self.sections.items[self.shstrtab_index.?];
- if (shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) {
+ if (self.shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) {
const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset);
const needed_size = self.shstrtab.items.len;
@@ -387,13 +393,14 @@ const Update = struct {
//std.debug.warn("shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size });
try self.file.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset);
- if (!shdr_table_dirty) {
+ if (!self.shdr_table_dirty) {
// Then it won't get written with the others and we need to do it.
try self.writeSectHeader(self.shstrtab_index.?);
}
+ self.shstrtab_dirty = false;
}
}
- if (shdr_table_dirty) {
+ if (self.shdr_table_dirty) {
const allocated_size = self.allocatedSize(self.shdr_table_offset.?);
const needed_size = self.sections.items.len * phsize;
@@ -403,7 +410,7 @@ const Update = struct {
}
const allocator = self.sections.allocator;
- switch (ptr_width) {
+ switch (self.ptr_width) {
.p32 => {
const buf = try allocator.alloc(elf.Elf32_Shdr, self.sections.items.len);
defer allocator.free(buf);
@@ -431,38 +438,36 @@ const Update = struct {
},
}
}
- if (self.entry_addr == null and self.module.output_mode == .Exe) {
- const msg = try std.fmt.allocPrint(self.errors.allocator, "no entry point found", .{});
- errdefer self.errors.allocator.free(msg);
- try self.errors.append(.{
- .byte_offset = 0,
- .msg = msg,
- });
+ if (self.entry_addr == null and self.options.output_mode == .Exe) {
+ self.error_flags.no_entry_point_found = true;
} else {
+ self.error_flags.no_entry_point_found = false;
try self.writeElfHeader();
}
// TODO find end pos and truncate
+
+ // The point of flush() is to commit changes, so nothing should be dirty after this.
+ assert(!self.phdr_load_re_dirty);
+ assert(!self.phdr_table_dirty);
+ assert(!self.shdr_table_dirty);
+ assert(!self.shstrtab_dirty);
+ assert(!self.symtab_dirty);
}
- fn writeElfHeader(self: *Update) !void {
+ fn writeElfHeader(self: *ElfFile) !void {
var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
var index: usize = 0;
hdr_buf[0..4].* = "\x7fELF".*;
index += 4;
- const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) {
- 32 => .p32,
- 64 => .p64,
- else => return error.UnsupportedArchitecture,
- };
- hdr_buf[index] = switch (ptr_width) {
+ hdr_buf[index] = switch (self.ptr_width) {
.p32 => elf.ELFCLASS32,
.p64 => elf.ELFCLASS64,
};
index += 1;
- const endian = self.module.target.cpu.arch.endian();
+ const endian = self.options.target.cpu.arch.endian();
hdr_buf[index] = switch (endian) {
.Little => elf.ELFDATA2LSB,
.Big => elf.ELFDATA2MSB,
@@ -480,10 +485,10 @@ const Update = struct {
assert(index == 16);
- const elf_type = switch (self.module.output_mode) {
+ const elf_type = switch (self.options.output_mode) {
.Exe => elf.ET.EXEC,
.Obj => elf.ET.REL,
- .Lib => switch (self.module.link_mode) {
+ .Lib => switch (self.options.link_mode) {
.Static => elf.ET.REL,
.Dynamic => elf.ET.DYN,
},
@@ -491,7 +496,7 @@ const Update = struct {
mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian);
index += 2;
- const machine = self.module.target.cpu.arch.toElfMachine();
+ const machine = self.options.target.cpu.arch.toElfMachine();
mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian);
index += 2;
@@ -501,7 +506,7 @@ const Update = struct {
const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?;
- switch (ptr_width) {
+ switch (self.ptr_width) {
.p32 => {
mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian);
index += 4;
@@ -533,14 +538,14 @@ const Update = struct {
mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian);
index += 4;
- const e_ehsize: u16 = switch (ptr_width) {
+ const e_ehsize: u16 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Ehdr),
.p64 => @sizeOf(elf.Elf64_Ehdr),
};
mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian);
index += 2;
- const e_phentsize: u16 = switch (ptr_width) {
+ const e_phentsize: u16 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Phdr),
.p64 => @sizeOf(elf.Elf64_Phdr),
};
@@ -551,7 +556,7 @@ const Update = struct {
mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian);
index += 2;
- const e_shentsize: u16 = switch (ptr_width) {
+ const e_shentsize: u16 = switch (self.ptr_width) {
.p32 => @sizeOf(elf.Elf32_Shdr),
.p64 => @sizeOf(elf.Elf64_Shdr),
};
@@ -570,81 +575,172 @@ const Update = struct {
try self.file.pwriteAll(hdr_buf[0..index], 0);
}
- fn writeCodeAndSymbols(self: *Update, phdr_table_dirty: bool, shdr_table_dirty: bool) !void {
- // index 0 is always a null symbol
- try self.symbols.resize(1);
- self.symbols.items[0] = .{
- .st_name = 0,
- .st_info = 0,
- .st_other = 0,
- .st_shndx = 0,
- .st_value = 0,
- .st_size = 0,
- };
+ /// TODO Look into making this smaller to save memory.
+ /// Lots of redundant info here with the data stored in symbol structs.
+ const DeclSymbol = struct {
+ symbol_indexes: []usize,
+ vaddr: u64,
+ file_offset: u64,
+ size: u64,
+ };
+ const AllocatedBlock = struct {
+ vaddr: u64,
+ file_offset: u64,
+ size_capacity: u64,
+ };
+
+ fn allocateDeclSymbol(self: *ElfFile, size: u64) AllocatedBlock {
const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
- var vaddr: u64 = phdr.p_vaddr;
- var file_off: u64 = phdr.p_offset;
+ todo();
+ //{
+ // // Now that we know the code size, we need to update the program header for executable code
+ // phdr.p_memsz = vaddr - phdr.p_vaddr;
+ // phdr.p_filesz = phdr.p_memsz;
+
+ // const shdr = &self.sections.items[self.text_section_index.?];
+ // shdr.sh_size = phdr.p_filesz;
+
+ // self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
+ // self.shdr_table_dirty = true; // TODO look into making only the one section dirty
+ //}
+
+ //return self.writeSymbols();
+ }
+
+ fn findAllocatedBlock(self: *ElfFile, vaddr: u64) AllocatedBlock {
+ todo();
+ }
- var code = std.ArrayList(u8).init(self.sections.allocator);
+ pub fn updateDecl(
+ self: *ElfFile,
+ module: ir.Module,
+ typed_value: ir.TypedValue,
+ decl_export_node: ?*std.LinkedList(std.builtin.ExportOptions).Node,
+ hash: ir.Module.Decl.Hash,
+ err_msg_allocator: *Allocator,
+ ) !?ir.ErrorMsg {
+ var code = std.ArrayList(u8).init(self.allocator);
defer code.deinit();
- for (self.module.exports) |exp| {
- code.shrink(0);
- var symbol = try codegen.generateSymbol(exp.typed_value, self.module.*, &code);
- defer symbol.deinit(code.allocator);
- if (symbol.errors.len != 0) {
- for (symbol.errors) |err| {
- const msg = try mem.dupe(self.errors.allocator, u8, err.msg);
- errdefer self.errors.allocator.free(msg);
- try self.errors.append(.{
- .byte_offset = err.byte_offset,
- .msg = msg,
- });
+ const err_msg = try codegen.generateSymbol(typed_value, module, &code, err_msg_allocator);
+ if (err_msg != null) |em| return em;
+
+ const export_count = blk: {
+ var export_node = decl_export_node;
+ var i: usize = 0;
+ while (export_node) |node| : (export_node = node.next) i += 1;
+ break :blk i;
+ };
+
+ // Find or create a symbol from the decl
+ var valid_sym_index_len: usize = 0;
+ const decl_symbol = blk: {
+ if (self.decl_table.getValue(hash)) |decl_symbol| {
+ valid_sym_index_len = decl_symbol.symbol_indexes.len;
+ decl_symbol.symbol_indexes = try self.allocator.realloc(usize, export_count);
+
+ const existing_block = self.findAllocatedBlock(decl_symbol.vaddr);
+ if (code.items.len > existing_block.size_capacity) {
+ const new_block = self.allocateDeclSymbol(code.items.len);
+ decl_symbol.vaddr = new_block.vaddr;
+ decl_symbol.file_offset = new_block.file_offset;
+ decl_symbol.size = code.items.len;
}
- continue;
+ break :blk decl_symbol;
+ } else {
+ const new_block = self.allocateDeclSymbol(code.items.len);
+
+ const decl_symbol = try self.allocator.create(DeclSymbol);
+ errdefer self.allocator.destroy(decl_symbol);
+
+ decl_symbol.* = .{
+ .symbol_indexes = try self.allocator.alloc(usize, export_count),
+ .vaddr = new_block.vaddr,
+ .file_offset = new_block.file_offset,
+ .size = code.items.len,
+ };
+ errdefer self.allocator.free(decl_symbol.symbol_indexes);
+
+ try self.decl_table.put(hash, decl_symbol);
+ break :blk decl_symbol;
+ }
+ };
+
+ // Allocate new symbols.
+ {
+ var i: usize = valid_sym_index_len;
+ const old_len = self.symbols.items.len;
+ try self.symbols.resize(old_len + (decl_symbol.symbol_indexes.len - i));
+ while (i < decl_symbol.symbol_indexes) : (i += 1) {
+ decl_symbol.symbol_indexes[i] = old_len + i;
}
- try self.file.pwriteAll(code.items, file_off);
+ }
- if (mem.eql(u8, exp.name, "_start")) {
- self.entry_addr = vaddr;
+ var export_node = decl_export_node;
+ var export_index: usize = 0;
+ while (export_node) |node| : ({
+ export_node = node.next;
+ export_index += 1;
+ }) {
+ if (node.data.section) |section_name| {
+ if (!mem.eql(u8, section_name, ".text")) {
+ try errors.ensureCapacity(errors.items.len + 1);
+ errors.appendAssumeCapacity(.{
+ .byte_offset = 0,
+ .msg = try std.fmt.allocPrint(errors.allocator, "Unimplemented: ExportOptions.section", .{}),
+ });
+ }
}
- (try self.symbols.addOne()).* = .{
- .st_name = try self.makeString(exp.name),
- .st_info = (elf.STB_LOCAL << 4) | elf.STT_FUNC,
+ const stb_bits = switch (node.data.linkage) {
+ .Internal => elf.STB_LOCAL,
+ .Strong => blk: {
+ if (mem.eql(u8, node.data.name, "_start")) {
+ self.entry_addr = decl_symbol.vaddr;
+ }
+ break :blk elf.STB_GLOBAL;
+ },
+ .Weak => elf.STB_WEAK,
+ .LinkOnce => {
+ try errors.ensureCapacity(errors.items.len + 1);
+ errors.appendAssumeCapacity(.{
+ .byte_offset = 0,
+ .msg = try std.fmt.allocPrint(errors.allocator, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
+ });
+ },
+ };
+ const stt_bits = switch (typed_value.ty.zigTypeTag()) {
+ .Fn => elf.STT_FUNC,
+ else => elf.STT_OBJECT,
+ };
+ const sym_index = decl_symbol.symbol_indexes[export_index];
+ const name = blk: {
+ if (i < valid_sym_index_len) {
+ const name_stroff = self.symbols.items[sym_index].st_name;
+ const existing_name = self.getString(name_stroff);
+ if (mem.eql(u8, existing_name, node.data.name)) {
+ break :blk name_stroff;
+ }
+ }
+ break :blk try self.makeString(node.data.name);
+ };
+ self.symbols.items[sym_index] = .{
+ .st_name = name,
+ .st_info = (stb_bits << 4) | stt_bits,
.st_other = 0,
.st_shndx = self.text_section_index.?,
- .st_value = vaddr,
+ .st_value = decl_symbol.vaddr,
.st_size = code.items.len,
};
- vaddr += code.items.len;
}
- {
- // Now that we know the code size, we need to update the program header for executable code
- phdr.p_memsz = vaddr - phdr.p_vaddr;
- phdr.p_filesz = phdr.p_memsz;
-
- const shdr = &self.sections.items[self.text_section_index.?];
- shdr.sh_size = phdr.p_filesz;
-
- if (!phdr_table_dirty) {
- // Then it won't get written with the others and we need to do it.
- try self.writeProgHeader(self.phdr_load_re_index.?);
- }
- if (!shdr_table_dirty) {
- // Then it won't get written with the others and we need to do it.
- try self.writeSectHeader(self.text_section_index.?);
- }
- }
-
- return self.writeSymbols();
+ try self.file.pwriteAll(code.items, decl_symbol.file_offset);
}
- fn writeProgHeader(self: *Update, index: usize) !void {
- const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ fn writeProgHeader(self: *ElfFile, index: usize) !void {
+ const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const offset = self.program_headers.items[index].p_offset;
- switch (self.module.target.cpu.arch.ptrBitWidth()) {
+ switch (self.options.target.cpu.arch.ptrBitWidth()) {
32 => {
var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
if (foreign_endian) {
@@ -663,10 +759,10 @@ const Update = struct {
}
}
- fn writeSectHeader(self: *Update, index: usize) !void {
- const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ fn writeSectHeader(self: *ElfFile, index: usize) !void {
+ const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
const offset = self.sections.items[index].sh_offset;
- switch (self.module.target.cpu.arch.ptrBitWidth()) {
+ switch (self.options.target.cpu.arch.ptrBitWidth()) {
32 => {
var shdr: [1]elf.Elf32_Shdr = undefined;
shdr[0] = sectHeaderTo32(self.sections.items[index]);
@@ -686,13 +782,8 @@ const Update = struct {
}
}
- fn writeSymbols(self: *Update) !void {
- const ptr_width: enum { p32, p64 } = switch (self.module.target.cpu.arch.ptrBitWidth()) {
- 32 => .p32,
- 64 => .p64,
- else => return error.UnsupportedArchitecture,
- };
- const small_ptr = ptr_width == .p32;
+ fn writeSymbols(self: *ElfFile) !void {
+ const small_ptr = self.ptr_width == .p32;
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_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);
@@ -708,8 +799,8 @@ const Update = struct {
syms_sect.sh_size = needed_size;
syms_sect.sh_info = @intCast(u32, self.symbols.items.len);
const allocator = self.symbols.allocator;
- const foreign_endian = self.module.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
- switch (ptr_width) {
+ const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
+ switch (self.ptr_width) {
.p32 => {
const buf = try allocator.alloc(elf.Elf32_Sym, self.symbols.items.len);
defer allocator.free(buf);
@@ -754,13 +845,13 @@ const Update = struct {
/// Truncates the existing file contents and overwrites the contents.
/// Returns an error if `file` is not already open with +read +write +seek abilities.
-pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
- switch (module.output_mode) {
+pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile {
+ switch (options.output_mode) {
.Exe => {},
.Obj => {},
.Lib => return error.TODOImplementWritingLibFiles,
}
- switch (module.object_format) {
+ switch (options.object_format) {
.unknown => unreachable, // TODO remove this tag from the enum
.coff => return error.TODOImplementWritingCOFF,
.elf => {},
@@ -768,38 +859,79 @@ pub fn writeFile(allocator: *Allocator, module: ir.Module, file: fs.File) !Resul
.wasm => return error.TODOImplementWritingWasmObjects,
}
- var update = Update{
+ var self: ElfFile = .{
+ .allocator = allocator,
.file = file,
- .module = &module,
- .sections = std.ArrayList(elf.Elf64_Shdr).init(allocator),
- .shdr_table_offset = null,
- .program_headers = std.ArrayList(elf.Elf64_Phdr).init(allocator),
- .phdr_table_offset = null,
- .phdr_load_re_index = null,
- .entry_addr = null,
- .shstrtab = std.ArrayList(u8).init(allocator),
- .shstrtab_index = null,
- .text_section_index = null,
- .symtab_section_index = null,
-
- .symbols = std.ArrayList(elf.Elf64_Sym).init(allocator),
-
- .errors = std.ArrayList(ErrorMsg).init(allocator),
- };
- defer update.deinit();
-
- try update.perform();
- return Result{
- .errors = update.errors.toOwnedSlice(),
+ .options = options,
+ .ptr_width = switch (self.options.target.cpu.arch.ptrBitWidth()) {
+ 32 => .p32,
+ 64 => .p64,
+ else => return error.UnsupportedELFArchitecture,
+ },
+ .symtab_dirty = true,
+ .shdr_table_dirty = true,
};
+ errdefer self.deinit();
+
+ // Index 0 is always a null symbol.
+ try self.symbols.append(allocator, .{
+ .st_name = 0,
+ .st_info = 0,
+ .st_other = 0,
+ .st_shndx = 0,
+ .st_value = 0,
+ .st_size = 0,
+ });
+
+ // There must always be a null section in index 0
+ try self.sections.append(allocator, .{
+ .sh_name = 0,
+ .sh_type = elf.SHT_NULL,
+ .sh_flags = 0,
+ .sh_addr = 0,
+ .sh_offset = 0,
+ .sh_size = 0,
+ .sh_link = 0,
+ .sh_info = 0,
+ .sh_addralign = 0,
+ .sh_entsize = 0,
+ });
+
+ try self.populateMissingMetadata();
+
+ return self;
}
/// Returns error.IncrFailed if incremental update could not be performed.
-fn updateFileInner(allocator: *Allocator, module: ir.Module, file: fs.File) !Result {
- //var ehdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
+fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !ElfFile {
+ switch (options.output_mode) {
+ .Exe => {},
+ .Obj => {},
+ .Lib => return error.IncrFailed,
+ }
+ switch (options.object_format) {
+ .unknown => unreachable, // TODO remove this tag from the enum
+ .coff => return error.IncrFailed,
+ .elf => {},
+ .macho => return error.IncrFailed,
+ .wasm => return error.IncrFailed,
+ }
+ var self: ElfFile = .{
+ .allocator = allocator,
+ .file = file,
+ .options = options,
+ .ptr_width = switch (self.options.target.cpu.arch.ptrBitWidth()) {
+ 32 => .p32,
+ 64 => .p64,
+ else => return error.UnsupportedELFArchitecture,
+ },
+ };
+ errdefer self.deinit();
- // TODO implement incremental linking
+ // TODO implement reading the elf file
return error.IncrFailed;
+ //try self.populateMissingMetadata();
+ //return self;
}
/// Saturating multiplication
@@ -840,14 +972,14 @@ fn sectHeaderTo32(shdr: elf.Elf64_Shdr) elf.Elf32_Shdr {
};
}
-fn determineMode(module: ir.Module) fs.File.Mode {
+fn determineMode(options: Options) fs.File.Mode {
// On common systems with a 0o022 umask, 0o777 will still result in a file created
// with 0o755 permissions, but it works appropriately if the system is configured
// more leniently. As another data point, C's fopen seems to open files with the
// 666 mode.
const executable_mode = if (std.Target.current.os.tag == .windows) 0 else 0o777;
- switch (module.output_mode) {
- .Lib => return switch (module.link_mode) {
+ switch (options.output_mode) {
+ .Lib => return switch (options.link_mode) {
.Dynamic => executable_mode,
.Static => fs.File.default_mode,
},
src-self-hosted/Package.zig
@@ -0,0 +1,52 @@
+pub const Table = std.StringHashMap(*Package);
+
+root_src_dir: std.fs.Dir,
+/// Relative to `root_src_dir`.
+root_src_path: []const u8,
+table: Table,
+
+/// No references to `root_src_dir` and `root_src_path` are kept.
+pub fn create(
+ allocator: *mem.Allocator,
+ base_dir: std.fs.Dir,
+ /// Relative to `base_dir`.
+ root_src_dir: []const u8,
+ /// Relative to `root_src_dir`.
+ root_src_path: []const u8,
+) !*Package {
+ const ptr = try allocator.create(Package);
+ errdefer allocator.destroy(ptr);
+ const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path);
+ errdefer allocator.free(root_src_path_dupe);
+ ptr.* = .{
+ .root_src_dir = try base_dir.openDir(root_src_dir, .{}),
+ .root_src_path = root_src_path_dupe,
+ .table = Table.init(allocator),
+ };
+ return ptr;
+}
+
+pub fn destroy(self: *Package) void {
+ const allocator = self.table.allocator;
+ self.root_src_dir.close();
+ allocator.free(self.root_src_path);
+ {
+ var it = self.table.iterator();
+ while (it.next()) |kv| {
+ allocator.free(kv.key);
+ }
+ }
+ self.table.deinit();
+ allocator.destroy(self);
+}
+
+pub fn add(self: *Package, name: []const u8, package: *Package) !void {
+ const name_dupe = try mem.dupe(self.table.allocator, u8, name);
+ errdefer self.table.allocator.deinit(name_dupe);
+ const entry = try self.table.put(name_dupe, package);
+ assert(entry == null);
+}
+
+const std = @import("std");
+const mem = std.mem;
+const assert = std.debug.assert;
src-self-hosted/package.zig
@@ -1,31 +0,0 @@
-const std = @import("std");
-const mem = std.mem;
-const assert = std.debug.assert;
-const ArrayListSentineled = std.ArrayListSentineled;
-
-pub const Package = struct {
- root_src_dir: ArrayListSentineled(u8, 0),
- root_src_path: ArrayListSentineled(u8, 0),
-
- /// relative to root_src_dir
- table: Table,
-
- pub const Table = std.StringHashMap(*Package);
-
- /// makes internal copies of root_src_dir and root_src_path
- /// allocator should be an arena allocator because Package never frees anything
- pub fn create(allocator: *mem.Allocator, root_src_dir: []const u8, root_src_path: []const u8) !*Package {
- const ptr = try allocator.create(Package);
- ptr.* = Package{
- .root_src_dir = try ArrayListSentineled(u8, 0).init(allocator, root_src_dir),
- .root_src_path = try ArrayListSentineled(u8, 0).init(allocator, root_src_path),
- .table = Table.init(allocator),
- };
- return ptr;
- }
-
- pub fn add(self: *Package, name: []const u8, package: *Package) !void {
- const entry = try self.table.put(try mem.dupe(self.table.allocator, u8, name), package);
- assert(entry == null);
- }
-};
src-self-hosted/scope.zig
@@ -1,418 +0,0 @@
-const std = @import("std");
-const Allocator = mem.Allocator;
-const Decl = @import("decl.zig").Decl;
-const Compilation = @import("compilation.zig").Compilation;
-const mem = std.mem;
-const ast = std.zig.ast;
-const Value = @import("value.zig").Value;
-const Type = @import("type.zig").Type;
-const ir = @import("ir.zig");
-const Span = @import("errmsg.zig").Span;
-const assert = std.debug.assert;
-const event = std.event;
-const llvm = @import("llvm.zig");
-
-pub const Scope = struct {
- id: Id,
- parent: ?*Scope,
- ref_count: std.atomic.Int(usize),
-
- /// Thread-safe
- pub fn ref(base: *Scope) void {
- _ = base.ref_count.incr();
- }
-
- /// Thread-safe
- pub fn deref(base: *Scope, comp: *Compilation) void {
- if (base.ref_count.decr() == 1) {
- if (base.parent) |parent| parent.deref(comp);
- switch (base.id) {
- .Root => @fieldParentPtr(Root, "base", base).destroy(comp),
- .Decls => @fieldParentPtr(Decls, "base", base).destroy(comp),
- .Block => @fieldParentPtr(Block, "base", base).destroy(comp),
- .FnDef => @fieldParentPtr(FnDef, "base", base).destroy(comp),
- .CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp),
- .Defer => @fieldParentPtr(Defer, "base", base).destroy(comp),
- .DeferExpr => @fieldParentPtr(DeferExpr, "base", base).destroy(comp),
- .Var => @fieldParentPtr(Var, "base", base).destroy(comp),
- .AstTree => @fieldParentPtr(AstTree, "base", base).destroy(comp),
- }
- }
- }
-
- pub fn findRoot(base: *Scope) *Root {
- var scope = base;
- while (scope.parent) |parent| {
- scope = parent;
- }
- assert(scope.id == .Root);
- return @fieldParentPtr(Root, "base", scope);
- }
-
- pub fn findFnDef(base: *Scope) ?*FnDef {
- var scope = base;
- while (true) {
- switch (scope.id) {
- .FnDef => return @fieldParentPtr(FnDef, "base", scope),
- .Root, .Decls => return null,
-
- .Block,
- .Defer,
- .DeferExpr,
- .CompTime,
- .Var,
- => scope = scope.parent.?,
-
- .AstTree => unreachable,
- }
- }
- }
-
- pub fn findDeferExpr(base: *Scope) ?*DeferExpr {
- var scope = base;
- while (true) {
- switch (scope.id) {
- .DeferExpr => return @fieldParentPtr(DeferExpr, "base", scope),
-
- .FnDef,
- .Decls,
- => return null,
-
- .Block,
- .Defer,
- .CompTime,
- .Root,
- .Var,
- => scope = scope.parent orelse return null,
-
- .AstTree => unreachable,
- }
- }
- }
-
- fn init(base: *Scope, id: Id, parent: *Scope) void {
- base.* = Scope{
- .id = id,
- .parent = parent,
- .ref_count = std.atomic.Int(usize).init(1),
- };
- parent.ref();
- }
-
- pub const Id = enum {
- Root,
- AstTree,
- Decls,
- Block,
- FnDef,
- CompTime,
- Defer,
- DeferExpr,
- Var,
- };
-
- pub const Root = struct {
- base: Scope,
- realpath: []const u8,
- decls: *Decls,
-
- /// Creates a Root scope with 1 reference
- /// Takes ownership of realpath
- pub fn create(comp: *Compilation, realpath: []u8) !*Root {
- const self = try comp.gpa().create(Root);
- self.* = Root{
- .base = Scope{
- .id = .Root,
- .parent = null,
- .ref_count = std.atomic.Int(usize).init(1),
- },
- .realpath = realpath,
- .decls = undefined,
- };
- errdefer comp.gpa().destroy(self);
- self.decls = try Decls.create(comp, &self.base);
- return self;
- }
-
- pub fn destroy(self: *Root, comp: *Compilation) void {
- // TODO comp.fs_watch.removeFile(self.realpath);
- self.decls.base.deref(comp);
- comp.gpa().free(self.realpath);
- comp.gpa().destroy(self);
- }
- };
-
- pub const AstTree = struct {
- base: Scope,
- tree: *ast.Tree,
-
- /// Creates a scope with 1 reference
- /// Takes ownership of tree, will deinit and destroy when done.
- pub fn create(comp: *Compilation, tree: *ast.Tree, root_scope: *Root) !*AstTree {
- const self = try comp.gpa().create(AstTree);
- self.* = AstTree{
- .base = undefined,
- .tree = tree,
- };
- self.base.init(.AstTree, &root_scope.base);
-
- return self;
- }
-
- pub fn destroy(self: *AstTree, comp: *Compilation) void {
- comp.gpa().free(self.tree.source);
- self.tree.deinit();
- comp.gpa().destroy(self);
- }
-
- pub fn root(self: *AstTree) *Root {
- return self.base.findRoot();
- }
- };
-
- pub const Decls = struct {
- base: Scope,
-
- /// This table remains Write Locked when the names are incomplete or possibly outdated.
- /// So if a reader manages to grab a lock, it can be sure that the set of names is complete
- /// and correct.
- table: event.RwLocked(Decl.Table),
-
- /// Creates a Decls scope with 1 reference
- pub fn create(comp: *Compilation, parent: *Scope) !*Decls {
- const self = try comp.gpa().create(Decls);
- self.* = Decls{
- .base = undefined,
- .table = event.RwLocked(Decl.Table).init(Decl.Table.init(comp.gpa())),
- };
- self.base.init(.Decls, parent);
- return self;
- }
-
- pub fn destroy(self: *Decls, comp: *Compilation) void {
- self.table.deinit();
- comp.gpa().destroy(self);
- }
- };
-
- pub const Block = struct {
- base: Scope,
- incoming_values: std.ArrayList(*ir.Inst),
- incoming_blocks: std.ArrayList(*ir.BasicBlock),
- end_block: *ir.BasicBlock,
- is_comptime: *ir.Inst,
-
- safety: Safety,
-
- const Safety = union(enum) {
- Auto,
- Manual: Manual,
-
- const Manual = struct {
- /// the source span that disabled the safety value
- span: Span,
-
- /// whether safety is enabled
- enabled: bool,
- };
-
- fn get(self: Safety, comp: *Compilation) bool {
- return switch (self) {
- .Auto => switch (comp.build_mode) {
- .Debug,
- .ReleaseSafe,
- => true,
- .ReleaseFast,
- .ReleaseSmall,
- => false,
- },
- .Manual => |man| man.enabled,
- };
- }
- };
-
- /// Creates a Block scope with 1 reference
- pub fn create(comp: *Compilation, parent: *Scope) !*Block {
- const self = try comp.gpa().create(Block);
- self.* = Block{
- .base = undefined,
- .incoming_values = undefined,
- .incoming_blocks = undefined,
- .end_block = undefined,
- .is_comptime = undefined,
- .safety = Safety.Auto,
- };
- self.base.init(.Block, parent);
- return self;
- }
-
- pub fn destroy(self: *Block, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const FnDef = struct {
- base: Scope,
-
- /// This reference is not counted so that the scope can get destroyed with the function
- fn_val: ?*Value.Fn,
-
- /// Creates a FnDef scope with 1 reference
- /// Must set the fn_val later
- pub fn create(comp: *Compilation, parent: *Scope) !*FnDef {
- const self = try comp.gpa().create(FnDef);
- self.* = FnDef{
- .base = undefined,
- .fn_val = null,
- };
- self.base.init(.FnDef, parent);
- return self;
- }
-
- pub fn destroy(self: *FnDef, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const CompTime = struct {
- base: Scope,
-
- /// Creates a CompTime scope with 1 reference
- pub fn create(comp: *Compilation, parent: *Scope) !*CompTime {
- const self = try comp.gpa().create(CompTime);
- self.* = CompTime{ .base = undefined };
- self.base.init(.CompTime, parent);
- return self;
- }
-
- pub fn destroy(self: *CompTime, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Defer = struct {
- base: Scope,
- defer_expr_scope: *DeferExpr,
- kind: Kind,
-
- pub const Kind = enum {
- ScopeExit,
- ErrorExit,
- };
-
- /// Creates a Defer scope with 1 reference
- pub fn create(
- comp: *Compilation,
- parent: *Scope,
- kind: Kind,
- defer_expr_scope: *DeferExpr,
- ) !*Defer {
- const self = try comp.gpa().create(Defer);
- self.* = Defer{
- .base = undefined,
- .defer_expr_scope = defer_expr_scope,
- .kind = kind,
- };
- self.base.init(.Defer, parent);
- defer_expr_scope.base.ref();
- return self;
- }
-
- pub fn destroy(self: *Defer, comp: *Compilation) void {
- self.defer_expr_scope.base.deref(comp);
- comp.gpa().destroy(self);
- }
- };
-
- pub const DeferExpr = struct {
- base: Scope,
- expr_node: *ast.Node,
- reported_err: bool,
-
- /// Creates a DeferExpr scope with 1 reference
- pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr {
- const self = try comp.gpa().create(DeferExpr);
- self.* = DeferExpr{
- .base = undefined,
- .expr_node = expr_node,
- .reported_err = false,
- };
- self.base.init(.DeferExpr, parent);
- return self;
- }
-
- pub fn destroy(self: *DeferExpr, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Var = struct {
- base: Scope,
- name: []const u8,
- src_node: *ast.Node,
- data: Data,
-
- pub const Data = union(enum) {
- Param: Param,
- Const: *Value,
- };
-
- pub const Param = struct {
- index: usize,
- typ: *Type,
- llvm_value: *llvm.Value,
- };
-
- pub fn createParam(
- comp: *Compilation,
- parent: *Scope,
- name: []const u8,
- src_node: *ast.Node,
- param_index: usize,
- param_type: *Type,
- ) !*Var {
- const self = try create(comp, parent, name, src_node);
- self.data = Data{
- .Param = Param{
- .index = param_index,
- .typ = param_type,
- .llvm_value = undefined,
- },
- };
- return self;
- }
-
- pub fn createConst(
- comp: *Compilation,
- parent: *Scope,
- name: []const u8,
- src_node: *ast.Node,
- value: *Value,
- ) !*Var {
- const self = try create(comp, parent, name, src_node);
- self.data = Data{ .Const = value };
- value.ref();
- return self;
- }
-
- fn create(comp: *Compilation, parent: *Scope, name: []const u8, src_node: *ast.Node) !*Var {
- const self = try comp.gpa().create(Var);
- self.* = Var{
- .base = undefined,
- .name = name,
- .src_node = src_node,
- .data = undefined,
- };
- self.base.init(.Var, parent);
- return self;
- }
-
- pub fn destroy(self: *Var, comp: *Compilation) void {
- switch (self.data) {
- .Param => {},
- .Const => |value| value.deref(comp),
- }
- comp.gpa().destroy(self);
- }
- };
-};
src-self-hosted/test.zig
@@ -3,15 +3,14 @@ const link = @import("link.zig");
const ir = @import("ir.zig");
const Allocator = std.mem.Allocator;
-var global_ctx: TestContext = undefined;
-
test "self-hosted" {
- try global_ctx.init();
- defer global_ctx.deinit();
+ var ctx: TestContext = undefined;
+ try ctx.init();
+ defer ctx.deinit();
- try @import("stage2_tests").addCases(&global_ctx);
+ try @import("stage2_tests").addCases(&ctx);
- try global_ctx.run();
+ try ctx.run();
}
pub const TestContext = struct {
src-self-hosted/type.zig
@@ -52,6 +52,7 @@ pub const Type = extern union {
.comptime_float => return .ComptimeFloat,
.noreturn => return .NoReturn,
+ .fn_noreturn_no_args => return .Fn,
.fn_naked_noreturn_no_args => return .Fn,
.fn_ccc_void_no_args => return .Fn,
@@ -184,6 +185,7 @@ pub const Type = extern union {
=> return out_stream.writeAll(@tagName(t)),
.const_slice_u8 => return out_stream.writeAll("[]const u8"),
+ .fn_noreturn_no_args => return out_stream.writeAll("fn() noreturn"),
.fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
.fn_ccc_void_no_args => return out_stream.writeAll("fn() callconv(.C) void"),
.single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"),
@@ -244,6 +246,7 @@ pub const Type = extern union {
.comptime_int => return Value.initTag(.comptime_int_type),
.comptime_float => return Value.initTag(.comptime_float_type),
.noreturn => return Value.initTag(.noreturn_type),
+ .fn_noreturn_no_args => return Value.initTag(.fn_noreturn_no_args_type),
.fn_naked_noreturn_no_args => return Value.initTag(.fn_naked_noreturn_no_args_type),
.fn_ccc_void_no_args => return Value.initTag(.fn_ccc_void_no_args_type),
.single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type),
@@ -286,6 +289,7 @@ pub const Type = extern union {
.array,
.array_u8_sentinel_0,
.const_slice_u8,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.int_unsigned,
@@ -329,6 +333,7 @@ pub const Type = extern union {
.array_u8_sentinel_0,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.int_unsigned,
@@ -369,6 +374,7 @@ pub const Type = extern union {
.noreturn,
.array,
.array_u8_sentinel_0,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.int_unsigned,
@@ -410,6 +416,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.int_unsigned,
@@ -451,6 +458,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.single_const_pointer,
@@ -481,6 +489,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.array,
@@ -524,6 +533,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.array,
@@ -579,6 +589,7 @@ pub const Type = extern union {
/// Asserts the type is a function.
pub fn fnParamLen(self: Type) usize {
return switch (self.tag()) {
+ .fn_noreturn_no_args => 0,
.fn_naked_noreturn_no_args => 0,
.fn_ccc_void_no_args => 0,
@@ -622,6 +633,7 @@ pub const Type = extern union {
/// given by `fnParamLen`.
pub fn fnParamTypes(self: Type, types: []Type) void {
switch (self.tag()) {
+ .fn_noreturn_no_args => return,
.fn_naked_noreturn_no_args => return,
.fn_ccc_void_no_args => return,
@@ -664,6 +676,7 @@ pub const Type = extern union {
/// Asserts the type is a function.
pub fn fnReturnType(self: Type) Type {
return switch (self.tag()) {
+ .fn_noreturn_no_args => Type.initTag(.noreturn),
.fn_naked_noreturn_no_args => Type.initTag(.noreturn),
.fn_ccc_void_no_args => Type.initTag(.void),
@@ -706,6 +719,7 @@ pub const Type = extern union {
/// Asserts the type is a function.
pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention {
return switch (self.tag()) {
+ .fn_noreturn_no_args => .Unspecified,
.fn_naked_noreturn_no_args => .Naked,
.fn_ccc_void_no_args => .C,
@@ -745,6 +759,49 @@ pub const Type = extern union {
};
}
+ /// Asserts the type is a function.
+ pub fn fnIsVarArgs(self: Type) bool {
+ return switch (self.tag()) {
+ .fn_noreturn_no_args => false,
+ .fn_naked_noreturn_no_args => false,
+ .fn_ccc_void_no_args => false,
+
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .int_unsigned,
+ .int_signed,
+ => unreachable,
+ };
+ }
+
pub fn isNumeric(self: Type) bool {
return switch (self.tag()) {
.f16,
@@ -776,6 +833,7 @@ pub const Type = extern union {
.type,
.anyerror,
.noreturn,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.array,
@@ -812,6 +870,7 @@ pub const Type = extern union {
.bool,
.type,
.anyerror,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.single_const_pointer_to_comptime_int,
@@ -865,6 +924,7 @@ pub const Type = extern union {
.bool,
.type,
.anyerror,
+ .fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.single_const_pointer_to_comptime_int,
@@ -902,11 +962,11 @@ pub const Type = extern union {
c_longlong,
c_ulonglong,
c_longdouble,
- c_void,
f16,
f32,
f64,
f128,
+ c_void,
bool,
void,
type,
@@ -914,6 +974,7 @@ pub const Type = extern union {
comptime_int,
comptime_float,
noreturn,
+ fn_noreturn_no_args,
fn_naked_noreturn_no_args,
fn_ccc_void_no_args,
single_const_pointer_to_comptime_int,
src-self-hosted/util.zig
@@ -1,47 +0,0 @@
-const std = @import("std");
-const Target = std.Target;
-const llvm = @import("llvm.zig");
-
-pub fn getDarwinArchString(self: Target) [:0]const u8 {
- switch (self.cpu.arch) {
- .aarch64 => return "arm64",
- .thumb,
- .arm,
- => return "arm",
- .powerpc => return "ppc",
- .powerpc64 => return "ppc64",
- .powerpc64le => return "ppc64le",
- // @tagName should be able to return sentinel terminated slice
- else => @panic("TODO https://github.com/ziglang/zig/issues/3779"), //return @tagName(arch),
- }
-}
-
-pub fn llvmTargetFromTriple(triple: [:0]const u8) !*llvm.Target {
- var result: *llvm.Target = undefined;
- var err_msg: [*:0]u8 = undefined;
- if (llvm.GetTargetFromTriple(triple, &result, &err_msg) != 0) {
- std.debug.warn("triple: {s} error: {s}\n", .{ triple, err_msg });
- return error.UnsupportedTarget;
- }
- return result;
-}
-
-pub fn initializeAllTargets() void {
- llvm.InitializeAllTargets();
- llvm.InitializeAllTargetInfos();
- llvm.InitializeAllTargetMCs();
- llvm.InitializeAllAsmPrinters();
- llvm.InitializeAllAsmParsers();
-}
-
-pub fn getLLVMTriple(allocator: *std.mem.Allocator, target: std.Target) ![:0]u8 {
- var result = try std.ArrayListSentineled(u8, 0).initSize(allocator, 0);
- defer result.deinit();
-
- try result.outStream().print(
- "{}-unknown-{}-{}",
- .{ @tagName(target.cpu.arch), @tagName(target.os.tag), @tagName(target.abi) },
- );
-
- return result.toOwnedSlice();
-}
src-self-hosted/value.zig
@@ -6,6 +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");
/// This is the raw data, with no bookkeeping, no memory awareness,
/// no de-duplication, and no type system awareness.
@@ -45,6 +46,7 @@ pub const Value = extern union {
comptime_int_type,
comptime_float_type,
noreturn_type,
+ fn_noreturn_no_args_type,
fn_naked_noreturn_no_args_type,
fn_ccc_void_no_args_type,
single_const_pointer_to_comptime_int_type,
@@ -64,8 +66,8 @@ pub const Value = extern union {
int_big_positive,
int_big_negative,
function,
- ref,
- ref_val,
+ decl_ref,
+ elem_ptr,
bytes,
repeated, // the value is a value repeated some number of times
@@ -136,6 +138,7 @@ pub const Value = extern union {
.comptime_int_type => return out_stream.writeAll("comptime_int"),
.comptime_float_type => return out_stream.writeAll("comptime_float"),
.noreturn_type => return out_stream.writeAll("noreturn"),
+ .fn_noreturn_no_args_type => return out_stream.writeAll("fn() noreturn"),
.fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
.fn_ccc_void_no_args_type => return out_stream.writeAll("fn() callconv(.C) void"),
.single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
@@ -153,11 +156,11 @@ pub const Value = extern union {
.int_big_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
.int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
.function => return out_stream.writeAll("(function)"),
- .ref => return out_stream.writeAll("(ref)"),
- .ref_val => {
- try out_stream.writeAll("*const ");
- val = val.cast(Payload.RefVal).?.val;
- continue;
+ .decl_ref => return out_stream.writeAll("(decl ref)"),
+ .elem_ptr => {
+ const elem_ptr = val.cast(Payload.Int_u64).?;
+ try out_stream.print("&[{}] ", .{elem_ptr.index});
+ val = elem_ptr.array_ptr;
},
.bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
.repeated => {
@@ -181,31 +184,32 @@ pub const Value = extern union {
return switch (self.tag()) {
.ty => self.cast(Payload.Ty).?.ty,
- .u8_type => Type.initTag(.@"u8"),
- .i8_type => Type.initTag(.@"i8"),
- .isize_type => Type.initTag(.@"isize"),
- .usize_type => Type.initTag(.@"usize"),
- .c_short_type => Type.initTag(.@"c_short"),
- .c_ushort_type => Type.initTag(.@"c_ushort"),
- .c_int_type => Type.initTag(.@"c_int"),
- .c_uint_type => Type.initTag(.@"c_uint"),
- .c_long_type => Type.initTag(.@"c_long"),
- .c_ulong_type => Type.initTag(.@"c_ulong"),
- .c_longlong_type => Type.initTag(.@"c_longlong"),
- .c_ulonglong_type => Type.initTag(.@"c_ulonglong"),
- .c_longdouble_type => Type.initTag(.@"c_longdouble"),
- .f16_type => Type.initTag(.@"f16"),
- .f32_type => Type.initTag(.@"f32"),
- .f64_type => Type.initTag(.@"f64"),
- .f128_type => Type.initTag(.@"f128"),
- .c_void_type => Type.initTag(.@"c_void"),
- .bool_type => Type.initTag(.@"bool"),
- .void_type => Type.initTag(.@"void"),
- .type_type => Type.initTag(.@"type"),
- .anyerror_type => Type.initTag(.@"anyerror"),
- .comptime_int_type => Type.initTag(.@"comptime_int"),
- .comptime_float_type => Type.initTag(.@"comptime_float"),
- .noreturn_type => Type.initTag(.@"noreturn"),
+ .u8_type => Type.initTag(.u8),
+ .i8_type => Type.initTag(.i8),
+ .isize_type => Type.initTag(.isize),
+ .usize_type => Type.initTag(.usize),
+ .c_short_type => Type.initTag(.c_short),
+ .c_ushort_type => Type.initTag(.c_ushort),
+ .c_int_type => Type.initTag(.c_int),
+ .c_uint_type => Type.initTag(.c_uint),
+ .c_long_type => Type.initTag(.c_long),
+ .c_ulong_type => Type.initTag(.c_ulong),
+ .c_longlong_type => Type.initTag(.c_longlong),
+ .c_ulonglong_type => Type.initTag(.c_ulonglong),
+ .c_longdouble_type => Type.initTag(.c_longdouble),
+ .f16_type => Type.initTag(.f16),
+ .f32_type => Type.initTag(.f32),
+ .f64_type => Type.initTag(.f64),
+ .f128_type => Type.initTag(.f128),
+ .c_void_type => Type.initTag(.c_void),
+ .bool_type => Type.initTag(.bool),
+ .void_type => Type.initTag(.void),
+ .type_type => Type.initTag(.type),
+ .anyerror_type => Type.initTag(.anyerror),
+ .comptime_int_type => Type.initTag(.comptime_int),
+ .comptime_float_type => Type.initTag(.comptime_float),
+ .noreturn_type => Type.initTag(.noreturn),
+ .fn_noreturn_no_args_type => Type.initTag(.fn_noreturn_no_args),
.fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args),
.fn_ccc_void_no_args_type => Type.initTag(.fn_ccc_void_no_args),
.single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
@@ -222,8 +226,8 @@ pub const Value = extern union {
.int_big_positive,
.int_big_negative,
.function,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.repeated,
=> unreachable,
@@ -259,6 +263,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -267,8 +272,8 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.undef,
.repeated,
@@ -314,6 +319,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -322,8 +328,8 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.undef,
.repeated,
@@ -370,6 +376,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -378,8 +385,8 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.undef,
.repeated,
@@ -431,6 +438,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -439,8 +447,8 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.repeated,
=> unreachable,
@@ -521,6 +529,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -529,8 +538,8 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.repeated,
.undef,
@@ -573,6 +582,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -581,8 +591,8 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.repeated,
.undef,
@@ -636,7 +646,7 @@ pub const Value = extern union {
}
/// Asserts the value is a pointer and dereferences it.
- pub fn pointerDeref(self: Value) Value {
+ pub fn pointerDeref(self: Value, module: *ir.Module) !Value {
return switch (self.tag()) {
.ty,
.u8_type,
@@ -664,6 +674,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -683,14 +694,21 @@ pub const Value = extern union {
=> unreachable,
.the_one_possible_value => Value.initTag(.the_one_possible_value),
- .ref => self.cast(Payload.Ref).?.cell.contents,
- .ref_val => self.cast(Payload.RefVal).?.val,
+ .decl_ref => {
+ const index = self.cast(Payload.DeclRef).?.index;
+ return module.getDeclValue(index);
+ },
+ .elem_ptr => {
+ const elem_ptr = self.cast(ElemPtr).?;
+ const array_val = try elem_ptr.array_ptr.pointerDeref(module);
+ return self.elemValue(array_val, elem_ptr.index);
+ },
};
}
/// 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.
- pub fn elemValueAt(self: Value, allocator: *Allocator, index: usize) Allocator.Error!Value {
+ pub fn elemValue(self: Value, index: usize) Value {
switch (self.tag()) {
.ty,
.u8_type,
@@ -718,6 +736,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -733,13 +752,12 @@ pub const Value = extern union {
.int_big_positive,
.int_big_negative,
.undef,
+ .elem_ptr,
+ .decl_ref,
=> unreachable,
- .ref => @panic("TODO figure out how MemoryCell works"),
- .ref_val => @panic("TODO figure out how MemoryCell works"),
-
.bytes => {
- const int_payload = try allocator.create(Value.Payload.Int_u64);
+ const int_payload = try allocator.create(Payload.Int_u64);
int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
return Value.initPayload(&int_payload.base);
},
@@ -749,6 +767,17 @@ pub const Value = extern union {
}
}
+ /// Returns a pointer to the element value at the index.
+ pub fn elemPtr(self: Value, allocator: *Allocator, index: usize) !Value {
+ const payload = try allocator.create(Payload.ElemPtr);
+ if (self.cast(Payload.ElemPtr)) |elem_ptr| {
+ payload.* = .{ .array_ptr = elem_ptr.array_ptr, .index = elem_ptr.index + index };
+ } else {
+ payload.* = .{ .array_ptr = self, .index = index };
+ }
+ return Value.initPayload(&payload.base);
+ }
+
pub fn isUndef(self: Value) bool {
return self.tag() == .undef;
}
@@ -783,6 +812,7 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
+ .fn_noreturn_no_args_type,
.fn_naked_noreturn_no_args_type,
.fn_ccc_void_no_args_type,
.single_const_pointer_to_comptime_int_type,
@@ -796,8 +826,8 @@ pub const Value = extern union {
.int_i64,
.int_big_positive,
.int_big_negative,
- .ref,
- .ref_val,
+ .decl_ref,
+ .elem_ptr,
.bytes,
.repeated,
=> false,
@@ -841,8 +871,7 @@ pub const Value = extern union {
pub const Function = struct {
base: Payload = Payload{ .tag = .function },
- /// Index into the `fns` array of the `ir.Module`
- index: usize,
+ func: *ir.Module.Fn,
};
pub const ArraySentinel0_u8_Type = struct {
@@ -855,14 +884,17 @@ pub const Value = extern union {
elem_type: *Type,
};
- pub const Ref = struct {
- base: Payload = Payload{ .tag = .ref },
- cell: *MemoryCell,
+ /// Represents a pointer to a decl, not the value of the decl.
+ pub const DeclRef = struct {
+ base: Payload = Payload{ .tag = .decl_ref },
+ /// Index into the Module's decls list
+ index: usize,
};
- pub const RefVal = struct {
- base: Payload = Payload{ .tag = .ref_val },
- val: Value,
+ pub const ElemPtr = struct {
+ base: Payload = Payload{ .tag = .elem_ptr },
+ array_ptr: Value,
+ index: usize,
};
pub const Bytes = struct {
@@ -890,29 +922,3 @@ pub const Value = extern union {
limbs: [(@sizeOf(u64) / @sizeOf(std.math.big.Limb)) + 1]std.math.big.Limb,
};
};
-
-/// This is the heart of resource management of the Zig compiler. The Zig compiler uses
-/// stop-the-world mark-and-sweep garbage collection during compilation to manage the resources
-/// associated with evaluating compile-time code and semantic analysis. Each `MemoryCell` represents
-/// a root.
-pub const MemoryCell = struct {
- parent: Parent,
- contents: Value,
-
- pub const Parent = union(enum) {
- none,
- struct_field: struct {
- struct_base: *MemoryCell,
- field_index: usize,
- },
- array_elem: struct {
- array_base: *MemoryCell,
- elem_index: usize,
- },
- union_field: *MemoryCell,
- err_union_code: *MemoryCell,
- err_union_payload: *MemoryCell,
- optional_payload: *MemoryCell,
- optional_flag: *MemoryCell,
- };
-};
src-self-hosted/visib.zig
@@ -1,4 +0,0 @@
-pub const Visib = enum {
- Private,
- Pub,
-};