Merge pull request #5307 from ziglang/self-hosted-incremental-compilation
rework self-hosted compiler for incremental buildsmaster
commit
16f100b82e
37
README.md
37
README.md
|
@ -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
|
||||
```
|
||||
|
|
15
build.zig
15
build.zig
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
};
|
|
@ -49,7 +49,7 @@ pub fn SinglyLinkedList(comptime T: type) type {
|
|||
}
|
||||
};
|
||||
|
||||
first: ?*Node,
|
||||
first: ?*Node = null,
|
||||
|
||||
/// Initialize a linked list.
|
||||
///
|
||||
|
|
|
@ -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" {
|
||||
|
|
|
@ -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;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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");
|
||||
});
|
|
@ -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"),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
@ -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,
|
||||
};
|
||||
};
|
|
@ -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
|
@ -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
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
};
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
pub const Visib = enum {
|
||||
Private,
|
||||
Pub,
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
\\
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue