Merge pull request #5307 from ziglang/self-hosted-incremental-compilation
rework self-hosted compiler for incremental builds
This commit is contained in:
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
|
##### Windows
|
||||||
|
|
||||||
See https://github.com/ziglang/zig/wiki/Building-Zig-on-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");
|
var exe = b.addExecutable("zig", "src-self-hosted/main.zig");
|
||||||
exe.setBuildMode(mode);
|
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 = 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;
|
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_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_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_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;
|
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);
|
var ctx = parseConfigH(b, config_h_text);
|
||||||
ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
|
ctx.llvm = try findLLVM(b, ctx.llvm_config_exe);
|
||||||
|
|
||||||
try configureStage2(b, exe, ctx);
|
try configureStage2(b, exe, ctx);
|
||||||
|
}
|
||||||
b.default_step.dependOn(&exe.step);
|
if (!only_install_lib_files) {
|
||||||
exe.install();
|
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{
|
b.installDirectory(InstallDirectoryOptions{
|
||||||
.source_dir = "lib",
|
.source_dir = "lib",
|
||||||
|
@ -8,13 +8,13 @@ const Allocator = mem.Allocator;
|
|||||||
/// A contiguous, growable list of items in memory.
|
/// A contiguous, growable list of items in memory.
|
||||||
/// This is a wrapper around an array of T values. Initialize with `init`.
|
/// This is a wrapper around an array of T values. Initialize with `init`.
|
||||||
pub fn ArrayList(comptime T: type) type {
|
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 (alignment) |a| {
|
||||||
if (a == @alignOf(T)) {
|
if (a == @alignOf(T)) {
|
||||||
return AlignedArrayList(T, null);
|
return ArrayListAligned(T, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return struct {
|
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.
|
/// The caller owns the returned memory. ArrayList becomes empty.
|
||||||
pub fn toOwnedSlice(self: *Self) Slice {
|
pub fn toOwnedSlice(self: *Self) Slice {
|
||||||
const allocator = self.allocator;
|
const allocator = self.allocator;
|
||||||
@ -84,8 +88,8 @@ pub fn AlignedArrayList(comptime T: type, comptime alignment: ?u29) type {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert `item` at index `n`. Moves `list[n .. list.len]`
|
/// Insert `item` at index `n` by moving `list[n .. list.len]` to make room.
|
||||||
/// to make room.
|
/// This operation is O(N).
|
||||||
pub fn insert(self: *Self, n: usize, item: T) !void {
|
pub fn insert(self: *Self, n: usize, item: T) !void {
|
||||||
try self.ensureCapacity(self.items.len + 1);
|
try self.ensureCapacity(self.items.len + 1);
|
||||||
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;
|
self.items[n] = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert slice `items` at index `i`. Moves
|
/// Insert slice `items` at index `i` by moving `list[i .. list.len]` to make room.
|
||||||
/// `list[i .. list.len]` to make room.
|
|
||||||
/// This operation is O(N).
|
/// This operation is O(N).
|
||||||
pub fn insertSlice(self: *Self, i: usize, items: SliceConst) !void {
|
pub fn insertSlice(self: *Self, i: usize, items: SliceConst) !void {
|
||||||
try self.ensureCapacity(self.items.len + items.len);
|
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
|
/// Append the slice of items to the list. Allocates more
|
||||||
/// memory as necessary.
|
/// memory as necessary.
|
||||||
pub fn appendSlice(self: *Self, items: SliceConst) !void {
|
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 oldlen = self.items.len;
|
||||||
const newlen = self.items.len + items.len;
|
const newlen = self.items.len + items.len;
|
||||||
|
|
||||||
try self.ensureCapacity(newlen);
|
|
||||||
self.items.len = newlen;
|
self.items.len = newlen;
|
||||||
mem.copy(T, self.items[oldlen..], items);
|
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" {
|
test "std.ArrayList.init" {
|
||||||
var list = ArrayList(i32).init(testing.allocator);
|
var list = ArrayList(i32).init(testing.allocator);
|
||||||
defer list.deinit();
|
defer list.deinit();
|
||||||
|
@ -191,8 +191,8 @@ pub fn LinearFifo(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Read the next item from the fifo
|
/// Read the next item from the fifo
|
||||||
pub fn readItem(self: *Self) !T {
|
pub fn readItem(self: *Self) ?T {
|
||||||
if (self.count == 0) return error.EndOfStream;
|
if (self.count == 0) return null;
|
||||||
|
|
||||||
const c = self.buf[self.head];
|
const c = self.buf[self.head];
|
||||||
self.discard(1);
|
self.discard(1);
|
||||||
@ -282,7 +282,10 @@ pub fn LinearFifo(
|
|||||||
/// Write a single item to the fifo
|
/// Write a single item to the fifo
|
||||||
pub fn writeItem(self: *Self, item: T) !void {
|
pub fn writeItem(self: *Self, item: T) !void {
|
||||||
try self.ensureUnusedCapacity(1);
|
try self.ensureUnusedCapacity(1);
|
||||||
|
return self.writeItemAssumeCapacity(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeItemAssumeCapacity(self: *Self, item: T) void {
|
||||||
var tail = self.head + self.count;
|
var tail = self.head + self.count;
|
||||||
if (powers_of_two) {
|
if (powers_of_two) {
|
||||||
tail &= self.buf.len - 1;
|
tail &= self.buf.len - 1;
|
||||||
@ -342,10 +345,10 @@ pub fn LinearFifo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Peek at the item at `offset`
|
/// Returns the item at `offset`.
|
||||||
pub fn peekItem(self: Self, offset: usize) error{EndOfStream}!T {
|
/// Asserts offset is within bounds.
|
||||||
if (offset >= self.count)
|
pub fn peekItem(self: Self, offset: usize) T {
|
||||||
return error.EndOfStream;
|
assert(offset < self.count);
|
||||||
|
|
||||||
var index = self.head + offset;
|
var index = self.head + offset;
|
||||||
if (powers_of_two) {
|
if (powers_of_two) {
|
||||||
@ -369,18 +372,18 @@ test "LinearFifo(u8, .Dynamic)" {
|
|||||||
{
|
{
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < 5) : (i += 1) {
|
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.expectEqual(@as(usize, 10), fifo.readableLength());
|
||||||
testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0));
|
testing.expectEqualSlices(u8, "HELLOHELLO", fifo.readableSlice(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
testing.expectEqual(@as(u8, 'H'), try fifo.readItem());
|
testing.expectEqual(@as(u8, 'H'), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(u8, 'E'), try fifo.readItem());
|
testing.expectEqual(@as(u8, 'E'), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(u8, 'L'), try fifo.readItem());
|
testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(u8, 'L'), try fifo.readItem());
|
testing.expectEqual(@as(u8, 'L'), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(u8, 'O'), try fifo.readItem());
|
testing.expectEqual(@as(u8, 'O'), fifo.readItem().?);
|
||||||
}
|
}
|
||||||
testing.expectEqual(@as(usize, 5), fifo.readableLength());
|
testing.expectEqual(@as(usize, 5), fifo.readableLength());
|
||||||
|
|
||||||
@ -451,11 +454,11 @@ test "LinearFifo" {
|
|||||||
testing.expectEqual(@as(usize, 5), fifo.readableLength());
|
testing.expectEqual(@as(usize, 5), fifo.readableLength());
|
||||||
|
|
||||||
{
|
{
|
||||||
testing.expectEqual(@as(T, 0), try fifo.readItem());
|
testing.expectEqual(@as(T, 0), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(T, 1), try fifo.readItem());
|
testing.expectEqual(@as(T, 1), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(T, 1), try fifo.readItem());
|
testing.expectEqual(@as(T, 1), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(T, 0), try fifo.readItem());
|
testing.expectEqual(@as(T, 0), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(T, 1), try fifo.readItem());
|
testing.expectEqual(@as(T, 1), fifo.readItem().?);
|
||||||
testing.expectEqual(@as(usize, 0), fifo.readableLength());
|
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 {
|
pub const WriteFileOptions = struct {
|
||||||
in_offset: u64 = 0,
|
in_offset: u64 = 0,
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ const Wyhash = std.hash.Wyhash;
|
|||||||
const Allocator = mem.Allocator;
|
const Allocator = mem.Allocator;
|
||||||
const builtin = @import("builtin");
|
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;
|
const debug_u32 = if (want_modification_safety) u32 else void;
|
||||||
|
|
||||||
pub fn AutoHashMap(comptime K: type, comptime V: type) type {
|
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;
|
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 {
|
pub fn get(hm: *const Self, key: K) ?*KV {
|
||||||
if (hm.entries.len == 0) {
|
if (hm.entries.len == 0) {
|
||||||
return null;
|
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 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;
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
@ -510,95 +511,6 @@ pub const HeapAllocator = switch (builtin.os.tag) {
|
|||||||
else => @compileError("Unsupported OS"),
|
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 {
|
pub const FixedBufferAllocator = struct {
|
||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
end_index: usize,
|
end_index: usize,
|
||||||
|
102
lib/std/heap/arena_allocator.zig
Normal file
102
lib/std/heap/arena_allocator.zig
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
const std = @import("../std.zig");
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const mem = std.mem;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
|
/// This allocator takes an existing allocator, wraps it, and provides an interface
|
||||||
|
/// where you can allocate without freeing, and then free it all together.
|
||||||
|
pub const ArenaAllocator = struct {
|
||||||
|
allocator: Allocator,
|
||||||
|
|
||||||
|
child_allocator: *Allocator,
|
||||||
|
state: State,
|
||||||
|
|
||||||
|
/// Inner state of ArenaAllocator. Can be stored rather than the entire ArenaAllocator
|
||||||
|
/// as a memory-saving optimization.
|
||||||
|
pub const State = struct {
|
||||||
|
buffer_list: std.SinglyLinkedList([]u8) = @as(std.SinglyLinkedList([]u8), .{}),
|
||||||
|
end_index: usize = 0,
|
||||||
|
|
||||||
|
pub fn promote(self: State, child_allocator: *Allocator) ArenaAllocator {
|
||||||
|
return .{
|
||||||
|
.allocator = Allocator{
|
||||||
|
.reallocFn = realloc,
|
||||||
|
.shrinkFn = shrink,
|
||||||
|
},
|
||||||
|
.child_allocator = child_allocator,
|
||||||
|
.state = self,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const BufNode = std.SinglyLinkedList([]u8).Node;
|
||||||
|
|
||||||
|
pub fn init(child_allocator: *Allocator) ArenaAllocator {
|
||||||
|
return (State{}).promote(child_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: ArenaAllocator) void {
|
||||||
|
var it = self.state.buffer_list.first;
|
||||||
|
while (it) |node| {
|
||||||
|
// this has to occur before the free because the free frees node
|
||||||
|
const next_it = node.next;
|
||||||
|
self.child_allocator.free(node.data);
|
||||||
|
it = next_it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn createNode(self: *ArenaAllocator, prev_len: usize, minimum_size: usize) !*BufNode {
|
||||||
|
const actual_min_size = minimum_size + @sizeOf(BufNode);
|
||||||
|
var len = prev_len;
|
||||||
|
while (true) {
|
||||||
|
len += len / 2;
|
||||||
|
len += mem.page_size - @rem(len, mem.page_size);
|
||||||
|
if (len >= actual_min_size) break;
|
||||||
|
}
|
||||||
|
const buf = try self.child_allocator.alignedAlloc(u8, @alignOf(BufNode), len);
|
||||||
|
const buf_node_slice = mem.bytesAsSlice(BufNode, buf[0..@sizeOf(BufNode)]);
|
||||||
|
const buf_node = &buf_node_slice[0];
|
||||||
|
buf_node.* = BufNode{
|
||||||
|
.data = buf,
|
||||||
|
.next = null,
|
||||||
|
};
|
||||||
|
self.state.buffer_list.prepend(buf_node);
|
||||||
|
self.state.end_index = 0;
|
||||||
|
return buf_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alloc(allocator: *Allocator, n: usize, alignment: u29) ![]u8 {
|
||||||
|
const self = @fieldParentPtr(ArenaAllocator, "allocator", allocator);
|
||||||
|
|
||||||
|
var cur_node = if (self.state.buffer_list.first) |first_node| first_node else try self.createNode(0, n + alignment);
|
||||||
|
while (true) {
|
||||||
|
const cur_buf = cur_node.data[@sizeOf(BufNode)..];
|
||||||
|
const addr = @ptrToInt(cur_buf.ptr) + self.state.end_index;
|
||||||
|
const adjusted_addr = mem.alignForward(addr, alignment);
|
||||||
|
const adjusted_index = self.state.end_index + (adjusted_addr - addr);
|
||||||
|
const new_end_index = adjusted_index + n;
|
||||||
|
if (new_end_index > cur_buf.len) {
|
||||||
|
cur_node = try self.createNode(cur_buf.len, n + alignment);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const result = cur_buf[adjusted_index..new_end_index];
|
||||||
|
self.state.end_index = new_end_index;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn realloc(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) ![]u8 {
|
||||||
|
if (new_size <= old_mem.len and new_align <= new_size) {
|
||||||
|
// We can't do anything with the memory, so tell the client to keep it.
|
||||||
|
return error.OutOfMemory;
|
||||||
|
} else {
|
||||||
|
const result = try alloc(allocator, new_size, new_align);
|
||||||
|
@memcpy(result.ptr, old_mem.ptr, std.math.min(old_mem.len, result.len));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn shrink(allocator: *Allocator, old_mem: []u8, old_align: u29, new_size: usize, new_align: u29) []u8 {
|
||||||
|
return old_mem[0..new_size];
|
||||||
|
}
|
||||||
|
};
|
@ -49,7 +49,7 @@ pub fn SinglyLinkedList(comptime T: type) type {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
first: ?*Node,
|
first: ?*Node = null,
|
||||||
|
|
||||||
/// Initialize a linked list.
|
/// 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);
|
const shrink_result = self.shrinkFn(self, non_const_ptr[0..bytes_len], Slice.alignment, 0, 1);
|
||||||
assert(shrink_result.len == 0);
|
assert(shrink_result.len == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Copies `m` to newly allocated memory. Caller owns the memory.
|
||||||
|
pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
|
||||||
|
const new_buf = try allocator.alloc(T, m.len);
|
||||||
|
copy(T, new_buf, m);
|
||||||
|
return new_buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
|
||||||
|
pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
|
||||||
|
const new_buf = try allocator.alloc(T, m.len + 1);
|
||||||
|
copy(T, new_buf, m);
|
||||||
|
new_buf[m.len] = 0;
|
||||||
|
return new_buf[0..m.len :0];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var failAllocator = Allocator {
|
var failAllocator = Allocator {
|
||||||
@ -785,19 +800,14 @@ pub fn allEqual(comptime T: type, slice: []const T, scalar: T) bool {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies `m` to newly allocated memory. Caller owns the memory.
|
/// Deprecated, use `Allocator.dupe`.
|
||||||
pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
|
pub fn dupe(allocator: *Allocator, comptime T: type, m: []const T) ![]T {
|
||||||
const new_buf = try allocator.alloc(T, m.len);
|
return allocator.dupe(T, m);
|
||||||
copy(T, new_buf, m);
|
|
||||||
return new_buf;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory.
|
/// Deprecated, use `Allocator.dupeZ`.
|
||||||
pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
|
pub fn dupeZ(allocator: *Allocator, comptime T: type, m: []const T) ![:0]T {
|
||||||
const new_buf = try allocator.alloc(T, m.len + 1);
|
return allocator.dupeZ(T, m);
|
||||||
copy(T, new_buf, m);
|
|
||||||
new_buf[m.len] = 0;
|
|
||||||
return new_buf[0..m.len :0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove values from the beginning of a slice.
|
/// Remove values from the beginning of a slice.
|
||||||
@ -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
|
/// 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.
|
/// The alignment must be a power of 2 and greater than 0.
|
||||||
pub fn isAligned(addr: usize, alignment: usize) bool {
|
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" {
|
test "isAligned" {
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
pub const AlignedArrayList = @import("array_list.zig").AlignedArrayList;
|
|
||||||
pub const ArrayList = @import("array_list.zig").ArrayList;
|
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 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 AutoHashMap = @import("hash_map.zig").AutoHashMap;
|
||||||
pub const BloomFilter = @import("bloom_filter.zig").BloomFilter;
|
pub const BloomFilter = @import("bloom_filter.zig").BloomFilter;
|
||||||
pub const BufMap = @import("buf_map.zig").BufMap;
|
pub const BufMap = @import("buf_map.zig").BufMap;
|
||||||
|
2091
src-self-hosted/Module.zig
Normal file
2091
src-self-hosted/Module.zig
Normal file
File diff suppressed because it is too large
Load Diff
53
src-self-hosted/Package.zig
Normal file
53
src-self-hosted/Package.zig
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
pub const Table = std.StringHashMap(*Package);
|
||||||
|
|
||||||
|
root_src_dir: std.fs.Dir,
|
||||||
|
/// Relative to `root_src_dir`.
|
||||||
|
root_src_path: []const u8,
|
||||||
|
table: Table,
|
||||||
|
|
||||||
|
/// No references to `root_src_dir` and `root_src_path` are kept.
|
||||||
|
pub fn create(
|
||||||
|
allocator: *mem.Allocator,
|
||||||
|
base_dir: std.fs.Dir,
|
||||||
|
/// Relative to `base_dir`.
|
||||||
|
root_src_dir: []const u8,
|
||||||
|
/// Relative to `root_src_dir`.
|
||||||
|
root_src_path: []const u8,
|
||||||
|
) !*Package {
|
||||||
|
const ptr = try allocator.create(Package);
|
||||||
|
errdefer allocator.destroy(ptr);
|
||||||
|
const root_src_path_dupe = try mem.dupe(allocator, u8, root_src_path);
|
||||||
|
errdefer allocator.free(root_src_path_dupe);
|
||||||
|
ptr.* = .{
|
||||||
|
.root_src_dir = try base_dir.openDir(root_src_dir, .{}),
|
||||||
|
.root_src_path = root_src_path_dupe,
|
||||||
|
.table = Table.init(allocator),
|
||||||
|
};
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Package) void {
|
||||||
|
const allocator = self.table.allocator;
|
||||||
|
self.root_src_dir.close();
|
||||||
|
allocator.free(self.root_src_path);
|
||||||
|
{
|
||||||
|
var it = self.table.iterator();
|
||||||
|
while (it.next()) |kv| {
|
||||||
|
allocator.free(kv.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.table.deinit();
|
||||||
|
allocator.destroy(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add(self: *Package, name: []const u8, package: *Package) !void {
|
||||||
|
const name_dupe = try mem.dupe(self.table.allocator, u8, name);
|
||||||
|
errdefer self.table.allocator.deinit(name_dupe);
|
||||||
|
const entry = try self.table.put(name_dupe, package);
|
||||||
|
assert(entry == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
const mem = std.mem;
|
||||||
|
const assert = std.debug.assert;
|
||||||
|
const Package = @This();
|
23
src-self-hosted/TypedValue.zig
Normal file
23
src-self-hosted/TypedValue.zig
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const Type = @import("type.zig").Type;
|
||||||
|
const Value = @import("value.zig").Value;
|
||||||
|
const Allocator = std.mem.Allocator;
|
||||||
|
const TypedValue = @This();
|
||||||
|
|
||||||
|
ty: Type,
|
||||||
|
val: Value,
|
||||||
|
|
||||||
|
/// Memory management for TypedValue. The main purpose of this type
|
||||||
|
/// is to be small and have a deinit() function to free associated resources.
|
||||||
|
pub const Managed = struct {
|
||||||
|
/// If the tag value is less than Tag.no_payload_count, then no pointer
|
||||||
|
/// dereference is needed.
|
||||||
|
typed_value: TypedValue,
|
||||||
|
/// If this is `null` then there is no memory management needed.
|
||||||
|
arena: ?*std.heap.ArenaAllocator.State = null,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Managed, allocator: *Allocator) void {
|
||||||
|
if (self.arena) |a| a.promote(allocator).deinit();
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
};
|
@ -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 ir = @import("ir.zig");
|
||||||
const Type = @import("type.zig").Type;
|
const Type = @import("type.zig").Type;
|
||||||
const Value = @import("value.zig").Value;
|
const Value = @import("value.zig").Value;
|
||||||
|
const TypedValue = @import("TypedValue.zig");
|
||||||
|
const link = @import("link.zig");
|
||||||
|
const Module = @import("Module.zig");
|
||||||
|
const ErrorMsg = Module.ErrorMsg;
|
||||||
const Target = std.Target;
|
const Target = std.Target;
|
||||||
|
const Allocator = mem.Allocator;
|
||||||
|
|
||||||
pub const ErrorMsg = struct {
|
pub const Result = union(enum) {
|
||||||
byte_offset: usize,
|
/// The `code` parameter passed to `generateSymbol` has the value appended.
|
||||||
msg: []const u8,
|
appended: void,
|
||||||
|
/// The value is available externally, `code` is unused.
|
||||||
|
externally_managed: []const u8,
|
||||||
|
fail: *Module.ErrorMsg,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Symbol = struct {
|
pub fn generateSymbol(
|
||||||
errors: []ErrorMsg,
|
bin_file: *link.ElfFile,
|
||||||
|
src: usize,
|
||||||
pub fn deinit(self: *Symbol, allocator: *mem.Allocator) void {
|
typed_value: TypedValue,
|
||||||
for (self.errors) |err| {
|
code: *std.ArrayList(u8),
|
||||||
allocator.free(err.msg);
|
) error{
|
||||||
}
|
OutOfMemory,
|
||||||
allocator.free(self.errors);
|
/// A Decl that this symbol depends on had a semantic analysis failure.
|
||||||
self.* = undefined;
|
AnalysisFail,
|
||||||
}
|
}!Result {
|
||||||
};
|
|
||||||
|
|
||||||
pub fn generateSymbol(typed_value: ir.TypedValue, module: ir.Module, code: *std.ArrayList(u8)) !Symbol {
|
|
||||||
switch (typed_value.ty.zigTypeTag()) {
|
switch (typed_value.ty.zigTypeTag()) {
|
||||||
.Fn => {
|
.Fn => {
|
||||||
const index = typed_value.val.cast(Value.Payload.Function).?.index;
|
const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
|
||||||
const module_fn = module.fns[index];
|
|
||||||
|
|
||||||
var function = Function{
|
var function = Function{
|
||||||
.module = &module,
|
.target = &bin_file.options.target,
|
||||||
.mod_fn = &module_fn,
|
.bin_file = bin_file,
|
||||||
|
.mod_fn = module_fn,
|
||||||
.code = code,
|
.code = code,
|
||||||
.inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(code.allocator),
|
.inst_table = std.AutoHashMap(*ir.Inst, Function.MCValue).init(bin_file.allocator),
|
||||||
.errors = std.ArrayList(ErrorMsg).init(code.allocator),
|
.err_msg = null,
|
||||||
};
|
};
|
||||||
defer function.inst_table.deinit();
|
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) {
|
const new_inst = function.genFuncInst(inst) catch |err| switch (err) {
|
||||||
error.CodegenFail => {
|
error.CodegenFail => return Result{ .fail = function.err_msg.? },
|
||||||
assert(function.errors.items.len != 0);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
try function.inst_table.putNoClobber(inst, new_inst);
|
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 {
|
const Function = struct {
|
||||||
module: *const ir.Module,
|
bin_file: *link.ElfFile,
|
||||||
mod_fn: *const ir.Module.Fn,
|
target: *const std.Target,
|
||||||
|
mod_fn: *const Module.Fn,
|
||||||
code: *std.ArrayList(u8),
|
code: *std.ArrayList(u8),
|
||||||
inst_table: std.AutoHashMap(*ir.Inst, MCValue),
|
inst_table: std.AutoHashMap(*ir.Inst, MCValue),
|
||||||
errors: std.ArrayList(ErrorMsg),
|
err_msg: ?*ErrorMsg,
|
||||||
|
|
||||||
const MCValue = union(enum) {
|
const MCValue = union(enum) {
|
||||||
none,
|
none,
|
||||||
@ -73,11 +164,14 @@ const Function = struct {
|
|||||||
/// The value is in a target-specific register. The value can
|
/// The value is in a target-specific register. The value can
|
||||||
/// be @intToEnum casted to the respective Reg enum.
|
/// be @intToEnum casted to the respective Reg enum.
|
||||||
register: usize,
|
register: usize,
|
||||||
|
/// The value is in memory at a hard-coded address.
|
||||||
|
memory: u64,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue {
|
fn genFuncInst(self: *Function, inst: *ir.Inst) !MCValue {
|
||||||
switch (inst.tag) {
|
switch (inst.tag) {
|
||||||
.breakpoint => return self.genBreakpoint(inst.src),
|
.breakpoint => return self.genBreakpoint(inst.src),
|
||||||
|
.call => return self.genCall(inst.cast(ir.Inst.Call).?),
|
||||||
.unreach => return MCValue{ .unreach = {} },
|
.unreach => return MCValue{ .unreach = {} },
|
||||||
.constant => unreachable, // excluded from function bodies
|
.constant => unreachable, // excluded from function bodies
|
||||||
.assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
|
.assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?),
|
||||||
@ -92,54 +186,76 @@ const Function = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn genBreakpoint(self: *Function, src: usize) !MCValue {
|
fn genBreakpoint(self: *Function, src: usize) !MCValue {
|
||||||
switch (self.module.target.cpu.arch) {
|
switch (self.target.cpu.arch) {
|
||||||
.i386, .x86_64 => {
|
.i386, .x86_64 => {
|
||||||
try self.code.append(0xcc); // int3
|
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 {
|
fn genRet(self: *Function, inst: *ir.Inst.Ret) !MCValue {
|
||||||
switch (self.module.target.cpu.arch) {
|
switch (self.target.cpu.arch) {
|
||||||
.i386, .x86_64 => {
|
.i386, .x86_64 => {
|
||||||
try self.code.append(0xc3); // ret
|
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;
|
return .unreach;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue {
|
fn genCmp(self: *Function, inst: *ir.Inst.Cmp) !MCValue {
|
||||||
switch (self.module.target.cpu.arch) {
|
switch (self.target.cpu.arch) {
|
||||||
else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.module.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 {
|
fn genCondBr(self: *Function, inst: *ir.Inst.CondBr) !MCValue {
|
||||||
switch (self.module.target.cpu.arch) {
|
switch (self.target.cpu.arch) {
|
||||||
else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.module.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 {
|
fn genIsNull(self: *Function, inst: *ir.Inst.IsNull) !MCValue {
|
||||||
switch (self.module.target.cpu.arch) {
|
switch (self.target.cpu.arch) {
|
||||||
else => return self.fail(inst.base.src, "TODO implement isnull for {}", .{self.module.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 {
|
fn genIsNonNull(self: *Function, inst: *ir.Inst.IsNonNull) !MCValue {
|
||||||
// Here you can specialize this instruction if it makes sense to, otherwise the default
|
// Here you can specialize this instruction if it makes sense to, otherwise the default
|
||||||
// will call genIsNull and invert the result.
|
// 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 ", .{}),
|
else => return self.fail(inst.base.src, "TODO call genIsNull and invert the result ", .{}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void {
|
fn genRelativeFwdJump(self: *Function, src: usize, amount: u32) !void {
|
||||||
switch (self.module.target.cpu.arch) {
|
switch (self.target.cpu.arch) {
|
||||||
.i386, .x86_64 => {
|
.i386, .x86_64 => {
|
||||||
|
// TODO x86 treats the operands as signed
|
||||||
if (amount <= std.math.maxInt(u8)) {
|
if (amount <= std.math.maxInt(u8)) {
|
||||||
try self.code.resize(self.code.items.len + 2);
|
try self.code.resize(self.code.items.len + 2);
|
||||||
self.code.items[self.code.items.len - 2] = 0xeb;
|
self.code.items[self.code.items.len - 2] = 0xeb;
|
||||||
@ -151,13 +267,13 @@ const Function = struct {
|
|||||||
mem.writeIntLittle(u32, imm_ptr, amount);
|
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 {
|
fn genAsm(self: *Function, inst: *ir.Inst.Assembly) !MCValue {
|
||||||
// TODO convert to inline function
|
// TODO convert to inline function
|
||||||
switch (self.module.target.cpu.arch) {
|
switch (self.target.cpu.arch) {
|
||||||
.arm => return self.genAsmArch(.arm, inst),
|
.arm => return self.genAsmArch(.arm, inst),
|
||||||
.armeb => return self.genAsmArch(.armeb, inst),
|
.armeb => return self.genAsmArch(.armeb, inst),
|
||||||
.aarch64 => return self.genAsmArch(.aarch64, 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) {
|
switch (arch) {
|
||||||
.x86_64 => switch (reg) {
|
.x86_64 => switch (mcv) {
|
||||||
.rax => switch (mcv) {
|
|
||||||
.none, .unreach => unreachable,
|
.none, .unreach => unreachable,
|
||||||
.immediate => |x| {
|
.immediate => |x| {
|
||||||
// Setting the eax register zeroes the upper part of rax, so if the number is small
|
if (reg.size() != 64) {
|
||||||
// enough, that is preferable.
|
return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{});
|
||||||
// Best case: zero
|
}
|
||||||
// 31 c0 xor eax,eax
|
// 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) {
|
if (x == 0) {
|
||||||
return self.code.appendSlice(&[_]u8{ 0x31, 0xc0 });
|
// 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
|
||||||
// Next best case: set eax with 4 bytes
|
// ModR/M byte of the instruction contains a register operand and an r/m operand."
|
||||||
// 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>
|
|
||||||
//
|
//
|
||||||
|
// 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(u32, imm_ptr, @intCast(u32, x));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// 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.
|
// 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.
|
// 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);
|
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 rip = self.code.items.len;
|
||||||
const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
|
const big_offset = @intCast(i64, code_offset) - @intCast(i64, rip);
|
||||||
const offset = @intCast(i32, big_offset);
|
const offset = @intCast(i32, big_offset);
|
||||||
self.code.items[self.code.items.len - 7] = 0x48;
|
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 - 6] = 0x8D;
|
||||||
self.code.items[self.code.items.len - 5] = 0x35;
|
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];
|
const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4];
|
||||||
mem.writeIntLittle(i32, imm_ptr, offset);
|
mem.writeIntLittle(i32, imm_ptr, offset);
|
||||||
return;
|
|
||||||
},
|
},
|
||||||
.register => return self.fail(src, "TODO implement x86_64 genSetReg %rsi = register", .{}),
|
.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", .{}),
|
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()) {
|
switch (typed_value.ty.zigTypeTag()) {
|
||||||
.Pointer => {
|
.Pointer => {
|
||||||
const ptr_elem_type = typed_value.ty.elemType();
|
if (typed_value.val.cast(Value.Payload.DeclRef)) |payload| {
|
||||||
switch (ptr_elem_type.zigTypeTag()) {
|
const got = &self.bin_file.program_headers.items[self.bin_file.phdr_got_index.?];
|
||||||
.Array => {
|
const decl = payload.decl;
|
||||||
// TODO more checks to make sure this can be emitted as a string literal
|
const got_addr = got.p_vaddr + decl.link.offset_table_index * ptr_bytes;
|
||||||
const bytes = try typed_value.val.toAllocatedBytes(self.code.allocator);
|
return MCValue{ .memory = got_addr };
|
||||||
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)}),
|
|
||||||
}
|
}
|
||||||
|
return self.fail(src, "TODO codegen more kinds of const pointers", .{});
|
||||||
},
|
},
|
||||||
.Int => {
|
.Int => {
|
||||||
const info = typed_value.ty.intInfo(self.module.target);
|
const info = typed_value.ty.intInfo(self.target.*);
|
||||||
const ptr_bits = self.module.target.cpu.arch.ptrBitWidth();
|
|
||||||
if (info.bits > ptr_bits or info.signed) {
|
if (info.bits > ptr_bits or info.signed) {
|
||||||
return self.fail(src, "TODO const int bigger than ptr and signed int", .{});
|
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 } {
|
fn fail(self: *Function, src: usize, comptime format: []const u8, args: var) error{ CodegenFail, OutOfMemory } {
|
||||||
@setCold(true);
|
@setCold(true);
|
||||||
const msg = try std.fmt.allocPrint(self.errors.allocator, format, args);
|
assert(self.err_msg == null);
|
||||||
{
|
self.err_msg = try ErrorMsg.create(self.code.allocator, src, format, args);
|
||||||
errdefer self.errors.allocator.free(msg);
|
|
||||||
(try self.errors.addOne()).* = .{
|
|
||||||
.byte_offset = src,
|
|
||||||
.msg = msg,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return error.CodegenFail;
|
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 {
|
fn Reg(comptime arch: Target.Cpu.Arch) type {
|
||||||
return switch (arch) {
|
return switch (arch) {
|
||||||
.i386 => enum {
|
.i386 => x86.Register,
|
||||||
eax,
|
.x86_64 => x86_64.Register,
|
||||||
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,
|
|
||||||
},
|
|
||||||
else => @compileError("TODO add more register enums"),
|
else => @compileError("TODO add more register enums"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
30
src-self-hosted/codegen/x86.zig
Normal file
30
src-self-hosted/codegen/x86.zig
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
pub const Register = enum(u8) {
|
||||||
|
// 0 through 7, 32-bit registers. id is int value
|
||||||
|
eax, ecx, edx, ebx, esp, ebp, esi, edi,
|
||||||
|
|
||||||
|
// 8-15, 16-bit registers. id is int value - 8.
|
||||||
|
ax, cx, dx, bx, sp, bp, si, di,
|
||||||
|
|
||||||
|
// 16-23, 8-bit registers. id is int value - 16.
|
||||||
|
al, bl, cl, dl, ah, ch, dh, bh,
|
||||||
|
|
||||||
|
/// Returns the bit-width of the register.
|
||||||
|
pub fn size(self: @This()) u7 {
|
||||||
|
return switch (@enumToInt(self)) {
|
||||||
|
0...7 => 32,
|
||||||
|
8...15 => 16,
|
||||||
|
16...23 => 8,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the register's id. This is used in practically every opcode the
|
||||||
|
/// x86 has. It is embedded in some instructions, such as the `B8 +rd` move
|
||||||
|
/// instruction, and is used in the R/M byte.
|
||||||
|
pub fn id(self: @This()) u3 {
|
||||||
|
return @truncate(u3, @enumToInt(self));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// zig fmt: on
|
53
src-self-hosted/codegen/x86_64.zig
Normal file
53
src-self-hosted/codegen/x86_64.zig
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// zig fmt: off
|
||||||
|
pub const Register = enum(u8) {
|
||||||
|
// 0 through 15, 64-bit registers. 8-15 are extended.
|
||||||
|
// id is just the int value.
|
||||||
|
rax, rcx, rdx, rbx, rsp, rbp, rsi, rdi,
|
||||||
|
r8, r9, r10, r11, r12, r13, r14, r15,
|
||||||
|
|
||||||
|
// 16 through 31, 32-bit registers. 24-31 are extended.
|
||||||
|
// id is int value - 16.
|
||||||
|
eax, ecx, edx, ebx, esp, ebp, esi, edi,
|
||||||
|
r8d, r9d, r10d, r11d, r12d, r13d, r14d, r15d,
|
||||||
|
|
||||||
|
// 32-47, 16-bit registers. 40-47 are extended.
|
||||||
|
// id is int value - 32.
|
||||||
|
ax, cx, dx, bx, sp, bp, si, di,
|
||||||
|
r8w, r9w, r10w, r11w, r12w, r13w, r14w, r15w,
|
||||||
|
|
||||||
|
// 48-63, 8-bit registers. 56-63 are extended.
|
||||||
|
// id is int value - 48.
|
||||||
|
al, bl, cl, dl, ah, ch, dh, bh,
|
||||||
|
r8b, r9b, r10b, r11b, r12b, r13b, r14b, r15b,
|
||||||
|
|
||||||
|
/// Returns the bit-width of the register.
|
||||||
|
pub fn size(self: @This()) u7 {
|
||||||
|
return switch (@enumToInt(self)) {
|
||||||
|
0...15 => 64,
|
||||||
|
16...31 => 32,
|
||||||
|
32...47 => 16,
|
||||||
|
48...64 => 8,
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the register is *extended*. Extended registers are the
|
||||||
|
/// new registers added with amd64, r8 through r15. This also includes any
|
||||||
|
/// other variant of access to those registers, such as r8b, r15d, and so
|
||||||
|
/// on. This is needed because access to these registers requires special
|
||||||
|
/// handling via the REX prefix, via the B or R bits, depending on context.
|
||||||
|
pub fn isExtended(self: @This()) bool {
|
||||||
|
return @enumToInt(self) & 0x08 != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This returns the 4-bit register ID, which is used in practically every
|
||||||
|
/// opcode. Note that bit 3 (the highest bit) is *never* used directly in
|
||||||
|
/// an instruction (@see isExtended), and requires special handling. The
|
||||||
|
/// lower three bits are often embedded directly in instructions (such as
|
||||||
|
/// the B8 variant of moves), or used in R/M bytes.
|
||||||
|
pub fn id(self: @This()) u4 {
|
||||||
|
return @truncate(u4, @enumToInt(self));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// zig fmt: on
|
File diff suppressed because it is too large
Load Diff
@ -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 std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const util = @import("util.zig");
|
|
||||||
const Target = std.Target;
|
const Target = std.Target;
|
||||||
const fs = std.fs;
|
const fs = std.fs;
|
||||||
const Allocator = std.mem.Allocator;
|
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 Target = std.Target;
|
||||||
const CrossTarget = std.zig.CrossTarget;
|
const CrossTarget = std.zig.CrossTarget;
|
||||||
const self_hosted_main = @import("main.zig");
|
const self_hosted_main = @import("main.zig");
|
||||||
const errmsg = @import("errmsg.zig");
|
|
||||||
const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer;
|
const DepTokenizer = @import("dep_tokenizer.zig").Tokenizer;
|
||||||
const assert = std.debug.assert;
|
const assert = std.debug.assert;
|
||||||
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
|
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;
|
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 {
|
export fn stage2_fmt(argc: c_int, argv: [*]const [*:0]const u8) c_int {
|
||||||
if (std.debug.runtime_safety) {
|
if (std.debug.runtime_safety) {
|
||||||
fmtMain(argc, argv) catch unreachable;
|
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]));
|
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..];
|
const args = args_list.span()[2..];
|
||||||
|
|
||||||
var color: errmsg.Color = .Auto;
|
return self_hosted_main.cmdFmt(allocator, args);
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export fn stage2_DepTokenizer_init(input: [*]const u8, len: usize) stage2_DepTokenizer {
|
export fn stage2_DepTokenizer_init(input: [*]const u8, len: usize) stage2_DepTokenizer {
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const link = @import("link.zig");
|
const link = @import("link.zig");
|
||||||
const ir = @import("ir.zig");
|
const Module = @import("Module.zig");
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const zir = @import("zir.zig");
|
||||||
var global_ctx: TestContext = undefined;
|
const Package = @import("Package.zig");
|
||||||
|
|
||||||
test "self-hosted" {
|
test "self-hosted" {
|
||||||
try global_ctx.init();
|
var ctx: TestContext = undefined;
|
||||||
defer global_ctx.deinit();
|
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 {
|
pub const TestContext = struct {
|
||||||
@ -20,32 +21,34 @@ pub const TestContext = struct {
|
|||||||
|
|
||||||
pub const ZIRCompareOutputCase = struct {
|
pub const ZIRCompareOutputCase = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
src: [:0]const u8,
|
src_list: []const []const u8,
|
||||||
expected_stdout: []const u8,
|
expected_stdout_list: []const []const u8,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ZIRTransformCase = struct {
|
pub const ZIRTransformCase = struct {
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
src: [:0]const u8,
|
src: [:0]const u8,
|
||||||
expected_zir: []const u8,
|
expected_zir: []const u8,
|
||||||
|
cross_target: std.zig.CrossTarget,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn addZIRCompareOutput(
|
pub fn addZIRCompareOutput(
|
||||||
ctx: *TestContext,
|
ctx: *TestContext,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
src: [:0]const u8,
|
src_list: []const []const u8,
|
||||||
expected_stdout: []const u8,
|
expected_stdout_list: []const []const u8,
|
||||||
) void {
|
) void {
|
||||||
ctx.zir_cmp_output_cases.append(.{
|
ctx.zir_cmp_output_cases.append(.{
|
||||||
.name = name,
|
.name = name,
|
||||||
.src = src,
|
.src_list = src_list,
|
||||||
.expected_stdout = expected_stdout,
|
.expected_stdout_list = expected_stdout_list,
|
||||||
}) catch unreachable;
|
}) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn addZIRTransform(
|
pub fn addZIRTransform(
|
||||||
ctx: *TestContext,
|
ctx: *TestContext,
|
||||||
name: []const u8,
|
name: []const u8,
|
||||||
|
cross_target: std.zig.CrossTarget,
|
||||||
src: [:0]const u8,
|
src: [:0]const u8,
|
||||||
expected_zir: []const u8,
|
expected_zir: []const u8,
|
||||||
) void {
|
) void {
|
||||||
@ -53,6 +56,7 @@ pub const TestContext = struct {
|
|||||||
.name = name,
|
.name = name,
|
||||||
.src = src,
|
.src = src,
|
||||||
.expected_zir = expected_zir,
|
.expected_zir = expected_zir,
|
||||||
|
.cross_target = cross_target,
|
||||||
}) catch unreachable;
|
}) catch unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +88,8 @@ pub const TestContext = struct {
|
|||||||
}
|
}
|
||||||
for (self.zir_transform_cases.items) |case| {
|
for (self.zir_transform_cases.items) |case| {
|
||||||
std.testing.base_allocator_instance.reset();
|
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();
|
try std.testing.allocator_instance.validate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,59 +104,43 @@ pub const TestContext = struct {
|
|||||||
var tmp = std.testing.tmpDir(.{});
|
var tmp = std.testing.tmpDir(.{});
|
||||||
defer tmp.cleanup();
|
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();
|
prg_node.activate();
|
||||||
defer prg_node.end();
|
defer prg_node.end();
|
||||||
|
|
||||||
var zir_module = x: {
|
var module = try Module.init(allocator, .{
|
||||||
var parse_node = prg_node.start("parse", null);
|
|
||||||
parse_node.activate();
|
|
||||||
defer parse_node.end();
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
.target = target,
|
||||||
.output_mode = .Exe,
|
.output_mode = .Exe,
|
||||||
.link_mode = .Static,
|
|
||||||
.optimize_mode = .Debug,
|
.optimize_mode = .Debug,
|
||||||
|
.bin_file_dir = tmp.dir,
|
||||||
|
.bin_file_path = "a.out",
|
||||||
|
.root_pkg = root_pkg,
|
||||||
});
|
});
|
||||||
};
|
defer module.deinit();
|
||||||
defer analyzed_module.deinit(allocator);
|
|
||||||
if (analyzed_module.errors.len != 0) {
|
|
||||||
debugPrintErrors(case.src, analyzed_module.errors);
|
|
||||||
return error.ParseFailure;
|
|
||||||
}
|
|
||||||
|
|
||||||
var link_result = x: {
|
for (case.src_list) |source, i| {
|
||||||
var link_node = prg_node.start("link", null);
|
var src_node = prg_node.start("update", 2);
|
||||||
link_node.activate();
|
src_node.activate();
|
||||||
defer link_node.end();
|
defer src_node.end();
|
||||||
|
|
||||||
break :x try link.updateFilePath(allocator, analyzed_module, tmp.dir, "a.out");
|
try tmp.dir.writeFile(tmp_src_path, source);
|
||||||
};
|
|
||||||
defer link_result.deinit(allocator);
|
var update_node = src_node.start("parse,analysis,codegen", null);
|
||||||
if (link_result.errors.len != 0) {
|
update_node.activate();
|
||||||
debugPrintErrors(case.src, link_result.errors);
|
try module.makeBinFileWritable();
|
||||||
return error.LinkFailure;
|
try module.update();
|
||||||
}
|
update_node.end();
|
||||||
|
|
||||||
var exec_result = x: {
|
var exec_result = x: {
|
||||||
var exec_node = prg_node.start("execute", null);
|
var exec_node = src_node.start("execute", null);
|
||||||
exec_node.activate();
|
exec_node.activate();
|
||||||
defer exec_node.end();
|
defer exec_node.end();
|
||||||
|
|
||||||
|
try module.makeBinFileExecutable();
|
||||||
break :x try std.ChildProcess.exec(.{
|
break :x try std.ChildProcess.exec(.{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.argv = &[_][]const u8{"./a.out"},
|
.argv = &[_][]const u8{"./a.out"},
|
||||||
@ -169,7 +158,14 @@ pub const TestContext = struct {
|
|||||||
},
|
},
|
||||||
else => return error.BinaryCrashed,
|
else => return error.BinaryCrashed,
|
||||||
}
|
}
|
||||||
std.testing.expectEqualSlices(u8, case.expected_stdout, exec_result.stdout);
|
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 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn runOneZIRTransformCase(
|
fn runOneZIRTransformCase(
|
||||||
@ -179,38 +175,37 @@ pub const TestContext = struct {
|
|||||||
case: ZIRTransformCase,
|
case: ZIRTransformCase,
|
||||||
target: std.Target,
|
target: std.Target,
|
||||||
) !void {
|
) !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();
|
prg_node.activate();
|
||||||
defer prg_node.end();
|
defer prg_node.end();
|
||||||
|
|
||||||
var parse_node = prg_node.start("parse", null);
|
const tmp_src_path = "test-case.zir";
|
||||||
parse_node.activate();
|
try tmp.dir.writeFile(tmp_src_path, case.src);
|
||||||
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();
|
|
||||||
|
|
||||||
var analyze_node = prg_node.start("analyze", null);
|
const root_pkg = try Package.create(allocator, tmp.dir, ".", tmp_src_path);
|
||||||
analyze_node.activate();
|
defer root_pkg.destroy();
|
||||||
var analyzed_module = try ir.analyze(allocator, zir_module, .{
|
|
||||||
|
var module = try Module.init(allocator, .{
|
||||||
.target = target,
|
.target = target,
|
||||||
.output_mode = .Obj,
|
.output_mode = .Obj,
|
||||||
.link_mode = .Static,
|
|
||||||
.optimize_mode = .Debug,
|
.optimize_mode = .Debug,
|
||||||
|
.bin_file_dir = tmp.dir,
|
||||||
|
.bin_file_path = "test-case.o",
|
||||||
|
.root_pkg = root_pkg,
|
||||||
});
|
});
|
||||||
defer analyzed_module.deinit(allocator);
|
defer module.deinit();
|
||||||
if (analyzed_module.errors.len != 0) {
|
|
||||||
debugPrintErrors(case.src, analyzed_module.errors);
|
var module_node = prg_node.start("parse/analysis/codegen", null);
|
||||||
return error.ParseFailure;
|
module_node.activate();
|
||||||
}
|
try module.update();
|
||||||
analyze_node.end();
|
module_node.end();
|
||||||
|
|
||||||
var emit_node = prg_node.start("emit", null);
|
var emit_node = prg_node.start("emit", null);
|
||||||
emit_node.activate();
|
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);
|
defer new_zir_module.deinit(allocator);
|
||||||
emit_node.end();
|
emit_node.end();
|
||||||
|
|
||||||
|
@ -5,8 +5,7 @@ const Allocator = std.mem.Allocator;
|
|||||||
const Target = std.Target;
|
const Target = std.Target;
|
||||||
|
|
||||||
/// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication.
|
/// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication.
|
||||||
/// It's important for this struct to be small.
|
/// It's important for this type to be small.
|
||||||
/// It is not copyable since it may contain references to its inner data.
|
|
||||||
/// Types are not de-duplicated, which helps with multi-threading since it obviates the requirement
|
/// 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
|
/// of obtaining a lock on a global type table, as well as making the
|
||||||
/// garbage collection bookkeeping simpler.
|
/// garbage collection bookkeeping simpler.
|
||||||
@ -51,7 +50,9 @@ pub const Type = extern union {
|
|||||||
.comptime_int => return .ComptimeInt,
|
.comptime_int => return .ComptimeInt,
|
||||||
.comptime_float => return .ComptimeFloat,
|
.comptime_float => return .ComptimeFloat,
|
||||||
.noreturn => return .NoReturn,
|
.noreturn => return .NoReturn,
|
||||||
|
.@"null" => return .Null,
|
||||||
|
|
||||||
|
.fn_noreturn_no_args => return .Fn,
|
||||||
.fn_naked_noreturn_no_args => return .Fn,
|
.fn_naked_noreturn_no_args => return .Fn,
|
||||||
.fn_ccc_void_no_args => return .Fn,
|
.fn_ccc_void_no_args => return .Fn,
|
||||||
|
|
||||||
@ -183,7 +184,10 @@ pub const Type = extern union {
|
|||||||
.noreturn,
|
.noreturn,
|
||||||
=> return out_stream.writeAll(@tagName(t)),
|
=> return out_stream.writeAll(@tagName(t)),
|
||||||
|
|
||||||
|
.@"null" => return out_stream.writeAll("@TypeOf(null)"),
|
||||||
|
|
||||||
.const_slice_u8 => return out_stream.writeAll("[]const u8"),
|
.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_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"),
|
.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"),
|
.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_int => return Value.initTag(.comptime_int_type),
|
||||||
.comptime_float => return Value.initTag(.comptime_float_type),
|
.comptime_float => return Value.initTag(.comptime_float_type),
|
||||||
.noreturn => return Value.initTag(.noreturn_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_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),
|
.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),
|
.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 {
|
pub fn isSinglePointer(self: Type) bool {
|
||||||
return switch (self.tag()) {
|
return switch (self.tag()) {
|
||||||
.u8,
|
.u8,
|
||||||
@ -283,9 +393,11 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
.array,
|
.array,
|
||||||
.array_u8_sentinel_0,
|
.array_u8_sentinel_0,
|
||||||
.const_slice_u8,
|
.const_slice_u8,
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.int_unsigned,
|
.int_unsigned,
|
||||||
@ -325,10 +437,12 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
.array,
|
.array,
|
||||||
.array_u8_sentinel_0,
|
.array_u8_sentinel_0,
|
||||||
.single_const_pointer,
|
.single_const_pointer,
|
||||||
.single_const_pointer_to_comptime_int,
|
.single_const_pointer_to_comptime_int,
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.int_unsigned,
|
.int_unsigned,
|
||||||
@ -367,8 +481,10 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
.array,
|
.array,
|
||||||
.array_u8_sentinel_0,
|
.array_u8_sentinel_0,
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.int_unsigned,
|
.int_unsigned,
|
||||||
@ -410,6 +526,8 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.int_unsigned,
|
.int_unsigned,
|
||||||
@ -451,6 +569,8 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.single_const_pointer,
|
.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.
|
/// Returns true if and only if the type is a fixed-width, signed integer.
|
||||||
pub fn isSignedInt(self: Type) bool {
|
pub fn isSignedInt(self: Type) bool {
|
||||||
return switch (self.tag()) {
|
return switch (self.tag()) {
|
||||||
@ -481,6 +645,8 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.array,
|
.array,
|
||||||
@ -524,6 +690,8 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.array,
|
.array,
|
||||||
@ -579,6 +747,7 @@ pub const Type = extern union {
|
|||||||
/// Asserts the type is a function.
|
/// Asserts the type is a function.
|
||||||
pub fn fnParamLen(self: Type) usize {
|
pub fn fnParamLen(self: Type) usize {
|
||||||
return switch (self.tag()) {
|
return switch (self.tag()) {
|
||||||
|
.fn_noreturn_no_args => 0,
|
||||||
.fn_naked_noreturn_no_args => 0,
|
.fn_naked_noreturn_no_args => 0,
|
||||||
.fn_ccc_void_no_args => 0,
|
.fn_ccc_void_no_args => 0,
|
||||||
|
|
||||||
@ -595,6 +764,7 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
.array,
|
.array,
|
||||||
.single_const_pointer,
|
.single_const_pointer,
|
||||||
.single_const_pointer_to_comptime_int,
|
.single_const_pointer_to_comptime_int,
|
||||||
@ -622,6 +792,7 @@ pub const Type = extern union {
|
|||||||
/// given by `fnParamLen`.
|
/// given by `fnParamLen`.
|
||||||
pub fn fnParamTypes(self: Type, types: []Type) void {
|
pub fn fnParamTypes(self: Type, types: []Type) void {
|
||||||
switch (self.tag()) {
|
switch (self.tag()) {
|
||||||
|
.fn_noreturn_no_args => return,
|
||||||
.fn_naked_noreturn_no_args => return,
|
.fn_naked_noreturn_no_args => return,
|
||||||
.fn_ccc_void_no_args => return,
|
.fn_ccc_void_no_args => return,
|
||||||
|
|
||||||
@ -638,6 +809,7 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
.array,
|
.array,
|
||||||
.single_const_pointer,
|
.single_const_pointer,
|
||||||
.single_const_pointer_to_comptime_int,
|
.single_const_pointer_to_comptime_int,
|
||||||
@ -664,6 +836,7 @@ pub const Type = extern union {
|
|||||||
/// Asserts the type is a function.
|
/// Asserts the type is a function.
|
||||||
pub fn fnReturnType(self: Type) Type {
|
pub fn fnReturnType(self: Type) Type {
|
||||||
return switch (self.tag()) {
|
return switch (self.tag()) {
|
||||||
|
.fn_noreturn_no_args => Type.initTag(.noreturn),
|
||||||
.fn_naked_noreturn_no_args => Type.initTag(.noreturn),
|
.fn_naked_noreturn_no_args => Type.initTag(.noreturn),
|
||||||
.fn_ccc_void_no_args => Type.initTag(.void),
|
.fn_ccc_void_no_args => Type.initTag(.void),
|
||||||
|
|
||||||
@ -680,6 +853,7 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
.array,
|
.array,
|
||||||
.single_const_pointer,
|
.single_const_pointer,
|
||||||
.single_const_pointer_to_comptime_int,
|
.single_const_pointer_to_comptime_int,
|
||||||
@ -706,6 +880,7 @@ pub const Type = extern union {
|
|||||||
/// Asserts the type is a function.
|
/// Asserts the type is a function.
|
||||||
pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention {
|
pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention {
|
||||||
return switch (self.tag()) {
|
return switch (self.tag()) {
|
||||||
|
.fn_noreturn_no_args => .Unspecified,
|
||||||
.fn_naked_noreturn_no_args => .Naked,
|
.fn_naked_noreturn_no_args => .Naked,
|
||||||
.fn_ccc_void_no_args => .C,
|
.fn_ccc_void_no_args => .C,
|
||||||
|
|
||||||
@ -722,6 +897,51 @@ pub const Type = extern union {
|
|||||||
.comptime_int,
|
.comptime_int,
|
||||||
.comptime_float,
|
.comptime_float,
|
||||||
.noreturn,
|
.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,
|
.array,
|
||||||
.single_const_pointer,
|
.single_const_pointer,
|
||||||
.single_const_pointer_to_comptime_int,
|
.single_const_pointer_to_comptime_int,
|
||||||
@ -776,6 +996,8 @@ pub const Type = extern union {
|
|||||||
.type,
|
.type,
|
||||||
.anyerror,
|
.anyerror,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.array,
|
.array,
|
||||||
@ -812,6 +1034,7 @@ pub const Type = extern union {
|
|||||||
.bool,
|
.bool,
|
||||||
.type,
|
.type,
|
||||||
.anyerror,
|
.anyerror,
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.single_const_pointer_to_comptime_int,
|
.single_const_pointer_to_comptime_int,
|
||||||
@ -822,6 +1045,7 @@ pub const Type = extern union {
|
|||||||
.c_void,
|
.c_void,
|
||||||
.void,
|
.void,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
=> return true,
|
=> return true,
|
||||||
|
|
||||||
.int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
|
.int_unsigned => return ty.cast(Payload.IntUnsigned).?.bits == 0,
|
||||||
@ -865,6 +1089,7 @@ pub const Type = extern union {
|
|||||||
.bool,
|
.bool,
|
||||||
.type,
|
.type,
|
||||||
.anyerror,
|
.anyerror,
|
||||||
|
.fn_noreturn_no_args,
|
||||||
.fn_naked_noreturn_no_args,
|
.fn_naked_noreturn_no_args,
|
||||||
.fn_ccc_void_no_args,
|
.fn_ccc_void_no_args,
|
||||||
.single_const_pointer_to_comptime_int,
|
.single_const_pointer_to_comptime_int,
|
||||||
@ -873,6 +1098,7 @@ pub const Type = extern union {
|
|||||||
.c_void,
|
.c_void,
|
||||||
.void,
|
.void,
|
||||||
.noreturn,
|
.noreturn,
|
||||||
|
.@"null",
|
||||||
.int_unsigned,
|
.int_unsigned,
|
||||||
.int_signed,
|
.int_signed,
|
||||||
.array,
|
.array,
|
||||||
@ -902,11 +1128,11 @@ pub const Type = extern union {
|
|||||||
c_longlong,
|
c_longlong,
|
||||||
c_ulonglong,
|
c_ulonglong,
|
||||||
c_longdouble,
|
c_longdouble,
|
||||||
c_void,
|
|
||||||
f16,
|
f16,
|
||||||
f32,
|
f32,
|
||||||
f64,
|
f64,
|
||||||
f128,
|
f128,
|
||||||
|
c_void,
|
||||||
bool,
|
bool,
|
||||||
void,
|
void,
|
||||||
type,
|
type,
|
||||||
@ -914,6 +1140,8 @@ pub const Type = extern union {
|
|||||||
comptime_int,
|
comptime_int,
|
||||||
comptime_float,
|
comptime_float,
|
||||||
noreturn,
|
noreturn,
|
||||||
|
@"null",
|
||||||
|
fn_noreturn_no_args,
|
||||||
fn_naked_noreturn_no_args,
|
fn_naked_noreturn_no_args,
|
||||||
fn_ccc_void_no_args,
|
fn_ccc_void_no_args,
|
||||||
single_const_pointer_to_comptime_int,
|
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 BigIntMutable = std.math.big.int.Mutable;
|
||||||
const Target = std.Target;
|
const Target = std.Target;
|
||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
const Module = @import("Module.zig");
|
||||||
|
|
||||||
/// This is the raw data, with no bookkeeping, no memory awareness,
|
/// This is the raw data, with no bookkeeping, no memory awareness,
|
||||||
/// no de-duplication, and no type system 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
|
/// 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.
|
/// is unmapped, giving us 4096 possible enum tags that have no payload.
|
||||||
pub const Value = extern union {
|
pub const Value = extern union {
|
||||||
@ -45,6 +46,8 @@ pub const Value = extern union {
|
|||||||
comptime_int_type,
|
comptime_int_type,
|
||||||
comptime_float_type,
|
comptime_float_type,
|
||||||
noreturn_type,
|
noreturn_type,
|
||||||
|
null_type,
|
||||||
|
fn_noreturn_no_args_type,
|
||||||
fn_naked_noreturn_no_args_type,
|
fn_naked_noreturn_no_args_type,
|
||||||
fn_ccc_void_no_args_type,
|
fn_ccc_void_no_args_type,
|
||||||
single_const_pointer_to_comptime_int_type,
|
single_const_pointer_to_comptime_int_type,
|
||||||
@ -64,8 +67,9 @@ pub const Value = extern union {
|
|||||||
int_big_positive,
|
int_big_positive,
|
||||||
int_big_negative,
|
int_big_negative,
|
||||||
function,
|
function,
|
||||||
ref,
|
|
||||||
ref_val,
|
ref_val,
|
||||||
|
decl_ref,
|
||||||
|
elem_ptr,
|
||||||
bytes,
|
bytes,
|
||||||
repeated, // the value is a value repeated some number of times
|
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_int_type => return out_stream.writeAll("comptime_int"),
|
||||||
.comptime_float_type => return out_stream.writeAll("comptime_float"),
|
.comptime_float_type => return out_stream.writeAll("comptime_float"),
|
||||||
.noreturn_type => return out_stream.writeAll("noreturn"),
|
.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_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"),
|
.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"),
|
.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_positive => return out_stream.print("{}", .{val.cast(Payload.IntBigPositive).?.asBigInt()}),
|
||||||
.int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
|
.int_big_negative => return out_stream.print("{}", .{val.cast(Payload.IntBigNegative).?.asBigInt()}),
|
||||||
.function => return out_stream.writeAll("(function)"),
|
.function => return out_stream.writeAll("(function)"),
|
||||||
.ref => return out_stream.writeAll("(ref)"),
|
|
||||||
.ref_val => {
|
.ref_val => {
|
||||||
try out_stream.writeAll("*const ");
|
const ref_val = val.cast(Payload.RefVal).?;
|
||||||
val = val.cast(Payload.RefVal).?.val;
|
try out_stream.writeAll("&const ");
|
||||||
continue;
|
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),
|
.bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
|
||||||
.repeated => {
|
.repeated => {
|
||||||
@ -169,10 +180,17 @@ pub const Value = extern union {
|
|||||||
|
|
||||||
/// Asserts that the value is representable as an array of bytes.
|
/// 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.
|
/// 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| {
|
if (self.cast(Payload.Bytes)) |bytes| {
|
||||||
return std.mem.dupe(allocator, u8, bytes.data);
|
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;
|
unreachable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,31 +199,33 @@ pub const Value = extern union {
|
|||||||
return switch (self.tag()) {
|
return switch (self.tag()) {
|
||||||
.ty => self.cast(Payload.Ty).?.ty,
|
.ty => self.cast(Payload.Ty).?.ty,
|
||||||
|
|
||||||
.u8_type => Type.initTag(.@"u8"),
|
.u8_type => Type.initTag(.u8),
|
||||||
.i8_type => Type.initTag(.@"i8"),
|
.i8_type => Type.initTag(.i8),
|
||||||
.isize_type => Type.initTag(.@"isize"),
|
.isize_type => Type.initTag(.isize),
|
||||||
.usize_type => Type.initTag(.@"usize"),
|
.usize_type => Type.initTag(.usize),
|
||||||
.c_short_type => Type.initTag(.@"c_short"),
|
.c_short_type => Type.initTag(.c_short),
|
||||||
.c_ushort_type => Type.initTag(.@"c_ushort"),
|
.c_ushort_type => Type.initTag(.c_ushort),
|
||||||
.c_int_type => Type.initTag(.@"c_int"),
|
.c_int_type => Type.initTag(.c_int),
|
||||||
.c_uint_type => Type.initTag(.@"c_uint"),
|
.c_uint_type => Type.initTag(.c_uint),
|
||||||
.c_long_type => Type.initTag(.@"c_long"),
|
.c_long_type => Type.initTag(.c_long),
|
||||||
.c_ulong_type => Type.initTag(.@"c_ulong"),
|
.c_ulong_type => Type.initTag(.c_ulong),
|
||||||
.c_longlong_type => Type.initTag(.@"c_longlong"),
|
.c_longlong_type => Type.initTag(.c_longlong),
|
||||||
.c_ulonglong_type => Type.initTag(.@"c_ulonglong"),
|
.c_ulonglong_type => Type.initTag(.c_ulonglong),
|
||||||
.c_longdouble_type => Type.initTag(.@"c_longdouble"),
|
.c_longdouble_type => Type.initTag(.c_longdouble),
|
||||||
.f16_type => Type.initTag(.@"f16"),
|
.f16_type => Type.initTag(.f16),
|
||||||
.f32_type => Type.initTag(.@"f32"),
|
.f32_type => Type.initTag(.f32),
|
||||||
.f64_type => Type.initTag(.@"f64"),
|
.f64_type => Type.initTag(.f64),
|
||||||
.f128_type => Type.initTag(.@"f128"),
|
.f128_type => Type.initTag(.f128),
|
||||||
.c_void_type => Type.initTag(.@"c_void"),
|
.c_void_type => Type.initTag(.c_void),
|
||||||
.bool_type => Type.initTag(.@"bool"),
|
.bool_type => Type.initTag(.bool),
|
||||||
.void_type => Type.initTag(.@"void"),
|
.void_type => Type.initTag(.void),
|
||||||
.type_type => Type.initTag(.@"type"),
|
.type_type => Type.initTag(.type),
|
||||||
.anyerror_type => Type.initTag(.@"anyerror"),
|
.anyerror_type => Type.initTag(.anyerror),
|
||||||
.comptime_int_type => Type.initTag(.@"comptime_int"),
|
.comptime_int_type => Type.initTag(.comptime_int),
|
||||||
.comptime_float_type => Type.initTag(.@"comptime_float"),
|
.comptime_float_type => Type.initTag(.comptime_float),
|
||||||
.noreturn_type => Type.initTag(.@"noreturn"),
|
.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_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),
|
.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),
|
.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_positive,
|
||||||
.int_big_negative,
|
.int_big_negative,
|
||||||
.function,
|
.function,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.repeated,
|
.repeated,
|
||||||
=> unreachable,
|
=> unreachable,
|
||||||
@ -259,6 +280,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -267,8 +290,9 @@ pub const Value = extern union {
|
|||||||
.bool_false,
|
.bool_false,
|
||||||
.null_value,
|
.null_value,
|
||||||
.function,
|
.function,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.undef,
|
.undef,
|
||||||
.repeated,
|
.repeated,
|
||||||
@ -314,6 +338,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -322,8 +348,9 @@ pub const Value = extern union {
|
|||||||
.bool_false,
|
.bool_false,
|
||||||
.null_value,
|
.null_value,
|
||||||
.function,
|
.function,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.undef,
|
.undef,
|
||||||
.repeated,
|
.repeated,
|
||||||
@ -370,6 +397,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -378,8 +407,9 @@ pub const Value = extern union {
|
|||||||
.bool_false,
|
.bool_false,
|
||||||
.null_value,
|
.null_value,
|
||||||
.function,
|
.function,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.undef,
|
.undef,
|
||||||
.repeated,
|
.repeated,
|
||||||
@ -431,6 +461,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -439,8 +471,9 @@ pub const Value = extern union {
|
|||||||
.bool_false,
|
.bool_false,
|
||||||
.null_value,
|
.null_value,
|
||||||
.function,
|
.function,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.repeated,
|
.repeated,
|
||||||
=> unreachable,
|
=> unreachable,
|
||||||
@ -521,6 +554,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -529,8 +564,9 @@ pub const Value = extern union {
|
|||||||
.bool_false,
|
.bool_false,
|
||||||
.null_value,
|
.null_value,
|
||||||
.function,
|
.function,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.repeated,
|
.repeated,
|
||||||
.undef,
|
.undef,
|
||||||
@ -573,6 +609,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -581,8 +619,9 @@ pub const Value = extern union {
|
|||||||
.bool_false,
|
.bool_false,
|
||||||
.null_value,
|
.null_value,
|
||||||
.function,
|
.function,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.repeated,
|
.repeated,
|
||||||
.undef,
|
.undef,
|
||||||
@ -636,7 +675,8 @@ pub const Value = extern union {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Asserts the value is a pointer and dereferences it.
|
/// Asserts the value is a pointer and dereferences it.
|
||||||
pub fn pointerDeref(self: Value) Value {
|
/// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
|
||||||
|
pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value {
|
||||||
return switch (self.tag()) {
|
return switch (self.tag()) {
|
||||||
.ty,
|
.ty,
|
||||||
.u8_type,
|
.u8_type,
|
||||||
@ -664,6 +704,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -683,14 +725,19 @@ pub const Value = extern union {
|
|||||||
=> unreachable,
|
=> unreachable,
|
||||||
|
|
||||||
.the_one_possible_value => Value.initTag(.the_one_possible_value),
|
.the_one_possible_value => Value.initTag(.the_one_possible_value),
|
||||||
.ref => self.cast(Payload.Ref).?.cell.contents,
|
|
||||||
.ref_val => self.cast(Payload.RefVal).?.val,
|
.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,
|
/// Asserts the value is a single-item pointer to an array, or an array,
|
||||||
/// or an unknown-length pointer, and returns the element value at the index.
|
/// or an unknown-length pointer, and returns the element value at the index.
|
||||||
pub fn 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()) {
|
switch (self.tag()) {
|
||||||
.ty,
|
.ty,
|
||||||
.u8_type,
|
.u8_type,
|
||||||
@ -718,6 +765,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -733,13 +782,13 @@ pub const Value = extern union {
|
|||||||
.int_big_positive,
|
.int_big_positive,
|
||||||
.int_big_negative,
|
.int_big_negative,
|
||||||
.undef,
|
.undef,
|
||||||
|
.elem_ptr,
|
||||||
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
=> unreachable,
|
=> unreachable,
|
||||||
|
|
||||||
.ref => @panic("TODO figure out how MemoryCell works"),
|
|
||||||
.ref_val => @panic("TODO figure out how MemoryCell works"),
|
|
||||||
|
|
||||||
.bytes => {
|
.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] };
|
int_payload.* = .{ .int = self.cast(Payload.Bytes).?.data[index] };
|
||||||
return Value.initPayload(&int_payload.base);
|
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 {
|
pub fn isUndef(self: Value) bool {
|
||||||
return self.tag() == .undef;
|
return self.tag() == .undef;
|
||||||
}
|
}
|
||||||
@ -783,6 +843,8 @@ pub const Value = extern union {
|
|||||||
.comptime_int_type,
|
.comptime_int_type,
|
||||||
.comptime_float_type,
|
.comptime_float_type,
|
||||||
.noreturn_type,
|
.noreturn_type,
|
||||||
|
.null_type,
|
||||||
|
.fn_noreturn_no_args_type,
|
||||||
.fn_naked_noreturn_no_args_type,
|
.fn_naked_noreturn_no_args_type,
|
||||||
.fn_ccc_void_no_args_type,
|
.fn_ccc_void_no_args_type,
|
||||||
.single_const_pointer_to_comptime_int_type,
|
.single_const_pointer_to_comptime_int_type,
|
||||||
@ -796,8 +858,9 @@ pub const Value = extern union {
|
|||||||
.int_i64,
|
.int_i64,
|
||||||
.int_big_positive,
|
.int_big_positive,
|
||||||
.int_big_negative,
|
.int_big_negative,
|
||||||
.ref,
|
|
||||||
.ref_val,
|
.ref_val,
|
||||||
|
.decl_ref,
|
||||||
|
.elem_ptr,
|
||||||
.bytes,
|
.bytes,
|
||||||
.repeated,
|
.repeated,
|
||||||
=> false,
|
=> false,
|
||||||
@ -841,8 +904,7 @@ pub const Value = extern union {
|
|||||||
|
|
||||||
pub const Function = struct {
|
pub const Function = struct {
|
||||||
base: Payload = Payload{ .tag = .function },
|
base: Payload = Payload{ .tag = .function },
|
||||||
/// Index into the `fns` array of the `ir.Module`
|
func: *Module.Fn,
|
||||||
index: usize,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const ArraySentinel0_u8_Type = struct {
|
pub const ArraySentinel0_u8_Type = struct {
|
||||||
@ -855,16 +917,24 @@ pub const Value = extern union {
|
|||||||
elem_type: *Type,
|
elem_type: *Type,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Ref = struct {
|
/// Represents a pointer to another immutable value.
|
||||||
base: Payload = Payload{ .tag = .ref },
|
|
||||||
cell: *MemoryCell,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RefVal = struct {
|
pub const RefVal = struct {
|
||||||
base: Payload = Payload{ .tag = .ref_val },
|
base: Payload = Payload{ .tag = .ref_val },
|
||||||
val: Value,
|
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 {
|
pub const Bytes = struct {
|
||||||
base: Payload = Payload{ .tag = .bytes },
|
base: Payload = Payload{ .tag = .bytes },
|
||||||
data: []const u8,
|
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,
|
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 assert = std.debug.assert;
|
||||||
const BigIntConst = std.math.big.int.Const;
|
const BigIntConst = std.math.big.int.Const;
|
||||||
const BigIntMutable = std.math.big.int.Mutable;
|
const BigIntMutable = std.math.big.int.Mutable;
|
||||||
const Type = @import("../type.zig").Type;
|
const Type = @import("type.zig").Type;
|
||||||
const Value = @import("../value.zig").Value;
|
const Value = @import("value.zig").Value;
|
||||||
const ir = @import("../ir.zig");
|
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
|
/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
|
||||||
/// in-memory, analyzed instructions with types and values.
|
/// in-memory, analyzed instructions with types and values.
|
||||||
@ -16,10 +18,18 @@ pub const Inst = struct {
|
|||||||
tag: Tag,
|
tag: Tag,
|
||||||
/// Byte offset into the source.
|
/// Byte offset into the source.
|
||||||
src: usize,
|
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.
|
/// These names are used directly as the instruction names in the text format.
|
||||||
pub const Tag = enum {
|
pub const Tag = enum {
|
||||||
breakpoint,
|
breakpoint,
|
||||||
|
call,
|
||||||
|
/// Represents a reference to a global decl by name.
|
||||||
|
/// The syntax `@foo` is equivalent to `declref("foo")`.
|
||||||
|
declref,
|
||||||
str,
|
str,
|
||||||
int,
|
int,
|
||||||
ptrtoint,
|
ptrtoint,
|
||||||
@ -32,6 +42,7 @@ pub const Inst = struct {
|
|||||||
@"fn",
|
@"fn",
|
||||||
@"export",
|
@"export",
|
||||||
primitive,
|
primitive,
|
||||||
|
ref,
|
||||||
fntype,
|
fntype,
|
||||||
intcast,
|
intcast,
|
||||||
bitcast,
|
bitcast,
|
||||||
@ -46,6 +57,8 @@ pub const Inst = struct {
|
|||||||
pub fn TagToType(tag: Tag) type {
|
pub fn TagToType(tag: Tag) type {
|
||||||
return switch (tag) {
|
return switch (tag) {
|
||||||
.breakpoint => Breakpoint,
|
.breakpoint => Breakpoint,
|
||||||
|
.call => Call,
|
||||||
|
.declref => DeclRef,
|
||||||
.str => Str,
|
.str => Str,
|
||||||
.int => Int,
|
.int => Int,
|
||||||
.ptrtoint => PtrToInt,
|
.ptrtoint => PtrToInt,
|
||||||
@ -58,6 +71,7 @@ pub const Inst = struct {
|
|||||||
.@"fn" => Fn,
|
.@"fn" => Fn,
|
||||||
.@"export" => Export,
|
.@"export" => Export,
|
||||||
.primitive => Primitive,
|
.primitive => Primitive,
|
||||||
|
.ref => Ref,
|
||||||
.fntype => FnType,
|
.fntype => FnType,
|
||||||
.intcast => IntCast,
|
.intcast => IntCast,
|
||||||
.bitcast => BitCast,
|
.bitcast => BitCast,
|
||||||
@ -85,6 +99,29 @@ pub const Inst = struct {
|
|||||||
kw_args: 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 Str = struct {
|
||||||
pub const base_tag = Tag.str;
|
pub const base_tag = Tag.str;
|
||||||
base: Inst,
|
base: Inst,
|
||||||
@ -202,6 +239,16 @@ pub const Inst = struct {
|
|||||||
kw_args: 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 Primitive = struct {
|
||||||
pub const base_tag = Tag.primitive;
|
pub const base_tag = Tag.primitive;
|
||||||
base: Inst,
|
base: Inst,
|
||||||
@ -212,55 +259,55 @@ pub const Inst = struct {
|
|||||||
kw_args: struct {},
|
kw_args: struct {},
|
||||||
|
|
||||||
pub const BuiltinType = enum {
|
pub const BuiltinType = enum {
|
||||||
@"isize",
|
isize,
|
||||||
@"usize",
|
usize,
|
||||||
@"c_short",
|
c_short,
|
||||||
@"c_ushort",
|
c_ushort,
|
||||||
@"c_int",
|
c_int,
|
||||||
@"c_uint",
|
c_uint,
|
||||||
@"c_long",
|
c_long,
|
||||||
@"c_ulong",
|
c_ulong,
|
||||||
@"c_longlong",
|
c_longlong,
|
||||||
@"c_ulonglong",
|
c_ulonglong,
|
||||||
@"c_longdouble",
|
c_longdouble,
|
||||||
@"c_void",
|
c_void,
|
||||||
@"f16",
|
f16,
|
||||||
@"f32",
|
f32,
|
||||||
@"f64",
|
f64,
|
||||||
@"f128",
|
f128,
|
||||||
@"bool",
|
bool,
|
||||||
@"void",
|
void,
|
||||||
@"noreturn",
|
noreturn,
|
||||||
@"type",
|
type,
|
||||||
@"anyerror",
|
anyerror,
|
||||||
@"comptime_int",
|
comptime_int,
|
||||||
@"comptime_float",
|
comptime_float,
|
||||||
|
|
||||||
pub fn toType(self: BuiltinType) Type {
|
pub fn toType(self: BuiltinType) Type {
|
||||||
return switch (self) {
|
return switch (self) {
|
||||||
.@"isize" => Type.initTag(.@"isize"),
|
.isize => Type.initTag(.isize),
|
||||||
.@"usize" => Type.initTag(.@"usize"),
|
.usize => Type.initTag(.usize),
|
||||||
.@"c_short" => Type.initTag(.@"c_short"),
|
.c_short => Type.initTag(.c_short),
|
||||||
.@"c_ushort" => Type.initTag(.@"c_ushort"),
|
.c_ushort => Type.initTag(.c_ushort),
|
||||||
.@"c_int" => Type.initTag(.@"c_int"),
|
.c_int => Type.initTag(.c_int),
|
||||||
.@"c_uint" => Type.initTag(.@"c_uint"),
|
.c_uint => Type.initTag(.c_uint),
|
||||||
.@"c_long" => Type.initTag(.@"c_long"),
|
.c_long => Type.initTag(.c_long),
|
||||||
.@"c_ulong" => Type.initTag(.@"c_ulong"),
|
.c_ulong => Type.initTag(.c_ulong),
|
||||||
.@"c_longlong" => Type.initTag(.@"c_longlong"),
|
.c_longlong => Type.initTag(.c_longlong),
|
||||||
.@"c_ulonglong" => Type.initTag(.@"c_ulonglong"),
|
.c_ulonglong => Type.initTag(.c_ulonglong),
|
||||||
.@"c_longdouble" => Type.initTag(.@"c_longdouble"),
|
.c_longdouble => Type.initTag(.c_longdouble),
|
||||||
.@"c_void" => Type.initTag(.@"c_void"),
|
.c_void => Type.initTag(.c_void),
|
||||||
.@"f16" => Type.initTag(.@"f16"),
|
.f16 => Type.initTag(.f16),
|
||||||
.@"f32" => Type.initTag(.@"f32"),
|
.f32 => Type.initTag(.f32),
|
||||||
.@"f64" => Type.initTag(.@"f64"),
|
.f64 => Type.initTag(.f64),
|
||||||
.@"f128" => Type.initTag(.@"f128"),
|
.f128 => Type.initTag(.f128),
|
||||||
.@"bool" => Type.initTag(.@"bool"),
|
.bool => Type.initTag(.bool),
|
||||||
.@"void" => Type.initTag(.@"void"),
|
.void => Type.initTag(.void),
|
||||||
.@"noreturn" => Type.initTag(.@"noreturn"),
|
.noreturn => Type.initTag(.noreturn),
|
||||||
.@"type" => Type.initTag(.@"type"),
|
.type => Type.initTag(.type),
|
||||||
.@"anyerror" => Type.initTag(.@"anyerror"),
|
.anyerror => Type.initTag(.anyerror),
|
||||||
.@"comptime_int" => Type.initTag(.@"comptime_int"),
|
.comptime_int => Type.initTag(.comptime_int),
|
||||||
.@"comptime_float" => Type.initTag(.@"comptime_float"),
|
.comptime_float => Type.initTag(.comptime_float),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -375,8 +422,8 @@ pub const ErrorMsg = struct {
|
|||||||
|
|
||||||
pub const Module = struct {
|
pub const Module = struct {
|
||||||
decls: []*Inst,
|
decls: []*Inst,
|
||||||
errors: []ErrorMsg,
|
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
|
error_msg: ?ErrorMsg = null,
|
||||||
|
|
||||||
pub const Body = struct {
|
pub const Body = struct {
|
||||||
instructions: []*Inst,
|
instructions: []*Inst,
|
||||||
@ -384,7 +431,6 @@ pub const Module = struct {
|
|||||||
|
|
||||||
pub fn deinit(self: *Module, allocator: *Allocator) void {
|
pub fn deinit(self: *Module, allocator: *Allocator) void {
|
||||||
allocator.free(self.decls);
|
allocator.free(self.decls);
|
||||||
allocator.free(self.errors);
|
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
self.* = undefined;
|
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
|
// TODO I tried implementing this with an inline for loop and hit a compiler bug
|
||||||
switch (decl.tag) {
|
switch (decl.tag) {
|
||||||
.breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table),
|
.breakpoint => return self.writeInstToStreamGeneric(stream, .breakpoint, decl, inst_table),
|
||||||
|
.call => return self.writeInstToStreamGeneric(stream, .call, decl, inst_table),
|
||||||
|
.declref => return self.writeInstToStreamGeneric(stream, .declref, decl, inst_table),
|
||||||
.str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table),
|
.str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table),
|
||||||
.int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table),
|
.int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table),
|
||||||
.ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table),
|
.ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table),
|
||||||
@ -442,6 +490,7 @@ pub const Module = struct {
|
|||||||
.@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table),
|
.@"return" => return self.writeInstToStreamGeneric(stream, .@"return", decl, inst_table),
|
||||||
.@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table),
|
.@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table),
|
||||||
.@"export" => return self.writeInstToStreamGeneric(stream, .@"export", 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),
|
.primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table),
|
||||||
.fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table),
|
.fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table),
|
||||||
.intcast => return self.writeInstToStreamGeneric(stream, .intcast, 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),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.i = 0,
|
.i = 0,
|
||||||
.source = source,
|
.source = source,
|
||||||
.decls = std.ArrayList(*Inst).init(allocator),
|
|
||||||
.errors = std.ArrayList(ErrorMsg).init(allocator),
|
|
||||||
.global_name_map = &global_name_map,
|
.global_name_map = &global_name_map,
|
||||||
|
.decls = .{},
|
||||||
|
.unnamed_index = 0,
|
||||||
};
|
};
|
||||||
errdefer parser.arena.deinit();
|
errdefer parser.arena.deinit();
|
||||||
|
|
||||||
parser.parseRoot() catch |err| switch (err) {
|
parser.parseRoot() catch |err| switch (err) {
|
||||||
error.ParseFailure => {
|
error.ParseFailure => {
|
||||||
assert(parser.errors.items.len != 0);
|
assert(parser.error_msg != null);
|
||||||
},
|
},
|
||||||
else => |e| return e,
|
else => |e| return e,
|
||||||
};
|
};
|
||||||
|
|
||||||
return Module{
|
return Module{
|
||||||
.decls = parser.decls.toOwnedSlice(),
|
.decls = parser.decls.toOwnedSlice(allocator),
|
||||||
.errors = parser.errors.toOwnedSlice(),
|
|
||||||
.arena = parser.arena,
|
.arena = parser.arena,
|
||||||
|
.error_msg = parser.error_msg,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,9 +617,10 @@ const Parser = struct {
|
|||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
i: usize,
|
i: usize,
|
||||||
source: [:0]const u8,
|
source: [:0]const u8,
|
||||||
errors: std.ArrayList(ErrorMsg),
|
decls: std.ArrayListUnmanaged(*Inst),
|
||||||
decls: std.ArrayList(*Inst),
|
|
||||||
global_name_map: *std.StringHashMap(usize),
|
global_name_map: *std.StringHashMap(usize),
|
||||||
|
error_msg: ?ErrorMsg = null,
|
||||||
|
unnamed_index: usize,
|
||||||
|
|
||||||
const Body = struct {
|
const Body = struct {
|
||||||
instructions: std.ArrayList(*Inst),
|
instructions: std.ArrayList(*Inst),
|
||||||
@ -595,7 +646,7 @@ const Parser = struct {
|
|||||||
skipSpace(self);
|
skipSpace(self);
|
||||||
try requireEatBytes(self, "=");
|
try requireEatBytes(self, "=");
|
||||||
skipSpace(self);
|
skipSpace(self);
|
||||||
const inst = try parseInstruction(self, &body_context);
|
const inst = try parseInstruction(self, &body_context, ident);
|
||||||
const ident_index = body_context.instructions.items.len;
|
const ident_index = body_context.instructions.items.len;
|
||||||
if (try body_context.name_map.put(ident, ident_index)) |_| {
|
if (try body_context.name_map.put(ident, ident_index)) |_| {
|
||||||
return self.fail("redefinition of identifier '{}'", .{ident});
|
return self.fail("redefinition of identifier '{}'", .{ident});
|
||||||
@ -681,12 +732,12 @@ const Parser = struct {
|
|||||||
skipSpace(self);
|
skipSpace(self);
|
||||||
try requireEatBytes(self, "=");
|
try requireEatBytes(self, "=");
|
||||||
skipSpace(self);
|
skipSpace(self);
|
||||||
const inst = try parseInstruction(self, null);
|
const inst = try parseInstruction(self, null, ident);
|
||||||
const ident_index = self.decls.items.len;
|
const ident_index = self.decls.items.len;
|
||||||
if (try self.global_name_map.put(ident, ident_index)) |_| {
|
if (try self.global_name_map.put(ident, ident_index)) |_| {
|
||||||
return self.fail("redefinition of identifier '{}'", .{ident});
|
return self.fail("redefinition of identifier '{}'", .{ident});
|
||||||
}
|
}
|
||||||
try self.decls.append(inst);
|
try self.decls.append(self.allocator, inst);
|
||||||
},
|
},
|
||||||
' ', '\n' => self.i += 1,
|
' ', '\n' => self.i += 1,
|
||||||
0 => break,
|
0 => break,
|
||||||
@ -743,20 +794,20 @@ const Parser = struct {
|
|||||||
|
|
||||||
fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError {
|
fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError {
|
||||||
@setCold(true);
|
@setCold(true);
|
||||||
const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args);
|
self.error_msg = ErrorMsg{
|
||||||
(try self.errors.addOne()).* = .{
|
|
||||||
.byte_offset = self.i,
|
.byte_offset = self.i,
|
||||||
.msg = msg,
|
.msg = try std.fmt.allocPrint(&self.arena.allocator, format, args),
|
||||||
};
|
};
|
||||||
return error.ParseFailure;
|
return error.ParseFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parseInstruction(self: *Parser, body_ctx: ?*Body) InnerError!*Inst {
|
fn parseInstruction(self: *Parser, body_ctx: ?*Body, name: []const u8) InnerError!*Inst {
|
||||||
|
const contents_start = self.i;
|
||||||
const fn_name = try skipToAndOver(self, '(');
|
const fn_name = try skipToAndOver(self, '(');
|
||||||
inline for (@typeInfo(Inst.Tag).Enum.fields) |field| {
|
inline for (@typeInfo(Inst.Tag).Enum.fields) |field| {
|
||||||
if (mem.eql(u8, field.name, fn_name)) {
|
if (mem.eql(u8, field.name, fn_name)) {
|
||||||
const tag = @field(Inst.Tag, field.name);
|
const tag = @field(Inst.Tag, field.name);
|
||||||
return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx);
|
return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx, name, contents_start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self.fail("unknown instruction '{}'", .{fn_name});
|
return self.fail("unknown instruction '{}'", .{fn_name});
|
||||||
@ -767,9 +818,12 @@ const Parser = struct {
|
|||||||
comptime fn_name: []const u8,
|
comptime fn_name: []const u8,
|
||||||
comptime InstType: type,
|
comptime InstType: type,
|
||||||
body_ctx: ?*Body,
|
body_ctx: ?*Body,
|
||||||
) !*Inst {
|
inst_name: []const u8,
|
||||||
|
contents_start: usize,
|
||||||
|
) InnerError!*Inst {
|
||||||
const inst_specific = try self.arena.allocator.create(InstType);
|
const inst_specific = try self.arena.allocator.create(InstType);
|
||||||
inst_specific.base = .{
|
inst_specific.base = .{
|
||||||
|
.name = inst_name,
|
||||||
.src = self.i,
|
.src = self.i,
|
||||||
.tag = InstType.base_tag,
|
.tag = InstType.base_tag,
|
||||||
};
|
};
|
||||||
@ -819,6 +873,8 @@ const Parser = struct {
|
|||||||
}
|
}
|
||||||
try requireEatBytes(self, ")");
|
try requireEatBytes(self, ")");
|
||||||
|
|
||||||
|
inst_specific.base.contents = self.source[contents_start..self.i];
|
||||||
|
|
||||||
return &inst_specific.base;
|
return &inst_specific.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -893,8 +949,33 @@ const Parser = struct {
|
|||||||
const ident = self.source[name_start..self.i];
|
const ident = self.source[name_start..self.i];
|
||||||
const kv = map.get(ident) orelse {
|
const kv = map.get(ident) orelse {
|
||||||
const bad_name = self.source[name_start - 1 .. self.i];
|
const bad_name = self.source[name_start - 1 .. self.i];
|
||||||
self.i = name_start - 1;
|
const src = name_start - 1;
|
||||||
|
if (local_ref) {
|
||||||
|
self.i = src;
|
||||||
return self.fail("unrecognized identifier: {}", .{bad_name});
|
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) {
|
if (local_ref) {
|
||||||
return body_ctx.?.instructions.items[kv.value];
|
return body_ctx.?.instructions.items[kv.value];
|
||||||
@ -902,50 +983,64 @@ const Parser = struct {
|
|||||||
return self.decls.items[kv.value];
|
return self.decls.items[kv.value];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generateName(self: *Parser) ![]u8 {
|
||||||
|
const result = try std.fmt.allocPrint(&self.arena.allocator, "unnamed${}", .{self.unnamed_index});
|
||||||
|
self.unnamed_index += 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module {
|
pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
|
||||||
var ctx: EmitZIR = .{
|
var ctx: EmitZIR = .{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.decls = std.ArrayList(*Inst).init(allocator),
|
.decls = .{},
|
||||||
.decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator),
|
.decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator),
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
.old_module = &old_module,
|
.old_module = &old_module,
|
||||||
};
|
};
|
||||||
defer ctx.decls.deinit();
|
defer ctx.decls.deinit(allocator);
|
||||||
defer ctx.decl_table.deinit();
|
defer ctx.decl_table.deinit();
|
||||||
errdefer ctx.arena.deinit();
|
errdefer ctx.arena.deinit();
|
||||||
|
|
||||||
try ctx.emit();
|
try ctx.emit();
|
||||||
|
|
||||||
return Module{
|
return Module{
|
||||||
.decls = ctx.decls.toOwnedSlice(),
|
.decls = ctx.decls.toOwnedSlice(allocator),
|
||||||
.arena = ctx.arena,
|
.arena = ctx.arena,
|
||||||
.errors = &[0]ErrorMsg{},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const EmitZIR = struct {
|
const EmitZIR = struct {
|
||||||
allocator: *Allocator,
|
allocator: *Allocator,
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
old_module: *const ir.Module,
|
old_module: *const IrModule,
|
||||||
decls: std.ArrayList(*Inst),
|
decls: std.ArrayListUnmanaged(*Inst),
|
||||||
decl_table: std.AutoHashMap(*ir.Inst, *Inst),
|
decl_table: std.AutoHashMap(*ir.Inst, *Inst),
|
||||||
|
|
||||||
fn emit(self: *EmitZIR) !void {
|
fn emit(self: *EmitZIR) !void {
|
||||||
for (self.old_module.exports) |module_export| {
|
var it = self.old_module.decl_exports.iterator();
|
||||||
const export_value = try self.emitTypedValue(module_export.src, module_export.typed_value);
|
while (it.next()) |kv| {
|
||||||
const symbol_name = try self.emitStringLiteral(module_export.src, module_export.name);
|
const decl = kv.key;
|
||||||
|
const 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);
|
const export_inst = try self.arena.allocator.create(Inst.Export);
|
||||||
export_inst.* = .{
|
export_inst.* = .{
|
||||||
.base = .{ .src = module_export.src, .tag = Inst.Export.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = module_export.src,
|
||||||
|
.tag = Inst.Export.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.symbol_name = symbol_name,
|
.symbol_name = symbol_name,
|
||||||
.value = export_value,
|
.value = export_value,
|
||||||
},
|
},
|
||||||
.kw_args = .{},
|
.kw_args = .{},
|
||||||
};
|
};
|
||||||
try self.decls.append(&export_inst.base);
|
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 big_int_space = try self.arena.allocator.create(Value.BigIntSpace);
|
||||||
const int_inst = try self.arena.allocator.create(Inst.Int);
|
const int_inst = try self.arena.allocator.create(Inst.Int);
|
||||||
int_inst.* = .{
|
int_inst.* = .{
|
||||||
.base = .{ .src = src, .tag = Inst.Int.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = src,
|
||||||
|
.tag = Inst.Int.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.int = val.toBigInt(big_int_space),
|
.int = val.toBigInt(big_int_space),
|
||||||
},
|
},
|
||||||
.kw_args = .{},
|
.kw_args = .{},
|
||||||
};
|
};
|
||||||
try self.decls.append(&int_inst.base);
|
try self.decls.append(self.allocator, &int_inst.base);
|
||||||
return &int_inst.base;
|
return &int_inst.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: ir.TypedValue) Allocator.Error!*Inst {
|
fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: TypedValue) Allocator.Error!*Inst {
|
||||||
|
const allocator = &self.arena.allocator;
|
||||||
switch (typed_value.ty.zigTypeTag()) {
|
switch (typed_value.ty.zigTypeTag()) {
|
||||||
.Pointer => {
|
.Pointer => {
|
||||||
const ptr_elem_type = typed_value.ty.elemType();
|
const ptr_elem_type = typed_value.ty.elemType();
|
||||||
@ -988,7 +1088,10 @@ const EmitZIR = struct {
|
|||||||
// ptr_elem_type.hasSentinel(Value.initTag(.zero)))
|
// 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);
|
return self.emitStringLiteral(src, bytes);
|
||||||
},
|
},
|
||||||
else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}),
|
else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}),
|
||||||
@ -998,14 +1101,18 @@ const EmitZIR = struct {
|
|||||||
.Int => {
|
.Int => {
|
||||||
const as_inst = try self.arena.allocator.create(Inst.As);
|
const as_inst = try self.arena.allocator.create(Inst.As);
|
||||||
as_inst.* = .{
|
as_inst.* = .{
|
||||||
.base = .{ .src = src, .tag = Inst.As.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = src,
|
||||||
|
.tag = Inst.As.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.dest_type = try self.emitType(src, typed_value.ty),
|
.dest_type = try self.emitType(src, typed_value.ty),
|
||||||
.value = try self.emitComptimeIntVal(src, typed_value.val),
|
.value = try self.emitComptimeIntVal(src, typed_value.val),
|
||||||
},
|
},
|
||||||
.kw_args = .{},
|
.kw_args = .{},
|
||||||
};
|
};
|
||||||
try self.decls.append(&as_inst.base);
|
try self.decls.append(self.allocator, &as_inst.base);
|
||||||
|
|
||||||
return &as_inst.base;
|
return &as_inst.base;
|
||||||
},
|
},
|
||||||
@ -1014,8 +1121,7 @@ const EmitZIR = struct {
|
|||||||
return self.emitType(src, ty);
|
return self.emitType(src, ty);
|
||||||
},
|
},
|
||||||
.Fn => {
|
.Fn => {
|
||||||
const index = typed_value.val.cast(Value.Payload.Function).?.index;
|
const module_fn = typed_value.val.cast(Value.Payload.Function).?.func;
|
||||||
const module_fn = self.old_module.fns[index];
|
|
||||||
|
|
||||||
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
|
var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
|
||||||
defer inst_table.deinit();
|
defer inst_table.deinit();
|
||||||
@ -1023,7 +1129,7 @@ const EmitZIR = struct {
|
|||||||
var instructions = std.ArrayList(*Inst).init(self.allocator);
|
var instructions = std.ArrayList(*Inst).init(self.allocator);
|
||||||
defer instructions.deinit();
|
defer instructions.deinit();
|
||||||
|
|
||||||
try self.emitBody(module_fn.body, &inst_table, &instructions);
|
try self.emitBody(module_fn.analysis.success, &inst_table, &instructions);
|
||||||
|
|
||||||
const fn_type = try self.emitType(src, module_fn.fn_type);
|
const fn_type = try self.emitType(src, module_fn.fn_type);
|
||||||
|
|
||||||
@ -1032,14 +1138,18 @@ const EmitZIR = struct {
|
|||||||
|
|
||||||
const fn_inst = try self.arena.allocator.create(Inst.Fn);
|
const fn_inst = try self.arena.allocator.create(Inst.Fn);
|
||||||
fn_inst.* = .{
|
fn_inst.* = .{
|
||||||
.base = .{ .src = src, .tag = Inst.Fn.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = src,
|
||||||
|
.tag = Inst.Fn.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.fn_type = fn_type,
|
.fn_type = fn_type,
|
||||||
.body = .{ .instructions = arena_instrs },
|
.body = .{ .instructions = arena_instrs },
|
||||||
},
|
},
|
||||||
.kw_args = .{},
|
.kw_args = .{},
|
||||||
};
|
};
|
||||||
try self.decls.append(&fn_inst.base);
|
try self.decls.append(self.allocator, &fn_inst.base);
|
||||||
return &fn_inst.base;
|
return &fn_inst.base;
|
||||||
},
|
},
|
||||||
else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}),
|
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 {
|
fn emitTrivial(self: *EmitZIR, src: usize, comptime T: type) Allocator.Error!*Inst {
|
||||||
const new_inst = try self.arena.allocator.create(T);
|
const new_inst = try self.arena.allocator.create(T);
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = src, .tag = T.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = src,
|
||||||
|
.tag = T.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{},
|
.positionals = .{},
|
||||||
.kw_args = .{},
|
.kw_args = .{},
|
||||||
};
|
};
|
||||||
@ -1058,13 +1172,35 @@ const EmitZIR = struct {
|
|||||||
|
|
||||||
fn emitBody(
|
fn emitBody(
|
||||||
self: *EmitZIR,
|
self: *EmitZIR,
|
||||||
body: ir.Module.Body,
|
body: IrModule.Body,
|
||||||
inst_table: *std.AutoHashMap(*ir.Inst, *Inst),
|
inst_table: *std.AutoHashMap(*ir.Inst, *Inst),
|
||||||
instructions: *std.ArrayList(*Inst),
|
instructions: *std.ArrayList(*Inst),
|
||||||
) Allocator.Error!void {
|
) Allocator.Error!void {
|
||||||
for (body.instructions) |inst| {
|
for (body.instructions) |inst| {
|
||||||
const new_inst = switch (inst.tag) {
|
const new_inst = switch (inst.tag) {
|
||||||
.breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint),
|
.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),
|
.unreach => try self.emitTrivial(inst.src, Inst.Unreachable),
|
||||||
.ret => try self.emitTrivial(inst.src, Inst.Return),
|
.ret => try self.emitTrivial(inst.src, Inst.Return),
|
||||||
.constant => unreachable, // excluded from function bodies
|
.constant => unreachable, // excluded from function bodies
|
||||||
@ -1088,7 +1224,11 @@ const EmitZIR = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = inst.src, .tag = Inst.Asm.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = inst.src,
|
||||||
|
.tag = Inst.Asm.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source),
|
.asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source),
|
||||||
.return_type = try self.emitType(inst.src, inst.ty),
|
.return_type = try self.emitType(inst.src, inst.ty),
|
||||||
@ -1110,7 +1250,11 @@ const EmitZIR = struct {
|
|||||||
const old_inst = inst.cast(ir.Inst.PtrToInt).?;
|
const old_inst = inst.cast(ir.Inst.PtrToInt).?;
|
||||||
const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
|
const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = inst.src, .tag = Inst.PtrToInt.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = inst.src,
|
||||||
|
.tag = Inst.PtrToInt.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.ptr = try self.resolveInst(inst_table, old_inst.args.ptr),
|
.ptr = try self.resolveInst(inst_table, old_inst.args.ptr),
|
||||||
},
|
},
|
||||||
@ -1122,7 +1266,11 @@ const EmitZIR = struct {
|
|||||||
const old_inst = inst.cast(ir.Inst.BitCast).?;
|
const old_inst = inst.cast(ir.Inst.BitCast).?;
|
||||||
const new_inst = try self.arena.allocator.create(Inst.BitCast);
|
const new_inst = try self.arena.allocator.create(Inst.BitCast);
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = inst.src, .tag = Inst.BitCast.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = inst.src,
|
||||||
|
.tag = Inst.BitCast.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.dest_type = try self.emitType(inst.src, inst.ty),
|
.dest_type = try self.emitType(inst.src, inst.ty),
|
||||||
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
|
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
|
||||||
@ -1135,7 +1283,11 @@ const EmitZIR = struct {
|
|||||||
const old_inst = inst.cast(ir.Inst.Cmp).?;
|
const old_inst = inst.cast(ir.Inst.Cmp).?;
|
||||||
const new_inst = try self.arena.allocator.create(Inst.Cmp);
|
const new_inst = try self.arena.allocator.create(Inst.Cmp);
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = inst.src, .tag = Inst.Cmp.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = inst.src,
|
||||||
|
.tag = Inst.Cmp.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.lhs = try self.resolveInst(inst_table, old_inst.args.lhs),
|
.lhs = try self.resolveInst(inst_table, old_inst.args.lhs),
|
||||||
.rhs = try self.resolveInst(inst_table, old_inst.args.rhs),
|
.rhs = try self.resolveInst(inst_table, old_inst.args.rhs),
|
||||||
@ -1159,7 +1311,11 @@ const EmitZIR = struct {
|
|||||||
|
|
||||||
const new_inst = try self.arena.allocator.create(Inst.CondBr);
|
const new_inst = try self.arena.allocator.create(Inst.CondBr);
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = inst.src, .tag = Inst.CondBr.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = inst.src,
|
||||||
|
.tag = Inst.CondBr.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.condition = try self.resolveInst(inst_table, old_inst.args.condition),
|
.condition = try self.resolveInst(inst_table, old_inst.args.condition),
|
||||||
.true_body = .{ .instructions = true_body.toOwnedSlice() },
|
.true_body = .{ .instructions = true_body.toOwnedSlice() },
|
||||||
@ -1173,7 +1329,11 @@ const EmitZIR = struct {
|
|||||||
const old_inst = inst.cast(ir.Inst.IsNull).?;
|
const old_inst = inst.cast(ir.Inst.IsNull).?;
|
||||||
const new_inst = try self.arena.allocator.create(Inst.IsNull);
|
const new_inst = try self.arena.allocator.create(Inst.IsNull);
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = inst.src, .tag = Inst.IsNull.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = inst.src,
|
||||||
|
.tag = Inst.IsNull.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
|
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
|
||||||
},
|
},
|
||||||
@ -1185,7 +1345,11 @@ const EmitZIR = struct {
|
|||||||
const old_inst = inst.cast(ir.Inst.IsNonNull).?;
|
const old_inst = inst.cast(ir.Inst.IsNonNull).?;
|
||||||
const new_inst = try self.arena.allocator.create(Inst.IsNonNull);
|
const new_inst = try self.arena.allocator.create(Inst.IsNonNull);
|
||||||
new_inst.* = .{
|
new_inst.* = .{
|
||||||
.base = .{ .src = inst.src, .tag = Inst.IsNonNull.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = inst.src,
|
||||||
|
.tag = Inst.IsNonNull.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
|
.operand = try self.resolveInst(inst_table, old_inst.args.operand),
|
||||||
},
|
},
|
||||||
@ -1237,7 +1401,11 @@ const EmitZIR = struct {
|
|||||||
|
|
||||||
const fntype_inst = try self.arena.allocator.create(Inst.FnType);
|
const fntype_inst = try self.arena.allocator.create(Inst.FnType);
|
||||||
fntype_inst.* = .{
|
fntype_inst.* = .{
|
||||||
.base = .{ .src = src, .tag = Inst.FnType.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = src,
|
||||||
|
.tag = Inst.FnType.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.param_types = emitted_params,
|
.param_types = emitted_params,
|
||||||
.return_type = try self.emitType(src, ty.fnReturnType()),
|
.return_type = try self.emitType(src, ty.fnReturnType()),
|
||||||
@ -1246,7 +1414,7 @@ const EmitZIR = struct {
|
|||||||
.cc = ty.fnCallingConvention(),
|
.cc = ty.fnCallingConvention(),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
try self.decls.append(&fntype_inst.base);
|
try self.decls.append(self.allocator, &fntype_inst.base);
|
||||||
return &fntype_inst.base;
|
return &fntype_inst.base;
|
||||||
},
|
},
|
||||||
else => std.debug.panic("TODO implement emitType for {}", .{ty}),
|
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 {
|
fn emitPrimitiveType(self: *EmitZIR, src: usize, tag: Inst.Primitive.BuiltinType) !*Inst {
|
||||||
const primitive_inst = try self.arena.allocator.create(Inst.Primitive);
|
const primitive_inst = try self.arena.allocator.create(Inst.Primitive);
|
||||||
primitive_inst.* = .{
|
primitive_inst.* = .{
|
||||||
.base = .{ .src = src, .tag = Inst.Primitive.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = src,
|
||||||
|
.tag = Inst.Primitive.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.tag = tag,
|
.tag = tag,
|
||||||
},
|
},
|
||||||
.kw_args = .{},
|
.kw_args = .{},
|
||||||
};
|
};
|
||||||
try self.decls.append(&primitive_inst.base);
|
try self.decls.append(self.allocator, &primitive_inst.base);
|
||||||
return &primitive_inst.base;
|
return &primitive_inst.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst {
|
fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst {
|
||||||
const str_inst = try self.arena.allocator.create(Inst.Str);
|
const str_inst = try self.arena.allocator.create(Inst.Str);
|
||||||
str_inst.* = .{
|
str_inst.* = .{
|
||||||
.base = .{ .src = src, .tag = Inst.Str.base_tag },
|
.base = .{
|
||||||
|
.name = try self.autoName(),
|
||||||
|
.src = src,
|
||||||
|
.tag = Inst.Str.base_tag,
|
||||||
|
},
|
||||||
.positionals = .{
|
.positionals = .{
|
||||||
.bytes = str,
|
.bytes = str,
|
||||||
},
|
},
|
||||||
.kw_args = .{},
|
.kw_args = .{},
|
||||||
};
|
};
|
||||||
try self.decls.append(&str_inst.base);
|
try self.decls.append(self.allocator, &str_inst.base);
|
||||||
return &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) {
|
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);
|
assert(g->errors.length != 0);
|
||||||
for (size_t i = 0; i < g->errors.length; i += 1) {
|
for (size_t i = 0; i < g->errors.length; i += 1) {
|
||||||
ErrorMsg *err = g->errors.at(i);
|
ErrorMsg *err = g->errors.at(i);
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
|
const std = @import("std");
|
||||||
const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
|
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 {
|
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)
|
\\@void = primitive(void)
|
||||||
\\@usize = primitive(usize)
|
\\@usize = primitive(usize)
|
||||||
\\@fnty = fntype([], @void, cc=C)
|
\\@fnty = fntype([], @void, cc=C)
|
||||||
@ -12,10 +20,11 @@ pub fn addCases(ctx: *TestContext) void {
|
|||||||
\\
|
\\
|
||||||
\\@entry = fn(@fnty, {
|
\\@entry = fn(@fnty, {
|
||||||
\\ %a = str("\x32\x08\x01\x0a")
|
\\ %a = str("\x32\x08\x01\x0a")
|
||||||
\\ %eptr0 = elemptr(%a, @0)
|
\\ %aref = ref(%a)
|
||||||
\\ %eptr1 = elemptr(%a, @1)
|
\\ %eptr0 = elemptr(%aref, @0)
|
||||||
\\ %eptr2 = elemptr(%a, @2)
|
\\ %eptr1 = elemptr(%aref, @1)
|
||||||
\\ %eptr3 = elemptr(%a, @3)
|
\\ %eptr2 = elemptr(%aref, @2)
|
||||||
|
\\ %eptr3 = elemptr(%aref, @3)
|
||||||
\\ %v0 = deref(%eptr0)
|
\\ %v0 = deref(%eptr0)
|
||||||
\\ %v1 = deref(%eptr1)
|
\\ %v1 = deref(%eptr1)
|
||||||
\\ %v2 = deref(%eptr2)
|
\\ %v2 = deref(%eptr2)
|
||||||
@ -34,7 +43,8 @@ pub fn addCases(ctx: *TestContext) void {
|
|||||||
\\})
|
\\})
|
||||||
\\
|
\\
|
||||||
\\@9 = str("entry")
|
\\@9 = str("entry")
|
||||||
\\@10 = export(@9, @entry)
|
\\@10 = ref(@9)
|
||||||
|
\\@11 = export(@10, @entry)
|
||||||
,
|
,
|
||||||
\\@0 = primitive(void)
|
\\@0 = primitive(void)
|
||||||
\\@1 = fntype([], @0, cc=C)
|
\\@1 = fntype([], @0, cc=C)
|
||||||
@ -42,66 +52,161 @@ pub fn addCases(ctx: *TestContext) void {
|
|||||||
\\ %0 = return()
|
\\ %0 = return()
|
||||||
\\})
|
\\})
|
||||||
\\@3 = str("entry")
|
\\@3 = str("entry")
|
||||||
\\@4 = export(@3, @2)
|
\\@4 = ref(@3)
|
||||||
|
\\@5 = export(@4, @2)
|
||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
|
||||||
if (@import("std").Target.current.os.tag != .linux or
|
if (std.Target.current.os.tag != .linux or
|
||||||
@import("std").Target.current.cpu.arch != .x86_64)
|
std.Target.current.cpu.arch != .x86_64)
|
||||||
{
|
{
|
||||||
// TODO implement self-hosted PE (.exe file) linking
|
// TODO implement self-hosted PE (.exe file) linking
|
||||||
// TODO implement more ZIR so we don't depend on x86_64-linux
|
// TODO implement more ZIR so we don't depend on x86_64-linux
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.addZIRCompareOutput("hello world ZIR",
|
ctx.addZIRCompareOutput(
|
||||||
\\@0 = str("Hello, world!\n")
|
"hello world ZIR, update msg",
|
||||||
\\@1 = primitive(noreturn)
|
&[_][]const u8{
|
||||||
\\@2 = primitive(usize)
|
\\@noreturn = primitive(noreturn)
|
||||||
\\@3 = fntype([], @1, cc=Naked)
|
\\@void = primitive(void)
|
||||||
\\@4 = int(0)
|
\\@usize = primitive(usize)
|
||||||
\\@5 = int(1)
|
\\@0 = int(0)
|
||||||
\\@6 = int(231)
|
\\@1 = int(1)
|
||||||
\\@7 = str("len")
|
\\@2 = int(2)
|
||||||
|
\\@3 = int(3)
|
||||||
\\
|
\\
|
||||||
\\@8 = fn(@3, {
|
\\@syscall_array = str("syscall")
|
||||||
\\ %0 = as(@2, @5) ; SYS_write
|
\\@sysoutreg_array = str("={rax}")
|
||||||
\\ %1 = as(@2, @5) ; STDOUT_FILENO
|
\\@rax_array = str("{rax}")
|
||||||
\\ %2 = ptrtoint(@0) ; msg ptr
|
\\@rdi_array = str("{rdi}")
|
||||||
\\ %3 = fieldptr(@0, @7) ; msg len ptr
|
\\@rcx_array = str("rcx")
|
||||||
\\ %4 = deref(%3) ; msg len
|
\\@r11_array = str("r11")
|
||||||
\\ %sysoutreg = str("={rax}")
|
\\@rdx_array = str("{rdx}")
|
||||||
\\ %rax = str("{rax}")
|
\\@rsi_array = str("{rsi}")
|
||||||
\\ %rdi = str("{rdi}")
|
\\@memory_array = str("memory")
|
||||||
\\ %rsi = str("{rsi}")
|
\\@len_array = str("len")
|
||||||
\\ %rdx = str("{rdx}")
|
\\
|
||||||
\\ %rcx = str("rcx")
|
\\@msg = str("Hello, world!\n")
|
||||||
\\ %r11 = str("r11")
|
\\
|
||||||
\\ %memory = str("memory")
|
\\@start_fnty = fntype([], @noreturn, cc=Naked)
|
||||||
\\ %syscall = str("syscall")
|
\\@start = fn(@start_fnty, {
|
||||||
\\ %5 = asm(%syscall, @2,
|
\\ %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,
|
\\ volatile=1,
|
||||||
\\ output=%sysoutreg,
|
\\ output=%sysoutreg,
|
||||||
\\ inputs=[%rax, %rdi, %rsi, %rdx],
|
\\ inputs=[%rax, %rdi, %rsi, %rdx],
|
||||||
\\ clobbers=[%rcx, %r11, %memory],
|
\\ clobbers=[%rcx, %r11, %memory],
|
||||||
\\ args=[%0, %1, %2, %4])
|
\\ args=[%SYS_write, %STDOUT_FILENO, %msg_addr, %msg_len])
|
||||||
\\
|
\\
|
||||||
\\ %6 = as(@2, @6) ;SYS_exit_group
|
\\ %rc_exit = asm(%syscall, @usize,
|
||||||
\\ %7 = as(@2, @4) ;exit code
|
|
||||||
\\ %8 = asm(%syscall, @2,
|
|
||||||
\\ volatile=1,
|
\\ volatile=1,
|
||||||
\\ output=%sysoutreg,
|
\\ output=%sysoutreg,
|
||||||
\\ inputs=[%rax, %rdi],
|
\\ inputs=[%rax, %rdi],
|
||||||
\\ clobbers=[%rcx, %r11, %memory],
|
\\ clobbers=[%rcx, %r11, %memory],
|
||||||
\\ args=[%6, %7])
|
\\ args=[%SYS_exit_group, %exit_code])
|
||||||
\\
|
\\
|
||||||
\\ %9 = unreachable()
|
\\ %99 = unreachable()
|
||||||
\\})
|
\\});
|
||||||
\\
|
\\
|
||||||
\\@9 = str("_start")
|
\\@9 = str("_start")
|
||||||
\\@10 = export(@9, @8)
|
\\@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!
|
\\Hello, world!
|
||||||
\\
|
\\
|
||||||
|
,
|
||||||
|
\\HELL WORLD
|
||||||
|
\\
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user