Merge pull request #5307 from ziglang/self-hosted-incremental-compilation

rework self-hosted compiler for incremental builds
master
Andrew Kelley 2020-05-17 13:53:27 -04:00 committed by GitHub
commit 16f100b82e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 5162 additions and 5448 deletions

View File

@ -71,40 +71,3 @@ can do about it. See that issue for a workaround you can do in the meantime.
##### Windows
See https://github.com/ziglang/zig/wiki/Building-Zig-on-Windows
### Stage 2: Build Self-Hosted Zig from Zig Source Code
*Note: Stage 2 compiler is not complete. Beta users of Zig should use the
Stage 1 compiler for now.*
Dependencies are the same as Stage 1, except now you can use stage 1 to compile
Zig code.
```
bin/zig build --prefix $(pwd)/stage2
```
This produces `./stage2/bin/zig` which can be used for testing and development.
Once it is feature complete, it will be used to build stage 3 - the final compiler
binary.
### Stage 3: Rebuild Self-Hosted Zig Using the Self-Hosted Compiler
*Note: Stage 2 compiler is not yet able to build Stage 3. Building Stage 3 is
not yet supported.*
Once the self-hosted compiler can build itself, this will be the actual
compiler binary that we will install to the system. Until then, users should
use stage 1.
#### Debug / Development Build
```
./stage2/bin/zig build --prefix $(pwd)/stage3
```
#### Release / Install Build
```
./stage2/bin/zig build install -Drelease
```

View File

@ -51,6 +51,8 @@ pub fn build(b: *Builder) !void {
var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
exe.setBuildMode(mode);
test_step.dependOn(&exe.step);
b.default_step.dependOn(&exe.step);
const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release;
@ -58,21 +60,20 @@ pub fn build(b: *Builder) !void {
const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;
const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;
const skip_self_hosted = (b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false) or true; // TODO evented I/O good enough that this passes everywhere
if (!skip_self_hosted) {
test_step.dependOn(&exe.step);
}
const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
if (!only_install_lib_files and !skip_self_hosted) {
const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false;
if (enable_llvm) {
var ctx = parseConfigH(b, config_h_text);
ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
try configureStage2(b, exe, ctx);
b.default_step.dependOn(&exe.step);
}
if (!only_install_lib_files) {
exe.install();
}
const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse false;
if (link_libc) exe.linkLibC();
b.installDirectory(InstallDirectoryOptions{
.source_dir = "lib",

View File

@ -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);
@ -146,10 +149,15 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type {
/// Append the slice of items to the list. Allocates more
/// memory as necessary.
pub fn appendSlice(self: *Self, items: SliceConst) !void {
try self.ensureCapacity(self.items.len + items.len);
self.appendSliceAssumeCapacity(items);
}
/// Append the slice of items to the list, asserting the capacity is already
/// enough to store the new items.
pub fn appendSliceAssumeCapacity(self: *Self, items: SliceConst) void {
const oldlen = self.items.len;
const newlen = self.items.len + items.len;
try self.ensureCapacity(newlen);
self.items.len = newlen;
mem.copy(T, self.items[oldlen..], items);
}
@ -259,6 +267,231 @@ 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 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.* = Self{};
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 {
try self.ensureCapacity(allocator, self.items.len + items.len);
self.appendSliceAssumeCapacity(items);
}
/// Append the slice of items to the list, asserting the capacity is enough
/// to store the new items.
pub fn appendSliceAssumeCapacity(self: *Self, items: SliceConst) void {
const oldlen = self.items.len;
const newlen = self.items.len + items.len;
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();

View File

@ -191,8 +191,8 @@ pub fn LinearFifo(
}
/// Read the next item from the fifo
pub fn readItem(self: *Self) !T {
if (self.count == 0) return error.EndOfStream;
pub fn readItem(self: *Self) ?T {
if (self.count == 0) return null;
const c = self.buf[self.head];
self.discard(1);
@ -282,7 +282,10 @@ pub fn LinearFifo(
/// Write a single item to the fifo
pub fn writeItem(self: *Self, item: T) !void {
try self.ensureUnusedCapacity(1);
return self.writeItemAssumeCapacity(item);
}
pub fn writeItemAssumeCapacity(self: *Self, item: T) void {
var tail = self.head + self.count;
if (powers_of_two) {
tail &= self.buf.len - 1;
@ -342,10 +345,10 @@ pub fn LinearFifo(
}
}
/// Peek at the item at `offset`
pub fn peekItem(self: Self, offset: usize) error{EndOfStream}!T {
if (offset >= self.count)
return error.EndOfStream;
/// Returns the item at `offset`.
/// Asserts offset is within bounds.
pub fn peekItem(self: Self, offset: usize) T {
assert(offset < self.count);
var index = self.head + offset;
if (powers_of_two) {
@ -369,18 +372,18 @@ test "LinearFifo(u8, .Dynamic)" {
{
var i: usize = 0;
while (i < 5) : (i += 1) {
try fifo.write(&[_]u8{try fifo.peekItem(i)});
try fifo.write(&[_]u8{fifo.peekItem(i)});
}
testing.expectEqual(@as(usize, 10), fifo.readableLength());
testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0));
}
{
testing.expectEqual(@as(u8, 'H'), try fifo.readItem());
testing.expectEqual(@as(u8, 'E'), try fifo.readItem());
testing.expectEqual(@as(u8, 'L'), try fifo.readItem());
testing.expectEqual(@as(u8, 'L'), try fifo.readItem());
testing.expectEqual(@as(u8, 'O'), try fifo.readItem());
testing.expectEqual(@as(u8, 'H'), fifo.readItem().?);
testing.expectEqual(@as(u8, 'E'), fifo.readItem().?);
testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
testing.expectEqual(@as(u8, 'O'), fifo.readItem().?);
}
testing.expectEqual(@as(usize, 5), fifo.readableLength());
@ -451,11 +454,11 @@ test "LinearFifo" {
testing.expectEqual(@as(usize, 5), fifo.readableLength());
{
testing.expectEqual(@as(T, 0), try fifo.readItem());
testing.expectEqual(@as(T, 1), try fifo.readItem());
testing.expectEqual(@as(T, 1), try fifo.readItem());
testing.expectEqual(@as(T, 0), try fifo.readItem());
testing.expectEqual(@as(T, 1), try fifo.readItem());
testing.expectEqual(@as(T, 0), fifo.readItem().?);
testing.expectEqual(@as(T, 1), fifo.readItem().?);
testing.expectEqual(@as(T, 1), fifo.readItem().?);
testing.expectEqual(@as(T, 0), fifo.readItem().?);
testing.expectEqual(@as(T, 1), fifo.readItem().?);
testing.expectEqual(@as(usize, 0), fifo.readableLength());
}

View File

@ -527,6 +527,33 @@ pub const File = struct {
}
}
pub const CopyRangeError = PWriteError || PReadError;
pub fn copyRange(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize {
// TODO take advantage of copy_file_range OS APIs
var buf: [8 * 4096]u8 = undefined;
const adjusted_count = math.min(buf.len, len);
const amt_read = try in.pread(buf[0..adjusted_count], in_offset);
if (amt_read == 0) return @as(usize, 0);
return out.pwrite(buf[0..amt_read], out_offset);
}
/// Returns the number of bytes copied. If the number read is smaller than `buffer.len`, it
/// means the in file reached the end. Reaching the end of a file is not an error condition.
pub fn copyRangeAll(in: File, in_offset: u64, out: File, out_offset: u64, len: usize) CopyRangeError!usize {
var total_bytes_copied: usize = 0;
var in_off = in_offset;
var out_off = out_offset;
while (total_bytes_copied < len) {
const amt_copied = try copyRange(in, in_off, out, out_off, len - total_bytes_copied);
if (amt_copied == 0) return total_bytes_copied;
total_bytes_copied += amt_copied;
in_off += amt_copied;
out_off += amt_copied;
}
return total_bytes_copied;
}
pub const WriteFileOptions = struct {
in_offset: u64 = 0,

View File

@ -10,7 +10,7 @@ const Wyhash = std.hash.Wyhash;
const Allocator = mem.Allocator;
const builtin = @import("builtin");
const want_modification_safety = builtin.mode != .ReleaseFast;
const want_modification_safety = std.debug.runtime_safety;
const debug_u32 = if (want_modification_safety) u32 else void;
pub fn AutoHashMap(comptime K: type, comptime V: type) type {
@ -219,6 +219,10 @@ pub fn HashMap(comptime K: type, comptime V: type, comptime hash: fn (key: K) u3
return put_result.old_kv;
}
pub fn putAssumeCapacityNoClobber(self: *Self, key: K, value: V) void {
assert(self.putAssumeCapacity(key, value) == null);
}
pub fn get(hm: *const Self, key: K) ?*KV {
if (hm.entries.len == 0) {
return null;

View File

@ -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,

View File

@ -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];
}
};

View File

@ -49,7 +49,7 @@ pub fn SinglyLinkedList(comptime T: type) type {
}
};
first: ?*Node,
first: ?*Node = null,
/// Initialize a linked list.
///

View File

@ -279,6 +279,21 @@ pub const Allocator = struct {
const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1);
assert(shrink_result.len == 0);
}
/// Copies `m` to newly allocated memory. Caller owns the memory.
pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
const new_buf = try allocator.alloc(T, m.len);
copy(T, new_buf, m);
return new_buf;
}
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
const new_buf = try allocator.alloc(T, m.len + 1);
copy(T, new_buf, m);
new_buf[m.len] = 0;
return new_buf[0..m.len :0];
}
};
var failAllocator = Allocator {
@ -785,19 +800,14 @@ pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
return true;
}
/// Copies `m` to newly allocated memory. Caller owns the memory.
/// Deprecated, use `Allocator.dupe`.
pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
const new_buf = try allocator.alloc(T, m.len);
copy(T, new_buf, m);
return new_buf;
return allocator.dupe(T, m);
}
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
/// Deprecated, use `Allocator.dupeZ`.
pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
const new_buf = try allocator.alloc(T, m.len + 1);
copy(T, new_buf, m);
new_buf[m.len] = 0;
return new_buf[0..m.len :0];
return allocator.dupeZ(T, m);
}
/// Remove values from the beginning of a slice.
@ -2112,7 +2122,11 @@ pub fn alignBackwardGeneric(comptime T: type, addr: T, alignment: T) T {
/// Given an address and an alignment, return true if the address is a multiple of the alignment
/// The alignment must be a power of 2 and greater than 0.
pub fn isAligned(addr: usize, alignment: usize) bool {
return alignBackward(addr, alignment) == addr;
return isAlignedGeneric(u64, addr, alignment);
}
pub fn isAlignedGeneric(comptime T: type, addr: T, alignment: T) bool {
return alignBackwardGeneric(T, addr, alignment) == addr;
}
test "isAligned" {

View File

@ -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;

2091
src-self-hosted/Module.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
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;
const Package = @This();

View File

@ -0,0 +1,23 @@
const std = @import("std");
const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
const Allocator = std.mem.Allocator;
const TypedValue = @This();
ty: Type,
val: Value,
/// Memory management for TypedValue. The main purpose of this type
/// is to be small and have a deinit() function to free associated resources.
pub const Managed = struct {
/// If the tag value is less than Tag.no_payload_count, then no pointer
/// dereference is needed.
typed_value: TypedValue,
/// If this is `null` then there is no memory management needed.
arena: ?*std.heap.ArenaAllocator.State = null,
pub fn deinit(self: *Managed, allocator: *Allocator) void {
if (self.arena) |a| a.promote(allocator).deinit();
self.* = undefined;
}
};

View File

@ -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");
});

View File

@ -4,64 +4,155 @@ const assert = std.debug.assert;
const ir = @import("ir.zig");
const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
const TypedValue = @import("TypedValue.zig");
const link = @import("link.zig");
const Module = @import("Module.zig");
const ErrorMsg = Module.ErrorMsg;
const Target = std.Target;
const Allocator = mem.Allocator;
pub const ErrorMsg = struct {
byte_offset: usize,
msg: []const u8,
pub const Result = union(enum) {
/// The `code` parameter passed to `generateSymbol` has the value appended.
appended: void,
/// The value is available externally, `code` is unused.
externally_managed: []const u8,
fail: *Module.ErrorMsg,
};
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(
bin_file: *link.ElfFile,
src: usize,
typed_value: TypedValue,
code: *std.ArrayList(u8),
) error{
OutOfMemory,
/// A Decl that this symbol depends on had a semantic analysis failure.
AnalysisFail,
}!Result {
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,
.target = &bin_file.options.target,
.bin_file = bin_file,
.mod_fn = module_fn,
.code = code,
.inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator),
.errors = std.ArrayList(ErrorMsg).init(code.allocator),
.inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator),
.err_msg = null,
};
defer function.inst_table.deinit();
defer function.errors.deinit();
for (module_fn.body.instructions) |inst| {
for (module_fn.analysis.success.instructions) |inst| {
const new_inst = function.genFuncInst(inst) catch |err| switch (err) {
error.CodegenFail => {
assert(function.errors.items.len != 0);
break;
},
error.CodegenFail => return Result{ .fail = function.err_msg.? },
else => |e| return e,
};
try function.inst_table.putNoClobber(inst, new_inst);
}
return Symbol{ .errors = function.errors.toOwnedSlice() };
if (function.err_msg) |em| {
return Result{ .fail = em };
} else {
return Result{ .appended = {} };
}
},
.Array => {
if (typed_value.val.cast(Value.Payload.Bytes)) |payload| {
if (typed_value.ty.arraySentinel()) |sentinel| {
try code.ensureCapacity(code.items.len + payload.data.len + 1);
code.appendSliceAssumeCapacity(payload.data);
const prev_len = code.items.len;
switch (try generateSymbol(bin_file, src, .{
.ty = typed_value.ty.elemType(),
.val = sentinel,
}, code)) {
.appended => return Result{ .appended = {} },
.externally_managed => |slice| {
code.appendSliceAssumeCapacity(slice);
return Result{ .appended = {} };
},
.fail => |em| return Result{ .fail = em },
}
} else {
return Result{ .externally_managed = payload.data };
}
}
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for more kinds of arrays",
.{},
),
};
},
.Pointer => {
if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| {
const decl = payload.decl;
if (decl.analysis != .complete) return error.AnalysisFail;
assert(decl.link.local_sym_index != 0);
// TODO handle the dependency of this symbol on the decl's vaddr.
// If the decl changes vaddr, then this symbol needs to get regenerated.
const vaddr = bin_file.local_symbols.items[decl.link.local_sym_index].st_value;
const endian = bin_file.options.target.cpu.arch.endian();
switch (bin_file.ptr_width) {
.p32 => {
try code.resize(4);
mem.writeInt(u32, code.items[0..4], @intCast(u32, vaddr), endian);
},
.p64 => {
try code.resize(8);
mem.writeInt(u64, code.items[0..8], vaddr, endian);
},
}
return Result{ .appended = {} };
}
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for pointer {}",
.{typed_value.val},
),
};
},
.Int => {
const info = typed_value.ty.intInfo(bin_file.options.target);
if (info.bits == 8 and !info.signed) {
const x = typed_value.val.toUnsignedInt();
try code.append(@intCast(u8, x));
return Result{ .appended = {} };
}
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for int type '{}'",
.{typed_value.ty},
),
};
},
else => |t| {
return Result{
.fail = try ErrorMsg.create(
bin_file.allocator,
src,
"TODO implement generateSymbol for type '{}'",
.{@tagName(t)},
),
};
},
else => @panic("TODO implement generateSymbol for non-function types"),
}
}
const Function = struct {
module: *const ir.Module,
mod_fn: *const ir.Module.Fn,
bin_file: *link.ElfFile,
target: *const std.Target,
mod_fn: *const Module.Fn,
code: *std.ArrayList(u8),
inst_table: std.AutoHashMap(*ir.Inst, MCValue),
errors: std.ArrayList(ErrorMsg),
err_msg: ?*ErrorMsg,
const MCValue = union(enum) {
none,
@ -73,11 +164,14 @@ const Function = struct {
/// The value is in a target-specific register. The value can
/// be @intToEnum casted to the respective Reg enum.
register: usize,
/// The value is in memory at a hard-coded address.
memory: u64,
};
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).?),
@ -92,54 +186,76 @@ const Function = struct {
}
fn genBreakpoint(self: *Function, src: usize) !MCValue {
switch (self.module.target.cpu.arch) {
switch (self.target.cpu.arch) {
.i386, .x86_64 => {
try self.code.append(0xcc); // int3
},
else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.module.target.cpu.arch}),
else => return self.fail(src, "TODO implement @breakpoint() for {}", .{self.target.cpu.arch}),
}
return .none;
}
fn genCall(self: *Function, inst: *ir.Inst.Call) !MCValue {
if (inst.args.func.cast(ir.Inst.Constant)) |func_inst| {
if (inst.args.args.len != 0) {
return self.fail(inst.base.src, "TODO implement call with more than 0 parameters", .{});
}
if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
const func = func_val.func;
return self.fail(inst.base.src, "TODO implement calling function", .{});
} else {
return self.fail(inst.base.src, "TODO implement calling weird function values", .{});
}
} else {
return self.fail(inst.base.src, "TODO implement calling runtime known function pointer", .{});
}
switch (self.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO implement call for {}", .{self.target.cpu.arch}),
}
return .unreach;
}
fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue {
switch (self.module.target.cpu.arch) {
switch (self.target.cpu.arch) {
.i386, .x86_64 => {
try self.code.append(0xc3); // ret
},
else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.module.target.cpu.arch}),
else => return self.fail(inst.base.src, "TODO implement return for {}", .{self.target.cpu.arch}),
}
return .unreach;
}
fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue {
switch (self.module.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.target.cpu.arch}),
switch (self.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}),
}
}
fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue {
switch (self.module.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.target.cpu.arch}),
switch (self.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}),
}
}
fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue {
switch (self.module.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.target.cpu.arch}),
switch (self.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.target.cpu.arch}),
}
}
fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue {
// Here you can specialize this instruction if it makes sense to, otherwise the default
// will call genIsNull and invert the result.
switch (self.module.target.cpu.arch) {
switch (self.target.cpu.arch) {
else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}),
}
}
fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void {
switch (self.module.target.cpu.arch) {
switch (self.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;
@ -151,13 +267,13 @@ const Function = struct {
mem.writeIntLittle(u32, imm_ptr, amount);
}
},
else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.module.target.cpu.arch}),
else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.target.cpu.arch}),
}
}
fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue {
// TODO convert to inline function
switch (self.module.target.cpu.arch) {
switch (self.target.cpu.arch) {
.arm => return self.genAsmArch(.arm, inst),
.armeb => return self.genAsmArch(.armeb, inst),
.aarch64 => return self.genAsmArch(.aarch64, inst),
@ -246,128 +362,182 @@ const Function = struct {
}
}
fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) !void {
fn genSetReg(self: *Function, src: usize, comptime arch: Target.Cpu.Arch, reg: Reg(arch), mcv: MCValue) error{ CodegenFail, OutOfMemory }!void {
switch (arch) {
.x86_64 => switch (reg) {
.rax => switch (mcv) {
.none, .unreach => unreachable,
.immediate => |x| {
// Setting the eax register zeroes the upper part of rax, so if the number is small
// enough, that is preferable.
// Best case: zero
// 31 c0 xor eax,eax
if (x == 0) {
return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 });
}
// Next best case: set eax with 4 bytes
// b8 04 03 02 01 mov eax,0x01020304
if (x <= std.math.maxInt(u32)) {
try self.code.resize(self.code.items.len + 5);
self.code.items[self.code.items.len - 5] = 0xb8;
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
return;
}
// Worst case: set rax with 8 bytes
// 48 b8 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708
try self.code.resize(self.code.items.len + 10);
self.code.items[self.code.items.len - 10] = 0x48;
self.code.items[self.code.items.len - 9] = 0xb8;
const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
mem.writeIntLittle(u64, imm_ptr, x);
return;
},
.embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rax = embedded_in_code", .{}),
.register => return self.fail(src, "TODO implement x86_64 genSetReg %rax = register", .{}),
},
.rdx => switch (mcv) {
.none, .unreach => unreachable,
.immediate => |x| {
// Setting the edx register zeroes the upper part of rdx, so if the number is small
// enough, that is preferable.
// Best case: zero
// 31 d2 xor edx,edx
if (x == 0) {
return self.code.appendSlice(&[_]u8{ 0x31, 0xd2 });
}
// Next best case: set edx with 4 bytes
// ba 04 03 02 01 mov edx,0x1020304
if (x <= std.math.maxInt(u32)) {
try self.code.resize(self.code.items.len + 5);
self.code.items[self.code.items.len - 5] = 0xba;
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
return;
}
// Worst case: set rdx with 8 bytes
// 48 ba 08 07 06 05 04 03 02 01 movabs rdx,0x0102030405060708
try self.code.resize(self.code.items.len + 10);
self.code.items[self.code.items.len - 10] = 0x48;
self.code.items[self.code.items.len - 9] = 0xba;
const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
mem.writeIntLittle(u64, imm_ptr, x);
return;
},
.embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = embedded_in_code", .{}),
.register => return self.fail(src, "TODO implement x86_64 genSetReg %rdx = register", .{}),
},
.rdi => switch (mcv) {
.none, .unreach => unreachable,
.immediate => |x| {
// Setting the edi register zeroes the upper part of rdi, so if the number is small
// enough, that is preferable.
// Best case: zero
// 31 ff xor edi,edi
if (x == 0) {
return self.code.appendSlice(&[_]u8{ 0x31, 0xff });
}
// Next best case: set edi with 4 bytes
// bf 04 03 02 01 mov edi,0x1020304
if (x <= std.math.maxInt(u32)) {
try self.code.resize(self.code.items.len + 5);
self.code.items[self.code.items.len - 5] = 0xbf;
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
return;
}
// Worst case: set rdi with 8 bytes
// 48 bf 08 07 06 05 04 03 02 01 movabs rax,0x0102030405060708
try self.code.resize(self.code.items.len + 10);
self.code.items[self.code.items.len - 10] = 0x48;
self.code.items[self.code.items.len - 9] = 0xbf;
const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
mem.writeIntLittle(u64, imm_ptr, x);
return;
},
.embedded_in_code => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = embedded_in_code", .{}),
.register => return self.fail(src, "TODO implement x86_64 genSetReg %rdi = register", .{}),
},
.rsi => switch (mcv) {
.none, .unreach => unreachable,
.immediate => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = immediate", .{}),
.embedded_in_code => |code_offset| {
// Examples:
// lea rsi, [rip + 0x01020304]
// lea rsi, [rip - 7]
// f: 48 8d 35 04 03 02 01 lea rsi,[rip+0x1020304] # 102031a <_start+0x102031a>
// 16: 48 8d 35 f9 ff ff ff lea rsi,[rip+0xfffffffffffffff9] # 16 <_start+0x16>
.x86_64 => switch (mcv) {
.none, .unreach => unreachable,
.immediate => |x| {
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
// 32-bit moves zero-extend to 64-bit, so xoring the 32-bit
// register is the fastest way to zero a register.
if (x == 0) {
// The encoding for `xor r32, r32` is `0x31 /r`.
// Section 3.1.1.1 of the Intel x64 Manual states that "/r indicates that the
// ModR/M byte of the instruction contains a register operand and an r/m operand."
//
// We need the offset from RIP in a signed i32 twos complement.
// The instruction is 7 bytes long and RIP points to the next instruction.
try self.code.resize(self.code.items.len + 7);
const rip = self.code.items.len;
const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
const offset = @intCast(i32, big_offset);
self.code.items[self.code.items.len - 7] = 0x48;
self.code.items[self.code.items.len - 6] = 0x8d;
self.code.items[self.code.items.len - 5] = 0x35;
// R/M bytes are composed of two bits for the mode, then three bits for the register,
// then three bits for the operand. Since we're zeroing a register, the two three-bit
// values will be identical, and the mode is three (the raw register value).
//
if (reg.isExtended()) {
// If we're accessing e.g. r8d, we need to use a REX prefix before the actual operation. Since
// this is a 32-bit operation, the W flag is set to zero. X is also zero, as we're not using a SIB.
// Both R and B are set, as we're extending, in effect, the register bits *and* the operand.
//
// From section 2.2.1.2 of the manual, REX is encoded as b0100WRXB. In this case, that's
// b01000101, or 0x45.
return self.code.appendSlice(&[_]u8{
0x45,
0x31,
0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id()),
});
} else {
return self.code.appendSlice(&[_]u8{
0x31,
0xC0 | (@as(u8, reg.id()) << 3) | reg.id(),
});
}
}
if (x <= std.math.maxInt(u32)) {
// Next best case: if we set the lower four bytes, the upper four will be zeroed.
//
// The encoding for `mov IMM32 -> REG` is (0xB8 + R) IMM.
if (reg.isExtended()) {
// Just as with XORing, we need a REX prefix. This time though, we only
// need the B bit set, as we're extending the opcode's register field,
// and there is no Mod R/M byte.
//
// Thus, we need b01000001, or 0x41.
try self.code.resize(self.code.items.len + 6);
self.code.items[self.code.items.len - 6] = 0x41;
} else {
try self.code.resize(self.code.items.len + 5);
}
self.code.items[self.code.items.len - 5] = 0xB8 | @as(u8, reg.id() & 0b111);
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
mem.writeIntLittle(i32, imm_ptr, offset);
mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
return;
},
.register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}),
}
// Worst case: we need to load the 64-bit register with the IMM. GNU's assemblers calls
// this `movabs`, though this is officially just a different variant of the plain `mov`
// instruction.
//
// This encoding is, in fact, the *same* as the one used for 32-bit loads. The only
// difference is that we set REX.W before the instruction, which extends the load to
// 64-bit and uses the full bit-width of the register.
//
// Since we always need a REX here, let's just check if we also need to set REX.B.
//
// In this case, the encoding of the REX byte is 0b0100100B
const REX = 0x48 | (if (reg.isExtended()) @as(u8, 0x01) else 0);
try self.code.resize(self.code.items.len + 10);
self.code.items[self.code.items.len - 10] = REX;
self.code.items[self.code.items.len - 9] = 0xB8 | @as(u8, reg.id() & 0b111);
const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
mem.writeIntLittle(u64, imm_ptr, x);
},
.embedded_in_code => |code_offset| {
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
// We need the offset from RIP in a signed i32 twos complement.
// The instruction is 7 bytes long and RIP points to the next instruction.
//
// 64-bit LEA is encoded as REX.W 8D /r. If the register is extended, the REX byte is modified,
// but the operation size is unchanged. Since we're using a disp32, we want mode 0 and lower three
// bits as five.
// REX 0x8D 0b00RRR101, where RRR is the lower three bits of the id.
try self.code.resize(self.code.items.len + 7);
const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0;
const rip = self.code.items.len;
const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
const offset = @intCast(i32, big_offset);
self.code.items[self.code.items.len - 7] = REX;
self.code.items[self.code.items.len - 6] = 0x8D;
self.code.items[self.code.items.len - 5] = 0b101 | (@as(u8, reg.id() & 0b111) << 3);
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
mem.writeIntLittle(i32, imm_ptr, offset);
},
.register => |r| {
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
const src_reg = @intToEnum(Reg(arch), @intCast(u8, r));
// This is a varient of 8B /r. Since we're using 64-bit moves, we require a REX.
// This is thus three bytes: REX 0x8B R/M.
// If the destination is extended, the R field must be 1.
// If the *source* is extended, the B field must be 1.
// Since the register is being accessed directly, the R/M mode is three. The reg field (the middle
// three bits) contain the destination, and the R/M field (the lower three bits) contain the source.
const REX = 0x48 | (if (reg.isExtended()) @as(u8, 4) else 0) | (if (src_reg.isExtended()) @as(u8, 1) else 0);
const R = 0xC0 | (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, src_reg.id());
try self.code.appendSlice(&[_]u8{ REX, 0x8B, R });
},
.memory => |x| {
if (reg.size() != 64) {
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
}
if (x <= std.math.maxInt(u32)) {
// Moving from memory to a register is a variant of `8B /r`.
// Since we're using 64-bit moves, we require a REX.
// This variant also requires a SIB, as it would otherwise be RIP-relative.
// We want mode zero with the lower three bits set to four to indicate an SIB with no other displacement.
// The SIB must be 0x25, to indicate a disp32 with no scaled index.
// 0b00RRR100, where RRR is the lower three bits of the register ID.
// The instruction is thus eight bytes; REX 0x8B 0b00RRR100 0x25 followed by a four-byte disp32.
try self.code.resize(self.code.items.len + 8);
const REX = 0x48 | if (reg.isExtended()) @as(u8, 1) else 0;
const r = 0x04 | (@as(u8, reg.id() & 0b111) << 3);
self.code.items[self.code.items.len - 8] = REX;
self.code.items[self.code.items.len - 7] = 0x8B;
self.code.items[self.code.items.len - 6] = r;
self.code.items[self.code.items.len - 5] = 0x25;
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
mem.writeIntLittle(u32, imm_ptr, @intCast(u32, x));
} else {
// If this is RAX, we can use a direct load; otherwise, we need to load the address, then indirectly load
// the value.
if (reg.id() == 0) {
// REX.W 0xA1 moffs64*
// moffs64* is a 64-bit offset "relative to segment base", which really just means the
// absolute address for all practical purposes.
try self.code.resize(self.code.items.len + 10);
// REX.W == 0x48
self.code.items[self.code.items.len - 10] = 0x48;
self.code.items[self.code.items.len - 9] = 0xA1;
const imm_ptr = self.code.items[self.code.items.len - 8 ..][0..8];
mem.writeIntLittle(u64, imm_ptr, x);
} else {
// This requires two instructions; a move imm as used above, followed by an indirect load using the register
// as the address and the register as the destination.
//
// This cannot be used if the lower three bits of the id are equal to four or five, as there
// is no way to possibly encode it. This means that RSP, RBP, R12, and R13 cannot be used with
// this instruction.
const id3 = @truncate(u3, reg.id());
std.debug.assert(id3 != 4 and id3 != 5);
// Rather than duplicate the logic used for the move, we just use a self-call with a new MCValue.
try self.genSetReg(src, arch, reg, MCValue{ .immediate = x });
// Now, the register contains the address of the value to load into it
// Currently, we're only allowing 64-bit registers, so we need the `REX.W 8B /r` variant.
// TODO: determine whether to allow other sized registers, and if so, handle them properly.
// This operation requires three bytes: REX 0x8B R/M
//
// For this operation, we want R/M mode *zero* (use register indirectly), and the two register
// values must match. Thus, it's 00ABCABC where ABC is the lower three bits of the register ID.
//
// Furthermore, if this is an extended register, both B and R must be set in the REX byte, as *both*
// register operands need to be marked as extended.
const REX = 0x48 | if (reg.isExtended()) @as(u8, 0b0101) else 0;
const RM = (@as(u8, reg.id() & 0b111) << 3) | @truncate(u3, reg.id());
try self.code.appendSlice(&[_]u8{ REX, 0x8B, RM });
}
}
},
else => return self.fail(src, "TODO implement genSetReg for x86_64 '{}'", .{@tagName(reg)}),
},
else => return self.fail(src, "TODO implement genSetReg for more architectures", .{}),
}
@ -396,30 +566,22 @@ const Function = struct {
}
}
fn genTypedValue(self: *Function, src: usize, typed_value: ir.TypedValue) !MCValue {
fn genTypedValue(self: *Function, src: usize, typed_value: TypedValue) !MCValue {
const ptr_bits = self.target.cpu.arch.ptrBitWidth();
const ptr_bytes: u64 = @divExact(ptr_bits, 8);
const allocator = self.code.allocator;
switch (typed_value.ty.zigTypeTag()) {
.Pointer => {
const ptr_elem_type = typed_value.ty.elemType();
switch (ptr_elem_type.zigTypeTag()) {
.Array => {
// TODO more checks to make sure this can be emitted as a string literal
const bytes = try typed_value.val.toAllocatedBytes(self.code.allocator);
defer self.code.allocator.free(bytes);
const smaller_len = std.math.cast(u32, bytes.len) catch
return self.fail(src, "TODO handle a larger string constant", .{});
// Emit the string literal directly into the code; jump over it.
try self.genRelativeFwdJump(src, smaller_len);
const offset = self.code.items.len;
try self.code.appendSlice(bytes);
return MCValue{ .embedded_in_code = offset };
},
else => |t| return self.fail(src, "TODO implement emitTypedValue for pointer to '{}'", .{@tagName(t)}),
if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| {
const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
const decl = payload.decl;
const got_addr = got.p_vaddr + decl.link.offset_table_index * ptr_bytes;
return MCValue{ .memory = got_addr };
}
return self.fail(src, "TODO codegen more kinds of const pointers", .{});
},
.Int => {
const info = typed_value.ty.intInfo(self.module.target);
const ptr_bits = self.module.target.cpu.arch.ptrBitWidth();
const info = typed_value.ty.intInfo(self.target.*);
if (info.bits > ptr_bits or info.signed) {
return self.fail(src, "TODO const int bigger than ptr and signed int", .{});
}
@ -433,127 +595,19 @@ 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,
};
}
assert(self.err_msg == null);
self.err_msg = try ErrorMsg.create(self.code.allocator, src, format, args);
return error.CodegenFail;
}
};
const x86_64 = @import("codegen/x86_64.zig");
const x86 = @import("codegen/x86.zig");
fn Reg(comptime arch: Target.Cpu.Arch) type {
return switch (arch) {
.i386 => enum {
eax,
ebx,
ecx,
edx,
ebp,
esp,
esi,
edi,
ax,
bx,
cx,
dx,
bp,
sp,
si,
di,
ah,
bh,
ch,
dh,
al,
bl,
cl,
dl,
},
.x86_64 => enum {
rax,
rbx,
rcx,
rdx,
rbp,
rsp,
rsi,
rdi,
r8,
r9,
r10,
r11,
r12,
r13,
r14,
r15,
eax,
ebx,
ecx,
edx,
ebp,
esp,
esi,
edi,
r8d,
r9d,
r10d,
r11d,
r12d,
r13d,
r14d,
r15d,
ax,
bx,
cx,
dx,
bp,
sp,
si,
di,
r8w,
r9w,
r10w,
r11w,
r12w,
r13w,
r14w,
r15w,
ah,
bh,
ch,
dh,
bph,
sph,
sih,
dih,
al,
bl,
cl,
dl,
bpl,
spl,
sil,
dil,
r8b,
r9b,
r10b,
r11b,
r12b,
r13b,
r14b,
r15b,
},
.i386 => x86.Register,
.x86_64 => x86_64.Register,
else => @compileError("TODO add more register enums"),
};
}

View File

@ -0,0 +1,30 @@
// zig fmt: off
pub const Register = enum(u8) {
// 0 through 7, 32-bit registers. id is int value
eax, ecx, edx, ebx, esp, ebp, esi, edi,
// 8-15, 16-bit registers. id is int value - 8.
ax, cx, dx, bx, sp, bp, si, di,
// 16-23, 8-bit registers. id is int value - 16.
al, bl, cl, dl, ah, ch, dh, bh,
/// Returns the bit-width of the register.
pub fn size(self: @This()) u7 {
return switch (@enumToInt(self)) {
0...7 => 32,
8...15 => 16,
16...23 => 8,
else => unreachable,
};
}
/// Returns the register's id. This is used in practically every opcode the
/// x86 has. It is embedded in some instructions, such as the `B8 +rd` move
/// instruction, and is used in the R/M byte.
pub fn id(self: @This()) u3 {
return @truncate(u3, @enumToInt(self));
}
};
// zig fmt: on

View File

@ -0,0 +1,53 @@
// zig fmt: off
pub const Register = enum(u8) {
// 0 through 15, 64-bit registers. 8-15 are extended.
// id is just the int value.
rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi,
r8, r9, r10, r11, r12, r13, r14, r15,
// 16 through 31, 32-bit registers. 24-31 are extended.
// id is int value - 16.
eax, ecx, edx, ebx, esp, ebp, esi, edi,
r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d,
// 32-47, 16-bit registers. 40-47 are extended.
// id is int value - 32.
ax, cx, dx, bx, sp, bp, si, di,
r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w,
// 48-63, 8-bit registers. 56-63 are extended.
// id is int value - 48.
al, bl, cl, dl, ah, ch, dh, bh,
r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
/// Returns the bit-width of the register.
pub fn size(self: @This()) u7 {
return switch (@enumToInt(self)) {
0...15 => 64,
16...31 => 32,
32...47 => 16,
48...64 => 8,
else => unreachable,
};
}
/// Returns whether the register is *extended*. Extended registers are the
/// new registers added with amd64, r8 through r15. This also includes any
/// other variant of access to those registers, such as r8b, r15d, and so
/// on. This is needed because access to these registers requires special
/// handling via the REX prefix, via the B or R bits, depending on context.
pub fn isExtended(self: @This()) bool {
return @enumToInt(self) & 0x08 != 0;
}
/// This returns the 4-bit register ID, which is used in practically every
/// opcode. Note that bit 3 (the highest bit) is *never* used directly in
/// an instruction (@see isExtended), and requires special handling. The
/// lower three bits are often embedded directly in instructions (such as
/// the B8 variant of moves), or used in R/M bytes.
pub fn id(self: @This()) u4 {
return @truncate(u4, @enumToInt(self));
}
};
// zig fmt: on

File diff suppressed because it is too large Load Diff

View File

@ -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,
};
};

View File

@ -1,284 +0,0 @@
const std = @import("std");
const mem = std.mem;
const fs = std.fs;
const process = std.process;
const Token = std.zig.Token;
const ast = std.zig.ast;
const TokenIndex = std.zig.ast.TokenIndex;
const Compilation = @import("compilation.zig").Compilation;
const Scope = @import("scope.zig").Scope;
pub const Color = enum {
Auto,
Off,
On,
};
pub const Span = struct {
first: ast.TokenIndex,
last: ast.TokenIndex,
pub fn token(i: TokenIndex) Span {
return Span{
.first = i,
.last = i,
};
}
pub fn node(n: *ast.Node) Span {
return Span{
.first = n.firstToken(),
.last = n.lastToken(),
};
}
};
pub const Msg = struct {
text: []u8,
realpath: []u8,
data: Data,
const Data = union(enum) {
Cli: Cli,
PathAndTree: PathAndTree,
ScopeAndComp: ScopeAndComp,
};
const PathAndTree = struct {
span: Span,
tree: *ast.Tree,
allocator: *mem.Allocator,
};
const ScopeAndComp = struct {
span: Span,
tree_scope: *Scope.AstTree,
compilation: *Compilation,
};
const Cli = struct {
allocator: *mem.Allocator,
};
pub fn destroy(self: *Msg) void {
switch (self.data) {
.Cli => |cli| {
cli.allocator.free(self.text);
cli.allocator.free(self.realpath);
cli.allocator.destroy(self);
},
.PathAndTree => |path_and_tree| {
path_and_tree.allocator.free(self.text);
path_and_tree.allocator.free(self.realpath);
path_and_tree.allocator.destroy(self);
},
.ScopeAndComp => |scope_and_comp| {
scope_and_comp.tree_scope.base.deref(scope_and_comp.compilation);
scope_and_comp.compilation.gpa().free(self.text);
scope_and_comp.compilation.gpa().free(self.realpath);
scope_and_comp.compilation.gpa().destroy(self);
},
}
}
fn getAllocator(self: *const Msg) *mem.Allocator {
switch (self.data) {
.Cli => |cli| return cli.allocator,
.PathAndTree => |path_and_tree| {
return path_and_tree.allocator;
},
.ScopeAndComp => |scope_and_comp| {
return scope_and_comp.compilation.gpa();
},
}
}
pub fn getTree(self: *const Msg) *ast.Tree {
switch (self.data) {
.Cli => unreachable,
.PathAndTree => |path_and_tree| {
return path_and_tree.tree;
},
.ScopeAndComp => |scope_and_comp| {
return scope_and_comp.tree_scope.tree;
},
}
}
pub fn getSpan(self: *const Msg) Span {
return switch (self.data) {
.Cli => unreachable,
.PathAndTree => |path_and_tree| path_and_tree.span,
.ScopeAndComp => |scope_and_comp| scope_and_comp.span,
};
}
/// Takes ownership of text
/// References tree_scope, and derefs when the msg is freed
pub fn createFromScope(comp: *Compilation, tree_scope: *Scope.AstTree, span: Span, text: []u8) !*Msg {
const realpath = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath);
errdefer comp.gpa().free(realpath);
const msg = try comp.gpa().create(Msg);
msg.* = Msg{
.text = text,
.realpath = realpath,
.data = Data{
.ScopeAndComp = ScopeAndComp{
.tree_scope = tree_scope,
.compilation = comp,
.span = span,
},
},
};
tree_scope.base.ref();
return msg;
}
/// Caller owns returned Msg and must free with `allocator`
/// allocator will additionally be used for printing messages later.
pub fn createFromCli(comp: *Compilation, realpath: []const u8, text: []u8) !*Msg {
const realpath_copy = try mem.dupe(comp.gpa(), u8, realpath);
errdefer comp.gpa().free(realpath_copy);
const msg = try comp.gpa().create(Msg);
msg.* = Msg{
.text = text,
.realpath = realpath_copy,
.data = Data{
.Cli = Cli{ .allocator = comp.gpa() },
},
};
return msg;
}
pub fn createFromParseErrorAndScope(
comp: *Compilation,
tree_scope: *Scope.AstTree,
parse_error: *const ast.Error,
) !*Msg {
const loc_token = parse_error.loc();
var text_buf = std.ArrayList(u8).init(comp.gpa());
defer text_buf.deinit();
const realpath_copy = try mem.dupe(comp.gpa(), u8, tree_scope.root().realpath);
errdefer comp.gpa().free(realpath_copy);
try parse_error.render(&tree_scope.tree.tokens, text_buf.outStream());
const msg = try comp.gpa().create(Msg);
msg.* = Msg{
.text = undefined,
.realpath = realpath_copy,
.data = Data{
.ScopeAndComp = ScopeAndComp{
.tree_scope = tree_scope,
.compilation = comp,
.span = Span{
.first = loc_token,
.last = loc_token,
},
},
},
};
tree_scope.base.ref();
msg.text = text_buf.toOwnedSlice();
return msg;
}
/// `realpath` must outlive the returned Msg
/// `tree` must outlive the returned Msg
/// Caller owns returned Msg and must free with `allocator`
/// allocator will additionally be used for printing messages later.
pub fn createFromParseError(
allocator: *mem.Allocator,
parse_error: *const ast.Error,
tree: *ast.Tree,
realpath: []const u8,
) !*Msg {
const loc_token = parse_error.loc();
var text_buf = std.ArrayList(u8).init(allocator);
defer text_buf.deinit();
const realpath_copy = try mem.dupe(allocator, u8, realpath);
errdefer allocator.free(realpath_copy);
try parse_error.render(&tree.tokens, text_buf.outStream());
const msg = try allocator.create(Msg);
msg.* = Msg{
.text = undefined,
.realpath = realpath_copy,
.data = Data{
.PathAndTree = PathAndTree{
.allocator = allocator,
.tree = tree,
.span = Span{
.first = loc_token,
.last = loc_token,
},
},
},
};
msg.text = text_buf.toOwnedSlice();
errdefer allocator.destroy(msg);
return msg;
}
pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void {
switch (msg.data) {
.Cli => {
try stream.print("{}:-:-: error: {}\n", .{ msg.realpath, msg.text });
return;
},
else => {},
}
const allocator = msg.getAllocator();
const tree = msg.getTree();
const cwd = try process.getCwdAlloc(allocator);
defer allocator.free(cwd);
const relpath = try fs.path.relative(allocator, cwd, msg.realpath);
defer allocator.free(relpath);
const path = if (relpath.len < msg.realpath.len) relpath else msg.realpath;
const span = msg.getSpan();
const first_token = tree.tokens.at(span.first);
const last_token = tree.tokens.at(span.last);
const start_loc = tree.tokenLocationPtr(0, first_token);
const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
if (!color_on) {
try stream.print("{}:{}:{}: error: {}\n", .{
path,
start_loc.line + 1,
start_loc.column + 1,
msg.text,
});
return;
}
try stream.print("{}:{}:{}: error: {}\n{}\n", .{
path,
start_loc.line + 1,
start_loc.column + 1,
msg.text,
tree.source[start_loc.line_start..start_loc.line_end],
});
try stream.writeByteNTimes(' ', start_loc.column);
try stream.writeByteNTimes('~', last_token.end - first_token.start);
try stream.writeAll("\n");
}
pub fn printToFile(msg: *const Msg, file: fs.File, color: Color) !void {
const color_on = switch (color) {
.Auto => file.isTty(),
.On => true,
.Off => false,
};
return msg.printToStream(file.outStream(), color_on);
}
};

File diff suppressed because it is too large Load Diff

View File

@ -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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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);
}
};

View File

@ -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);
}
};
};

View File

@ -12,7 +12,6 @@ const ArrayListSentineled = std.ArrayListSentineled;
const Target = std.Target;
const CrossTarget = std.zig.CrossTarget;
const self_hosted_main = @import("main.zig");
const errmsg = @import("errmsg.zig");
const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer;
const assert = std.debug.assert;
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
@ -168,8 +167,6 @@ export fn stage2_render_ast(tree: *ast.Tree, output_file: *FILE) Error {
return .None;
}
// TODO: just use the actual self-hosted zig fmt. Until https://github.com/ziglang/zig/issues/2377,
// we use a blocking implementation.
export fn stage2_fmt(argc: c_int, argv: [*]const [*:0]const u8) c_int {
if (std.debug.runtime_safety) {
fmtMain(argc, argv) catch unreachable;
@ -191,258 +188,9 @@ fn fmtMain(argc: c_int, argv: [*]const [*:0]const u8) !void {
try args_list.append(mem.spanZ(argv[arg_i]));
}
stdout = std.io.getStdOut().outStream();
stderr_file = std.io.getStdErr();
stderr = stderr_file.outStream();
const args = args_list.span()[2..];
var color: errmsg.Color = .Auto;
var stdin_flag: bool = false;
var check_flag: bool = false;
var input_files = ArrayList([]const u8).init(allocator);
{
var i: usize = 0;
while (i < args.len) : (i += 1) {
const arg = args[i];
if (mem.startsWith(u8, arg, "-")) {
if (mem.eql(u8, arg, "--help")) {
try stdout.writeAll(self_hosted_main.usage_fmt);
process.exit(0);
} else if (mem.eql(u8, arg, "--color")) {
if (i + 1 >= args.len) {
try stderr.writeAll("expected [auto|on|off] after --color\n");
process.exit(1);
}
i += 1;
const next_arg = args[i];
if (mem.eql(u8, next_arg, "auto")) {
color = .Auto;
} else if (mem.eql(u8, next_arg, "on")) {
color = .On;
} else if (mem.eql(u8, next_arg, "off")) {
color = .Off;
} else {
try stderr.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
process.exit(1);
}
} else if (mem.eql(u8, arg, "--stdin")) {
stdin_flag = true;
} else if (mem.eql(u8, arg, "--check")) {
check_flag = true;
} else {
try stderr.print("unrecognized parameter: '{}'", .{arg});
process.exit(1);
}
} else {
try input_files.append(arg);
}
}
}
if (stdin_flag) {
if (input_files.items.len != 0) {
try stderr.writeAll("cannot use --stdin with positional arguments\n");
process.exit(1);
}
const stdin_file = io.getStdIn();
var stdin = stdin_file.inStream();
const source_code = try stdin.readAllAlloc(allocator, self_hosted_main.max_src_size);
defer allocator.free(source_code);
const tree = std.zig.parse(allocator, source_code) catch |err| {
try stderr.print("error parsing stdin: {}\n", .{err});
process.exit(1);
};
defer tree.deinit();
var error_it = tree.errors.iterator(0);
while (error_it.next()) |parse_error| {
try printErrMsgToFile(allocator, parse_error, tree, "<stdin>", stderr_file, color);
}
if (tree.errors.len != 0) {
process.exit(1);
}
if (check_flag) {
const anything_changed = try std.zig.render(allocator, io.null_out_stream, tree);
const code = if (anything_changed) @as(u8, 1) else @as(u8, 0);
process.exit(code);
}
_ = try std.zig.render(allocator, stdout, tree);
return;
}
if (input_files.items.len == 0) {
try stderr.writeAll("expected at least one source file argument\n");
process.exit(1);
}
var fmt = Fmt{
.seen = Fmt.SeenMap.init(allocator),
.any_error = false,
.color = color,
.allocator = allocator,
};
for (input_files.span()) |file_path| {
try fmtPath(&fmt, file_path, check_flag);
}
if (fmt.any_error) {
process.exit(1);
}
}
const FmtError = error{
SystemResources,
OperationAborted,
IoPending,
BrokenPipe,
Unexpected,
WouldBlock,
FileClosed,
DestinationAddressRequired,
DiskQuota,
FileTooBig,
InputOutput,
NoSpaceLeft,
AccessDenied,
OutOfMemory,
RenameAcrossMountPoints,
ReadOnlyFileSystem,
LinkQuotaExceeded,
FileBusy,
} || fs.File.OpenError;
fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool) FmtError!void {
// get the real path here to avoid Windows failing on relative file paths with . or .. in them
var real_path = fs.realpathAlloc(fmt.allocator, file_path) catch |err| {
try stderr.print("unable to open '{}': {}\n", .{ file_path, err });
fmt.any_error = true;
return;
};
defer fmt.allocator.free(real_path);
if (fmt.seen.exists(real_path)) return;
try fmt.seen.put(real_path);
const source_code = fs.cwd().readFileAlloc(fmt.allocator, real_path, self_hosted_main.max_src_size) catch |err| switch (err) {
error.IsDir, error.AccessDenied => {
// TODO make event based (and dir.next())
var dir = try fs.cwd().openDir(file_path, .{ .iterate = true });
defer dir.close();
var dir_it = dir.iterate();
while (try dir_it.next()) |entry| {
if (entry.kind == .Directory or mem.endsWith(u8, entry.name, ".zig")) {
const full_path = try fs.path.join(fmt.allocator, &[_][]const u8{ file_path, entry.name });
try fmtPath(fmt, full_path, check_mode);
}
}
return;
},
else => {
// TODO lock stderr printing
try stderr.print("unable to open '{}': {}\n", .{ file_path, err });
fmt.any_error = true;
return;
},
};
defer fmt.allocator.free(source_code);
const tree = std.zig.parse(fmt.allocator, source_code) catch |err| {
try stderr.print("error parsing file '{}': {}\n", .{ file_path, err });
fmt.any_error = true;
return;
};
defer tree.deinit();
var error_it = tree.errors.iterator(0);
while (error_it.next()) |parse_error| {
try printErrMsgToFile(fmt.allocator, parse_error, tree, file_path, stderr_file, fmt.color);
}
if (tree.errors.len != 0) {
fmt.any_error = true;
return;
}
if (check_mode) {
const anything_changed = try std.zig.render(fmt.allocator, io.null_out_stream, tree);
if (anything_changed) {
try stderr.print("{}\n", .{file_path});
fmt.any_error = true;
}
} else {
const baf = try io.BufferedAtomicFile.create(fmt.allocator, fs.cwd(), real_path, .{});
defer baf.destroy();
const anything_changed = try std.zig.render(fmt.allocator, baf.stream(), tree);
if (anything_changed) {
try stderr.print("{}\n", .{file_path});
try baf.finish();
}
}
}
const Fmt = struct {
seen: SeenMap,
any_error: bool,
color: errmsg.Color,
allocator: *mem.Allocator,
const SeenMap = std.BufSet;
};
fn printErrMsgToFile(
allocator: *mem.Allocator,
parse_error: *const ast.Error,
tree: *ast.Tree,
path: []const u8,
file: fs.File,
color: errmsg.Color,
) !void {
const color_on = switch (color) {
.Auto => file.isTty(),
.On => true,
.Off => false,
};
const lok_token = parse_error.loc();
const span = errmsg.Span{
.first = lok_token,
.last = lok_token,
};
const first_token = tree.tokens.at(span.first);
const last_token = tree.tokens.at(span.last);
const start_loc = tree.tokenLocationPtr(0, first_token);
const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
var text_buf = std.ArrayList(u8).init(allocator);
defer text_buf.deinit();
const out_stream = text_buf.outStream();
try parse_error.render(&tree.tokens, out_stream);
const text = text_buf.span();
const stream = file.outStream();
try stream.print("{}:{}:{}: error: {}\n", .{ path, start_loc.line + 1, start_loc.column + 1, text });
if (!color_on) return;
// Print \r and \t as one space each so that column counts line up
for (tree.source[start_loc.line_start..start_loc.line_end]) |byte| {
try stream.writeByte(switch (byte) {
'\r', '\t' => ' ',
else => byte,
});
}
try stream.writeByte('\n');
try stream.writeByteNTimes(' ', start_loc.column);
try stream.writeByteNTimes('~', last_token.end - first_token.start);
try stream.writeByte('\n');
return self_hosted_main.cmdFmt(allocator, args);
}
export fn stage2_DepTokenizer_init(input: [*]const u8, len: usize) stage2_DepTokenizer {

View File

@ -1,17 +1,18 @@
const std = @import("std");
const link = @import("link.zig");
const ir = @import("ir.zig");
const Module = @import("Module.zig");
const Allocator = std.mem.Allocator;
var global_ctx: TestContext = undefined;
const zir = @import("zir.zig");
const Package = @import("Package.zig");
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 {
@ -20,32 +21,34 @@ pub const TestContext = struct {
pub const ZIRCompareOutputCase = struct {
name: []const u8,
src: [:0]const u8,
expected_stdout: []const u8,
src_list: []const []const u8,
expected_stdout_list: []const []const u8,
};
pub const ZIRTransformCase = struct {
name: []const u8,
src: [:0]const u8,
expected_zir: []const u8,
cross_target: std.zig.CrossTarget,
};
pub fn addZIRCompareOutput(
ctx: *TestContext,
name: []const u8,
src: [:0]const u8,
expected_stdout: []const u8,
src_list: []const []const u8,
expected_stdout_list: []const []const u8,
) void {
ctx.zir_cmp_output_cases.append(.{
.name = name,
.src = src,
.expected_stdout = expected_stdout,
.src_list = src_list,
.expected_stdout_list = expected_stdout_list,
}) catch unreachable;
}
pub fn addZIRTransform(
ctx: *TestContext,
name: []const u8,
cross_target: std.zig.CrossTarget,
src: [:0]const u8,
expected_zir: []const u8,
) void {
@ -53,6 +56,7 @@ pub const TestContext = struct {
.name = name,
.src = src,
.expected_zir = expected_zir,
.cross_target = cross_target,
}) catch unreachable;
}
@ -84,7 +88,8 @@ pub const TestContext = struct {
}
for (self.zir_transform_cases.items) |case| {
std.testing.base_allocator_instance.reset();
try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, native_info.target);
const info = try std.zig.system.NativeTargetInfo.detect(std.testing.allocator, case.cross_target);
try self.runOneZIRTransformCase(std.testing.allocator, root_node, case, info.target);
try std.testing.allocator_instance.validate();
}
}
@ -99,77 +104,68 @@ pub const TestContext = struct {
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var prg_node = root_node.start(case.name, 4);
const tmp_src_path = "test-case.zir";
const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
defer root_pkg.destroy();
var prg_node = root_node.start(case.name, case.src_list.len);
prg_node.activate();
defer prg_node.end();
var zir_module = x: {
var parse_node = prg_node.start("parse", null);
parse_node.activate();
defer parse_node.end();
var module = try Module.init(allocator, .{
.target = target,
.output_mode = .Exe,
.optimize_mode = .Debug,
.bin_file_dir = tmp.dir,
.bin_file_path = "a.out",
.root_pkg = root_pkg,
});
defer module.deinit();
break :x try ir.text.parse(allocator, case.src);
};
defer zir_module.deinit(allocator);
if (zir_module.errors.len != 0) {
debugPrintErrors(case.src, zir_module.errors);
return error.ParseFailure;
for (case.src_list) |source, i| {
var src_node = prg_node.start("update", 2);
src_node.activate();
defer src_node.end();
try tmp.dir.writeFile(tmp_src_path, source);
var update_node = src_node.start("parse,analysis,codegen", null);
update_node.activate();
try module.makeBinFileWritable();
try module.update();
update_node.end();
var exec_result = x: {
var exec_node = src_node.start("execute", null);
exec_node.activate();
defer exec_node.end();
try module.makeBinFileExecutable();
break :x try std.ChildProcess.exec(.{
.allocator = allocator,
.argv = &[_][]const u8{"./a.out"},
.cwd_dir = tmp.dir,
});
};
defer allocator.free(exec_result.stdout);
defer allocator.free(exec_result.stderr);
switch (exec_result.term) {
.Exited => |code| {
if (code != 0) {
std.debug.warn("elf file exited with code {}\n", .{code});
return error.BinaryBadExitCode;
}
},
else => return error.BinaryCrashed,
}
const expected_stdout = case.expected_stdout_list[i];
if (!std.mem.eql(u8, expected_stdout, exec_result.stdout)) {
std.debug.panic(
"update index {}, mismatched stdout\n====Expected (len={}):====\n{}\n====Actual (len={}):====\n{}\n========\n",
.{ i, expected_stdout.len, expected_stdout, exec_result.stdout.len, exec_result.stdout },
);
}
}
var analyzed_module = x: {
var analyze_node = prg_node.start("analyze", null);
analyze_node.activate();
defer analyze_node.end();
break :x try ir.analyze(allocator, zir_module, .{
.target = target,
.output_mode = .Exe,
.link_mode = .Static,
.optimize_mode = .Debug,
});
};
defer analyzed_module.deinit(allocator);
if (analyzed_module.errors.len != 0) {
debugPrintErrors(case.src, analyzed_module.errors);
return error.ParseFailure;
}
var link_result = x: {
var link_node = prg_node.start("link", null);
link_node.activate();
defer link_node.end();
break :x try link.updateFilePath(allocator, analyzed_module, tmp.dir, "a.out");
};
defer link_result.deinit(allocator);
if (link_result.errors.len != 0) {
debugPrintErrors(case.src, link_result.errors);
return error.LinkFailure;
}
var exec_result = x: {
var exec_node = prg_node.start("execute", null);
exec_node.activate();
defer exec_node.end();
break :x try std.ChildProcess.exec(.{
.allocator = allocator,
.argv = &[_][]const u8{"./a.out"},
.cwd_dir = tmp.dir,
});
};
defer allocator.free(exec_result.stdout);
defer allocator.free(exec_result.stderr);
switch (exec_result.term) {
.Exited => |code| {
if (code != 0) {
std.debug.warn("elf file exited with code {}\n", .{code});
return error.BinaryBadExitCode;
}
},
else => return error.BinaryCrashed,
}
std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout);
}
fn runOneZIRTransformCase(
@ -179,38 +175,37 @@ pub const TestContext = struct {
case: ZIRTransformCase,
target: std.Target,
) !void {
var prg_node = root_node.start(case.name, 4);
var tmp = std.testing.tmpDir(.{});
defer tmp.cleanup();
var prg_node = root_node.start(case.name, 3);
prg_node.activate();
defer prg_node.end();
var parse_node = prg_node.start("parse", null);
parse_node.activate();
var zir_module = try ir.text.parse(allocator, case.src);
defer zir_module.deinit(allocator);
if (zir_module.errors.len != 0) {
debugPrintErrors(case.src, zir_module.errors);
return error.ParseFailure;
}
parse_node.end();
const tmp_src_path = "test-case.zir";
try tmp.dir.writeFile(tmp_src_path, case.src);
var analyze_node = prg_node.start("analyze", null);
analyze_node.activate();
var analyzed_module = try ir.analyze(allocator, zir_module, .{
const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
defer root_pkg.destroy();
var module = try Module.init(allocator, .{
.target = target,
.output_mode = .Obj,
.link_mode = .Static,
.optimize_mode = .Debug,
.bin_file_dir = tmp.dir,
.bin_file_path = "test-case.o",
.root_pkg = root_pkg,
});
defer analyzed_module.deinit(allocator);
if (analyzed_module.errors.len != 0) {
debugPrintErrors(case.src, analyzed_module.errors);
return error.ParseFailure;
}
analyze_node.end();
defer module.deinit();
var module_node = prg_node.start("parse/analysis/codegen", null);
module_node.activate();
try module.update();
module_node.end();
var emit_node = prg_node.start("emit", null);
emit_node.activate();
var new_zir_module = try ir.text.emit_zir(allocator, analyzed_module);
var new_zir_module = try zir.emit(allocator, module);
defer new_zir_module.deinit(allocator);
emit_node.end();

View File

@ -5,8 +5,7 @@ const Allocator = std.mem.Allocator;
const Target = std.Target;
/// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication.
/// It's important for this struct to be small.
/// It is not copyable since it may contain references to its inner data.
/// It's important for this type to be small.
/// Types are not de-duplicated, which helps with multi-threading since it obviates the requirement
/// of obtaining a lock on a global type table, as well as making the
/// garbage collection bookkeeping simpler.
@ -51,7 +50,9 @@ pub const Type = extern union {
.comptime_int => return .ComptimeInt,
.comptime_float => return .ComptimeFloat,
.noreturn => return .NoReturn,
.@"null" => return .Null,
.fn_noreturn_no_args => return .Fn,
.fn_naked_noreturn_no_args => return .Fn,
.fn_ccc_void_no_args => return .Fn,
@ -183,7 +184,10 @@ pub const Type = extern union {
.noreturn,
=> return out_stream.writeAll(@tagName(t)),
.@"null" => return out_stream.writeAll("@TypeOf(null)"),
.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 +248,8 @@ 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),
.@"null" => return Value.initTag(.null_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),
@ -256,6 +262,110 @@ pub const Type = extern union {
}
}
pub fn hasCodeGenBits(self: Type) bool {
return switch (self.tag()) {
.u8,
.i8,
.isize,
.usize,
.c_short,
.c_ushort,
.c_int,
.c_uint,
.c_long,
.c_ulong,
.c_longlong,
.c_ulonglong,
.c_longdouble,
.f16,
.f32,
.f64,
.f128,
.bool,
.anyerror,
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.array_u8_sentinel_0,
.array, // TODO check for zero bits
.single_const_pointer,
.int_signed, // TODO check for zero bits
.int_unsigned, // TODO check for zero bits
=> true,
.c_void,
.void,
.type,
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
=> false,
};
}
/// Asserts that hasCodeGenBits() is true.
pub fn abiAlignment(self: Type, target: Target) u32 {
return switch (self.tag()) {
.u8,
.i8,
.bool,
.fn_noreturn_no_args, // represents machine code; not a pointer
.fn_naked_noreturn_no_args, // represents machine code; not a pointer
.fn_ccc_void_no_args, // represents machine code; not a pointer
.array_u8_sentinel_0,
=> return 1,
.isize,
.usize,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.single_const_pointer,
=> return @divExact(target.cpu.arch.ptrBitWidth(), 8),
.c_short => return @divExact(CType.short.sizeInBits(target), 8),
.c_ushort => return @divExact(CType.ushort.sizeInBits(target), 8),
.c_int => return @divExact(CType.int.sizeInBits(target), 8),
.c_uint => return @divExact(CType.uint.sizeInBits(target), 8),
.c_long => return @divExact(CType.long.sizeInBits(target), 8),
.c_ulong => return @divExact(CType.ulong.sizeInBits(target), 8),
.c_longlong => return @divExact(CType.longlong.sizeInBits(target), 8),
.c_ulonglong => return @divExact(CType.ulonglong.sizeInBits(target), 8),
.f16 => return 2,
.f32 => return 4,
.f64 => return 8,
.f128 => return 16,
.c_longdouble => return 16,
.anyerror => return 2, // TODO revisit this when we have the concept of the error tag type
.array => return self.cast(Payload.Array).?.elem_type.abiAlignment(target),
.int_signed, .int_unsigned => {
const bits: u16 = if (self.cast(Payload.IntSigned)) |pl|
pl.bits
else if (self.cast(Payload.IntUnsigned)) |pl|
pl.bits
else
unreachable;
return std.math.ceilPowerOfTwoPromote(u16, (bits + 7) / 8);
},
.c_void,
.void,
.type,
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
=> unreachable,
};
}
pub fn isSinglePointer(self: Type) bool {
return switch (self.tag()) {
.u8,
@ -283,9 +393,11 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.array,
.array_u8_sentinel_0,
.const_slice_u8,
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.int_unsigned,
@ -325,10 +437,12 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.array,
.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,
@ -367,8 +481,10 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.array,
.array_u8_sentinel_0,
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.int_unsigned,
@ -410,6 +526,8 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.int_unsigned,
@ -451,6 +569,8 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.single_const_pointer,
@ -465,6 +585,50 @@ pub const Type = extern union {
};
}
/// Asserts the type is an array or vector.
pub fn arraySentinel(self: Type) ?Value {
return switch (self.tag()) {
.u8,
.i8,
.isize,
.usize,
.c_short,
.c_ushort,
.c_int,
.c_uint,
.c_long,
.c_ulong,
.c_longlong,
.c_ulonglong,
.c_longdouble,
.f16,
.f32,
.f64,
.f128,
.c_void,
.bool,
.void,
.type,
.anyerror,
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
.const_slice_u8,
.int_unsigned,
.int_signed,
=> unreachable,
.array => return null,
.array_u8_sentinel_0 => return Value.initTag(.zero),
};
}
/// Returns true if and only if the type is a fixed-width, signed integer.
pub fn isSignedInt(self: Type) bool {
return switch (self.tag()) {
@ -481,6 +645,8 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.array,
@ -524,6 +690,8 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.array,
@ -579,6 +747,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,
@ -595,6 +764,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.array,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
@ -622,6 +792,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,
@ -638,6 +809,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.array,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
@ -664,6 +836,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),
@ -680,6 +853,7 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.array,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
@ -706,6 +880,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,
@ -722,6 +897,51 @@ pub const Type = extern union {
.comptime_int,
.comptime_float,
.noreturn,
.@"null",
.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,
};
}
/// 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,
.@"null",
.array,
.single_const_pointer,
.single_const_pointer_to_comptime_int,
@ -776,6 +996,8 @@ pub const Type = extern union {
.type,
.anyerror,
.noreturn,
.@"null",
.fn_noreturn_no_args,
.fn_naked_noreturn_no_args,
.fn_ccc_void_no_args,
.array,
@ -812,6 +1034,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,
@ -822,6 +1045,7 @@ pub const Type = extern union {
.c_void,
.void,
.noreturn,
.@"null",
=> return true,
.int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
@ -865,6 +1089,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,
@ -873,6 +1098,7 @@ pub const Type = extern union {
.c_void,
.void,
.noreturn,
.@"null",
.int_unsigned,
.int_signed,
.array,
@ -902,11 +1128,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 +1140,8 @@ pub const Type = extern union {
comptime_int,
comptime_float,
noreturn,
@"null",
fn_noreturn_no_args,
fn_naked_noreturn_no_args,
fn_ccc_void_no_args,
single_const_pointer_to_comptime_int,

View File

@ -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();
}

View File

@ -6,10 +6,11 @@ const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Target = std.Target;
const Allocator = std.mem.Allocator;
const Module = @import("Module.zig");
/// This is the raw data, with no bookkeeping, no memory awareness,
/// no de-duplication, and no type system awareness.
/// It's important for this struct to be small.
/// It's important for this type to be small.
/// This union takes advantage of the fact that the first page of memory
/// is unmapped, giving us 4096 possible enum tags that have no payload.
pub const Value = extern union {
@ -45,6 +46,8 @@ pub const Value = extern union {
comptime_int_type,
comptime_float_type,
noreturn_type,
null_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 +67,9 @@ 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 +140,8 @@ 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"),
.null_type => return out_stream.writeAll("@TypeOf(null)"),
.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 +159,16 @@ 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;
const ref_val = val.cast(Payload.RefVal).?;
try out_stream.writeAll("&const ");
val = ref_val.val;
},
.decl_ref => return out_stream.writeAll("(decl ref)"),
.elem_ptr => {
const elem_ptr = val.cast(Payload.ElemPtr).?;
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 => {
@ -169,10 +180,17 @@ pub const Value = extern union {
/// Asserts that the value is representable as an array of bytes.
/// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
pub fn toAllocatedBytes(self: Value, allocator: *Allocator) Allocator.Error![]u8 {
pub fn toAllocatedBytes(self: Value, allocator: *Allocator) ![]u8 {
if (self.cast(Payload.Bytes)) |bytes| {
return std.mem.dupe(allocator, u8, bytes.data);
}
if (self.cast(Payload.Repeated)) |repeated| {
@panic("TODO implement toAllocatedBytes for this Value tag");
}
if (self.cast(Payload.DeclRef)) |declref| {
const val = try declref.decl.value();
return val.toAllocatedBytes(allocator);
}
unreachable;
}
@ -181,31 +199,33 @@ 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),
.null_type => Type.initTag(.@"null"),
.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 +242,9 @@ pub const Value = extern union {
.int_big_positive,
.int_big_negative,
.function,
.ref,
.ref_val,
.decl_ref,
.elem_ptr,
.bytes,
.repeated,
=> unreachable,
@ -259,6 +280,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +290,9 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
.ref,
.ref_val,
.decl_ref,
.elem_ptr,
.bytes,
.undef,
.repeated,
@ -314,6 +338,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +348,9 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
.ref,
.ref_val,
.decl_ref,
.elem_ptr,
.bytes,
.undef,
.repeated,
@ -370,6 +397,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +407,9 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
.ref,
.ref_val,
.decl_ref,
.elem_ptr,
.bytes,
.undef,
.repeated,
@ -431,6 +461,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +471,9 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
.ref,
.ref_val,
.decl_ref,
.elem_ptr,
.bytes,
.repeated,
=> unreachable,
@ -521,6 +554,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +564,9 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
.ref,
.ref_val,
.decl_ref,
.elem_ptr,
.bytes,
.repeated,
.undef,
@ -573,6 +609,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +619,9 @@ pub const Value = extern union {
.bool_false,
.null_value,
.function,
.ref,
.ref_val,
.decl_ref,
.elem_ptr,
.bytes,
.repeated,
.undef,
@ -636,7 +675,8 @@ pub const Value = extern union {
}
/// Asserts the value is a pointer and dereferences it.
pub fn pointerDeref(self: Value) Value {
/// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
return switch (self.tag()) {
.ty,
.u8_type,
@ -664,6 +704,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +725,19 @@ 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 => self.cast(Payload.DeclRef).?.decl.value(),
.elem_ptr => {
const elem_ptr = self.cast(Payload.ElemPtr).?;
const array_val = try elem_ptr.array_ptr.pointerDeref(allocator);
return array_val.elemValue(allocator, 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, allocator: *Allocator, index: usize) error{OutOfMemory}!Value {
switch (self.tag()) {
.ty,
.u8_type,
@ -718,6 +765,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +782,13 @@ pub const Value = extern union {
.int_big_positive,
.int_big_negative,
.undef,
.elem_ptr,
.ref_val,
.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 +798,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 +843,8 @@ pub const Value = extern union {
.comptime_int_type,
.comptime_float_type,
.noreturn_type,
.null_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 +858,9 @@ 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 +904,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: *Module.Fn,
};
pub const ArraySentinel0_u8_Type = struct {
@ -855,16 +917,24 @@ pub const Value = extern union {
elem_type: *Type,
};
pub const Ref = struct {
base: Payload = Payload{ .tag = .ref },
cell: *MemoryCell,
};
/// Represents a pointer to another immutable value.
pub const RefVal = struct {
base: Payload = Payload{ .tag = .ref_val },
val: Value,
};
/// Represents a pointer to a decl, not the value of the decl.
pub const DeclRef = struct {
base: Payload = Payload{ .tag = .decl_ref },
decl: *Module.Decl,
};
pub const ElemPtr = struct {
base: Payload = Payload{ .tag = .elem_ptr },
array_ptr: Value,
index: usize,
};
pub const Bytes = struct {
base: Payload = Payload{ .tag = .bytes },
data: []const u8,
@ -890,29 +960,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,
};
};

View File

@ -1,4 +0,0 @@
pub const Visib = enum {
Private,
Pub,
};

View File

@ -6,9 +6,11 @@ const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const Type = @import("../type.zig").Type;
const Value = @import("../value.zig").Value;
const ir = @import("../ir.zig");
const Type = @import("type.zig").Type;
const Value = @import("value.zig").Value;
const TypedValue = @import("TypedValue.zig");
const ir = @import("ir.zig");
const IrModule = @import("Module.zig");
/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
/// in-memory, analyzed instructions with types and values.
@ -16,10 +18,18 @@ pub const Inst = struct {
tag: Tag,
/// Byte offset into the source.
src: usize,
name: []const u8,
/// Slice into the source of the part after the = and before the next instruction.
contents: []const u8 = &[0]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.
/// The syntax `@foo` is equivalent to `declref("foo")`.
declref,
str,
int,
ptrtoint,
@ -32,6 +42,7 @@ pub const Inst = struct {
@"fn",
@"export",
primitive,
ref,
fntype,
intcast,
bitcast,
@ -46,6 +57,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,
@ -58,6 +71,7 @@ pub const Inst = struct {
.@"fn" => Fn,
.@"export" => Export,
.primitive => Primitive,
.ref => Ref,
.fntype => FnType,
.intcast => IntCast,
.bitcast => BitCast,
@ -85,6 +99,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,
@ -202,6 +239,16 @@ pub const Inst = struct {
kw_args: struct {},
};
pub const Ref = struct {
pub const base_tag = Tag.ref;
base: Inst,
positionals: struct {
operand: *Inst,
},
kw_args: struct {},
};
pub const Primitive = struct {
pub const base_tag = Tag.primitive;
base: Inst,
@ -212,55 +259,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,
pub 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),
};
}
};
@ -375,8 +422,8 @@ pub const ErrorMsg = struct {
pub const Module = struct {
decls: []*Inst,
errors: []ErrorMsg,
arena: std.heap.ArenaAllocator,
error_msg: ?ErrorMsg = null,
pub const Body = struct {
instructions: []*Inst,
@ -384,7 +431,6 @@ pub const Module = struct {
pub fn deinit(self: *Module, allocator: *Allocator) void {
allocator.free(self.decls);
allocator.free(self.errors);
self.arena.deinit();
self.* = undefined;
}
@ -431,6 +477,8 @@ 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),
.declref => return self.writeInstToStreamGeneric(stream, .declref, 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),
@ -442,6 +490,7 @@ pub const Module = struct {
.@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table),
.@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table),
.@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table),
.ref => return self.writeInstToStreamGeneric(stream, .ref, decl, inst_table),
.primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table),
.fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table),
.intcast => return self.writeInstToStreamGeneric(stream, .intcast, decl, inst_table),
@ -543,22 +592,23 @@ 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,
.decls = .{},
.unnamed_index = 0,
};
errdefer parser.arena.deinit();
parser.parseRoot() catch |err| switch (err) {
error.ParseFailure => {
assert(parser.errors.items.len != 0);
assert(parser.error_msg != null);
},
else => |e| return e,
};
return Module{
.decls = parser.decls.toOwnedSlice(),
.errors = parser.errors.toOwnedSlice(),
.decls = parser.decls.toOwnedSlice(allocator),
.arena = parser.arena,
.error_msg = parser.error_msg,
};
}
@ -567,9 +617,10 @@ const Parser = struct {
arena: std.heap.ArenaAllocator,
i: usize,
source: [:0]const u8,
errors: std.ArrayList(ErrorMsg),
decls: std.ArrayList(*Inst),
decls: std.ArrayListUnmanaged(*Inst),
global_name_map: *std.StringHashMap(usize),
error_msg: ?ErrorMsg = null,
unnamed_index: usize,
const Body = struct {
instructions: std.ArrayList(*Inst),
@ -595,7 +646,7 @@ const Parser = struct {
skipSpace(self);
try requireEatBytes(self, "=");
skipSpace(self);
const inst = try parseInstruction(self, &body_context);
const inst = try parseInstruction(self, &body_context, ident);
const ident_index = body_context.instructions.items.len;
if (try body_context.name_map.put(ident, ident_index)) |_| {
return self.fail("redefinition of identifier '{}'", .{ident});
@ -681,12 +732,12 @@ const Parser = struct {
skipSpace(self);
try requireEatBytes(self, "=");
skipSpace(self);
const inst = try parseInstruction(self, null);
const inst = try parseInstruction(self, null, ident);
const ident_index = self.decls.items.len;
if (try self.global_name_map.put(ident, ident_index)) |_| {
return self.fail("redefinition of identifier '{}'", .{ident});
}
try self.decls.append(inst);
try self.decls.append(self.allocator, inst);
},
' ', '\n' => self.i += 1,
0 => break,
@ -743,20 +794,20 @@ const Parser = struct {
fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError {
@setCold(true);
const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args);
(try self.errors.addOne()).* = .{
self.error_msg = ErrorMsg{
.byte_offset = self.i,
.msg = msg,
.msg = try std.fmt.allocPrint(&self.arena.allocator, format, args),
};
return error.ParseFailure;
}
fn parseInstruction(self: *Parser, body_ctx: ?*Body) InnerError!*Inst {
fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Inst {
const contents_start = self.i;
const fn_name = try skipToAndOver(self, '(');
inline for (@typeInfo(Inst.Tag).Enum.fields) |field| {
if (mem.eql(u8, field.name, fn_name)) {
const tag = @field(Inst.Tag, field.name);
return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx);
return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx, name, contents_start);
}
}
return self.fail("unknown instruction '{}'", .{fn_name});
@ -767,9 +818,12 @@ const Parser = struct {
comptime fn_name: []const u8,
comptime InstType: type,
body_ctx: ?*Body,
) !*Inst {
inst_name: []const u8,
contents_start: usize,
) InnerError!*Inst {
const inst_specific = try self.arena.allocator.create(InstType);
inst_specific.base = .{
.name = inst_name,
.src = self.i,
.tag = InstType.base_tag,
};
@ -819,6 +873,8 @@ const Parser = struct {
}
try requireEatBytes(self, ")");
inst_specific.base.contents = self.source[contents_start..self.i];
return &inst_specific.base;
}
@ -893,8 +949,33 @@ 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 = .{
.name = try self.generateName(),
.src = src,
.tag = Inst.Str.base_tag,
},
.positionals = .{ .bytes = ident },
.kw_args = .{},
};
const declref = try self.arena.allocator.create(Inst.DeclRef);
declref.* = .{
.base = .{
.name = try self.generateName(),
.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];
@ -902,50 +983,64 @@ const Parser = struct {
return self.decls.items[kv.value];
}
}
fn generateName(self: *Parser) ![]u8 {
const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.unnamed_index});
self.unnamed_index += 1;
return result;
}
};
pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module {
pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
var ctx: EmitZIR = .{
.allocator = allocator,
.decls = std.ArrayList(*Inst).init(allocator),
.decls = .{},
.decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator),
.arena = std.heap.ArenaAllocator.init(allocator),
.old_module = &old_module,
};
defer ctx.decls.deinit();
defer ctx.decls.deinit(allocator);
defer ctx.decl_table.deinit();
errdefer ctx.arena.deinit();
try ctx.emit();
return Module{
.decls = ctx.decls.toOwnedSlice(),
.decls = ctx.decls.toOwnedSlice(allocator),
.arena = ctx.arena,
.errors = &[0]ErrorMsg{},
};
}
const EmitZIR = struct {
allocator: *Allocator,
arena: std.heap.ArenaAllocator,
old_module: *const ir.Module,
decls: std.ArrayList(*Inst),
old_module: *const IrModule,
decls: std.ArrayListUnmanaged(*Inst),
decl_table: std.AutoHashMap(*ir.Inst, *Inst),
fn emit(self: *EmitZIR) !void {
for (self.old_module.exports) |module_export| {
const export_value = try self.emitTypedValue(module_export.src, module_export.typed_value);
const symbol_name = try self.emitStringLiteral(module_export.src, module_export.name);
const export_inst = try self.arena.allocator.create(Inst.Export);
export_inst.* = .{
.base = .{ .src = module_export.src, .tag = Inst.Export.base_tag },
.positionals = .{
.symbol_name = symbol_name,
.value = export_value,
},
.kw_args = .{},
};
try self.decls.append(&export_inst.base);
var it = self.old_module.decl_exports.iterator();
while (it.next()) |kv| {
const decl = kv.key;
const exports = kv.value;
const export_value = try self.emitTypedValue(decl.src, decl.typed_value.most_recent.typed_value);
for (exports) |module_export| {
const symbol_name = try self.emitStringLiteral(module_export.src, module_export.options.name);
const export_inst = try self.arena.allocator.create(Inst.Export);
export_inst.* = .{
.base = .{
.name = try self.autoName(),
.src = module_export.src,
.tag = Inst.Export.base_tag,
},
.positionals = .{
.symbol_name = symbol_name,
.value = export_value,
},
.kw_args = .{},
};
try self.decls.append(self.allocator, &export_inst.base);
}
}
}
@ -966,17 +1061,22 @@ const EmitZIR = struct {
const big_int_space = try self.arena.allocator.create(Value.BigIntSpace);
const int_inst = try self.arena.allocator.create(Inst.Int);
int_inst.* = .{
.base = .{ .src = src, .tag = Inst.Int.base_tag },
.base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Int.base_tag,
},
.positionals = .{
.int = val.toBigInt(big_int_space),
},
.kw_args = .{},
};
try self.decls.append(&int_inst.base);
try self.decls.append(self.allocator, &int_inst.base);
return &int_inst.base;
}
fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: ir.TypedValue) Allocator.Error!*Inst {
fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Inst {
const allocator = &self.arena.allocator;
switch (typed_value.ty.zigTypeTag()) {
.Pointer => {
const ptr_elem_type = typed_value.ty.elemType();
@ -988,7 +1088,10 @@ const EmitZIR = struct {
// ptr_elem_type.hasSentinel(Value.initTag(.zero)))
//{
//}
const bytes = try typed_value.val.toAllocatedBytes(&self.arena.allocator);
const bytes = typed_value.val.toAllocatedBytes(allocator) catch |err| switch (err) {
error.AnalysisFail => unreachable,
else => |e| return e,
};
return self.emitStringLiteral(src, bytes);
},
else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}),
@ -998,14 +1101,18 @@ const EmitZIR = struct {
.Int => {
const as_inst = try self.arena.allocator.create(Inst.As);
as_inst.* = .{
.base = .{ .src = src, .tag = Inst.As.base_tag },
.base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.As.base_tag,
},
.positionals = .{
.dest_type = try self.emitType(src, typed_value.ty),
.value = try self.emitComptimeIntVal(src, typed_value.val),
},
.kw_args = .{},
};
try self.decls.append(&as_inst.base);
try self.decls.append(self.allocator, &as_inst.base);
return &as_inst.base;
},
@ -1014,8 +1121,7 @@ const EmitZIR = struct {
return self.emitType(src, ty);
},
.Fn => {
const index = typed_value.val.cast(Value.Payload.Function).?.index;
const module_fn = self.old_module.fns[index];
const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
defer inst_table.deinit();
@ -1023,7 +1129,7 @@ const EmitZIR = struct {
var instructions = std.ArrayList(*Inst).init(self.allocator);
defer instructions.deinit();
try self.emitBody(module_fn.body, &inst_table, &instructions);
try self.emitBody(module_fn.analysis.success, &inst_table, &instructions);
const fn_type = try self.emitType(src, module_fn.fn_type);
@ -1032,14 +1138,18 @@ const EmitZIR = struct {
const fn_inst = try self.arena.allocator.create(Inst.Fn);
fn_inst.* = .{
.base = .{ .src = src, .tag = Inst.Fn.base_tag },
.base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Fn.base_tag,
},
.positionals = .{
.fn_type = fn_type,
.body = .{ .instructions = arena_instrs },
},
.kw_args = .{},
};
try self.decls.append(&fn_inst.base);
try self.decls.append(self.allocator, &fn_inst.base);
return &fn_inst.base;
},
else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}),
@ -1049,7 +1159,11 @@ const EmitZIR = struct {
fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst {
const new_inst = try self.arena.allocator.create(T);
new_inst.* = .{
.base = .{ .src = src, .tag = T.base_tag },
.base = .{
.name = try self.autoName(),
.src = src,
.tag = T.base_tag,
},
.positionals = .{},
.kw_args = .{},
};
@ -1058,13 +1172,35 @@ const EmitZIR = struct {
fn emitBody(
self: *EmitZIR,
body: ir.Module.Body,
body: IrModule.Body,
inst_table: *std.AutoHashMap(*ir.Inst, *Inst),
instructions: *std.ArrayList(*Inst),
) Allocator.Error!void {
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 = .{
.name = try self.autoName(),
.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
@ -1088,7 +1224,11 @@ const EmitZIR = struct {
}
new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.Asm.base_tag },
.base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.Asm.base_tag,
},
.positionals = .{
.asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source),
.return_type = try self.emitType(inst.src, inst.ty),
@ -1110,7 +1250,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.PtrToInt).?;
const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.PtrToInt.base_tag },
.base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.PtrToInt.base_tag,
},
.positionals = .{
.ptr = try self.resolveInst(inst_table, old_inst.args.ptr),
},
@ -1122,7 +1266,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.BitCast).?;
const new_inst = try self.arena.allocator.create(Inst.BitCast);
new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.BitCast.base_tag },
.base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.BitCast.base_tag,
},
.positionals = .{
.dest_type = try self.emitType(inst.src, inst.ty),
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
@ -1135,7 +1283,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.Cmp).?;
const new_inst = try self.arena.allocator.create(Inst.Cmp);
new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.Cmp.base_tag },
.base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.Cmp.base_tag,
},
.positionals = .{
.lhs = try self.resolveInst(inst_table, old_inst.args.lhs),
.rhs = try self.resolveInst(inst_table, old_inst.args.rhs),
@ -1159,7 +1311,11 @@ const EmitZIR = struct {
const new_inst = try self.arena.allocator.create(Inst.CondBr);
new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.CondBr.base_tag },
.base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.CondBr.base_tag,
},
.positionals = .{
.condition = try self.resolveInst(inst_table, old_inst.args.condition),
.true_body = .{ .instructions = true_body.toOwnedSlice() },
@ -1173,7 +1329,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.IsNull).?;
const new_inst = try self.arena.allocator.create(Inst.IsNull);
new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.IsNull.base_tag },
.base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.IsNull.base_tag,
},
.positionals = .{
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
},
@ -1185,7 +1345,11 @@ const EmitZIR = struct {
const old_inst = inst.cast(ir.Inst.IsNonNull).?;
const new_inst = try self.arena.allocator.create(Inst.IsNonNull);
new_inst.* = .{
.base = .{ .src = inst.src, .tag = Inst.IsNonNull.base_tag },
.base = .{
.name = try self.autoName(),
.src = inst.src,
.tag = Inst.IsNonNull.base_tag,
},
.positionals = .{
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
},
@ -1237,7 +1401,11 @@ const EmitZIR = struct {
const fntype_inst = try self.arena.allocator.create(Inst.FnType);
fntype_inst.* = .{
.base = .{ .src = src, .tag = Inst.FnType.base_tag },
.base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.FnType.base_tag,
},
.positionals = .{
.param_types = emitted_params,
.return_type = try self.emitType(src, ty.fnReturnType()),
@ -1246,7 +1414,7 @@ const EmitZIR = struct {
.cc = ty.fnCallingConvention(),
},
};
try self.decls.append(&fntype_inst.base);
try self.decls.append(self.allocator, &fntype_inst.base);
return &fntype_inst.base;
},
else => std.debug.panic("TODO implement emitType for {}", .{ty}),
@ -1254,29 +1422,56 @@ const EmitZIR = struct {
}
}
fn autoName(self: *EmitZIR) ![]u8 {
return std.fmt.allocPrint(&self.arena.allocator, "{}", .{self.decls.items.len});
}
fn emitPrimitiveType(self: *EmitZIR, src: usize, tag: Inst.Primitive.BuiltinType) !*Inst {
const primitive_inst = try self.arena.allocator.create(Inst.Primitive);
primitive_inst.* = .{
.base = .{ .src = src, .tag = Inst.Primitive.base_tag },
.base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Primitive.base_tag,
},
.positionals = .{
.tag = tag,
},
.kw_args = .{},
};
try self.decls.append(&primitive_inst.base);
try self.decls.append(self.allocator, &primitive_inst.base);
return &primitive_inst.base;
}
fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst {
const str_inst = try self.arena.allocator.create(Inst.Str);
str_inst.* = .{
.base = .{ .src = src, .tag = Inst.Str.base_tag },
.base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Str.base_tag,
},
.positionals = .{
.bytes = str,
},
.kw_args = .{},
};
try self.decls.append(&str_inst.base);
return &str_inst.base;
try self.decls.append(self.allocator, &str_inst.base);
const ref_inst = try self.arena.allocator.create(Inst.Ref);
ref_inst.* = .{
.base = .{
.name = try self.autoName(),
.src = src,
.tag = Inst.Ref.base_tag,
},
.positionals = .{
.operand = &str_inst.base,
},
.kw_args = .{},
};
try self.decls.append(self.allocator, &ref_inst.base);
return &ref_inst.base;
}
};

View File

@ -1794,6 +1794,16 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstGen *instruction) {
}
void codegen_report_errors_and_exit(CodeGen *g) {
// Clear progress indicator before printing errors
if (g->sub_progress_node != nullptr) {
stage2_progress_end(g->sub_progress_node);
g->sub_progress_node = nullptr;
}
if (g->main_progress_node != nullptr) {
stage2_progress_end(g->main_progress_node);
g->main_progress_node = nullptr;
}
assert(g->errors.length != 0);
for (size_t i = 0; i < g->errors.length; i += 1) {
ErrorMsg *err = g->errors.at(i);

View File

@ -1,7 +1,15 @@
const std = @import("std");
const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
// self-hosted does not yet support PE executable files / COFF object files
// or mach-o files. So we do the ZIR transform test cases cross compiling for
// x86_64-linux.
const linux_x64 = std.zig.CrossTarget{
.cpu_arch = .x86_64,
.os_tag = .linux,
};
pub fn addCases(ctx: *TestContext) void {
ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint",
ctx.addZIRTransform("elemptr, add, cmp, condbr, return, breakpoint", linux_x64,
\\@void = primitive(void)
\\@usize = primitive(usize)
\\@fnty = fntype([], @void, cc=C)
@ -12,10 +20,11 @@ pub fn addCases(ctx: *TestContext) void {
\\
\\@entry = fn(@fnty, {
\\ %a = str("\x32\x08\x01\x0a")
\\ %eptr0 = elemptr(%a, @0)
\\ %eptr1 = elemptr(%a, @1)
\\ %eptr2 = elemptr(%a, @2)
\\ %eptr3 = elemptr(%a, @3)
\\ %aref = ref(%a)
\\ %eptr0 = elemptr(%aref, @0)
\\ %eptr1 = elemptr(%aref, @1)
\\ %eptr2 = elemptr(%aref, @2)
\\ %eptr3 = elemptr(%aref, @3)
\\ %v0 = deref(%eptr0)
\\ %v1 = deref(%eptr1)
\\ %v2 = deref(%eptr2)
@ -34,7 +43,8 @@ pub fn addCases(ctx: *TestContext) void {
\\})
\\
\\@9 = str("entry")
\\@10 = export(@9, @entry)
\\@10 = ref(@9)
\\@11 = export(@10, @entry)
,
\\@0 = primitive(void)
\\@1 = fntype([], @0, cc=C)
@ -42,66 +52,161 @@ pub fn addCases(ctx: *TestContext) void {
\\ %0 = return()
\\})
\\@3 = str("entry")
\\@4 = export(@3, @2)
\\@4 = ref(@3)
\\@5 = export(@4, @2)
\\
);
if (@import("std").Target.current.os.tag != .linux or
@import("std").Target.current.cpu.arch != .x86_64)
if (std.Target.current.os.tag != .linux or
std.Target.current.cpu.arch != .x86_64)
{
// TODO implement self-hosted PE (.exe file) linking
// TODO implement more ZIR so we don't depend on x86_64-linux
return;
}
ctx.addZIRCompareOutput("hello world ZIR",
\\@0 = str("Hello, world!\n")
\\@1 = primitive(noreturn)
\\@2 = primitive(usize)
\\@3 = fntype([], @1, cc=Naked)
\\@4 = int(0)
\\@5 = int(1)
\\@6 = int(231)
\\@7 = str("len")
\\
\\@8 = fn(@3, {
\\ %0 = as(@2, @5) ; SYS_write
\\ %1 = as(@2, @5) ; STDOUT_FILENO
\\ %2 = ptrtoint(@0) ; msg ptr
\\ %3 = fieldptr(@0, @7) ; msg len ptr
\\ %4 = deref(%3) ; msg len
\\ %sysoutreg = str("={rax}")
\\ %rax = str("{rax}")
\\ %rdi = str("{rdi}")
\\ %rsi = str("{rsi}")
\\ %rdx = str("{rdx}")
\\ %rcx = str("rcx")
\\ %r11 = str("r11")
\\ %memory = str("memory")
\\ %syscall = str("syscall")
\\ %5 = asm(%syscall, @2,
\\ volatile=1,
\\ output=%sysoutreg,
\\ inputs=[%rax, %rdi, %rsi, %rdx],
\\ clobbers=[%rcx, %r11, %memory],
\\ args=[%0, %1, %2, %4])
\\
\\ %6 = as(@2, @6) ;SYS_exit_group
\\ %7 = as(@2, @4) ;exit code
\\ %8 = asm(%syscall, @2,
\\ volatile=1,
\\ output=%sysoutreg,
\\ inputs=[%rax, %rdi],
\\ clobbers=[%rcx, %r11, %memory],
\\ args=[%6, %7])
\\
\\ %9 = unreachable()
\\})
\\
\\@9 = str("_start")
\\@10 = export(@9, @8)
,
\\Hello, world!
\\
ctx.addZIRCompareOutput(
"hello world ZIR, update msg",
&[_][]const u8{
\\@noreturn = primitive(noreturn)
\\@void = primitive(void)
\\@usize = primitive(usize)
\\@0 = int(0)
\\@1 = int(1)
\\@2 = int(2)
\\@3 = int(3)
\\
\\@syscall_array = str("syscall")
\\@sysoutreg_array = str("={rax}")
\\@rax_array = str("{rax}")
\\@rdi_array = str("{rdi}")
\\@rcx_array = str("rcx")
\\@r11_array = str("r11")
\\@rdx_array = str("{rdx}")
\\@rsi_array = str("{rsi}")
\\@memory_array = str("memory")
\\@len_array = str("len")
\\
\\@msg = str("Hello, world!\n")
\\
\\@start_fnty = fntype([], @noreturn, cc=Naked)
\\@start = fn(@start_fnty, {
\\ %SYS_exit_group = int(231)
\\ %exit_code = as(@usize, @0)
\\
\\ %syscall = ref(@syscall_array)
\\ %sysoutreg = ref(@sysoutreg_array)
\\ %rax = ref(@rax_array)
\\ %rdi = ref(@rdi_array)
\\ %rcx = ref(@rcx_array)
\\ %rdx = ref(@rdx_array)
\\ %rsi = ref(@rsi_array)
\\ %r11 = ref(@r11_array)
\\ %memory = ref(@memory_array)
\\
\\ %SYS_write = as(@usize, @1)
\\ %STDOUT_FILENO = as(@usize, @1)
\\
\\ %msg_ptr = ref(@msg)
\\ %msg_addr = ptrtoint(%msg_ptr)
\\
\\ %len_name = ref(@len_array)
\\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name)
\\ %msg_len = deref(%msg_len_ptr)
\\ %rc_write = asm(%syscall, @usize,
\\ volatile=1,
\\ output=%sysoutreg,
\\ inputs=[%rax, %rdi, %rsi, %rdx],
\\ clobbers=[%rcx, %r11, %memory],
\\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len])
\\
\\ %rc_exit = asm(%syscall, @usize,
\\ volatile=1,
\\ output=%sysoutreg,
\\ inputs=[%rax, %rdi],
\\ clobbers=[%rcx, %r11, %memory],
\\ args=[%SYS_exit_group, %exit_code])
\\
\\ %99 = unreachable()
\\});
\\
\\@9 = str("_start")
\\@10 = ref(@9)
\\@11 = export(@10, @start)
,
\\@noreturn = primitive(noreturn)
\\@void = primitive(void)
\\@usize = primitive(usize)
\\@0 = int(0)
\\@1 = int(1)
\\@2 = int(2)
\\@3 = int(3)
\\
\\@syscall_array = str("syscall")
\\@sysoutreg_array = str("={rax}")
\\@rax_array = str("{rax}")
\\@rdi_array = str("{rdi}")
\\@rcx_array = str("rcx")
\\@r11_array = str("r11")
\\@rdx_array = str("{rdx}")
\\@rsi_array = str("{rsi}")
\\@memory_array = str("memory")
\\@len_array = str("len")
\\
\\@msg = str("Hello, world!\n")
\\@msg2 = str("HELL WORLD\n")
\\
\\@start_fnty = fntype([], @noreturn, cc=Naked)
\\@start = fn(@start_fnty, {
\\ %SYS_exit_group = int(231)
\\ %exit_code = as(@usize, @0)
\\
\\ %syscall = ref(@syscall_array)
\\ %sysoutreg = ref(@sysoutreg_array)
\\ %rax = ref(@rax_array)
\\ %rdi = ref(@rdi_array)
\\ %rcx = ref(@rcx_array)
\\ %rdx = ref(@rdx_array)
\\ %rsi = ref(@rsi_array)
\\ %r11 = ref(@r11_array)
\\ %memory = ref(@memory_array)
\\
\\ %SYS_write = as(@usize, @1)
\\ %STDOUT_FILENO = as(@usize, @1)
\\
\\ %msg_ptr = ref(@msg2)
\\ %msg_addr = ptrtoint(%msg_ptr)
\\
\\ %len_name = ref(@len_array)
\\ %msg_len_ptr = fieldptr(%msg_ptr, %len_name)
\\ %msg_len = deref(%msg_len_ptr)
\\ %rc_write = asm(%syscall, @usize,
\\ volatile=1,
\\ output=%sysoutreg,
\\ inputs=[%rax, %rdi, %rsi, %rdx],
\\ clobbers=[%rcx, %r11, %memory],
\\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len])
\\
\\ %rc_exit = asm(%syscall, @usize,
\\ volatile=1,
\\ output=%sysoutreg,
\\ inputs=[%rax, %rdi],
\\ clobbers=[%rcx, %r11, %memory],
\\ args=[%SYS_exit_group, %exit_code])
\\
\\ %99 = unreachable()
\\});
\\
\\@9 = str("_start")
\\@10 = ref(@9)
\\@11 = export(@10, @start)
},
&[_][]const u8{
\\Hello, world!
\\
,
\\HELL WORLD
\\
},
);
}