mirror of
https://github.com/ziglang/zig.git
synced 2025-12-26 16:13:07 +00:00
Merge pull request #9353 from ziglang/stage2-air
stage2: rework AIR memory layout
This commit is contained in:
commit
26984852bd
@ -564,7 +564,7 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/codegen/x86_64.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/glibc.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/introspect.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/air.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/Air.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/libc_installation.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/libcxx.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/libtsan.zig"
|
||||
@ -597,7 +597,7 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/link/tapi/yaml.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/C/zig.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin"
|
||||
"${CMAKE_SOURCE_DIR}/src/liveness.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/Liveness.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/main.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/mingw.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/musl.zig"
|
||||
|
||||
@ -63,6 +63,10 @@ done: bool = true,
|
||||
/// while it was still being accessed by the `refresh` function.
|
||||
update_lock: std.Thread.Mutex = .{},
|
||||
|
||||
/// Keeps track of how many columns in the terminal have been output, so that
|
||||
/// we can move the cursor back later.
|
||||
columns_written: usize = undefined,
|
||||
|
||||
/// Represents one unit of progress. Each node can have children nodes, or
|
||||
/// one can use integers with `update`.
|
||||
pub const Node = struct {
|
||||
@ -160,6 +164,7 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) !*
|
||||
.unprotected_estimated_total_items = estimated_total_items,
|
||||
.unprotected_completed_items = 0,
|
||||
};
|
||||
self.columns_written = 0;
|
||||
self.prev_refresh_timestamp = 0;
|
||||
self.timer = try std.time.Timer.start();
|
||||
self.done = false;
|
||||
@ -187,15 +192,6 @@ pub fn refresh(self: *Progress) void {
|
||||
return self.refreshWithHeldLock();
|
||||
}
|
||||
|
||||
// ED -- Clear screen
|
||||
const ED = "\x1b[J";
|
||||
// DECSC -- Save cursor position
|
||||
const DECSC = "\x1b7";
|
||||
// DECRC -- Restore cursor position
|
||||
const DECRC = "\x1b8";
|
||||
// Note that ESC7/ESC8 are used instead of CSI s/CSI u as the latter are not
|
||||
// supported by some terminals (eg. Terminal.app).
|
||||
|
||||
fn refreshWithHeldLock(self: *Progress) void {
|
||||
const is_dumb = !self.supports_ansi_escape_codes and !self.is_windows_terminal;
|
||||
if (is_dumb and self.dont_print_on_dumb) return;
|
||||
@ -203,54 +199,59 @@ fn refreshWithHeldLock(self: *Progress) void {
|
||||
const file = self.terminal orelse return;
|
||||
|
||||
var end: usize = 0;
|
||||
// Save the cursor position and clear the part of the screen below.
|
||||
// Clearing only the line is not enough as the terminal may wrap the line
|
||||
// when it becomes too long.
|
||||
var saved_cursor_pos: windows.COORD = undefined;
|
||||
if (self.supports_ansi_escape_codes) {
|
||||
const seq_before = DECSC ++ ED;
|
||||
std.mem.copy(u8, self.output_buffer[end..], seq_before);
|
||||
end += seq_before.len;
|
||||
} else if (std.builtin.os.tag == .windows) winapi: {
|
||||
std.debug.assert(self.is_windows_terminal);
|
||||
if (self.columns_written > 0) {
|
||||
// restore the cursor position by moving the cursor
|
||||
// `columns_written` cells to the left, then clear the rest of the
|
||||
// line
|
||||
if (self.supports_ansi_escape_codes) {
|
||||
end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len;
|
||||
end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len;
|
||||
} else if (std.builtin.os.tag == .windows) winapi: {
|
||||
std.debug.assert(self.is_windows_terminal);
|
||||
|
||||
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
|
||||
unreachable;
|
||||
var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
|
||||
if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE)
|
||||
unreachable;
|
||||
|
||||
saved_cursor_pos = info.dwCursorPosition;
|
||||
var cursor_pos = windows.COORD{
|
||||
.X = info.dwCursorPosition.X - @intCast(windows.SHORT, self.columns_written),
|
||||
.Y = info.dwCursorPosition.Y,
|
||||
};
|
||||
|
||||
const window_height = @intCast(windows.DWORD, info.srWindow.Bottom - info.srWindow.Top + 1);
|
||||
const window_width = @intCast(windows.DWORD, info.srWindow.Right - info.srWindow.Left + 1);
|
||||
// Number of terminal cells to clear, starting from the cursor position
|
||||
// and ending at the window bottom right corner.
|
||||
const fill_chars = if (window_width == 0 or window_height == 0) 0 else chars: {
|
||||
break :chars window_width * (window_height -
|
||||
@intCast(windows.DWORD, info.dwCursorPosition.Y - info.srWindow.Top)) -
|
||||
@intCast(windows.DWORD, info.dwCursorPosition.X - info.srWindow.Left);
|
||||
};
|
||||
if (cursor_pos.X < 0)
|
||||
cursor_pos.X = 0;
|
||||
|
||||
var written: windows.DWORD = undefined;
|
||||
if (windows.kernel32.FillConsoleOutputAttribute(
|
||||
file.handle,
|
||||
info.wAttributes,
|
||||
fill_chars,
|
||||
saved_cursor_pos,
|
||||
&written,
|
||||
) != windows.TRUE) {
|
||||
// Stop trying to write to this file.
|
||||
self.terminal = null;
|
||||
break :winapi;
|
||||
}
|
||||
if (windows.kernel32.FillConsoleOutputCharacterW(
|
||||
file.handle,
|
||||
' ',
|
||||
fill_chars,
|
||||
saved_cursor_pos,
|
||||
&written,
|
||||
) != windows.TRUE) {
|
||||
unreachable;
|
||||
const fill_chars = @intCast(windows.DWORD, info.dwSize.X - cursor_pos.X);
|
||||
|
||||
var written: windows.DWORD = undefined;
|
||||
if (windows.kernel32.FillConsoleOutputAttribute(
|
||||
file.handle,
|
||||
info.wAttributes,
|
||||
fill_chars,
|
||||
cursor_pos,
|
||||
&written,
|
||||
) != windows.TRUE) {
|
||||
// Stop trying to write to this file.
|
||||
self.terminal = null;
|
||||
break :winapi;
|
||||
}
|
||||
if (windows.kernel32.FillConsoleOutputCharacterW(
|
||||
file.handle,
|
||||
' ',
|
||||
fill_chars,
|
||||
cursor_pos,
|
||||
&written,
|
||||
) != windows.TRUE) unreachable;
|
||||
|
||||
if (windows.kernel32.SetConsoleCursorPosition(file.handle, cursor_pos) != windows.TRUE)
|
||||
unreachable;
|
||||
} else {
|
||||
// we are in a "dumb" terminal like in acme or writing to a file
|
||||
self.output_buffer[end] = '\n';
|
||||
end += 1;
|
||||
}
|
||||
|
||||
self.columns_written = 0;
|
||||
}
|
||||
|
||||
if (!self.done) {
|
||||
@ -285,28 +286,10 @@ fn refreshWithHeldLock(self: *Progress) void {
|
||||
}
|
||||
}
|
||||
|
||||
// We're done printing the updated message, restore the cursor position.
|
||||
if (self.supports_ansi_escape_codes) {
|
||||
const seq_after = DECRC;
|
||||
std.mem.copy(u8, self.output_buffer[end..], seq_after);
|
||||
end += seq_after.len;
|
||||
} else if (!self.is_windows_terminal) {
|
||||
self.output_buffer[end] = '\n';
|
||||
end += 1;
|
||||
}
|
||||
|
||||
_ = file.write(self.output_buffer[0..end]) catch {
|
||||
// Stop trying to write to this file once it errors.
|
||||
self.terminal = null;
|
||||
};
|
||||
|
||||
if (std.builtin.os.tag == .windows) {
|
||||
if (self.is_windows_terminal) {
|
||||
const res = windows.kernel32.SetConsoleCursorPosition(file.handle, saved_cursor_pos);
|
||||
std.debug.assert(res == windows.TRUE);
|
||||
}
|
||||
}
|
||||
|
||||
self.prev_refresh_timestamp = self.timer.read();
|
||||
}
|
||||
|
||||
@ -317,14 +300,17 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void {
|
||||
self.terminal = null;
|
||||
return;
|
||||
};
|
||||
self.columns_written = 0;
|
||||
}
|
||||
|
||||
fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void {
|
||||
if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| {
|
||||
const amt = written.len;
|
||||
end.* += amt;
|
||||
self.columns_written += amt;
|
||||
} else |err| switch (err) {
|
||||
error.NoSpaceLeft => {
|
||||
self.columns_written += self.output_buffer.len - end.*;
|
||||
end.* = self.output_buffer.len;
|
||||
},
|
||||
}
|
||||
@ -332,6 +318,7 @@ fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: any
|
||||
const max_end = self.output_buffer.len - bytes_needed_for_esc_codes_at_end;
|
||||
if (end.* > max_end) {
|
||||
const suffix = "... ";
|
||||
self.columns_written = self.columns_written - (end.* - max_end) + suffix.len;
|
||||
std.mem.copy(u8, self.output_buffer[max_end..], suffix);
|
||||
end.* = max_end + suffix.len;
|
||||
}
|
||||
|
||||
@ -505,8 +505,8 @@ const LinuxThreadImpl = struct {
|
||||
/// Ported over from musl libc's pthread detached implementation:
|
||||
/// https://github.com/ifduyue/musl/search?q=__unmapself
|
||||
fn freeAndExit(self: *ThreadCompletion) noreturn {
|
||||
const unmap_and_exit: []const u8 = switch (target.cpu.arch) {
|
||||
.i386 => (
|
||||
switch (target.cpu.arch) {
|
||||
.i386 => asm volatile (
|
||||
\\ movl $91, %%eax
|
||||
\\ movl %[ptr], %%ebx
|
||||
\\ movl %[len], %%ecx
|
||||
@ -514,8 +514,12 @@ const LinuxThreadImpl = struct {
|
||||
\\ movl $1, %%eax
|
||||
\\ movl $0, %%ebx
|
||||
\\ int $128
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
.x86_64 => (
|
||||
.x86_64 => asm volatile (
|
||||
\\ movq $11, %%rax
|
||||
\\ movq %[ptr], %%rbx
|
||||
\\ movq %[len], %%rcx
|
||||
@ -523,8 +527,12 @@ const LinuxThreadImpl = struct {
|
||||
\\ movq $60, %%rax
|
||||
\\ movq $1, %%rdi
|
||||
\\ syscall
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
.arm, .armeb, .thumb, .thumbeb => (
|
||||
.arm, .armeb, .thumb, .thumbeb => asm volatile (
|
||||
\\ mov r7, #91
|
||||
\\ mov r0, %[ptr]
|
||||
\\ mov r1, %[len]
|
||||
@ -532,8 +540,12 @@ const LinuxThreadImpl = struct {
|
||||
\\ mov r7, #1
|
||||
\\ mov r0, #0
|
||||
\\ svc 0
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
.aarch64, .aarch64_be, .aarch64_32 => (
|
||||
.aarch64, .aarch64_be, .aarch64_32 => asm volatile (
|
||||
\\ mov x8, #215
|
||||
\\ mov x0, %[ptr]
|
||||
\\ mov x1, %[len]
|
||||
@ -541,8 +553,12 @@ const LinuxThreadImpl = struct {
|
||||
\\ mov x8, #93
|
||||
\\ mov x0, #0
|
||||
\\ svc 0
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
.mips, .mipsel => (
|
||||
.mips, .mipsel => asm volatile (
|
||||
\\ move $sp, $25
|
||||
\\ li $2, 4091
|
||||
\\ move $4, %[ptr]
|
||||
@ -551,8 +567,12 @@ const LinuxThreadImpl = struct {
|
||||
\\ li $2, 4001
|
||||
\\ li $4, 0
|
||||
\\ syscall
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
.mips64, .mips64el => (
|
||||
.mips64, .mips64el => asm volatile (
|
||||
\\ li $2, 4091
|
||||
\\ move $4, %[ptr]
|
||||
\\ move $5, %[len]
|
||||
@ -560,8 +580,12 @@ const LinuxThreadImpl = struct {
|
||||
\\ li $2, 4001
|
||||
\\ li $4, 0
|
||||
\\ syscall
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
.powerpc, .powerpcle, .powerpc64, .powerpc64le => (
|
||||
.powerpc, .powerpcle, .powerpc64, .powerpc64le => asm volatile (
|
||||
\\ li 0, 91
|
||||
\\ mr %[ptr], 3
|
||||
\\ mr %[len], 4
|
||||
@ -570,8 +594,12 @@ const LinuxThreadImpl = struct {
|
||||
\\ li 3, 0
|
||||
\\ sc
|
||||
\\ blr
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
.riscv64 => (
|
||||
.riscv64 => asm volatile (
|
||||
\\ li a7, 215
|
||||
\\ mv a0, %[ptr]
|
||||
\\ mv a1, %[len]
|
||||
@ -579,19 +607,13 @@ const LinuxThreadImpl = struct {
|
||||
\\ li a7, 93
|
||||
\\ mv a0, zero
|
||||
\\ ecall
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
),
|
||||
else => |cpu_arch| {
|
||||
@compileLog("Unsupported linux arch ", cpu_arch);
|
||||
},
|
||||
};
|
||||
|
||||
asm volatile (unmap_and_exit
|
||||
:
|
||||
: [ptr] "r" (@ptrToInt(self.mapped.ptr)),
|
||||
[len] "r" (self.mapped.len)
|
||||
: "memory"
|
||||
);
|
||||
|
||||
else => |cpu_arch| @compileError("Unsupported linux arch: " ++ @tagName(cpu_arch)),
|
||||
}
|
||||
unreachable;
|
||||
}
|
||||
};
|
||||
|
||||
@ -227,10 +227,11 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
|
||||
/// Append the slice of items to the list, asserting the capacity is already
|
||||
/// enough to store the new items. **Does not** invalidate pointers.
|
||||
pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void {
|
||||
const oldlen = self.items.len;
|
||||
const newlen = self.items.len + items.len;
|
||||
self.items.len = newlen;
|
||||
mem.copy(T, self.items[oldlen..], items);
|
||||
const old_len = self.items.len;
|
||||
const new_len = old_len + items.len;
|
||||
assert(new_len <= self.capacity);
|
||||
self.items.len = new_len;
|
||||
mem.copy(T, self.items[old_len..], items);
|
||||
}
|
||||
|
||||
pub usingnamespace if (T != u8) struct {} else struct {
|
||||
@ -570,11 +571,11 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
|
||||
/// Append the slice of items to the list, asserting the capacity is enough
|
||||
/// to store the new items.
|
||||
pub fn appendSliceAssumeCapacity(self: *Self, items: []const T) void {
|
||||
const oldlen = self.items.len;
|
||||
const newlen = self.items.len + items.len;
|
||||
|
||||
self.items.len = newlen;
|
||||
mem.copy(T, self.items[oldlen..], items);
|
||||
const old_len = self.items.len;
|
||||
const new_len = old_len + items.len;
|
||||
assert(new_len <= self.capacity);
|
||||
self.items.len = new_len;
|
||||
mem.copy(T, self.items[old_len..], items);
|
||||
}
|
||||
|
||||
/// Append a value to the list `n` times.
|
||||
|
||||
@ -46,34 +46,38 @@ test "fence/compilerFence" {
|
||||
|
||||
/// Signals to the processor that the caller is inside a busy-wait spin-loop.
|
||||
pub inline fn spinLoopHint() void {
|
||||
const hint_instruction = switch (target.cpu.arch) {
|
||||
// No-op instruction that can hint to save (or share with a hardware-thread) pipelining/power resources
|
||||
switch (target.cpu.arch) {
|
||||
// No-op instruction that can hint to save (or share with a hardware-thread)
|
||||
// pipelining/power resources
|
||||
// https://software.intel.com/content/www/us/en/develop/articles/benefitting-power-and-performance-sleep-loops.html
|
||||
.i386, .x86_64 => "pause",
|
||||
.i386, .x86_64 => asm volatile ("pause" ::: "memory"),
|
||||
|
||||
// No-op instruction that serves as a hardware-thread resource yield hint.
|
||||
// https://stackoverflow.com/a/7588941
|
||||
.powerpc64, .powerpc64le => "or 27, 27, 27",
|
||||
.powerpc64, .powerpc64le => asm volatile ("or 27, 27, 27" ::: "memory"),
|
||||
|
||||
// `isb` appears more reliable for releasing execution resources than `yield` on common aarch64 CPUs.
|
||||
// `isb` appears more reliable for releasing execution resources than `yield`
|
||||
// on common aarch64 CPUs.
|
||||
// https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8258604
|
||||
// https://bugs.mysql.com/bug.php?id=100664
|
||||
.aarch64, .aarch64_be, .aarch64_32 => "isb",
|
||||
.aarch64, .aarch64_be, .aarch64_32 => asm volatile ("isb" ::: "memory"),
|
||||
|
||||
// `yield` was introduced in v6k but is also available on v6m.
|
||||
// https://www.keil.com/support/man/docs/armasm/armasm_dom1361289926796.htm
|
||||
.arm, .armeb, .thumb, .thumbeb => blk: {
|
||||
const can_yield = comptime std.Target.arm.featureSetHasAny(target.cpu.features, .{ .has_v6k, .has_v6m });
|
||||
const instruction = if (can_yield) "yield" else "";
|
||||
break :blk instruction;
|
||||
.arm, .armeb, .thumb, .thumbeb => {
|
||||
const can_yield = comptime std.Target.arm.featureSetHasAny(target.cpu.features, .{
|
||||
.has_v6k, .has_v6m,
|
||||
});
|
||||
if (can_yield) {
|
||||
asm volatile ("yield" ::: "memory");
|
||||
} else {
|
||||
asm volatile ("" ::: "memory");
|
||||
}
|
||||
},
|
||||
|
||||
else => "",
|
||||
};
|
||||
|
||||
// Memory barrier to prevent the compiler from optimizing away the spin-loop
|
||||
// even if no hint_instruction was provided.
|
||||
asm volatile (hint_instruction ::: "memory");
|
||||
// Memory barrier to prevent the compiler from optimizing away the spin-loop
|
||||
// even if no hint_instruction was provided.
|
||||
else => asm volatile ("" ::: "memory"),
|
||||
}
|
||||
}
|
||||
|
||||
test "spinLoopHint" {
|
||||
|
||||
@ -178,26 +178,78 @@ pub fn Atomic(comptime T: type) type {
|
||||
) u1 {
|
||||
// x86 supports dedicated bitwise instructions
|
||||
if (comptime target.cpu.arch.isX86() and @sizeOf(T) >= 2 and @sizeOf(T) <= 8) {
|
||||
const instruction = switch (op) {
|
||||
.Set => "lock bts",
|
||||
.Reset => "lock btr",
|
||||
.Toggle => "lock btc",
|
||||
};
|
||||
|
||||
const suffix = switch (@sizeOf(T)) {
|
||||
2 => "w",
|
||||
4 => "l",
|
||||
8 => "q",
|
||||
const old_bit: u8 = switch (@sizeOf(T)) {
|
||||
2 => switch (op) {
|
||||
.Set => asm volatile ("lock btsw %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
.Reset => asm volatile ("lock btrw %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
.Toggle => asm volatile ("lock btcw %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
},
|
||||
4 => switch (op) {
|
||||
.Set => asm volatile ("lock btsl %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
.Reset => asm volatile ("lock btrl %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
.Toggle => asm volatile ("lock btcl %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
},
|
||||
8 => switch (op) {
|
||||
.Set => asm volatile ("lock btsq %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
.Reset => asm volatile ("lock btrq %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
.Toggle => asm volatile ("lock btcq %[bit], %[ptr]"
|
||||
// LLVM doesn't support u1 flag register return values
|
||||
: [result] "={@ccc}" (-> u8)
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
),
|
||||
},
|
||||
else => @compileError("Invalid atomic type " ++ @typeName(T)),
|
||||
};
|
||||
|
||||
const old_bit = asm volatile (instruction ++ suffix ++ " %[bit], %[ptr]"
|
||||
: [result] "={@ccc}" (-> u8) // LLVM doesn't support u1 flag register return values
|
||||
: [ptr] "*p" (&self.value),
|
||||
[bit] "X" (@as(T, bit))
|
||||
: "cc", "memory"
|
||||
);
|
||||
|
||||
return @intCast(u1, old_bit);
|
||||
}
|
||||
|
||||
|
||||
546
src/Air.zig
Normal file
546
src/Air.zig
Normal file
@ -0,0 +1,546 @@
|
||||
//! Analyzed Intermediate Representation.
|
||||
//! This data is produced by Sema and consumed by codegen.
|
||||
//! Unlike ZIR where there is one instance for an entire source file, each function
|
||||
//! gets its own `Air` instance.
|
||||
|
||||
const std = @import("std");
|
||||
const Value = @import("value.zig").Value;
|
||||
const Type = @import("type.zig").Type;
|
||||
const Module = @import("Module.zig");
|
||||
const assert = std.debug.assert;
|
||||
const Air = @This();
|
||||
|
||||
instructions: std.MultiArrayList(Inst).Slice,
|
||||
/// The meaning of this data is determined by `Inst.Tag` value.
|
||||
/// The first few indexes are reserved. See `ExtraIndex` for the values.
|
||||
extra: []const u32,
|
||||
values: []const Value,
|
||||
variables: []const *Module.Var,
|
||||
|
||||
pub const ExtraIndex = enum(u32) {
|
||||
/// Payload index of the main `Block` in the `extra` array.
|
||||
main_block,
|
||||
|
||||
_,
|
||||
};
|
||||
|
||||
pub const Inst = struct {
|
||||
tag: Tag,
|
||||
data: Data,
|
||||
|
||||
pub const Tag = enum(u8) {
|
||||
/// The first N instructions in the main block must be one arg instruction per
|
||||
/// function parameter. This makes function parameters participate in
|
||||
/// liveness analysis without any special handling.
|
||||
/// Uses the `ty_str` field.
|
||||
/// The string is the parameter name.
|
||||
arg,
|
||||
/// Float or integer addition. For integers, wrapping is undefined behavior.
|
||||
/// Both operands are guaranteed to be the same type, and the result type
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
add,
|
||||
/// Integer addition. Wrapping is defined to be twos complement wrapping.
|
||||
/// Both operands are guaranteed to be the same type, and the result type
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
addwrap,
|
||||
/// Float or integer subtraction. For integers, wrapping is undefined behavior.
|
||||
/// Both operands are guaranteed to be the same type, and the result type
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
sub,
|
||||
/// Integer subtraction. Wrapping is defined to be twos complement wrapping.
|
||||
/// Both operands are guaranteed to be the same type, and the result type
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
subwrap,
|
||||
/// Float or integer multiplication. For integers, wrapping is undefined behavior.
|
||||
/// Both operands are guaranteed to be the same type, and the result type
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
mul,
|
||||
/// Integer multiplication. Wrapping is defined to be twos complement wrapping.
|
||||
/// Both operands are guaranteed to be the same type, and the result type
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
mulwrap,
|
||||
/// Integer or float division. For integers, wrapping is undefined behavior.
|
||||
/// Both operands are guaranteed to be the same type, and the result type
|
||||
/// is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
div,
|
||||
/// Allocates stack local memory.
|
||||
/// Uses the `ty` field.
|
||||
alloc,
|
||||
/// Inline assembly. Uses the `ty_pl` field. Payload is `Asm`.
|
||||
assembly,
|
||||
/// Bitwise AND. `&`.
|
||||
/// Result type is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
bit_and,
|
||||
/// Bitwise OR. `|`.
|
||||
/// Result type is the same as both operands.
|
||||
/// Uses the `bin_op` field.
|
||||
bit_or,
|
||||
/// Bitwise XOR. `^`
|
||||
/// Uses the `bin_op` field.
|
||||
xor,
|
||||
/// Boolean or binary NOT.
|
||||
/// Uses the `ty_op` field.
|
||||
not,
|
||||
/// Reinterpret the memory representation of a value as a different type.
|
||||
/// Uses the `ty_op` field.
|
||||
bitcast,
|
||||
/// Uses the `ty_pl` field with payload `Block`.
|
||||
block,
|
||||
/// A labeled block of code that loops forever. At the end of the body it is implied
|
||||
/// to repeat; no explicit "repeat" instruction terminates loop bodies.
|
||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||
/// Uses the `ty_pl` field. Payload is `Block`.
|
||||
loop,
|
||||
/// Return from a block with a result.
|
||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||
/// Uses the `br` field.
|
||||
br,
|
||||
/// Lowers to a hardware trap instruction, or the next best thing.
|
||||
/// Result type is always void.
|
||||
breakpoint,
|
||||
/// Function call.
|
||||
/// Result type is the return type of the function being called.
|
||||
/// Uses the `pl_op` field with the `Call` payload. operand is the callee.
|
||||
call,
|
||||
/// `<`. Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
cmp_lt,
|
||||
/// `<=`. Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
cmp_lte,
|
||||
/// `==`. Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
cmp_eq,
|
||||
/// `>=`. Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
cmp_gte,
|
||||
/// `>`. Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
cmp_gt,
|
||||
/// `!=`. Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
cmp_neq,
|
||||
/// Conditional branch.
|
||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||
/// Uses the `pl_op` field. Operand is the condition. Payload is `CondBr`.
|
||||
cond_br,
|
||||
/// Switch branch.
|
||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||
/// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`.
|
||||
switch_br,
|
||||
/// A comptime-known value. Uses the `ty_pl` field, payload is index of
|
||||
/// `values` array.
|
||||
constant,
|
||||
/// A comptime-known type. Uses the `ty` field.
|
||||
const_ty,
|
||||
/// Notes the beginning of a source code statement and marks the line and column.
|
||||
/// Result type is always void.
|
||||
/// Uses the `dbg_stmt` field.
|
||||
dbg_stmt,
|
||||
/// ?T => bool
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_null,
|
||||
/// ?T => bool (inverted logic)
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_non_null,
|
||||
/// *?T => bool
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_null_ptr,
|
||||
/// *?T => bool (inverted logic)
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_non_null_ptr,
|
||||
/// E!T => bool
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_err,
|
||||
/// E!T => bool (inverted logic)
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_non_err,
|
||||
/// *E!T => bool
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_err_ptr,
|
||||
/// *E!T => bool (inverted logic)
|
||||
/// Result type is always bool.
|
||||
/// Uses the `un_op` field.
|
||||
is_non_err_ptr,
|
||||
/// Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
bool_and,
|
||||
/// Result type is always bool.
|
||||
/// Uses the `bin_op` field.
|
||||
bool_or,
|
||||
/// Read a value from a pointer.
|
||||
/// Uses the `ty_op` field.
|
||||
load,
|
||||
/// Converts a pointer to its address. Result type is always `usize`.
|
||||
/// Uses the `un_op` field.
|
||||
ptrtoint,
|
||||
/// Stores a value onto the stack and returns a pointer to it.
|
||||
/// TODO audit where this AIR instruction is emitted, maybe it should instead be emitting
|
||||
/// alloca instruction and storing to the alloca.
|
||||
/// Uses the `ty_op` field.
|
||||
ref,
|
||||
/// Return a value from a function.
|
||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||
/// Uses the `un_op` field.
|
||||
ret,
|
||||
/// Returns a pointer to a global variable.
|
||||
/// Uses the `ty_pl` field. Index is into the `variables` array.
|
||||
/// TODO this can be modeled simply as a constant with a decl ref and then
|
||||
/// the variables array can be removed from Air.
|
||||
varptr,
|
||||
/// Write a value to a pointer. LHS is pointer, RHS is value.
|
||||
/// Result type is always void.
|
||||
/// Uses the `bin_op` field.
|
||||
store,
|
||||
/// Indicates the program counter will never get to this instruction.
|
||||
/// Result type is always noreturn; no instructions in a block follow this one.
|
||||
unreach,
|
||||
/// Convert from one float type to another.
|
||||
/// Uses the `ty_op` field.
|
||||
floatcast,
|
||||
/// TODO audit uses of this. We should have explicit instructions for integer
|
||||
/// widening and truncating.
|
||||
/// Uses the `ty_op` field.
|
||||
intcast,
|
||||
/// ?T => T. If the value is null, undefined behavior.
|
||||
/// Uses the `ty_op` field.
|
||||
optional_payload,
|
||||
/// *?T => *T. If the value is null, undefined behavior.
|
||||
/// Uses the `ty_op` field.
|
||||
optional_payload_ptr,
|
||||
/// Given a payload value, wraps it in an optional type.
|
||||
/// Uses the `ty_op` field.
|
||||
wrap_optional,
|
||||
/// E!T -> T. If the value is an error, undefined behavior.
|
||||
/// Uses the `ty_op` field.
|
||||
unwrap_errunion_payload,
|
||||
/// E!T -> E. If the value is not an error, undefined behavior.
|
||||
/// Uses the `ty_op` field.
|
||||
unwrap_errunion_err,
|
||||
/// *(E!T) -> *T. If the value is an error, undefined behavior.
|
||||
/// Uses the `ty_op` field.
|
||||
unwrap_errunion_payload_ptr,
|
||||
/// *(E!T) -> E. If the value is not an error, undefined behavior.
|
||||
/// Uses the `ty_op` field.
|
||||
unwrap_errunion_err_ptr,
|
||||
/// wrap from T to E!T
|
||||
/// Uses the `ty_op` field.
|
||||
wrap_errunion_payload,
|
||||
/// wrap from E to E!T
|
||||
/// Uses the `ty_op` field.
|
||||
wrap_errunion_err,
|
||||
/// Given a pointer to a struct and a field index, returns a pointer to the field.
|
||||
/// Uses the `ty_pl` field, payload is `StructField`.
|
||||
struct_field_ptr,
|
||||
|
||||
pub fn fromCmpOp(op: std.math.CompareOperator) Tag {
|
||||
return switch (op) {
|
||||
.lt => .cmp_lt,
|
||||
.lte => .cmp_lte,
|
||||
.eq => .cmp_eq,
|
||||
.gte => .cmp_gte,
|
||||
.gt => .cmp_gt,
|
||||
.neq => .cmp_neq,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn toCmpOp(tag: Tag) ?std.math.CompareOperator {
|
||||
return switch (tag) {
|
||||
.cmp_lt => .lt,
|
||||
.cmp_lte => .lte,
|
||||
.cmp_eq => .eq,
|
||||
.cmp_gte => .gte,
|
||||
.cmp_gt => .gt,
|
||||
.cmp_neq => .neq,
|
||||
else => null,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// The position of an AIR instruction within the `Air` instructions array.
|
||||
pub const Index = u32;
|
||||
|
||||
pub const Ref = @import("Zir.zig").Inst.Ref;
|
||||
|
||||
/// All instructions have an 8-byte payload, which is contained within
|
||||
/// this union. `Tag` determines which union field is active, as well as
|
||||
/// how to interpret the data within.
|
||||
pub const Data = union {
|
||||
no_op: void,
|
||||
un_op: Ref,
|
||||
bin_op: struct {
|
||||
lhs: Ref,
|
||||
rhs: Ref,
|
||||
},
|
||||
ty: Type,
|
||||
ty_op: struct {
|
||||
ty: Ref,
|
||||
operand: Ref,
|
||||
},
|
||||
ty_pl: struct {
|
||||
ty: Ref,
|
||||
// Index into a different array.
|
||||
payload: u32,
|
||||
},
|
||||
ty_str: struct {
|
||||
ty: Ref,
|
||||
// ZIR string table index.
|
||||
str: u32,
|
||||
},
|
||||
br: struct {
|
||||
block_inst: Index,
|
||||
operand: Ref,
|
||||
},
|
||||
pl_op: struct {
|
||||
operand: Ref,
|
||||
payload: u32,
|
||||
},
|
||||
dbg_stmt: struct {
|
||||
line: u32,
|
||||
column: u32,
|
||||
},
|
||||
|
||||
// Make sure we don't accidentally add a field to make this union
|
||||
// bigger than expected. Note that in Debug builds, Zig is allowed
|
||||
// to insert a secret field for safety checks.
|
||||
comptime {
|
||||
if (std.builtin.mode != .Debug) {
|
||||
assert(@sizeOf(Data) == 8);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/// Trailing is a list of instruction indexes for every `body_len`.
|
||||
pub const Block = struct {
|
||||
body_len: u32,
|
||||
};
|
||||
|
||||
/// Trailing is a list of `Inst.Ref` for every `args_len`.
|
||||
pub const Call = struct {
|
||||
args_len: u32,
|
||||
};
|
||||
|
||||
/// This data is stored inside extra, with two sets of trailing `Inst.Ref`:
|
||||
/// * 0. the then body, according to `then_body_len`.
|
||||
/// * 1. the else body, according to `else_body_len`.
|
||||
pub const CondBr = struct {
|
||||
then_body_len: u32,
|
||||
else_body_len: u32,
|
||||
};
|
||||
|
||||
/// Trailing:
|
||||
/// * 0. `Case` for each `cases_len`
|
||||
/// * 1. the else body, according to `else_body_len`.
|
||||
pub const SwitchBr = struct {
|
||||
cases_len: u32,
|
||||
else_body_len: u32,
|
||||
|
||||
/// Trailing:
|
||||
/// * item: Inst.Ref // for each `items_len`.
|
||||
/// * instruction index for each `body_len`.
|
||||
pub const Case = struct {
|
||||
items_len: u32,
|
||||
body_len: u32,
|
||||
};
|
||||
};
|
||||
|
||||
pub const StructField = struct {
|
||||
struct_ptr: Inst.Ref,
|
||||
field_index: u32,
|
||||
};
|
||||
|
||||
/// Trailing:
|
||||
/// 0. `Inst.Ref` for every outputs_len
|
||||
/// 1. `Inst.Ref` for every inputs_len
|
||||
pub const Asm = struct {
|
||||
/// Index to the corresponding ZIR instruction.
|
||||
/// `asm_source`, `outputs_len`, `inputs_len`, `clobbers_len`, `is_volatile`, and
|
||||
/// clobbers are found via here.
|
||||
zir_index: u32,
|
||||
};
|
||||
|
||||
pub fn getMainBody(air: Air) []const Air.Inst.Index {
|
||||
const body_index = air.extra[@enumToInt(ExtraIndex.main_block)];
|
||||
const extra = air.extraData(Block, body_index);
|
||||
return air.extra[extra.end..][0..extra.data.body_len];
|
||||
}
|
||||
|
||||
pub fn typeOf(air: Air, inst: Air.Inst.Ref) Type {
|
||||
const ref_int = @enumToInt(inst);
|
||||
if (ref_int < Air.Inst.Ref.typed_value_map.len) {
|
||||
return Air.Inst.Ref.typed_value_map[ref_int].ty;
|
||||
}
|
||||
return air.typeOfIndex(@intCast(Air.Inst.Index, ref_int - Air.Inst.Ref.typed_value_map.len));
|
||||
}
|
||||
|
||||
pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
|
||||
const datas = air.instructions.items(.data);
|
||||
switch (air.instructions.items(.tag)[inst]) {
|
||||
.arg => return air.getRefType(datas[inst].ty_str.ty),
|
||||
|
||||
.add,
|
||||
.addwrap,
|
||||
.sub,
|
||||
.subwrap,
|
||||
.mul,
|
||||
.mulwrap,
|
||||
.div,
|
||||
.bit_and,
|
||||
.bit_or,
|
||||
.xor,
|
||||
=> return air.typeOf(datas[inst].bin_op.lhs),
|
||||
|
||||
.cmp_lt,
|
||||
.cmp_lte,
|
||||
.cmp_eq,
|
||||
.cmp_gte,
|
||||
.cmp_gt,
|
||||
.cmp_neq,
|
||||
.is_null,
|
||||
.is_non_null,
|
||||
.is_null_ptr,
|
||||
.is_non_null_ptr,
|
||||
.is_err,
|
||||
.is_non_err,
|
||||
.is_err_ptr,
|
||||
.is_non_err_ptr,
|
||||
.bool_and,
|
||||
.bool_or,
|
||||
=> return Type.initTag(.bool),
|
||||
|
||||
.const_ty => return Type.initTag(.type),
|
||||
|
||||
.alloc => return datas[inst].ty,
|
||||
|
||||
.assembly,
|
||||
.block,
|
||||
.constant,
|
||||
.varptr,
|
||||
.struct_field_ptr,
|
||||
=> return air.getRefType(datas[inst].ty_pl.ty),
|
||||
|
||||
.not,
|
||||
.bitcast,
|
||||
.load,
|
||||
.ref,
|
||||
.floatcast,
|
||||
.intcast,
|
||||
.optional_payload,
|
||||
.optional_payload_ptr,
|
||||
.wrap_optional,
|
||||
.unwrap_errunion_payload,
|
||||
.unwrap_errunion_err,
|
||||
.unwrap_errunion_payload_ptr,
|
||||
.unwrap_errunion_err_ptr,
|
||||
.wrap_errunion_payload,
|
||||
.wrap_errunion_err,
|
||||
=> return air.getRefType(datas[inst].ty_op.ty),
|
||||
|
||||
.loop,
|
||||
.br,
|
||||
.cond_br,
|
||||
.switch_br,
|
||||
.ret,
|
||||
.unreach,
|
||||
=> return Type.initTag(.noreturn),
|
||||
|
||||
.breakpoint,
|
||||
.dbg_stmt,
|
||||
.store,
|
||||
=> return Type.initTag(.void),
|
||||
|
||||
.ptrtoint => return Type.initTag(.usize),
|
||||
|
||||
.call => {
|
||||
const callee_ty = air.typeOf(datas[inst].pl_op.operand);
|
||||
return callee_ty.fnReturnType();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getRefType(air: Air, ref: Air.Inst.Ref) Type {
|
||||
const ref_int = @enumToInt(ref);
|
||||
if (ref_int < Air.Inst.Ref.typed_value_map.len) {
|
||||
return Air.Inst.Ref.typed_value_map[ref_int].val.toType(undefined) catch unreachable;
|
||||
}
|
||||
const inst_index = ref_int - Air.Inst.Ref.typed_value_map.len;
|
||||
const air_tags = air.instructions.items(.tag);
|
||||
const air_datas = air.instructions.items(.data);
|
||||
assert(air_tags[inst_index] == .const_ty);
|
||||
return air_datas[inst_index].ty;
|
||||
}
|
||||
|
||||
/// Returns the requested data, as well as the new index which is at the start of the
|
||||
/// trailers for the object.
|
||||
pub fn extraData(air: Air, comptime T: type, index: usize) struct { data: T, end: usize } {
|
||||
const fields = std.meta.fields(T);
|
||||
var i: usize = index;
|
||||
var result: T = undefined;
|
||||
inline for (fields) |field| {
|
||||
@field(result, field.name) = switch (field.field_type) {
|
||||
u32 => air.extra[i],
|
||||
Inst.Ref => @intToEnum(Inst.Ref, air.extra[i]),
|
||||
i32 => @bitCast(i32, air.extra[i]),
|
||||
else => @compileError("bad field type"),
|
||||
};
|
||||
i += 1;
|
||||
}
|
||||
return .{
|
||||
.data = result,
|
||||
.end = i,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(air: *Air, gpa: *std.mem.Allocator) void {
|
||||
air.instructions.deinit(gpa);
|
||||
gpa.free(air.extra);
|
||||
gpa.free(air.values);
|
||||
gpa.free(air.variables);
|
||||
air.* = undefined;
|
||||
}
|
||||
|
||||
const ref_start_index: u32 = Air.Inst.Ref.typed_value_map.len;
|
||||
|
||||
pub fn indexToRef(inst: Air.Inst.Index) Air.Inst.Ref {
|
||||
return @intToEnum(Air.Inst.Ref, ref_start_index + inst);
|
||||
}
|
||||
|
||||
pub fn refToIndex(inst: Air.Inst.Ref) ?Air.Inst.Index {
|
||||
const ref_int = @enumToInt(inst);
|
||||
if (ref_int >= ref_start_index) {
|
||||
return ref_int - ref_start_index;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `null` if runtime-known.
|
||||
pub fn value(air: Air, inst: Air.Inst.Ref) ?Value {
|
||||
const ref_int = @enumToInt(inst);
|
||||
if (ref_int < Air.Inst.Ref.typed_value_map.len) {
|
||||
return Air.Inst.Ref.typed_value_map[ref_int].val;
|
||||
}
|
||||
const inst_index = @intCast(Air.Inst.Index, ref_int - Air.Inst.Ref.typed_value_map.len);
|
||||
const air_datas = air.instructions.items(.data);
|
||||
switch (air.instructions.items(.tag)[inst_index]) {
|
||||
.constant => return air.values[air_datas[inst_index].ty_pl.payload],
|
||||
.const_ty => unreachable,
|
||||
else => return air.typeOfIndex(inst_index).onePossibleValue(),
|
||||
}
|
||||
}
|
||||
209
src/AstGen.zig
209
src/AstGen.zig
@ -989,7 +989,7 @@ fn suspendExpr(
|
||||
}
|
||||
try suspend_scope.setBlockBody(suspend_inst);
|
||||
|
||||
return gz.indexToRef(suspend_inst);
|
||||
return indexToRef(suspend_inst);
|
||||
}
|
||||
|
||||
fn awaitExpr(
|
||||
@ -1300,7 +1300,7 @@ fn arrayInitExprRlPtr(
|
||||
.lhs = result_ptr,
|
||||
.rhs = index_inst,
|
||||
});
|
||||
elem_ptr_list[i] = gz.refToIndex(elem_ptr).?;
|
||||
elem_ptr_list[i] = refToIndex(elem_ptr).?;
|
||||
_ = try expr(gz, scope, .{ .ptr = elem_ptr }, elem_init);
|
||||
}
|
||||
_ = try gz.addPlNode(.validate_array_init_ptr, node, Zir.Inst.Block{
|
||||
@ -1455,7 +1455,7 @@ fn structInitExprRlPtr(
|
||||
.lhs = result_ptr,
|
||||
.field_name_start = str_index,
|
||||
});
|
||||
field_ptr_list[i] = gz.refToIndex(field_ptr).?;
|
||||
field_ptr_list[i] = refToIndex(field_ptr).?;
|
||||
_ = try expr(gz, scope, .{ .ptr = field_ptr }, field_init);
|
||||
}
|
||||
_ = try gz.addPlNode(.validate_struct_init_ptr, node, Zir.Inst.Block{
|
||||
@ -1489,7 +1489,7 @@ fn structInitExprRlTy(
|
||||
.name_start = str_index,
|
||||
});
|
||||
fields_list[i] = .{
|
||||
.field_type = gz.refToIndex(field_ty_inst).?,
|
||||
.field_type = refToIndex(field_ty_inst).?,
|
||||
.init = try expr(gz, scope, .{ .ty = field_ty_inst }, field_init),
|
||||
};
|
||||
}
|
||||
@ -1786,7 +1786,7 @@ fn labeledBlockExpr(
|
||||
}
|
||||
try block_scope.setBlockBody(block_inst);
|
||||
|
||||
return gz.indexToRef(block_inst);
|
||||
return indexToRef(block_inst);
|
||||
},
|
||||
.break_operand => {
|
||||
// All break operands are values that did not use the result location pointer.
|
||||
@ -1800,7 +1800,7 @@ fn labeledBlockExpr(
|
||||
} else {
|
||||
try block_scope.setBlockBody(block_inst);
|
||||
}
|
||||
const block_ref = gz.indexToRef(block_inst);
|
||||
const block_ref = indexToRef(block_inst);
|
||||
switch (rl) {
|
||||
.ref => return block_ref,
|
||||
else => return rvalue(gz, rl, block_ref, block_node),
|
||||
@ -1878,7 +1878,7 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner
|
||||
// we want to avoid adding the ZIR instruction if possible for performance.
|
||||
const maybe_unused_result = try expr(gz, scope, .none, statement);
|
||||
var noreturn_src_node: ast.Node.Index = 0;
|
||||
const elide_check = if (gz.refToIndex(maybe_unused_result)) |inst| b: {
|
||||
const elide_check = if (refToIndex(maybe_unused_result)) |inst| b: {
|
||||
// Note that this array becomes invalid after appending more items to it
|
||||
// in the above while loop.
|
||||
const zir_tags = gz.astgen.instructions.items(.tag);
|
||||
@ -1922,8 +1922,6 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: ast.Node.Index) Inner
|
||||
.bool_br_and,
|
||||
.bool_br_or,
|
||||
.bool_not,
|
||||
.bool_and,
|
||||
.bool_or,
|
||||
.call_compile_time,
|
||||
.call_nosuspend,
|
||||
.call_async,
|
||||
@ -2440,7 +2438,7 @@ fn varDecl(
|
||||
// the alloc instruction and the store_to_block_ptr instruction.
|
||||
try parent_zir.ensureUnusedCapacity(gpa, init_scope.instructions.items.len);
|
||||
for (init_scope.instructions.items) |src_inst| {
|
||||
if (gz.indexToRef(src_inst) == init_scope.rl_ptr) continue;
|
||||
if (indexToRef(src_inst) == init_scope.rl_ptr) continue;
|
||||
if (zir_tags[src_inst] == .store_to_block_ptr) {
|
||||
if (zir_datas[src_inst].bin.lhs == init_scope.rl_ptr) continue;
|
||||
}
|
||||
@ -2743,7 +2741,7 @@ fn ptrType(
|
||||
}
|
||||
|
||||
const new_index = @intCast(Zir.Inst.Index, gz.astgen.instructions.len);
|
||||
const result = gz.indexToRef(new_index);
|
||||
const result = indexToRef(new_index);
|
||||
gz.astgen.instructions.appendAssumeCapacity(.{ .tag = .ptr_type, .data = .{
|
||||
.ptr_type = .{
|
||||
.flags = .{
|
||||
@ -3473,7 +3471,7 @@ fn structDeclInner(
|
||||
.body_len = 0,
|
||||
.decls_len = 0,
|
||||
});
|
||||
return gz.indexToRef(decl_inst);
|
||||
return indexToRef(decl_inst);
|
||||
}
|
||||
|
||||
const astgen = gz.astgen;
|
||||
@ -3492,7 +3490,6 @@ fn structDeclInner(
|
||||
.astgen = astgen,
|
||||
.force_comptime = true,
|
||||
.in_defer = false,
|
||||
.ref_start_index = gz.ref_start_index,
|
||||
};
|
||||
defer block_scope.instructions.deinit(gpa);
|
||||
|
||||
@ -3730,7 +3727,7 @@ fn structDeclInner(
|
||||
}
|
||||
astgen.extra.appendSliceAssumeCapacity(fields_data.items);
|
||||
|
||||
return gz.indexToRef(decl_inst);
|
||||
return indexToRef(decl_inst);
|
||||
}
|
||||
|
||||
fn unionDeclInner(
|
||||
@ -3758,7 +3755,6 @@ fn unionDeclInner(
|
||||
.astgen = astgen,
|
||||
.force_comptime = true,
|
||||
.in_defer = false,
|
||||
.ref_start_index = gz.ref_start_index,
|
||||
};
|
||||
defer block_scope.instructions.deinit(gpa);
|
||||
|
||||
@ -4006,7 +4002,7 @@ fn unionDeclInner(
|
||||
astgen.extra.appendAssumeCapacity(cur_bit_bag);
|
||||
astgen.extra.appendSliceAssumeCapacity(fields_data.items);
|
||||
|
||||
return gz.indexToRef(decl_inst);
|
||||
return indexToRef(decl_inst);
|
||||
}
|
||||
|
||||
fn containerDecl(
|
||||
@ -4170,7 +4166,6 @@ fn containerDecl(
|
||||
.astgen = astgen,
|
||||
.force_comptime = true,
|
||||
.in_defer = false,
|
||||
.ref_start_index = gz.ref_start_index,
|
||||
};
|
||||
defer block_scope.instructions.deinit(gpa);
|
||||
|
||||
@ -4398,7 +4393,7 @@ fn containerDecl(
|
||||
astgen.extra.appendAssumeCapacity(cur_bit_bag);
|
||||
astgen.extra.appendSliceAssumeCapacity(fields_data.items);
|
||||
|
||||
return rvalue(gz, rl, gz.indexToRef(decl_inst), node);
|
||||
return rvalue(gz, rl, indexToRef(decl_inst), node);
|
||||
},
|
||||
.keyword_opaque => {
|
||||
var namespace: Scope.Namespace = .{ .parent = scope };
|
||||
@ -4559,7 +4554,7 @@ fn containerDecl(
|
||||
}
|
||||
astgen.extra.appendSliceAssumeCapacity(wip_decls.payload.items);
|
||||
|
||||
return rvalue(gz, rl, gz.indexToRef(decl_inst), node);
|
||||
return rvalue(gz, rl, indexToRef(decl_inst), node);
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
@ -4797,7 +4792,7 @@ fn finishThenElseBlock(
|
||||
}
|
||||
assert(!strat.elide_store_to_block_ptr_instructions);
|
||||
try setCondBrPayload(condbr, cond, then_scope, else_scope);
|
||||
return parent_gz.indexToRef(main_block);
|
||||
return indexToRef(main_block);
|
||||
},
|
||||
.break_operand => {
|
||||
if (!parent_gz.refIsNoReturn(then_result)) {
|
||||
@ -4815,7 +4810,7 @@ fn finishThenElseBlock(
|
||||
} else {
|
||||
try setCondBrPayload(condbr, cond, then_scope, else_scope);
|
||||
}
|
||||
const block_ref = parent_gz.indexToRef(main_block);
|
||||
const block_ref = indexToRef(main_block);
|
||||
switch (rl) {
|
||||
.ref => return block_ref,
|
||||
else => return rvalue(parent_gz, rl, block_ref, node),
|
||||
@ -4937,7 +4932,7 @@ fn boolBinOp(
|
||||
}
|
||||
try rhs_scope.setBoolBrBody(bool_br);
|
||||
|
||||
const block_ref = gz.indexToRef(bool_br);
|
||||
const block_ref = indexToRef(bool_br);
|
||||
return rvalue(gz, rl, block_ref, node);
|
||||
}
|
||||
|
||||
@ -5959,7 +5954,7 @@ fn switchExpr(
|
||||
if (!strat.elide_store_to_block_ptr_instructions) {
|
||||
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items);
|
||||
astgen.extra.appendSliceAssumeCapacity(multi_cases_payload.items);
|
||||
return parent_gz.indexToRef(switch_block);
|
||||
return indexToRef(switch_block);
|
||||
}
|
||||
|
||||
// There will necessarily be a store_to_block_ptr for
|
||||
@ -6003,7 +5998,7 @@ fn switchExpr(
|
||||
.lhs = block_scope.rl_ty_inst,
|
||||
.rhs = zir_datas[break_inst].@"break".operand,
|
||||
};
|
||||
zir_datas[break_inst].@"break".operand = parent_gz.indexToRef(store_inst);
|
||||
zir_datas[break_inst].@"break".operand = indexToRef(store_inst);
|
||||
} else {
|
||||
scalar_cases_payload.items[body_len_index] -= 1;
|
||||
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[0..extra_index]);
|
||||
@ -6045,7 +6040,7 @@ fn switchExpr(
|
||||
.lhs = block_scope.rl_ty_inst,
|
||||
.rhs = zir_datas[break_inst].@"break".operand,
|
||||
};
|
||||
zir_datas[break_inst].@"break".operand = parent_gz.indexToRef(store_inst);
|
||||
zir_datas[break_inst].@"break".operand = indexToRef(store_inst);
|
||||
} else {
|
||||
scalar_cases_payload.items[body_len_index] -= 1;
|
||||
astgen.extra.appendSliceAssumeCapacity(scalar_cases_payload.items[start_index..extra_index]);
|
||||
@ -6091,7 +6086,7 @@ fn switchExpr(
|
||||
.lhs = block_scope.rl_ty_inst,
|
||||
.rhs = zir_datas[break_inst].@"break".operand,
|
||||
};
|
||||
zir_datas[break_inst].@"break".operand = parent_gz.indexToRef(store_inst);
|
||||
zir_datas[break_inst].@"break".operand = indexToRef(store_inst);
|
||||
} else {
|
||||
assert(zir_datas[store_inst].bin.lhs == block_scope.rl_ptr);
|
||||
multi_cases_payload.items[body_len_index] -= 1;
|
||||
@ -6102,7 +6097,7 @@ fn switchExpr(
|
||||
}
|
||||
}
|
||||
|
||||
const block_ref = parent_gz.indexToRef(switch_block);
|
||||
const block_ref = indexToRef(switch_block);
|
||||
switch (rl) {
|
||||
.ref => return block_ref,
|
||||
else => return rvalue(parent_gz, rl, block_ref, switch_node),
|
||||
@ -6162,7 +6157,7 @@ fn switchExpr(
|
||||
}
|
||||
}
|
||||
|
||||
return parent_gz.indexToRef(switch_block);
|
||||
return indexToRef(switch_block);
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -6417,37 +6412,12 @@ fn multilineStringLiteral(
|
||||
node: ast.Node.Index,
|
||||
) InnerError!Zir.Inst.Ref {
|
||||
const astgen = gz.astgen;
|
||||
const tree = astgen.tree;
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
|
||||
const start = node_datas[node].lhs;
|
||||
const end = node_datas[node].rhs;
|
||||
|
||||
const gpa = gz.astgen.gpa;
|
||||
const string_bytes = &gz.astgen.string_bytes;
|
||||
const str_index = string_bytes.items.len;
|
||||
|
||||
// First line: do not append a newline.
|
||||
var tok_i = start;
|
||||
{
|
||||
const slice = tree.tokenSlice(tok_i);
|
||||
const line_bytes = slice[2 .. slice.len - 1];
|
||||
try string_bytes.appendSlice(gpa, line_bytes);
|
||||
tok_i += 1;
|
||||
}
|
||||
// Following lines: each line prepends a newline.
|
||||
while (tok_i <= end) : (tok_i += 1) {
|
||||
const slice = tree.tokenSlice(tok_i);
|
||||
const line_bytes = slice[2 .. slice.len - 1];
|
||||
try string_bytes.ensureCapacity(gpa, string_bytes.items.len + line_bytes.len + 1);
|
||||
string_bytes.appendAssumeCapacity('\n');
|
||||
string_bytes.appendSliceAssumeCapacity(line_bytes);
|
||||
}
|
||||
const str = try astgen.strLitNodeAsString(node);
|
||||
const result = try gz.add(.{
|
||||
.tag = .str,
|
||||
.data = .{ .str = .{
|
||||
.start = @intCast(u32, str_index),
|
||||
.len = @intCast(u32, string_bytes.items.len - str_index),
|
||||
.start = str.index,
|
||||
.len = str.len,
|
||||
} },
|
||||
});
|
||||
return rvalue(gz, rl, result, node);
|
||||
@ -6594,12 +6564,12 @@ fn floatLiteral(gz: *GenZir, rl: ResultLoc, node: ast.Node.Index) InnerError!Zir
|
||||
} else std.fmt.parseFloat(f128, bytes) catch |err| switch (err) {
|
||||
error.InvalidCharacter => unreachable, // validated by tokenizer
|
||||
};
|
||||
// If the value fits into a f32 without losing any precision, store it that way.
|
||||
// If the value fits into a f64 without losing any precision, store it that way.
|
||||
@setFloatMode(.Strict);
|
||||
const smaller_float = @floatCast(f32, float_number);
|
||||
const smaller_float = @floatCast(f64, float_number);
|
||||
const bigger_again: f128 = smaller_float;
|
||||
if (bigger_again == float_number) {
|
||||
const result = try gz.addFloat(smaller_float, node);
|
||||
const result = try gz.addFloat(smaller_float);
|
||||
return rvalue(gz, rl, result, node);
|
||||
}
|
||||
// We need to use 128 bits. Break the float into 4 u32 values so we can
|
||||
@ -6625,9 +6595,14 @@ fn asmExpr(
|
||||
const tree = astgen.tree;
|
||||
const main_tokens = tree.nodes.items(.main_token);
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
const node_tags = tree.nodes.items(.tag);
|
||||
const token_tags = tree.tokens.items(.tag);
|
||||
|
||||
const asm_source = try comptimeExpr(gz, scope, .{ .ty = .const_slice_u8_type }, full.ast.template);
|
||||
const asm_source = switch (node_tags[full.ast.template]) {
|
||||
.string_literal => try astgen.strLitAsString(main_tokens[full.ast.template]),
|
||||
.multiline_string_literal => try astgen.strLitNodeAsString(full.ast.template),
|
||||
else => return astgen.failNode(full.ast.template, "assembly code must use string literal syntax", .{}),
|
||||
};
|
||||
|
||||
// See https://github.com/ziglang/zig/issues/215 and related issues discussing
|
||||
// possible inline assembly improvements. Until then here is status quo AstGen
|
||||
@ -6757,7 +6732,7 @@ fn asmExpr(
|
||||
|
||||
const result = try gz.addAsm(.{
|
||||
.node = node,
|
||||
.asm_source = asm_source,
|
||||
.asm_source = asm_source.index,
|
||||
.is_volatile = full.volatile_token != null,
|
||||
.output_type_bits = output_type_bits,
|
||||
.outputs = outputs,
|
||||
@ -6861,7 +6836,7 @@ fn asRlPtr(
|
||||
const zir_datas = astgen.instructions.items(.data);
|
||||
try parent_zir.ensureUnusedCapacity(astgen.gpa, as_scope.instructions.items.len);
|
||||
for (as_scope.instructions.items) |src_inst| {
|
||||
if (parent_gz.indexToRef(src_inst) == as_scope.rl_ptr) continue;
|
||||
if (indexToRef(src_inst) == as_scope.rl_ptr) continue;
|
||||
if (zir_tags[src_inst] == .store_to_block_ptr) {
|
||||
if (zir_datas[src_inst].bin.lhs == as_scope.rl_ptr) continue;
|
||||
}
|
||||
@ -6992,10 +6967,10 @@ fn builtinCall(
|
||||
const str_lit_token = main_tokens[operand_node];
|
||||
const str = try astgen.strLitAsString(str_lit_token);
|
||||
const result = try gz.addStrTok(.import, str.index, str_lit_token);
|
||||
const gop = try astgen.imports.getOrPut(astgen.gpa, str.index);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = str_lit_token;
|
||||
}
|
||||
const gop = try astgen.imports.getOrPut(astgen.gpa, str.index);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = str_lit_token;
|
||||
}
|
||||
return rvalue(gz, rl, result, node);
|
||||
},
|
||||
.compile_log => {
|
||||
@ -8584,6 +8559,41 @@ fn strLitAsString(astgen: *AstGen, str_lit_token: ast.TokenIndex) !IndexSlice {
|
||||
}
|
||||
}
|
||||
|
||||
fn strLitNodeAsString(astgen: *AstGen, node: ast.Node.Index) !IndexSlice {
|
||||
const tree = astgen.tree;
|
||||
const node_datas = tree.nodes.items(.data);
|
||||
|
||||
const start = node_datas[node].lhs;
|
||||
const end = node_datas[node].rhs;
|
||||
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
const str_index = string_bytes.items.len;
|
||||
|
||||
// First line: do not append a newline.
|
||||
var tok_i = start;
|
||||
{
|
||||
const slice = tree.tokenSlice(tok_i);
|
||||
const line_bytes = slice[2 .. slice.len - 1];
|
||||
try string_bytes.appendSlice(gpa, line_bytes);
|
||||
tok_i += 1;
|
||||
}
|
||||
// Following lines: each line prepends a newline.
|
||||
while (tok_i <= end) : (tok_i += 1) {
|
||||
const slice = tree.tokenSlice(tok_i);
|
||||
const line_bytes = slice[2 .. slice.len - 1];
|
||||
try string_bytes.ensureCapacity(gpa, string_bytes.items.len + line_bytes.len + 1);
|
||||
string_bytes.appendAssumeCapacity('\n');
|
||||
string_bytes.appendSliceAssumeCapacity(line_bytes);
|
||||
}
|
||||
const len = string_bytes.items.len - str_index;
|
||||
try string_bytes.append(gpa, 0);
|
||||
return IndexSlice{
|
||||
.index = @intCast(u32, str_index),
|
||||
.len = @intCast(u32, len),
|
||||
};
|
||||
}
|
||||
|
||||
fn testNameString(astgen: *AstGen, str_lit_token: ast.TokenIndex) !u32 {
|
||||
const gpa = astgen.gpa;
|
||||
const string_bytes = &astgen.string_bytes;
|
||||
@ -8705,9 +8715,6 @@ const GenZir = struct {
|
||||
in_defer: bool,
|
||||
/// How decls created in this scope should be named.
|
||||
anon_name_strategy: Zir.Inst.NameStrategy = .anon,
|
||||
/// The end of special indexes. `Zir.Inst.Ref` subtracts against this number to convert
|
||||
/// to `Zir.Inst.Index`. The default here is correct if there are 0 parameters.
|
||||
ref_start_index: u32 = Zir.Inst.Ref.typed_value_map.len,
|
||||
/// The containing decl AST node.
|
||||
decl_node_index: ast.Node.Index,
|
||||
/// The containing decl line index, absolute.
|
||||
@ -8751,7 +8758,6 @@ const GenZir = struct {
|
||||
return .{
|
||||
.force_comptime = gz.force_comptime,
|
||||
.in_defer = gz.in_defer,
|
||||
.ref_start_index = gz.ref_start_index,
|
||||
.decl_node_index = gz.decl_node_index,
|
||||
.decl_line = gz.decl_line,
|
||||
.parent = scope,
|
||||
@ -8769,7 +8775,7 @@ const GenZir = struct {
|
||||
|
||||
fn refIsNoReturn(gz: GenZir, inst_ref: Zir.Inst.Ref) bool {
|
||||
if (inst_ref == .unreachable_value) return true;
|
||||
if (gz.refToIndex(inst_ref)) |inst_index| {
|
||||
if (refToIndex(inst_ref)) |inst_index| {
|
||||
return gz.astgen.instructions.items(.tag)[inst_index].isNoReturn();
|
||||
}
|
||||
return false;
|
||||
@ -8807,19 +8813,6 @@ const GenZir = struct {
|
||||
return gz.astgen.tree.firstToken(gz.decl_node_index);
|
||||
}
|
||||
|
||||
fn indexToRef(gz: GenZir, inst: Zir.Inst.Index) Zir.Inst.Ref {
|
||||
return @intToEnum(Zir.Inst.Ref, gz.ref_start_index + inst);
|
||||
}
|
||||
|
||||
fn refToIndex(gz: GenZir, inst: Zir.Inst.Ref) ?Zir.Inst.Index {
|
||||
const ref_int = @enumToInt(inst);
|
||||
if (ref_int >= gz.ref_start_index) {
|
||||
return ref_int - gz.ref_start_index;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
fn setBreakResultLoc(gz: *GenZir, parent_rl: AstGen.ResultLoc) void {
|
||||
// Depending on whether the result location is a pointer or value, different
|
||||
// ZIR needs to be generated. In the former case we rely on storing to the
|
||||
@ -8998,7 +8991,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
} else {
|
||||
try gz.astgen.extra.ensureUnusedCapacity(
|
||||
gpa,
|
||||
@ -9025,7 +9018,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9079,7 +9072,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
fn addCall(
|
||||
@ -9113,7 +9106,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
/// Note that this returns a `Zir.Inst.Index` not a ref.
|
||||
@ -9164,16 +9157,13 @@ const GenZir = struct {
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
astgen.string_bytes.appendSliceAssumeCapacity(mem.sliceAsBytes(limbs));
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
fn addFloat(gz: *GenZir, number: f32, src_node: ast.Node.Index) !Zir.Inst.Ref {
|
||||
fn addFloat(gz: *GenZir, number: f64) !Zir.Inst.Ref {
|
||||
return gz.add(.{
|
||||
.tag = .float,
|
||||
.data = .{ .float = .{
|
||||
.src_node = gz.nodeIndexToRelative(src_node),
|
||||
.number = number,
|
||||
} },
|
||||
.data = .{ .float = number },
|
||||
});
|
||||
}
|
||||
|
||||
@ -9215,7 +9205,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
fn addExtendedPayload(
|
||||
@ -9239,7 +9229,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
fn addExtendedMultiOp(
|
||||
@ -9272,7 +9262,7 @@ const GenZir = struct {
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
astgen.appendRefsAssumeCapacity(operands);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
fn addArrayTypeSentinel(
|
||||
@ -9298,7 +9288,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
fn addUnTok(
|
||||
@ -9457,7 +9447,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
fn addAsm(
|
||||
@ -9465,7 +9455,7 @@ const GenZir = struct {
|
||||
args: struct {
|
||||
/// Absolute node index. This function does the conversion to offset from Decl.
|
||||
node: ast.Node.Index,
|
||||
asm_source: Zir.Inst.Ref,
|
||||
asm_source: u32,
|
||||
output_type_bits: u32,
|
||||
is_volatile: bool,
|
||||
outputs: []const Zir.Inst.Asm.Output,
|
||||
@ -9515,7 +9505,7 @@ const GenZir = struct {
|
||||
} },
|
||||
});
|
||||
gz.instructions.appendAssumeCapacity(new_index);
|
||||
return gz.indexToRef(new_index);
|
||||
return indexToRef(new_index);
|
||||
}
|
||||
|
||||
/// Note that this returns a `Zir.Inst.Index` not a ref.
|
||||
@ -9693,7 +9683,7 @@ const GenZir = struct {
|
||||
}
|
||||
|
||||
fn add(gz: *GenZir, inst: Zir.Inst) !Zir.Inst.Ref {
|
||||
return gz.indexToRef(try gz.addAsIndex(inst));
|
||||
return indexToRef(try gz.addAsIndex(inst));
|
||||
}
|
||||
|
||||
fn addAsIndex(gz: *GenZir, inst: Zir.Inst) !Zir.Inst.Index {
|
||||
@ -9840,3 +9830,18 @@ fn advanceSourceCursor(astgen: *AstGen, source: []const u8, end: usize) void {
|
||||
astgen.source_line = line;
|
||||
astgen.source_column = column;
|
||||
}
|
||||
|
||||
const ref_start_index: u32 = Zir.Inst.Ref.typed_value_map.len;
|
||||
|
||||
fn indexToRef(inst: Zir.Inst.Index) Zir.Inst.Ref {
|
||||
return @intToEnum(Zir.Inst.Ref, ref_start_index + inst);
|
||||
}
|
||||
|
||||
fn refToIndex(inst: Zir.Inst.Ref) ?Zir.Inst.Index {
|
||||
const ref_int = @enumToInt(inst);
|
||||
if (ref_int >= ref_start_index) {
|
||||
return ref_int - ref_start_index;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const Compilation = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
@ -13,7 +14,7 @@ const target_util = @import("target.zig");
|
||||
const Package = @import("Package.zig");
|
||||
const link = @import("link.zig");
|
||||
const trace = @import("tracy.zig").trace;
|
||||
const liveness = @import("liveness.zig");
|
||||
const Liveness = @import("Liveness.zig");
|
||||
const build_options = @import("build_options");
|
||||
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
|
||||
const glibc = @import("glibc.zig");
|
||||
@ -148,7 +149,7 @@ emit_docs: ?EmitLoc,
|
||||
work_queue_wait_group: WaitGroup,
|
||||
astgen_wait_group: WaitGroup,
|
||||
|
||||
pub const InnerError = Module.InnerError;
|
||||
pub const SemaError = Module.SemaError;
|
||||
|
||||
pub const CRTFile = struct {
|
||||
lock: Cache.Lock,
|
||||
@ -168,8 +169,10 @@ pub const CSourceFile = struct {
|
||||
};
|
||||
|
||||
const Job = union(enum) {
|
||||
/// Write the machine code for a Decl to the output file.
|
||||
/// Write the constant value for a Decl to the output file.
|
||||
codegen_decl: *Module.Decl,
|
||||
/// Write the machine code for a function to the output file.
|
||||
codegen_func: *Module.Fn,
|
||||
/// Render the .h file snippet for the Decl.
|
||||
emit_h_decl: *Module.Decl,
|
||||
/// The Decl needs to be analyzed and possibly export itself.
|
||||
@ -907,7 +910,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
// comptime conditions
|
||||
((build_options.have_llvm and comptime std.Target.current.isDarwin()) and
|
||||
// runtime conditions
|
||||
(use_lld and std.builtin.os.tag == .macos and options.target.isDarwin()));
|
||||
(use_lld and builtin.os.tag == .macos and options.target.isDarwin()));
|
||||
|
||||
const sysroot = blk: {
|
||||
if (options.sysroot) |sysroot| {
|
||||
@ -1922,6 +1925,7 @@ pub fn getCompileLogOutput(self: *Compilation) []const u8 {
|
||||
}
|
||||
|
||||
pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void {
|
||||
const gpa = self.gpa;
|
||||
// If the terminal is dumb, we dont want to show the user all the
|
||||
// output.
|
||||
var progress: std.Progress = .{ .dont_print_on_dumb = true };
|
||||
@ -2003,33 +2007,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
@panic("sadly stage2 is omitted from this build to save memory on the CI server");
|
||||
const module = self.bin_file.options.module.?;
|
||||
assert(decl.has_tv);
|
||||
if (decl.val.castTag(.function)) |payload| {
|
||||
const func = payload.data;
|
||||
switch (func.state) {
|
||||
.queued => module.analyzeFnBody(decl, func) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
assert(func.state != .in_progress);
|
||||
continue;
|
||||
},
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
},
|
||||
.in_progress => unreachable,
|
||||
.inline_only => unreachable, // don't queue work for this
|
||||
.sema_failure, .dependency_failure => continue,
|
||||
.success => {},
|
||||
}
|
||||
// Here we tack on additional allocations to the Decl's arena. The allocations
|
||||
// are lifetime annotations in the ZIR.
|
||||
var decl_arena = decl.value_arena.?.promote(module.gpa);
|
||||
defer decl.value_arena.?.* = decl_arena.state;
|
||||
log.debug("analyze liveness of {s}", .{decl.name});
|
||||
try liveness.analyze(module.gpa, &decl_arena.allocator, func.body);
|
||||
|
||||
if (std.builtin.mode == .Debug and self.verbose_air) {
|
||||
func.dump(module.*);
|
||||
}
|
||||
}
|
||||
|
||||
assert(decl.ty.hasCodeGenBits());
|
||||
|
||||
self.bin_file.updateDecl(module, decl) catch |err| switch (err) {
|
||||
@ -2039,9 +2016,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
continue;
|
||||
},
|
||||
else => {
|
||||
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.count() + 1);
|
||||
try module.failed_decls.ensureUnusedCapacity(gpa, 1);
|
||||
module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
|
||||
module.gpa,
|
||||
gpa,
|
||||
decl.srcLoc(),
|
||||
"unable to codegen: {s}",
|
||||
.{@errorName(err)},
|
||||
@ -2052,6 +2029,72 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
};
|
||||
},
|
||||
},
|
||||
.codegen_func => |func| switch (func.owner_decl.analysis) {
|
||||
.unreferenced => unreachable,
|
||||
.in_progress => unreachable,
|
||||
.outdated => unreachable,
|
||||
|
||||
.file_failure,
|
||||
.sema_failure,
|
||||
.codegen_failure,
|
||||
.dependency_failure,
|
||||
.sema_failure_retryable,
|
||||
=> continue,
|
||||
|
||||
.complete, .codegen_failure_retryable => {
|
||||
if (build_options.omit_stage2)
|
||||
@panic("sadly stage2 is omitted from this build to save memory on the CI server");
|
||||
switch (func.state) {
|
||||
.sema_failure, .dependency_failure => continue,
|
||||
.queued => {},
|
||||
.in_progress => unreachable,
|
||||
.inline_only => unreachable, // don't queue work for this
|
||||
.success => unreachable, // don't queue it twice
|
||||
}
|
||||
|
||||
const module = self.bin_file.options.module.?;
|
||||
const decl = func.owner_decl;
|
||||
|
||||
var air = module.analyzeFnBody(decl, func) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
assert(func.state != .in_progress);
|
||||
continue;
|
||||
},
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
};
|
||||
defer air.deinit(gpa);
|
||||
|
||||
log.debug("analyze liveness of {s}", .{decl.name});
|
||||
var liveness = try Liveness.analyze(gpa, air, decl.namespace.file_scope.zir);
|
||||
defer liveness.deinit(gpa);
|
||||
|
||||
if (builtin.mode == .Debug and self.verbose_air) {
|
||||
std.debug.print("# Begin Function AIR: {s}:\n", .{decl.name});
|
||||
@import("print_air.zig").dump(gpa, air, decl.namespace.file_scope.zir, liveness);
|
||||
std.debug.print("# End Function AIR: {s}:\n", .{decl.name});
|
||||
}
|
||||
|
||||
self.bin_file.updateFunc(module, func, air, liveness) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.AnalysisFail => {
|
||||
decl.analysis = .codegen_failure;
|
||||
continue;
|
||||
},
|
||||
else => {
|
||||
try module.failed_decls.ensureUnusedCapacity(gpa, 1);
|
||||
module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
|
||||
gpa,
|
||||
decl.srcLoc(),
|
||||
"unable to codegen: {s}",
|
||||
.{@errorName(err)},
|
||||
));
|
||||
decl.analysis = .codegen_failure_retryable;
|
||||
continue;
|
||||
},
|
||||
};
|
||||
continue;
|
||||
},
|
||||
},
|
||||
.emit_h_decl => |decl| switch (decl.analysis) {
|
||||
.unreferenced => unreachable,
|
||||
.in_progress => unreachable,
|
||||
@ -2070,7 +2113,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
@panic("sadly stage2 is omitted from this build to save memory on the CI server");
|
||||
const module = self.bin_file.options.module.?;
|
||||
const emit_h = module.emit_h.?;
|
||||
_ = try emit_h.decl_table.getOrPut(module.gpa, decl);
|
||||
_ = try emit_h.decl_table.getOrPut(gpa, decl);
|
||||
const decl_emit_h = decl.getEmitH(module);
|
||||
const fwd_decl = &decl_emit_h.fwd_decl;
|
||||
fwd_decl.shrinkRetainingCapacity(0);
|
||||
@ -2079,7 +2122,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
.module = module,
|
||||
.error_msg = null,
|
||||
.decl = decl,
|
||||
.fwd_decl = fwd_decl.toManaged(module.gpa),
|
||||
.fwd_decl = fwd_decl.toManaged(gpa),
|
||||
// we don't want to emit optionals and error unions to headers since they have no ABI
|
||||
.typedefs = undefined,
|
||||
};
|
||||
@ -2087,14 +2130,14 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
|
||||
c_codegen.genHeader(&dg) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
try emit_h.failed_decls.put(module.gpa, decl, dg.error_msg.?);
|
||||
try emit_h.failed_decls.put(gpa, decl, dg.error_msg.?);
|
||||
continue;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
fwd_decl.* = dg.fwd_decl.moveToUnmanaged();
|
||||
fwd_decl.shrinkAndFree(module.gpa, fwd_decl.items.len);
|
||||
fwd_decl.shrinkAndFree(gpa, fwd_decl.items.len);
|
||||
},
|
||||
},
|
||||
.analyze_decl => |decl| {
|
||||
@ -2111,9 +2154,9 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
@panic("sadly stage2 is omitted from this build to save memory on the CI server");
|
||||
const module = self.bin_file.options.module.?;
|
||||
self.bin_file.updateDeclLineNumber(module, decl) catch |err| {
|
||||
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.count() + 1);
|
||||
try module.failed_decls.ensureUnusedCapacity(gpa, 1);
|
||||
module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create(
|
||||
module.gpa,
|
||||
gpa,
|
||||
decl.srcLoc(),
|
||||
"unable to update line number: {s}",
|
||||
.{@errorName(err)},
|
||||
@ -3147,7 +3190,7 @@ pub fn addCCArgs(
|
||||
try argv.appendSlice(comp.clang_argv);
|
||||
}
|
||||
|
||||
fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) InnerError {
|
||||
fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) SemaError {
|
||||
@setCold(true);
|
||||
const err_msg = blk: {
|
||||
const msg = try std.fmt.allocPrint(comp.gpa, format, args);
|
||||
@ -3168,7 +3211,7 @@ fn failCObjWithOwnedErrorMsg(
|
||||
comp: *Compilation,
|
||||
c_object: *CObject,
|
||||
err_msg: *CObject.ErrorMsg,
|
||||
) InnerError {
|
||||
) SemaError {
|
||||
@setCold(true);
|
||||
{
|
||||
const lock = comp.mutex.acquire();
|
||||
|
||||
602
src/Liveness.zig
Normal file
602
src/Liveness.zig
Normal file
@ -0,0 +1,602 @@
|
||||
//! For each AIR instruction, we want to know:
|
||||
//! * Is the instruction unreferenced (e.g. dies immediately)?
|
||||
//! * For each of its operands, does the operand die with this instruction (e.g. is
|
||||
//! this the last reference to it)?
|
||||
//! Some instructions are special, such as:
|
||||
//! * Conditional Branches
|
||||
//! * Switch Branches
|
||||
const Liveness = @This();
|
||||
const std = @import("std");
|
||||
const trace = @import("tracy.zig").trace;
|
||||
const log = std.log.scoped(.liveness);
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Air = @import("Air.zig");
|
||||
const Zir = @import("Zir.zig");
|
||||
const Log2Int = std.math.Log2Int;
|
||||
|
||||
/// This array is split into sets of 4 bits per AIR instruction.
|
||||
/// The MSB (0bX000) is whether the instruction is unreferenced.
|
||||
/// The LSB (0b000X) is the first operand, and so on, up to 3 operands. A set bit means the
|
||||
/// operand dies after this instruction.
|
||||
/// Instructions which need more data to track liveness have special handling via the
|
||||
/// `special` table.
|
||||
tomb_bits: []usize,
|
||||
/// Sparse table of specially handled instructions. The value is an index into the `extra`
|
||||
/// array. The meaning of the data depends on the AIR tag.
|
||||
/// * `cond_br` - points to a `CondBr` in `extra` at this index.
|
||||
/// * `switch_br` - points to a `SwitchBr` in `extra` at this index.
|
||||
/// * `asm`, `call` - the value is a set of bits which are the extra tomb bits of operands.
|
||||
/// The main tomb bits are still used and the extra ones are starting with the lsb of the
|
||||
/// value here.
|
||||
special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
|
||||
/// Auxilliary data. The way this data is interpreted is determined contextually.
|
||||
extra: []const u32,
|
||||
|
||||
/// Trailing is the set of instructions whose lifetimes end at the start of the then branch,
|
||||
/// followed by the set of instructions whose lifetimes end at the start of the else branch.
|
||||
pub const CondBr = struct {
|
||||
then_death_count: u32,
|
||||
else_death_count: u32,
|
||||
};
|
||||
|
||||
/// Trailing is:
|
||||
/// * For each case in the same order as in the AIR:
|
||||
/// - case_death_count: u32
|
||||
/// - Air.Inst.Index for each `case_death_count`: set of instructions whose lifetimes
|
||||
/// end at the start of this case.
|
||||
/// * Air.Inst.Index for each `else_death_count`: set of instructions whose lifetimes
|
||||
/// end at the start of the else case.
|
||||
pub const SwitchBr = struct {
|
||||
else_death_count: u32,
|
||||
};
|
||||
|
||||
pub fn analyze(gpa: *Allocator, air: Air, zir: Zir) Allocator.Error!Liveness {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
var a: Analysis = .{
|
||||
.gpa = gpa,
|
||||
.air = air,
|
||||
.table = .{},
|
||||
.tomb_bits = try gpa.alloc(
|
||||
usize,
|
||||
(air.instructions.len * bpi + @bitSizeOf(usize) - 1) / @bitSizeOf(usize),
|
||||
),
|
||||
.extra = .{},
|
||||
.special = .{},
|
||||
.zir = &zir,
|
||||
};
|
||||
errdefer gpa.free(a.tomb_bits);
|
||||
errdefer a.special.deinit(gpa);
|
||||
defer a.extra.deinit(gpa);
|
||||
defer a.table.deinit(gpa);
|
||||
|
||||
std.mem.set(usize, a.tomb_bits, 0);
|
||||
|
||||
const main_body = air.getMainBody();
|
||||
try a.table.ensureTotalCapacity(gpa, @intCast(u32, main_body.len));
|
||||
try analyzeWithContext(&a, null, main_body);
|
||||
return Liveness{
|
||||
.tomb_bits = a.tomb_bits,
|
||||
.special = a.special,
|
||||
.extra = a.extra.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getTombBits(l: Liveness, inst: Air.Inst.Index) Bpi {
|
||||
const usize_index = (inst * bpi) / @bitSizeOf(usize);
|
||||
return @truncate(Bpi, l.tomb_bits[usize_index] >>
|
||||
@intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi));
|
||||
}
|
||||
|
||||
pub fn isUnused(l: Liveness, inst: Air.Inst.Index) bool {
|
||||
const usize_index = (inst * bpi) / @bitSizeOf(usize);
|
||||
const mask = @as(usize, 1) <<
|
||||
@intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi + (bpi - 1));
|
||||
return (l.tomb_bits[usize_index] & mask) != 0;
|
||||
}
|
||||
|
||||
pub fn operandDies(l: Liveness, inst: Air.Inst.Index, operand: OperandInt) bool {
|
||||
assert(operand < bpi - 1);
|
||||
const usize_index = (inst * bpi) / @bitSizeOf(usize);
|
||||
const mask = @as(usize, 1) <<
|
||||
@intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi + operand);
|
||||
return (l.tomb_bits[usize_index] & mask) != 0;
|
||||
}
|
||||
|
||||
pub fn clearOperandDeath(l: Liveness, inst: Air.Inst.Index, operand: OperandInt) void {
|
||||
assert(operand < bpi - 1);
|
||||
const usize_index = (inst * bpi) / @bitSizeOf(usize);
|
||||
const mask = @as(usize, 1) <<
|
||||
@intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi + operand);
|
||||
l.tomb_bits[usize_index] &= ~mask;
|
||||
}
|
||||
|
||||
/// Higher level API.
|
||||
pub const CondBrSlices = struct {
|
||||
then_deaths: []const Air.Inst.Index,
|
||||
else_deaths: []const Air.Inst.Index,
|
||||
};
|
||||
|
||||
pub fn getCondBr(l: Liveness, inst: Air.Inst.Index) CondBrSlices {
|
||||
var index: usize = l.special.get(inst) orelse return .{
|
||||
.then_deaths = &.{},
|
||||
.else_deaths = &.{},
|
||||
};
|
||||
const then_death_count = l.extra[index];
|
||||
index += 1;
|
||||
const else_death_count = l.extra[index];
|
||||
index += 1;
|
||||
const then_deaths = l.extra[index..][0..then_death_count];
|
||||
index += then_death_count;
|
||||
return .{
|
||||
.then_deaths = then_deaths,
|
||||
.else_deaths = l.extra[index..][0..else_death_count],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(l: *Liveness, gpa: *Allocator) void {
|
||||
gpa.free(l.tomb_bits);
|
||||
gpa.free(l.extra);
|
||||
l.special.deinit(gpa);
|
||||
l.* = undefined;
|
||||
}
|
||||
|
||||
/// How many tomb bits per AIR instruction.
|
||||
pub const bpi = 4;
|
||||
pub const Bpi = std.meta.Int(.unsigned, bpi);
|
||||
pub const OperandInt = std.math.Log2Int(Bpi);
|
||||
|
||||
/// In-progress data; on successful analysis converted into `Liveness`.
|
||||
const Analysis = struct {
|
||||
gpa: *Allocator,
|
||||
air: Air,
|
||||
table: std.AutoHashMapUnmanaged(Air.Inst.Index, void),
|
||||
tomb_bits: []usize,
|
||||
special: std.AutoHashMapUnmanaged(Air.Inst.Index, u32),
|
||||
extra: std.ArrayListUnmanaged(u32),
|
||||
zir: *const Zir,
|
||||
|
||||
fn storeTombBits(a: *Analysis, inst: Air.Inst.Index, tomb_bits: Bpi) void {
|
||||
const usize_index = (inst * bpi) / @bitSizeOf(usize);
|
||||
a.tomb_bits[usize_index] |= @as(usize, tomb_bits) <<
|
||||
@intCast(Log2Int(usize), (inst % (@bitSizeOf(usize) / bpi)) * bpi);
|
||||
}
|
||||
|
||||
fn addExtra(a: *Analysis, extra: anytype) Allocator.Error!u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
try a.extra.ensureUnusedCapacity(a.gpa, fields.len);
|
||||
return addExtraAssumeCapacity(a, extra);
|
||||
}
|
||||
|
||||
fn addExtraAssumeCapacity(a: *Analysis, extra: anytype) u32 {
|
||||
const fields = std.meta.fields(@TypeOf(extra));
|
||||
const result = @intCast(u32, a.extra.items.len);
|
||||
inline for (fields) |field| {
|
||||
a.extra.appendAssumeCapacity(switch (field.field_type) {
|
||||
u32 => @field(extra, field.name),
|
||||
else => @compileError("bad field type"),
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
fn analyzeWithContext(
|
||||
a: *Analysis,
|
||||
new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
|
||||
body: []const Air.Inst.Index,
|
||||
) Allocator.Error!void {
|
||||
var i: usize = body.len;
|
||||
|
||||
if (new_set) |ns| {
|
||||
// We are only interested in doing this for instructions which are born
|
||||
// before a conditional branch, so after obtaining the new set for
|
||||
// each branch we prune the instructions which were born within.
|
||||
while (i != 0) {
|
||||
i -= 1;
|
||||
const inst = body[i];
|
||||
_ = ns.remove(inst);
|
||||
try analyzeInst(a, new_set, inst);
|
||||
}
|
||||
} else {
|
||||
while (i != 0) {
|
||||
i -= 1;
|
||||
const inst = body[i];
|
||||
try analyzeInst(a, new_set, inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn analyzeInst(
|
||||
a: *Analysis,
|
||||
new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
|
||||
inst: Air.Inst.Index,
|
||||
) Allocator.Error!void {
|
||||
const gpa = a.gpa;
|
||||
const table = &a.table;
|
||||
const inst_tags = a.air.instructions.items(.tag);
|
||||
const inst_datas = a.air.instructions.items(.data);
|
||||
|
||||
// No tombstone for this instruction means it is never referenced,
|
||||
// and its birth marks its own death. Very metal 🤘
|
||||
const main_tomb = !table.contains(inst);
|
||||
|
||||
switch (inst_tags[inst]) {
|
||||
.add,
|
||||
.addwrap,
|
||||
.sub,
|
||||
.subwrap,
|
||||
.mul,
|
||||
.mulwrap,
|
||||
.div,
|
||||
.bit_and,
|
||||
.bit_or,
|
||||
.xor,
|
||||
.cmp_lt,
|
||||
.cmp_lte,
|
||||
.cmp_eq,
|
||||
.cmp_gte,
|
||||
.cmp_gt,
|
||||
.cmp_neq,
|
||||
.bool_and,
|
||||
.bool_or,
|
||||
.store,
|
||||
=> {
|
||||
const o = inst_datas[inst].bin_op;
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ o.lhs, o.rhs, .none });
|
||||
},
|
||||
|
||||
.arg,
|
||||
.alloc,
|
||||
.constant,
|
||||
.const_ty,
|
||||
.breakpoint,
|
||||
.dbg_stmt,
|
||||
.varptr,
|
||||
.unreach,
|
||||
=> return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
|
||||
|
||||
.not,
|
||||
.bitcast,
|
||||
.load,
|
||||
.ref,
|
||||
.floatcast,
|
||||
.intcast,
|
||||
.optional_payload,
|
||||
.optional_payload_ptr,
|
||||
.wrap_optional,
|
||||
.unwrap_errunion_payload,
|
||||
.unwrap_errunion_err,
|
||||
.unwrap_errunion_payload_ptr,
|
||||
.unwrap_errunion_err_ptr,
|
||||
.wrap_errunion_payload,
|
||||
.wrap_errunion_err,
|
||||
=> {
|
||||
const o = inst_datas[inst].ty_op;
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });
|
||||
},
|
||||
|
||||
.is_null,
|
||||
.is_non_null,
|
||||
.is_null_ptr,
|
||||
.is_non_null_ptr,
|
||||
.is_err,
|
||||
.is_non_err,
|
||||
.is_err_ptr,
|
||||
.is_non_err_ptr,
|
||||
.ptrtoint,
|
||||
.ret,
|
||||
=> {
|
||||
const operand = inst_datas[inst].un_op;
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ operand, .none, .none });
|
||||
},
|
||||
|
||||
.call => {
|
||||
const inst_data = inst_datas[inst].pl_op;
|
||||
const callee = inst_data.operand;
|
||||
const extra = a.air.extraData(Air.Call, inst_data.payload);
|
||||
const args = @bitCast([]const Air.Inst.Ref, a.air.extra[extra.end..][0..extra.data.args_len]);
|
||||
if (args.len <= bpi - 2) {
|
||||
var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
|
||||
buf[0] = callee;
|
||||
std.mem.copy(Air.Inst.Ref, buf[1..], args);
|
||||
return trackOperands(a, new_set, inst, main_tomb, buf);
|
||||
}
|
||||
var extra_tombs: ExtraTombs = .{
|
||||
.analysis = a,
|
||||
.new_set = new_set,
|
||||
.inst = inst,
|
||||
.main_tomb = main_tomb,
|
||||
};
|
||||
try extra_tombs.feed(callee);
|
||||
for (args) |arg| {
|
||||
try extra_tombs.feed(arg);
|
||||
}
|
||||
return extra_tombs.finish();
|
||||
},
|
||||
.struct_field_ptr => {
|
||||
const extra = a.air.extraData(Air.StructField, inst_datas[inst].ty_pl.payload).data;
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ extra.struct_ptr, .none, .none });
|
||||
},
|
||||
.br => {
|
||||
const br = inst_datas[inst].br;
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ br.operand, .none, .none });
|
||||
},
|
||||
.assembly => {
|
||||
const extra = a.air.extraData(Air.Asm, inst_datas[inst].ty_pl.payload);
|
||||
const extended = a.zir.instructions.items(.data)[extra.data.zir_index].extended;
|
||||
const outputs_len = @truncate(u5, extended.small);
|
||||
const inputs_len = @truncate(u5, extended.small >> 5);
|
||||
const outputs = @bitCast([]const Air.Inst.Ref, a.air.extra[extra.end..][0..outputs_len]);
|
||||
const args = @bitCast([]const Air.Inst.Ref, a.air.extra[extra.end + outputs.len ..][0..inputs_len]);
|
||||
if (outputs.len + args.len <= bpi - 1) {
|
||||
var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1);
|
||||
std.mem.copy(Air.Inst.Ref, &buf, outputs);
|
||||
std.mem.copy(Air.Inst.Ref, buf[outputs.len..], args);
|
||||
return trackOperands(a, new_set, inst, main_tomb, buf);
|
||||
}
|
||||
var extra_tombs: ExtraTombs = .{
|
||||
.analysis = a,
|
||||
.new_set = new_set,
|
||||
.inst = inst,
|
||||
.main_tomb = main_tomb,
|
||||
};
|
||||
for (outputs) |output| {
|
||||
try extra_tombs.feed(output);
|
||||
}
|
||||
for (args) |arg| {
|
||||
try extra_tombs.feed(arg);
|
||||
}
|
||||
return extra_tombs.finish();
|
||||
},
|
||||
.block => {
|
||||
const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
|
||||
const body = a.air.extra[extra.end..][0..extra.data.body_len];
|
||||
try analyzeWithContext(a, new_set, body);
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none });
|
||||
},
|
||||
.loop => {
|
||||
const extra = a.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
|
||||
const body = a.air.extra[extra.end..][0..extra.data.body_len];
|
||||
try analyzeWithContext(a, new_set, body);
|
||||
return; // Loop has no operands and it is always unreferenced.
|
||||
},
|
||||
.cond_br => {
|
||||
// Each death that occurs inside one branch, but not the other, needs
|
||||
// to be added as a death immediately upon entering the other branch.
|
||||
const inst_data = inst_datas[inst].pl_op;
|
||||
const condition = inst_data.operand;
|
||||
const extra = a.air.extraData(Air.CondBr, inst_data.payload);
|
||||
const then_body = a.air.extra[extra.end..][0..extra.data.then_body_len];
|
||||
const else_body = a.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
|
||||
|
||||
var then_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
|
||||
defer then_table.deinit(gpa);
|
||||
try analyzeWithContext(a, &then_table, then_body);
|
||||
|
||||
// Reset the table back to its state from before the branch.
|
||||
{
|
||||
var it = then_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
assert(table.remove(key.*));
|
||||
}
|
||||
}
|
||||
|
||||
var else_table: std.AutoHashMapUnmanaged(Air.Inst.Index, void) = .{};
|
||||
defer else_table.deinit(gpa);
|
||||
try analyzeWithContext(a, &else_table, else_body);
|
||||
|
||||
var then_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);
|
||||
defer then_entry_deaths.deinit();
|
||||
var else_entry_deaths = std.ArrayList(Air.Inst.Index).init(gpa);
|
||||
defer else_entry_deaths.deinit();
|
||||
|
||||
{
|
||||
var it = else_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
const else_death = key.*;
|
||||
if (!then_table.contains(else_death)) {
|
||||
try then_entry_deaths.append(else_death);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This loop is the same, except it's for the then branch, and it additionally
|
||||
// has to put its items back into the table to undo the reset.
|
||||
{
|
||||
var it = then_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
const then_death = key.*;
|
||||
if (!else_table.contains(then_death)) {
|
||||
try else_entry_deaths.append(then_death);
|
||||
}
|
||||
try table.put(gpa, then_death, {});
|
||||
}
|
||||
}
|
||||
// Now we have to correctly populate new_set.
|
||||
if (new_set) |ns| {
|
||||
try ns.ensureCapacity(gpa, @intCast(u32, ns.count() + then_table.count() + else_table.count()));
|
||||
var it = then_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
_ = ns.putAssumeCapacity(key.*, {});
|
||||
}
|
||||
it = else_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
_ = ns.putAssumeCapacity(key.*, {});
|
||||
}
|
||||
}
|
||||
const then_death_count = @intCast(u32, then_entry_deaths.items.len);
|
||||
const else_death_count = @intCast(u32, else_entry_deaths.items.len);
|
||||
|
||||
try a.extra.ensureUnusedCapacity(gpa, std.meta.fields(Air.CondBr).len +
|
||||
then_death_count + else_death_count);
|
||||
const extra_index = a.addExtraAssumeCapacity(CondBr{
|
||||
.then_death_count = then_death_count,
|
||||
.else_death_count = else_death_count,
|
||||
});
|
||||
a.extra.appendSliceAssumeCapacity(then_entry_deaths.items);
|
||||
a.extra.appendSliceAssumeCapacity(else_entry_deaths.items);
|
||||
try a.special.put(gpa, inst, extra_index);
|
||||
|
||||
// Continue on with the instruction analysis. The following code will find the condition
|
||||
// instruction, and the deaths flag for the CondBr instruction will indicate whether the
|
||||
// condition's lifetime ends immediately before entering any branch.
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });
|
||||
},
|
||||
.switch_br => {
|
||||
const pl_op = inst_datas[inst].pl_op;
|
||||
const condition = pl_op.operand;
|
||||
const switch_br = a.air.extraData(Air.SwitchBr, pl_op.payload);
|
||||
|
||||
const Table = std.AutoHashMapUnmanaged(Air.Inst.Index, void);
|
||||
const case_tables = try gpa.alloc(Table, switch_br.data.cases_len + 1); // +1 for else
|
||||
defer gpa.free(case_tables);
|
||||
|
||||
std.mem.set(Table, case_tables, .{});
|
||||
defer for (case_tables) |*ct| ct.deinit(gpa);
|
||||
|
||||
var air_extra_index: usize = switch_br.end;
|
||||
for (case_tables[0..switch_br.data.cases_len]) |*case_table| {
|
||||
const case = a.air.extraData(Air.SwitchBr.Case, air_extra_index);
|
||||
const case_body = a.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
|
||||
air_extra_index = case.end + case.data.items_len + case_body.len;
|
||||
try analyzeWithContext(a, case_table, case_body);
|
||||
|
||||
// Reset the table back to its state from before the case.
|
||||
var it = case_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
assert(table.remove(key.*));
|
||||
}
|
||||
}
|
||||
{ // else
|
||||
const else_table = &case_tables[case_tables.len - 1];
|
||||
const else_body = a.air.extra[air_extra_index..][0..switch_br.data.else_body_len];
|
||||
try analyzeWithContext(a, else_table, else_body);
|
||||
|
||||
// Reset the table back to its state from before the case.
|
||||
var it = else_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
assert(table.remove(key.*));
|
||||
}
|
||||
}
|
||||
|
||||
const List = std.ArrayListUnmanaged(Air.Inst.Index);
|
||||
const case_deaths = try gpa.alloc(List, case_tables.len); // includes else
|
||||
defer gpa.free(case_deaths);
|
||||
|
||||
std.mem.set(List, case_deaths, .{});
|
||||
defer for (case_deaths) |*cd| cd.deinit(gpa);
|
||||
|
||||
var total_deaths: u32 = 0;
|
||||
for (case_tables) |*ct, i| {
|
||||
total_deaths += ct.count();
|
||||
var it = ct.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
const case_death = key.*;
|
||||
for (case_tables) |*ct_inner, j| {
|
||||
if (i == j) continue;
|
||||
if (!ct_inner.contains(case_death)) {
|
||||
// instruction is not referenced in this case
|
||||
try case_deaths[j].append(gpa, case_death);
|
||||
}
|
||||
}
|
||||
// undo resetting the table
|
||||
try table.put(gpa, case_death, {});
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have to correctly populate new_set.
|
||||
if (new_set) |ns| {
|
||||
try ns.ensureUnusedCapacity(gpa, total_deaths);
|
||||
for (case_tables) |*ct| {
|
||||
var it = ct.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
_ = ns.putAssumeCapacity(key.*, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const else_death_count = @intCast(u32, case_deaths[case_deaths.len - 1].items.len);
|
||||
const extra_index = try a.addExtra(SwitchBr{
|
||||
.else_death_count = else_death_count,
|
||||
});
|
||||
for (case_deaths[0 .. case_deaths.len - 1]) |*cd| {
|
||||
const case_death_count = @intCast(u32, cd.items.len);
|
||||
try a.extra.ensureUnusedCapacity(gpa, 1 + case_death_count + else_death_count);
|
||||
a.extra.appendAssumeCapacity(case_death_count);
|
||||
a.extra.appendSliceAssumeCapacity(cd.items);
|
||||
}
|
||||
a.extra.appendSliceAssumeCapacity(case_deaths[case_deaths.len - 1].items);
|
||||
try a.special.put(gpa, inst, extra_index);
|
||||
|
||||
return trackOperands(a, new_set, inst, main_tomb, .{ condition, .none, .none });
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn trackOperands(
|
||||
a: *Analysis,
|
||||
new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
|
||||
inst: Air.Inst.Index,
|
||||
main_tomb: bool,
|
||||
operands: [bpi - 1]Air.Inst.Ref,
|
||||
) Allocator.Error!void {
|
||||
const table = &a.table;
|
||||
const gpa = a.gpa;
|
||||
|
||||
var tomb_bits: Bpi = @boolToInt(main_tomb);
|
||||
var i = operands.len;
|
||||
|
||||
while (i > 0) {
|
||||
i -= 1;
|
||||
tomb_bits <<= 1;
|
||||
const op_int = @enumToInt(operands[i]);
|
||||
if (op_int < Air.Inst.Ref.typed_value_map.len) continue;
|
||||
const operand: Air.Inst.Index = op_int - @intCast(u32, Air.Inst.Ref.typed_value_map.len);
|
||||
const prev = try table.fetchPut(gpa, operand, {});
|
||||
if (prev == null) {
|
||||
// Death.
|
||||
tomb_bits |= 1;
|
||||
if (new_set) |ns| try ns.putNoClobber(gpa, operand, {});
|
||||
}
|
||||
}
|
||||
a.storeTombBits(inst, tomb_bits);
|
||||
}
|
||||
|
||||
const ExtraTombs = struct {
|
||||
analysis: *Analysis,
|
||||
new_set: ?*std.AutoHashMapUnmanaged(Air.Inst.Index, void),
|
||||
inst: Air.Inst.Index,
|
||||
main_tomb: bool,
|
||||
bit_index: usize = 0,
|
||||
tomb_bits: Bpi = 0,
|
||||
big_tomb_bits: u32 = 0,
|
||||
|
||||
fn feed(et: *ExtraTombs, op_ref: Air.Inst.Ref) !void {
|
||||
const this_bit_index = et.bit_index;
|
||||
assert(this_bit_index < 32); // TODO mechanism for when there are greater than 32 operands
|
||||
et.bit_index += 1;
|
||||
const gpa = et.analysis.gpa;
|
||||
const op_int = @enumToInt(op_ref);
|
||||
if (op_int < Air.Inst.Ref.typed_value_map.len) return;
|
||||
const op_index: Air.Inst.Index = op_int - @intCast(u32, Air.Inst.Ref.typed_value_map.len);
|
||||
const prev = try et.analysis.table.fetchPut(gpa, op_index, {});
|
||||
if (prev == null) {
|
||||
// Death.
|
||||
if (et.new_set) |ns| try ns.putNoClobber(gpa, op_index, {});
|
||||
if (this_bit_index < bpi - 1) {
|
||||
et.tomb_bits |= @as(Bpi, 1) << @intCast(OperandInt, this_bit_index);
|
||||
} else {
|
||||
const big_bit_index = this_bit_index - (bpi - 1);
|
||||
et.big_tomb_bits |= @as(u32, 1) << @intCast(u5, big_bit_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(et: *ExtraTombs) !void {
|
||||
et.tomb_bits |= @as(Bpi, @boolToInt(et.main_tomb)) << (bpi - 1);
|
||||
et.analysis.storeTombBits(et.inst, et.tomb_bits);
|
||||
try et.analysis.special.put(et.analysis.gpa, et.inst, et.big_tomb_bits);
|
||||
}
|
||||
};
|
||||
667
src/Module.zig
667
src/Module.zig
@ -21,7 +21,7 @@ const Type = @import("type.zig").Type;
|
||||
const TypedValue = @import("TypedValue.zig");
|
||||
const Package = @import("Package.zig");
|
||||
const link = @import("link.zig");
|
||||
const ir = @import("air.zig");
|
||||
const Air = @import("Air.zig");
|
||||
const Zir = @import("Zir.zig");
|
||||
const trace = @import("tracy.zig").trace;
|
||||
const AstGen = @import("AstGen.zig");
|
||||
@ -739,8 +739,6 @@ pub const Union = struct {
|
||||
pub const Fn = struct {
|
||||
/// The Decl that corresponds to the function itself.
|
||||
owner_decl: *Decl,
|
||||
/// undefined unless analysis state is `success`.
|
||||
body: ir.Body,
|
||||
/// The ZIR instruction that is a function instruction. Use this to find
|
||||
/// the body. We store this rather than the body directly so that when ZIR
|
||||
/// is regenerated on update(), we can map this to the new corresponding
|
||||
@ -771,11 +769,6 @@ pub const Fn = struct {
|
||||
success,
|
||||
};
|
||||
|
||||
/// For debugging purposes.
|
||||
pub fn dump(func: *Fn, mod: Module) void {
|
||||
ir.dumpFn(mod, func);
|
||||
}
|
||||
|
||||
pub fn deinit(func: *Fn, gpa: *Allocator) void {
|
||||
if (func.getInferredErrorSet()) |map| {
|
||||
map.deinit(gpa);
|
||||
@ -1157,7 +1150,7 @@ pub const Scope = struct {
|
||||
/// This can vary during inline or comptime function calls. See `Sema.owner_decl`
|
||||
/// for the one that will be the same for all Block instances.
|
||||
src_decl: *Decl,
|
||||
instructions: ArrayListUnmanaged(*ir.Inst),
|
||||
instructions: ArrayListUnmanaged(Air.Inst.Index),
|
||||
label: ?*Label = null,
|
||||
inlining: ?*Inlining,
|
||||
/// If runtime_index is not 0 then one of these is guaranteed to be non null.
|
||||
@ -1189,14 +1182,14 @@ pub const Scope = struct {
|
||||
};
|
||||
|
||||
pub const Merges = struct {
|
||||
block_inst: *ir.Inst.Block,
|
||||
block_inst: Air.Inst.Index,
|
||||
/// Separate array list from break_inst_list so that it can be passed directly
|
||||
/// to resolvePeerTypes.
|
||||
results: ArrayListUnmanaged(*ir.Inst),
|
||||
results: ArrayListUnmanaged(Air.Inst.Ref),
|
||||
/// Keeps track of the break instructions so that the operand can be replaced
|
||||
/// if we need to add type coercion at the end of block analysis.
|
||||
/// Same indexes, capacity, length as `results`.
|
||||
br_list: ArrayListUnmanaged(*ir.Inst.Br),
|
||||
br_list: ArrayListUnmanaged(Air.Inst.Index),
|
||||
};
|
||||
|
||||
/// For debugging purposes.
|
||||
@ -1233,185 +1226,94 @@ pub const Scope = struct {
|
||||
return block.src_decl.namespace.file_scope;
|
||||
}
|
||||
|
||||
pub fn addNoOp(
|
||||
block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
pub fn addTy(
|
||||
block: *Block,
|
||||
tag: Air.Inst.Tag,
|
||||
ty: Type,
|
||||
comptime tag: ir.Inst.Tag,
|
||||
) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(tag.Type());
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = tag,
|
||||
.ty = ty,
|
||||
.src = src,
|
||||
},
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return block.addInst(.{
|
||||
.tag = tag,
|
||||
.data = .{ .ty = ty },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn addTyOp(
|
||||
block: *Block,
|
||||
tag: Air.Inst.Tag,
|
||||
ty: Type,
|
||||
operand: Air.Inst.Ref,
|
||||
) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return block.addInst(.{
|
||||
.tag = tag,
|
||||
.data = .{ .ty_op = .{
|
||||
.ty = try block.sema.addType(ty),
|
||||
.operand = operand,
|
||||
} },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn addNoOp(block: *Block, tag: Air.Inst.Tag) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return block.addInst(.{
|
||||
.tag = tag,
|
||||
.data = .{ .no_op = {} },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn addUnOp(
|
||||
block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
ty: Type,
|
||||
tag: ir.Inst.Tag,
|
||||
operand: *ir.Inst,
|
||||
) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(ir.Inst.UnOp);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = tag,
|
||||
.ty = ty,
|
||||
.src = src,
|
||||
},
|
||||
.operand = operand,
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
}
|
||||
|
||||
pub fn addBinOp(
|
||||
block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
ty: Type,
|
||||
tag: ir.Inst.Tag,
|
||||
lhs: *ir.Inst,
|
||||
rhs: *ir.Inst,
|
||||
) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(ir.Inst.BinOp);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = tag,
|
||||
.ty = ty,
|
||||
.src = src,
|
||||
},
|
||||
.lhs = lhs,
|
||||
.rhs = rhs,
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
block: *Block,
|
||||
tag: Air.Inst.Tag,
|
||||
operand: Air.Inst.Ref,
|
||||
) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return block.addInst(.{
|
||||
.tag = tag,
|
||||
.data = .{ .un_op = operand },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn addBr(
|
||||
scope_block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
target_block: *ir.Inst.Block,
|
||||
operand: *ir.Inst,
|
||||
) !*ir.Inst.Br {
|
||||
const inst = try scope_block.sema.arena.create(ir.Inst.Br);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = .br,
|
||||
.ty = Type.initTag(.noreturn),
|
||||
.src = src,
|
||||
},
|
||||
.operand = operand,
|
||||
.block = target_block,
|
||||
};
|
||||
try scope_block.instructions.append(scope_block.sema.gpa, &inst.base);
|
||||
return inst;
|
||||
block: *Block,
|
||||
target_block: Air.Inst.Index,
|
||||
operand: Air.Inst.Ref,
|
||||
) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return block.addInst(.{
|
||||
.tag = .br,
|
||||
.data = .{ .br = .{
|
||||
.block_inst = target_block,
|
||||
.operand = operand,
|
||||
} },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn addCondBr(
|
||||
block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
condition: *ir.Inst,
|
||||
then_body: ir.Body,
|
||||
else_body: ir.Body,
|
||||
) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(ir.Inst.CondBr);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = .condbr,
|
||||
.ty = Type.initTag(.noreturn),
|
||||
.src = src,
|
||||
},
|
||||
.condition = condition,
|
||||
.then_body = then_body,
|
||||
.else_body = else_body,
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
pub fn addBinOp(
|
||||
block: *Block,
|
||||
tag: Air.Inst.Tag,
|
||||
lhs: Air.Inst.Ref,
|
||||
rhs: Air.Inst.Ref,
|
||||
) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return block.addInst(.{
|
||||
.tag = tag,
|
||||
.data = .{ .bin_op = .{
|
||||
.lhs = lhs,
|
||||
.rhs = rhs,
|
||||
} },
|
||||
});
|
||||
}
|
||||
|
||||
pub fn addCall(
|
||||
block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
ty: Type,
|
||||
func: *ir.Inst,
|
||||
args: []const *ir.Inst,
|
||||
) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(ir.Inst.Call);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = .call,
|
||||
.ty = ty,
|
||||
.src = src,
|
||||
},
|
||||
.func = func,
|
||||
.args = args,
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
pub fn addInst(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Ref {
|
||||
return Air.indexToRef(try block.addInstAsIndex(inst));
|
||||
}
|
||||
|
||||
pub fn addSwitchBr(
|
||||
block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
operand: *ir.Inst,
|
||||
cases: []ir.Inst.SwitchBr.Case,
|
||||
else_body: ir.Body,
|
||||
) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(ir.Inst.SwitchBr);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = .switchbr,
|
||||
.ty = Type.initTag(.noreturn),
|
||||
.src = src,
|
||||
},
|
||||
.target = operand,
|
||||
.cases = cases,
|
||||
.else_body = else_body,
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
}
|
||||
pub fn addInstAsIndex(block: *Block, inst: Air.Inst) error{OutOfMemory}!Air.Inst.Index {
|
||||
const sema = block.sema;
|
||||
const gpa = sema.gpa;
|
||||
|
||||
pub fn addDbgStmt(block: *Scope.Block, src: LazySrcLoc, line: u32, column: u32) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(ir.Inst.DbgStmt);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = .dbg_stmt,
|
||||
.ty = Type.initTag(.void),
|
||||
.src = src,
|
||||
},
|
||||
.line = line,
|
||||
.column = column,
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
}
|
||||
try sema.air_instructions.ensureUnusedCapacity(gpa, 1);
|
||||
try block.instructions.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
pub fn addStructFieldPtr(
|
||||
block: *Scope.Block,
|
||||
src: LazySrcLoc,
|
||||
ty: Type,
|
||||
struct_ptr: *ir.Inst,
|
||||
field_index: u32,
|
||||
) !*ir.Inst {
|
||||
const inst = try block.sema.arena.create(ir.Inst.StructFieldPtr);
|
||||
inst.* = .{
|
||||
.base = .{
|
||||
.tag = .struct_field_ptr,
|
||||
.ty = ty,
|
||||
.src = src,
|
||||
},
|
||||
.struct_ptr = struct_ptr,
|
||||
.field_index = field_index,
|
||||
};
|
||||
try block.instructions.append(block.sema.gpa, &inst.base);
|
||||
return &inst.base;
|
||||
const result_index = @intCast(Air.Inst.Index, sema.air_instructions.len);
|
||||
sema.air_instructions.appendAssumeCapacity(inst);
|
||||
block.instructions.appendAssumeCapacity(result_index);
|
||||
return result_index;
|
||||
}
|
||||
};
|
||||
};
|
||||
@ -2130,7 +2032,8 @@ pub const LazySrcLoc = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub const InnerError = error{ OutOfMemory, AnalysisFail };
|
||||
pub const SemaError = error{ OutOfMemory, AnalysisFail };
|
||||
pub const CompileError = error{ OutOfMemory, AnalysisFail, NeededSourceLocation };
|
||||
|
||||
pub fn deinit(mod: *Module) void {
|
||||
const gpa = mod.gpa;
|
||||
@ -2769,7 +2672,7 @@ pub fn mapOldZirToNew(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) InnerError!void {
|
||||
pub fn ensureDeclAnalyzed(mod: *Module, decl: *Decl) SemaError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
@ -2869,7 +2772,7 @@ pub fn semaPkg(mod: *Module, pkg: *Package) !void {
|
||||
|
||||
/// Regardless of the file status, will create a `Decl` so that we
|
||||
/// can track dependencies and re-analyze when the file becomes outdated.
|
||||
pub fn semaFile(mod: *Module, file: *Scope.File) InnerError!void {
|
||||
pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
@ -2999,6 +2902,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
|
||||
decl.generation = mod.generation;
|
||||
return false;
|
||||
}
|
||||
log.debug("semaDecl {*} ({s})", .{ decl, decl.name });
|
||||
|
||||
var block_scope: Scope.Block = .{
|
||||
.parent = null,
|
||||
@ -3035,106 +2939,109 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
|
||||
const decl_arena_state = try decl_arena.allocator.create(std.heap.ArenaAllocator.State);
|
||||
|
||||
if (decl_tv.val.castTag(.function)) |fn_payload| {
|
||||
var prev_type_has_bits = false;
|
||||
var prev_is_inline = false;
|
||||
var type_changed = true;
|
||||
const func = fn_payload.data;
|
||||
const owns_tv = func.owner_decl == decl;
|
||||
if (owns_tv) {
|
||||
var prev_type_has_bits = false;
|
||||
var prev_is_inline = false;
|
||||
var type_changed = true;
|
||||
|
||||
if (decl.has_tv) {
|
||||
prev_type_has_bits = decl.ty.hasCodeGenBits();
|
||||
type_changed = !decl.ty.eql(decl_tv.ty);
|
||||
if (decl.getFunction()) |prev_func| {
|
||||
prev_is_inline = prev_func.state == .inline_only;
|
||||
if (decl.has_tv) {
|
||||
prev_type_has_bits = decl.ty.hasCodeGenBits();
|
||||
type_changed = !decl.ty.eql(decl_tv.ty);
|
||||
if (decl.getFunction()) |prev_func| {
|
||||
prev_is_inline = prev_func.state == .inline_only;
|
||||
}
|
||||
decl.clearValues(gpa);
|
||||
}
|
||||
decl.clearValues(gpa);
|
||||
}
|
||||
|
||||
decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
|
||||
decl.val = try decl_tv.val.copy(&decl_arena.allocator);
|
||||
decl.align_val = try align_val.copy(&decl_arena.allocator);
|
||||
decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
|
||||
decl.has_tv = true;
|
||||
decl.owns_tv = fn_payload.data.owner_decl == decl;
|
||||
decl_arena_state.* = decl_arena.state;
|
||||
decl.value_arena = decl_arena_state;
|
||||
decl.analysis = .complete;
|
||||
decl.generation = mod.generation;
|
||||
decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
|
||||
decl.val = try decl_tv.val.copy(&decl_arena.allocator);
|
||||
decl.align_val = try align_val.copy(&decl_arena.allocator);
|
||||
decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
|
||||
decl.has_tv = true;
|
||||
decl.owns_tv = owns_tv;
|
||||
decl_arena_state.* = decl_arena.state;
|
||||
decl.value_arena = decl_arena_state;
|
||||
decl.analysis = .complete;
|
||||
decl.generation = mod.generation;
|
||||
|
||||
const is_inline = decl_tv.ty.fnCallingConvention() == .Inline;
|
||||
if (!is_inline and decl_tv.ty.hasCodeGenBits()) {
|
||||
// We don't fully codegen the decl until later, but we do need to reserve a global
|
||||
// offset table index for it. This allows us to codegen decls out of dependency order,
|
||||
// increasing how many computations can be done in parallel.
|
||||
try mod.comp.bin_file.allocateDeclIndexes(decl);
|
||||
try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
|
||||
if (type_changed and mod.emit_h != null) {
|
||||
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
|
||||
const is_inline = decl_tv.ty.fnCallingConvention() == .Inline;
|
||||
if (!is_inline and decl_tv.ty.hasCodeGenBits()) {
|
||||
// We don't fully codegen the decl until later, but we do need to reserve a global
|
||||
// offset table index for it. This allows us to codegen decls out of dependency order,
|
||||
// increasing how many computations can be done in parallel.
|
||||
try mod.comp.bin_file.allocateDeclIndexes(decl);
|
||||
try mod.comp.work_queue.writeItem(.{ .codegen_func = func });
|
||||
if (type_changed and mod.emit_h != null) {
|
||||
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
|
||||
}
|
||||
} else if (!prev_is_inline and prev_type_has_bits) {
|
||||
mod.comp.bin_file.freeDecl(decl);
|
||||
}
|
||||
} else if (!prev_is_inline and prev_type_has_bits) {
|
||||
mod.comp.bin_file.freeDecl(decl);
|
||||
}
|
||||
|
||||
if (decl.is_exported) {
|
||||
const export_src = src; // TODO make this point at `export` token
|
||||
if (is_inline) {
|
||||
return mod.fail(&block_scope.base, export_src, "export of inline function", .{});
|
||||
if (decl.is_exported) {
|
||||
const export_src = src; // TODO make this point at `export` token
|
||||
if (is_inline) {
|
||||
return mod.fail(&block_scope.base, export_src, "export of inline function", .{});
|
||||
}
|
||||
// The scope needs to have the decl in it.
|
||||
try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
|
||||
}
|
||||
// The scope needs to have the decl in it.
|
||||
try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
|
||||
return type_changed or is_inline != prev_is_inline;
|
||||
}
|
||||
return type_changed or is_inline != prev_is_inline;
|
||||
} else {
|
||||
var type_changed = true;
|
||||
if (decl.has_tv) {
|
||||
type_changed = !decl.ty.eql(decl_tv.ty);
|
||||
decl.clearValues(gpa);
|
||||
}
|
||||
|
||||
decl.owns_tv = false;
|
||||
var queue_linker_work = false;
|
||||
if (decl_tv.val.castTag(.variable)) |payload| {
|
||||
const variable = payload.data;
|
||||
if (variable.owner_decl == decl) {
|
||||
decl.owns_tv = true;
|
||||
queue_linker_work = true;
|
||||
|
||||
const copied_init = try variable.init.copy(&decl_arena.allocator);
|
||||
variable.init = copied_init;
|
||||
}
|
||||
} else if (decl_tv.val.castTag(.extern_fn)) |payload| {
|
||||
const owner_decl = payload.data;
|
||||
if (decl == owner_decl) {
|
||||
decl.owns_tv = true;
|
||||
queue_linker_work = true;
|
||||
}
|
||||
}
|
||||
|
||||
decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
|
||||
decl.val = try decl_tv.val.copy(&decl_arena.allocator);
|
||||
decl.align_val = try align_val.copy(&decl_arena.allocator);
|
||||
decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
|
||||
decl.has_tv = true;
|
||||
decl_arena_state.* = decl_arena.state;
|
||||
decl.value_arena = decl_arena_state;
|
||||
decl.analysis = .complete;
|
||||
decl.generation = mod.generation;
|
||||
|
||||
if (queue_linker_work and decl.ty.hasCodeGenBits()) {
|
||||
try mod.comp.bin_file.allocateDeclIndexes(decl);
|
||||
try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
|
||||
|
||||
if (type_changed and mod.emit_h != null) {
|
||||
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
|
||||
}
|
||||
}
|
||||
|
||||
if (decl.is_exported) {
|
||||
const export_src = src; // TODO point to the export token
|
||||
// The scope needs to have the decl in it.
|
||||
try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
|
||||
}
|
||||
|
||||
return type_changed;
|
||||
}
|
||||
var type_changed = true;
|
||||
if (decl.has_tv) {
|
||||
type_changed = !decl.ty.eql(decl_tv.ty);
|
||||
decl.clearValues(gpa);
|
||||
}
|
||||
|
||||
decl.owns_tv = false;
|
||||
var queue_linker_work = false;
|
||||
if (decl_tv.val.castTag(.variable)) |payload| {
|
||||
const variable = payload.data;
|
||||
if (variable.owner_decl == decl) {
|
||||
decl.owns_tv = true;
|
||||
queue_linker_work = true;
|
||||
|
||||
const copied_init = try variable.init.copy(&decl_arena.allocator);
|
||||
variable.init = copied_init;
|
||||
}
|
||||
} else if (decl_tv.val.castTag(.extern_fn)) |payload| {
|
||||
const owner_decl = payload.data;
|
||||
if (decl == owner_decl) {
|
||||
decl.owns_tv = true;
|
||||
queue_linker_work = true;
|
||||
}
|
||||
}
|
||||
|
||||
decl.ty = try decl_tv.ty.copy(&decl_arena.allocator);
|
||||
decl.val = try decl_tv.val.copy(&decl_arena.allocator);
|
||||
decl.align_val = try align_val.copy(&decl_arena.allocator);
|
||||
decl.linksection_val = try linksection_val.copy(&decl_arena.allocator);
|
||||
decl.has_tv = true;
|
||||
decl_arena_state.* = decl_arena.state;
|
||||
decl.value_arena = decl_arena_state;
|
||||
decl.analysis = .complete;
|
||||
decl.generation = mod.generation;
|
||||
|
||||
if (queue_linker_work and decl.ty.hasCodeGenBits()) {
|
||||
try mod.comp.bin_file.allocateDeclIndexes(decl);
|
||||
try mod.comp.work_queue.writeItem(.{ .codegen_decl = decl });
|
||||
|
||||
if (type_changed and mod.emit_h != null) {
|
||||
try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
|
||||
}
|
||||
}
|
||||
|
||||
if (decl.is_exported) {
|
||||
const export_src = src; // TODO point to the export token
|
||||
// The scope needs to have the decl in it.
|
||||
try mod.analyzeExport(&block_scope.base, export_src, mem.spanZ(decl.name), decl);
|
||||
}
|
||||
|
||||
return type_changed;
|
||||
}
|
||||
|
||||
/// Returns the depender's index of the dependee.
|
||||
@ -3284,7 +3191,7 @@ pub fn scanNamespace(
|
||||
extra_start: usize,
|
||||
decls_len: u32,
|
||||
parent_decl: *Decl,
|
||||
) InnerError!usize {
|
||||
) SemaError!usize {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
@ -3331,7 +3238,7 @@ const ScanDeclIter = struct {
|
||||
unnamed_test_index: usize = 0,
|
||||
};
|
||||
|
||||
fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) InnerError!void {
|
||||
fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
@ -3585,39 +3492,25 @@ fn deleteDeclExports(mod: *Module, decl: *Decl) void {
|
||||
mod.gpa.free(kv.value);
|
||||
}
|
||||
|
||||
pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) !void {
|
||||
pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) SemaError!Air {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const gpa = mod.gpa;
|
||||
|
||||
// Use the Decl's arena for function memory.
|
||||
var arena = decl.value_arena.?.promote(mod.gpa);
|
||||
var arena = decl.value_arena.?.promote(gpa);
|
||||
defer decl.value_arena.?.* = arena.state;
|
||||
|
||||
const fn_ty = decl.ty;
|
||||
const param_inst_list = try mod.gpa.alloc(*ir.Inst, fn_ty.fnParamLen());
|
||||
defer mod.gpa.free(param_inst_list);
|
||||
|
||||
for (param_inst_list) |*param_inst, param_index| {
|
||||
const param_type = fn_ty.fnParamType(param_index);
|
||||
const arg_inst = try arena.allocator.create(ir.Inst.Arg);
|
||||
arg_inst.* = .{
|
||||
.base = .{
|
||||
.tag = .arg,
|
||||
.ty = param_type,
|
||||
.src = .unneeded,
|
||||
},
|
||||
.name = undefined, // Set in the semantic analysis of the arg instruction.
|
||||
};
|
||||
param_inst.* = &arg_inst.base;
|
||||
}
|
||||
|
||||
const zir = decl.namespace.file_scope.zir;
|
||||
const param_inst_list = try gpa.alloc(Air.Inst.Ref, fn_ty.fnParamLen());
|
||||
defer gpa.free(param_inst_list);
|
||||
|
||||
var sema: Sema = .{
|
||||
.mod = mod,
|
||||
.gpa = mod.gpa,
|
||||
.gpa = gpa,
|
||||
.arena = &arena.allocator,
|
||||
.code = zir,
|
||||
.code = decl.namespace.file_scope.zir,
|
||||
.owner_decl = decl,
|
||||
.namespace = decl.namespace,
|
||||
.func = func,
|
||||
@ -3626,6 +3519,11 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) !void {
|
||||
};
|
||||
defer sema.deinit();
|
||||
|
||||
// First few indexes of extra are reserved and set at the end.
|
||||
const reserved_count = @typeInfo(Air.ExtraIndex).Enum.fields.len;
|
||||
try sema.air_extra.ensureTotalCapacity(gpa, reserved_count);
|
||||
sema.air_extra.items.len += reserved_count;
|
||||
|
||||
var inner_block: Scope.Block = .{
|
||||
.parent = null,
|
||||
.sema = &sema,
|
||||
@ -3634,20 +3532,50 @@ pub fn analyzeFnBody(mod: *Module, decl: *Decl, func: *Fn) !void {
|
||||
.inlining = null,
|
||||
.is_comptime = false,
|
||||
};
|
||||
defer inner_block.instructions.deinit(mod.gpa);
|
||||
defer inner_block.instructions.deinit(gpa);
|
||||
|
||||
// AIR currently requires the arg parameters to be the first N instructions
|
||||
try inner_block.instructions.appendSlice(mod.gpa, param_inst_list);
|
||||
// AIR requires the arg parameters to be the first N instructions.
|
||||
try inner_block.instructions.ensureTotalCapacity(gpa, param_inst_list.len);
|
||||
for (param_inst_list) |*param_inst, param_index| {
|
||||
const param_type = fn_ty.fnParamType(param_index);
|
||||
const ty_ref = try sema.addType(param_type);
|
||||
const arg_index = @intCast(u32, sema.air_instructions.len);
|
||||
inner_block.instructions.appendAssumeCapacity(arg_index);
|
||||
param_inst.* = Air.indexToRef(arg_index);
|
||||
try sema.air_instructions.append(gpa, .{
|
||||
.tag = .arg,
|
||||
.data = .{
|
||||
.ty_str = .{
|
||||
.ty = ty_ref,
|
||||
.str = undefined, // Set in the semantic analysis of the arg instruction.
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
func.state = .in_progress;
|
||||
log.debug("set {s} to in_progress", .{decl.name});
|
||||
|
||||
try sema.analyzeFnBody(&inner_block, func.zir_body_inst);
|
||||
|
||||
const instructions = try arena.allocator.dupe(*ir.Inst, inner_block.instructions.items);
|
||||
// Copy the block into place and mark that as the main block.
|
||||
try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.Block).Struct.fields.len +
|
||||
inner_block.instructions.items.len);
|
||||
const main_block_index = sema.addExtraAssumeCapacity(Air.Block{
|
||||
.body_len = @intCast(u32, inner_block.instructions.items.len),
|
||||
});
|
||||
sema.air_extra.appendSliceAssumeCapacity(inner_block.instructions.items);
|
||||
sema.air_extra.items[@enumToInt(Air.ExtraIndex.main_block)] = main_block_index;
|
||||
|
||||
func.state = .success;
|
||||
func.body = .{ .instructions = instructions };
|
||||
log.debug("set {s} to success", .{decl.name});
|
||||
|
||||
return Air{
|
||||
.instructions = sema.air_instructions.toOwnedSlice(),
|
||||
.extra = sema.air_extra.toOwnedSlice(gpa),
|
||||
.values = sema.air_values.toOwnedSlice(gpa),
|
||||
.variables = sema.air_variables.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
||||
fn markOutdatedDecl(mod: *Module, decl: *Decl) !void {
|
||||
@ -3801,94 +3729,6 @@ pub fn analyzeExport(
|
||||
de_gop.value_ptr.*[de_gop.value_ptr.len - 1] = new_export;
|
||||
errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1);
|
||||
}
|
||||
pub fn constInst(mod: *Module, arena: *Allocator, src: LazySrcLoc, typed_value: TypedValue) !*ir.Inst {
|
||||
_ = mod;
|
||||
const const_inst = try arena.create(ir.Inst.Constant);
|
||||
const_inst.* = .{
|
||||
.base = .{
|
||||
.tag = ir.Inst.Constant.base_tag,
|
||||
.ty = typed_value.ty,
|
||||
.src = src,
|
||||
},
|
||||
.val = typed_value.val,
|
||||
};
|
||||
return &const_inst.base;
|
||||
}
|
||||
|
||||
pub fn constType(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type) !*ir.Inst {
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = Type.initTag(.type),
|
||||
.val = try ty.toValue(arena),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constVoid(mod: *Module, arena: *Allocator, src: LazySrcLoc) !*ir.Inst {
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = Type.initTag(.void),
|
||||
.val = Value.initTag(.void_value),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constNoReturn(mod: *Module, arena: *Allocator, src: LazySrcLoc) !*ir.Inst {
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = Type.initTag(.noreturn),
|
||||
.val = Value.initTag(.unreachable_value),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constUndef(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type) !*ir.Inst {
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = ty,
|
||||
.val = Value.initTag(.undef),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constBool(mod: *Module, arena: *Allocator, src: LazySrcLoc, v: bool) !*ir.Inst {
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = Type.initTag(.bool),
|
||||
.val = ([2]Value{ Value.initTag(.bool_false), Value.initTag(.bool_true) })[@boolToInt(v)],
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constIntUnsigned(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type, int: u64) !*ir.Inst {
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = ty,
|
||||
.val = try Value.Tag.int_u64.create(arena, int),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constIntSigned(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type, int: i64) !*ir.Inst {
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = ty,
|
||||
.val = try Value.Tag.int_i64.create(arena, int),
|
||||
});
|
||||
}
|
||||
|
||||
pub fn constIntBig(mod: *Module, arena: *Allocator, src: LazySrcLoc, ty: Type, big_int: BigIntConst) !*ir.Inst {
|
||||
if (big_int.positive) {
|
||||
if (big_int.to(u64)) |x| {
|
||||
return mod.constIntUnsigned(arena, src, ty, x);
|
||||
} else |err| switch (err) {
|
||||
error.NegativeIntoUnsigned => unreachable,
|
||||
error.TargetTooSmall => {}, // handled below
|
||||
}
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = ty,
|
||||
.val = try Value.Tag.int_big_positive.create(arena, big_int.limbs),
|
||||
});
|
||||
} else {
|
||||
if (big_int.to(i64)) |x| {
|
||||
return mod.constIntSigned(arena, src, ty, x);
|
||||
} else |err| switch (err) {
|
||||
error.NegativeIntoUnsigned => unreachable,
|
||||
error.TargetTooSmall => {}, // handled below
|
||||
}
|
||||
return mod.constInst(arena, src, .{
|
||||
.ty = ty,
|
||||
.val = try Value.Tag.int_big_negative.create(arena, big_int.limbs),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void {
|
||||
const scope_decl = scope.ownerDecl().?;
|
||||
@ -4006,7 +3846,7 @@ pub fn fail(
|
||||
src: LazySrcLoc,
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) InnerError {
|
||||
) CompileError {
|
||||
const err_msg = try mod.errMsg(scope, src, format, args);
|
||||
return mod.failWithOwnedErrorMsg(scope, err_msg);
|
||||
}
|
||||
@ -4019,7 +3859,7 @@ pub fn failTok(
|
||||
token_index: ast.TokenIndex,
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) InnerError {
|
||||
) CompileError {
|
||||
const src = scope.srcDecl().?.tokSrcLoc(token_index);
|
||||
return mod.fail(scope, src, format, args);
|
||||
}
|
||||
@ -4032,18 +3872,21 @@ pub fn failNode(
|
||||
node_index: ast.Node.Index,
|
||||
comptime format: []const u8,
|
||||
args: anytype,
|
||||
) InnerError {
|
||||
) CompileError {
|
||||
const src = scope.srcDecl().?.nodeSrcLoc(node_index);
|
||||
return mod.fail(scope, src, format, args);
|
||||
}
|
||||
|
||||
pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) InnerError {
|
||||
pub fn failWithOwnedErrorMsg(mod: *Module, scope: *Scope, err_msg: *ErrorMsg) CompileError {
|
||||
@setCold(true);
|
||||
|
||||
{
|
||||
errdefer err_msg.destroy(mod.gpa);
|
||||
try mod.failed_decls.ensureCapacity(mod.gpa, mod.failed_decls.count() + 1);
|
||||
try mod.failed_files.ensureCapacity(mod.gpa, mod.failed_files.count() + 1);
|
||||
if (err_msg.src_loc.lazy == .unneeded) {
|
||||
return error.NeededSourceLocation;
|
||||
}
|
||||
try mod.failed_decls.ensureUnusedCapacity(mod.gpa, 1);
|
||||
try mod.failed_files.ensureUnusedCapacity(mod.gpa, 1);
|
||||
}
|
||||
switch (scope.tag) {
|
||||
.block => {
|
||||
@ -4301,13 +4144,11 @@ pub fn floatMul(
|
||||
}
|
||||
|
||||
pub fn simplePtrType(
|
||||
mod: *Module,
|
||||
arena: *Allocator,
|
||||
elem_ty: Type,
|
||||
mutable: bool,
|
||||
size: std.builtin.TypeInfo.Pointer.Size,
|
||||
) Allocator.Error!Type {
|
||||
_ = mod;
|
||||
if (!mutable and size == .Slice and elem_ty.eql(Type.initTag(.u8))) {
|
||||
return Type.initTag(.const_slice_u8);
|
||||
}
|
||||
@ -4424,38 +4265,6 @@ pub fn errorUnionType(
|
||||
});
|
||||
}
|
||||
|
||||
pub fn dumpInst(mod: *Module, scope: *Scope, inst: *ir.Inst) void {
|
||||
const zir_module = scope.namespace();
|
||||
const source = zir_module.getSource(mod) catch @panic("dumpInst failed to get source");
|
||||
const loc = std.zig.findLineColumn(source, inst.src);
|
||||
if (inst.tag == .constant) {
|
||||
std.debug.print("constant ty={} val={} src={s}:{d}:{d}\n", .{
|
||||
inst.ty,
|
||||
inst.castTag(.constant).?.val,
|
||||
zir_module.subFilePath(),
|
||||
loc.line + 1,
|
||||
loc.column + 1,
|
||||
});
|
||||
} else if (inst.deaths == 0) {
|
||||
std.debug.print("{s} ty={} src={s}:{d}:{d}\n", .{
|
||||
@tagName(inst.tag),
|
||||
inst.ty,
|
||||
zir_module.subFilePath(),
|
||||
loc.line + 1,
|
||||
loc.column + 1,
|
||||
});
|
||||
} else {
|
||||
std.debug.print("{s} ty={} deaths={b} src={s}:{d}:{d}\n", .{
|
||||
@tagName(inst.tag),
|
||||
inst.ty,
|
||||
inst.deaths,
|
||||
zir_module.subFilePath(),
|
||||
loc.line + 1,
|
||||
loc.column + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getTarget(mod: Module) Target {
|
||||
return mod.comp.bin_file.options.target;
|
||||
}
|
||||
@ -4576,7 +4385,7 @@ pub const SwitchProngSrc = union(enum) {
|
||||
}
|
||||
};
|
||||
|
||||
pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) InnerError!void {
|
||||
pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) CompileError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
@ -4726,7 +4535,7 @@ pub fn analyzeStructFields(mod: *Module, struct_obj: *Struct) InnerError!void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) InnerError!void {
|
||||
pub fn analyzeUnionFields(mod: *Module, union_obj: *Union) CompileError!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
|
||||
3234
src/Sema.zig
3234
src/Sema.zig
File diff suppressed because it is too large
Load Diff
40
src/Zir.zig
40
src/Zir.zig
@ -22,7 +22,6 @@ const Zir = @This();
|
||||
const Type = @import("type.zig").Type;
|
||||
const Value = @import("value.zig").Value;
|
||||
const TypedValue = @import("TypedValue.zig");
|
||||
const ir = @import("air.zig");
|
||||
const Module = @import("Module.zig");
|
||||
const LazySrcLoc = Module.LazySrcLoc;
|
||||
|
||||
@ -214,7 +213,7 @@ pub const Inst = struct {
|
||||
as_node,
|
||||
/// Bitwise AND. `&`
|
||||
bit_and,
|
||||
/// Bitcast a value to a different type.
|
||||
/// Reinterpret the memory representation of a value as a different type.
|
||||
/// Uses the pl_node field with payload `Bin`.
|
||||
bitcast,
|
||||
/// A typed result location pointer is bitcasted to a new result location pointer.
|
||||
@ -237,15 +236,9 @@ pub const Inst = struct {
|
||||
/// Implements `suspend {...}`.
|
||||
/// Uses the `pl_node` union field. Payload is `Block`.
|
||||
suspend_block,
|
||||
/// Boolean AND. See also `bit_and`.
|
||||
/// Uses the `pl_node` union field. Payload is `Bin`.
|
||||
bool_and,
|
||||
/// Boolean NOT. See also `bit_not`.
|
||||
/// Uses the `un_node` field.
|
||||
bool_not,
|
||||
/// Boolean OR. See also `bit_or`.
|
||||
/// Uses the `pl_node` union field. Payload is `Bin`.
|
||||
bool_or,
|
||||
/// Short-circuiting boolean `and`. `lhs` is a boolean `Ref` and the other operand
|
||||
/// is a block, which is evaluated if `lhs` is `true`.
|
||||
/// Uses the `bool_br` union field.
|
||||
@ -393,7 +386,7 @@ pub const Inst = struct {
|
||||
int,
|
||||
/// Arbitrary sized integer literal. Uses the `str` union field.
|
||||
int_big,
|
||||
/// A float literal that fits in a f32. Uses the float union value.
|
||||
/// A float literal that fits in a f64. Uses the float union value.
|
||||
float,
|
||||
/// A float literal that fits in a f128. Uses the `pl_node` union value.
|
||||
/// Payload is `Float128`.
|
||||
@ -999,8 +992,6 @@ pub const Inst = struct {
|
||||
.bool_br_and,
|
||||
.bool_br_or,
|
||||
.bool_not,
|
||||
.bool_and,
|
||||
.bool_or,
|
||||
.breakpoint,
|
||||
.fence,
|
||||
.call,
|
||||
@ -1249,9 +1240,7 @@ pub const Inst = struct {
|
||||
.block = .pl_node,
|
||||
.block_inline = .pl_node,
|
||||
.suspend_block = .pl_node,
|
||||
.bool_and = .pl_node,
|
||||
.bool_not = .un_node,
|
||||
.bool_or = .pl_node,
|
||||
.bool_br_and = .bool_br,
|
||||
.bool_br_or = .bool_br,
|
||||
.@"break" = .@"break",
|
||||
@ -2069,16 +2058,7 @@ pub const Inst = struct {
|
||||
/// Offset from Decl AST node index.
|
||||
node: i32,
|
||||
int: u64,
|
||||
float: struct {
|
||||
/// Offset from Decl AST node index.
|
||||
/// `Tag` determines which kind of AST node this points to.
|
||||
src_node: i32,
|
||||
number: f32,
|
||||
|
||||
pub fn src(self: @This()) LazySrcLoc {
|
||||
return .{ .node_offset = self.src_node };
|
||||
}
|
||||
},
|
||||
float: f64,
|
||||
array_type_sentinel: struct {
|
||||
len: Ref,
|
||||
/// index into extra, points to an `ArrayTypeSentinel`
|
||||
@ -2196,7 +2176,8 @@ pub const Inst = struct {
|
||||
/// 2. clobber: u32 // index into string_bytes (null terminated) for every clobbers_len.
|
||||
pub const Asm = struct {
|
||||
src_node: i32,
|
||||
asm_source: Ref,
|
||||
// null-terminated string index
|
||||
asm_source: u32,
|
||||
/// 1 bit for each outputs_len: whether it uses `-> T` or not.
|
||||
/// 0b0 - operand is a pointer to where to store the output.
|
||||
/// 0b1 - operand is a type; asm expression has the output as the result.
|
||||
@ -2982,8 +2963,6 @@ const Writer = struct {
|
||||
.mulwrap,
|
||||
.sub,
|
||||
.subwrap,
|
||||
.bool_and,
|
||||
.bool_or,
|
||||
.cmp_lt,
|
||||
.cmp_lte,
|
||||
.cmp_eq,
|
||||
@ -3269,10 +3248,8 @@ const Writer = struct {
|
||||
}
|
||||
|
||||
fn writeFloat(self: *Writer, stream: anytype, inst: Inst.Index) !void {
|
||||
const inst_data = self.code.instructions.items(.data)[inst].float;
|
||||
const src = inst_data.src();
|
||||
try stream.print("{d}) ", .{inst_data.number});
|
||||
try self.writeSrc(stream, src);
|
||||
const number = self.code.instructions.items(.data)[inst].float;
|
||||
try stream.print("{d})", .{number});
|
||||
}
|
||||
|
||||
fn writeFloat128(self: *Writer, stream: anytype, inst: Inst.Index) !void {
|
||||
@ -3407,9 +3384,10 @@ const Writer = struct {
|
||||
const inputs_len = @truncate(u5, extended.small >> 5);
|
||||
const clobbers_len = @truncate(u5, extended.small >> 10);
|
||||
const is_volatile = @truncate(u1, extended.small >> 15) != 0;
|
||||
const asm_source = self.code.nullTerminatedString(extra.data.asm_source);
|
||||
|
||||
try self.writeFlag(stream, "volatile, ", is_volatile);
|
||||
try self.writeInstRef(stream, extra.data.asm_source);
|
||||
try stream.print("\"{}\", ", .{std.zig.fmtEscapes(asm_source)});
|
||||
try stream.writeAll(", ");
|
||||
|
||||
var extra_i: usize = extra.end;
|
||||
|
||||
1185
src/air.zig
1185
src/air.zig
File diff suppressed because it is too large
Load Diff
2338
src/codegen.zig
2338
src/codegen.zig
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -9,8 +9,8 @@ const math = std.math;
|
||||
|
||||
const Module = @import("../Module.zig");
|
||||
const TypedValue = @import("../TypedValue.zig");
|
||||
const ir = @import("../air.zig");
|
||||
const Inst = ir.Inst;
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
const Value = @import("../value.zig").Value;
|
||||
const Type = @import("../type.zig").Type;
|
||||
@ -276,6 +276,70 @@ pub const Object = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateFunc(
|
||||
self: *Object,
|
||||
module: *Module,
|
||||
func: *Module.Fn,
|
||||
air: Air,
|
||||
liveness: Liveness,
|
||||
) !void {
|
||||
var dg: DeclGen = .{
|
||||
.object = self,
|
||||
.module = module,
|
||||
.decl = func.owner_decl,
|
||||
.err_msg = null,
|
||||
.gpa = module.gpa,
|
||||
};
|
||||
|
||||
const llvm_func = try dg.resolveLLVMFunction(func.owner_decl);
|
||||
|
||||
// This gets the LLVM values from the function and stores them in `dg.args`.
|
||||
const fn_param_len = func.owner_decl.ty.fnParamLen();
|
||||
var args = try dg.gpa.alloc(*const llvm.Value, fn_param_len);
|
||||
|
||||
for (args) |*arg, i| {
|
||||
arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i));
|
||||
}
|
||||
|
||||
// We remove all the basic blocks of a function to support incremental
|
||||
// compilation!
|
||||
// TODO: remove all basic blocks if functions can have more than one
|
||||
if (llvm_func.getFirstBasicBlock()) |bb| {
|
||||
bb.deleteBasicBlock();
|
||||
}
|
||||
|
||||
const builder = dg.context().createBuilder();
|
||||
|
||||
const entry_block = dg.context().appendBasicBlock(llvm_func, "Entry");
|
||||
builder.positionBuilderAtEnd(entry_block);
|
||||
|
||||
var fg: FuncGen = .{
|
||||
.gpa = dg.gpa,
|
||||
.air = air,
|
||||
.liveness = liveness,
|
||||
.dg = &dg,
|
||||
.builder = builder,
|
||||
.args = args,
|
||||
.arg_index = 0,
|
||||
.func_inst_table = .{},
|
||||
.entry_block = entry_block,
|
||||
.latest_alloca_inst = null,
|
||||
.llvm_func = llvm_func,
|
||||
.blocks = .{},
|
||||
};
|
||||
defer fg.deinit();
|
||||
|
||||
fg.genBody(air.getMainBody()) catch |err| switch (err) {
|
||||
error.CodegenFail => {
|
||||
func.owner_decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, func.owner_decl, dg.err_msg.?);
|
||||
dg.err_msg = null;
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *Object, module: *Module, decl: *Module.Decl) !void {
|
||||
var dg: DeclGen = .{
|
||||
.object = self,
|
||||
@ -327,44 +391,8 @@ pub const DeclGen = struct {
|
||||
log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
|
||||
|
||||
if (decl.val.castTag(.function)) |func_payload| {
|
||||
const func = func_payload.data;
|
||||
|
||||
const llvm_func = try self.resolveLLVMFunction(func.owner_decl);
|
||||
|
||||
// This gets the LLVM values from the function and stores them in `self.args`.
|
||||
const fn_param_len = func.owner_decl.ty.fnParamLen();
|
||||
var args = try self.gpa.alloc(*const llvm.Value, fn_param_len);
|
||||
|
||||
for (args) |*arg, i| {
|
||||
arg.* = llvm.getParam(llvm_func, @intCast(c_uint, i));
|
||||
}
|
||||
|
||||
// We remove all the basic blocks of a function to support incremental
|
||||
// compilation!
|
||||
// TODO: remove all basic blocks if functions can have more than one
|
||||
if (llvm_func.getFirstBasicBlock()) |bb| {
|
||||
bb.deleteBasicBlock();
|
||||
}
|
||||
|
||||
const builder = self.context().createBuilder();
|
||||
|
||||
const entry_block = self.context().appendBasicBlock(llvm_func, "Entry");
|
||||
builder.positionBuilderAtEnd(entry_block);
|
||||
|
||||
var fg: FuncGen = .{
|
||||
.dg = self,
|
||||
.builder = builder,
|
||||
.args = args,
|
||||
.arg_index = 0,
|
||||
.func_inst_table = .{},
|
||||
.entry_block = entry_block,
|
||||
.latest_alloca_inst = null,
|
||||
.llvm_func = llvm_func,
|
||||
.blocks = .{},
|
||||
};
|
||||
defer fg.deinit();
|
||||
|
||||
try fg.genBody(func.body);
|
||||
_ = func_payload;
|
||||
@panic("TODO llvm backend genDecl function pointer");
|
||||
} else if (decl.val.castTag(.extern_fn)) |extern_fn| {
|
||||
_ = try self.resolveLLVMFunction(extern_fn.data);
|
||||
} else {
|
||||
@ -590,29 +618,31 @@ pub const DeclGen = struct {
|
||||
};
|
||||
|
||||
pub const FuncGen = struct {
|
||||
gpa: *Allocator,
|
||||
dg: *DeclGen,
|
||||
air: Air,
|
||||
liveness: Liveness,
|
||||
|
||||
builder: *const llvm.Builder,
|
||||
|
||||
/// This stores the LLVM values used in a function, such that they can be
|
||||
/// referred to in other instructions. This table is cleared before every function is generated.
|
||||
/// TODO: Change this to a stack of Branch. Currently we store all the values from all the blocks
|
||||
/// in here, however if a block ends, the instructions can be thrown away.
|
||||
func_inst_table: std.AutoHashMapUnmanaged(*Inst, *const llvm.Value),
|
||||
/// This stores the LLVM values used in a function, such that they can be referred to
|
||||
/// in other instructions. This table is cleared before every function is generated.
|
||||
func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Index, *const llvm.Value),
|
||||
|
||||
/// These fields are used to refer to the LLVM value of the function paramaters in an Arg instruction.
|
||||
/// These fields are used to refer to the LLVM value of the function paramaters
|
||||
/// in an Arg instruction.
|
||||
args: []*const llvm.Value,
|
||||
arg_index: usize,
|
||||
|
||||
entry_block: *const llvm.BasicBlock,
|
||||
/// This fields stores the last alloca instruction, such that we can append more alloca instructions
|
||||
/// to the top of the function.
|
||||
/// This fields stores the last alloca instruction, such that we can append
|
||||
/// more alloca instructions to the top of the function.
|
||||
latest_alloca_inst: ?*const llvm.Value,
|
||||
|
||||
llvm_func: *const llvm.Value,
|
||||
|
||||
/// This data structure is used to implement breaking to blocks.
|
||||
blocks: std.AutoHashMapUnmanaged(*Inst.Block, struct {
|
||||
blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
|
||||
parent_bb: *const llvm.BasicBlock,
|
||||
break_bbs: *BreakBasicBlocks,
|
||||
break_vals: *BreakValues,
|
||||
@ -623,9 +653,9 @@ pub const FuncGen = struct {
|
||||
|
||||
fn deinit(self: *FuncGen) void {
|
||||
self.builder.dispose();
|
||||
self.func_inst_table.deinit(self.gpa());
|
||||
self.gpa().free(self.args);
|
||||
self.blocks.deinit(self.gpa());
|
||||
self.func_inst_table.deinit(self.gpa);
|
||||
self.gpa.free(self.args);
|
||||
self.blocks.deinit(self.gpa);
|
||||
}
|
||||
|
||||
fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) error{ OutOfMemory, CodegenFail } {
|
||||
@ -641,65 +671,68 @@ pub const FuncGen = struct {
|
||||
return self.dg.object.context;
|
||||
}
|
||||
|
||||
fn gpa(self: *FuncGen) *Allocator {
|
||||
return self.dg.gpa;
|
||||
}
|
||||
|
||||
fn resolveInst(self: *FuncGen, inst: *ir.Inst) !*const llvm.Value {
|
||||
if (inst.value()) |val| {
|
||||
return self.dg.genTypedValue(.{ .ty = inst.ty, .val = val }, self);
|
||||
fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value {
|
||||
if (self.air.value(inst)) |val| {
|
||||
return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }, self);
|
||||
}
|
||||
if (self.func_inst_table.get(inst)) |value| return value;
|
||||
const inst_index = Air.refToIndex(inst).?;
|
||||
if (self.func_inst_table.get(inst_index)) |value| return value;
|
||||
|
||||
return self.todo("implement global llvm values (or the value is not in the func_inst_table table)", .{});
|
||||
}
|
||||
|
||||
fn genBody(self: *FuncGen, body: ir.Body) error{ OutOfMemory, CodegenFail }!void {
|
||||
for (body.instructions) |inst| {
|
||||
const opt_value = switch (inst.tag) {
|
||||
.add => try self.genAdd(inst.castTag(.add).?),
|
||||
.alloc => try self.genAlloc(inst.castTag(.alloc).?),
|
||||
.arg => try self.genArg(inst.castTag(.arg).?),
|
||||
.bitcast => try self.genBitCast(inst.castTag(.bitcast).?),
|
||||
.block => try self.genBlock(inst.castTag(.block).?),
|
||||
.br => try self.genBr(inst.castTag(.br).?),
|
||||
.breakpoint => try self.genBreakpoint(inst.castTag(.breakpoint).?),
|
||||
.br_void => try self.genBrVoid(inst.castTag(.br_void).?),
|
||||
.call => try self.genCall(inst.castTag(.call).?),
|
||||
.cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?, .eq),
|
||||
.cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?, .gt),
|
||||
.cmp_gte => try self.genCmp(inst.castTag(.cmp_gte).?, .gte),
|
||||
.cmp_lt => try self.genCmp(inst.castTag(.cmp_lt).?, .lt),
|
||||
.cmp_lte => try self.genCmp(inst.castTag(.cmp_lte).?, .lte),
|
||||
.cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?, .neq),
|
||||
.condbr => try self.genCondBr(inst.castTag(.condbr).?),
|
||||
.intcast => try self.genIntCast(inst.castTag(.intcast).?),
|
||||
.is_non_null => try self.genIsNonNull(inst.castTag(.is_non_null).?, false),
|
||||
.is_non_null_ptr => try self.genIsNonNull(inst.castTag(.is_non_null_ptr).?, true),
|
||||
.is_null => try self.genIsNull(inst.castTag(.is_null).?, false),
|
||||
.is_null_ptr => try self.genIsNull(inst.castTag(.is_null_ptr).?, true),
|
||||
.load => try self.genLoad(inst.castTag(.load).?),
|
||||
.loop => try self.genLoop(inst.castTag(.loop).?),
|
||||
.not => try self.genNot(inst.castTag(.not).?),
|
||||
.ret => try self.genRet(inst.castTag(.ret).?),
|
||||
.retvoid => self.genRetVoid(inst.castTag(.retvoid).?),
|
||||
.store => try self.genStore(inst.castTag(.store).?),
|
||||
.sub => try self.genSub(inst.castTag(.sub).?),
|
||||
.unreach => self.genUnreach(inst.castTag(.unreach).?),
|
||||
.optional_payload => try self.genOptionalPayload(inst.castTag(.optional_payload).?, false),
|
||||
.optional_payload_ptr => try self.genOptionalPayload(inst.castTag(.optional_payload_ptr).?, true),
|
||||
fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void {
|
||||
const air_tags = self.air.instructions.items(.tag);
|
||||
for (body) |inst| {
|
||||
const opt_value = switch (air_tags[inst]) {
|
||||
.add => try self.airAdd(inst),
|
||||
.sub => try self.airSub(inst),
|
||||
|
||||
.cmp_eq => try self.airCmp(inst, .eq),
|
||||
.cmp_gt => try self.airCmp(inst, .gt),
|
||||
.cmp_gte => try self.airCmp(inst, .gte),
|
||||
.cmp_lt => try self.airCmp(inst, .lt),
|
||||
.cmp_lte => try self.airCmp(inst, .lte),
|
||||
.cmp_neq => try self.airCmp(inst, .neq),
|
||||
|
||||
.is_non_null => try self.airIsNonNull(inst, false),
|
||||
.is_non_null_ptr => try self.airIsNonNull(inst, true),
|
||||
.is_null => try self.airIsNull(inst, false),
|
||||
.is_null_ptr => try self.airIsNull(inst, true),
|
||||
|
||||
.alloc => try self.airAlloc(inst),
|
||||
.arg => try self.airArg(inst),
|
||||
.bitcast => try self.airBitCast(inst),
|
||||
.block => try self.airBlock(inst),
|
||||
.br => try self.airBr(inst),
|
||||
.breakpoint => try self.airBreakpoint(inst),
|
||||
.call => try self.airCall(inst),
|
||||
.cond_br => try self.airCondBr(inst),
|
||||
.intcast => try self.airIntCast(inst),
|
||||
.load => try self.airLoad(inst),
|
||||
.loop => try self.airLoop(inst),
|
||||
.not => try self.airNot(inst),
|
||||
.ret => try self.airRet(inst),
|
||||
.store => try self.airStore(inst),
|
||||
.unreach => self.airUnreach(inst),
|
||||
.optional_payload => try self.airOptionalPayload(inst, false),
|
||||
.optional_payload_ptr => try self.airOptionalPayload(inst, true),
|
||||
.dbg_stmt => blk: {
|
||||
// TODO: implement debug info
|
||||
break :blk null;
|
||||
},
|
||||
else => |tag| return self.todo("implement TZIR instruction: {}", .{tag}),
|
||||
else => |tag| return self.todo("implement AIR instruction: {}", .{tag}),
|
||||
};
|
||||
if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa(), inst, val);
|
||||
if (opt_value) |val| try self.func_inst_table.putNoClobber(self.gpa, inst, val);
|
||||
}
|
||||
}
|
||||
|
||||
fn genCall(self: *FuncGen, inst: *Inst.Call) !?*const llvm.Value {
|
||||
if (inst.func.value()) |func_value| {
|
||||
fn airCall(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = self.air.extraData(Air.Call, pl_op.payload);
|
||||
const args = @bitCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]);
|
||||
|
||||
if (self.air.value(pl_op.operand)) |func_value| {
|
||||
const fn_decl = if (func_value.castTag(.extern_fn)) |extern_fn|
|
||||
extern_fn.data
|
||||
else if (func_value.castTag(.function)) |func_payload|
|
||||
@ -711,12 +744,10 @@ pub const FuncGen = struct {
|
||||
const zig_fn_type = fn_decl.ty;
|
||||
const llvm_fn = try self.dg.resolveLLVMFunction(fn_decl);
|
||||
|
||||
const num_args = inst.args.len;
|
||||
const llvm_param_vals = try self.gpa.alloc(*const llvm.Value, args.len);
|
||||
defer self.gpa.free(llvm_param_vals);
|
||||
|
||||
const llvm_param_vals = try self.gpa().alloc(*const llvm.Value, num_args);
|
||||
defer self.gpa().free(llvm_param_vals);
|
||||
|
||||
for (inst.args) |arg, i| {
|
||||
for (args) |arg, i| {
|
||||
llvm_param_vals[i] = try self.resolveInst(arg);
|
||||
}
|
||||
|
||||
@ -724,8 +755,8 @@ pub const FuncGen = struct {
|
||||
// Do we need that?
|
||||
const call = self.builder.buildCall(
|
||||
llvm_fn,
|
||||
if (num_args == 0) null else llvm_param_vals.ptr,
|
||||
@intCast(c_uint, num_args),
|
||||
if (args.len == 0) null else llvm_param_vals.ptr,
|
||||
@intCast(c_uint, args.len),
|
||||
"",
|
||||
);
|
||||
|
||||
@ -743,31 +774,31 @@ pub const FuncGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn genRetVoid(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value {
|
||||
_ = inst;
|
||||
_ = self.builder.buildRetVoid();
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genRet(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
|
||||
if (!inst.operand.ty.hasCodeGenBits()) {
|
||||
// TODO: in astgen these instructions should turn into `retvoid` instructions.
|
||||
fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
||||
if (!self.air.typeOf(un_op).hasCodeGenBits()) {
|
||||
_ = self.builder.buildRetVoid();
|
||||
return null;
|
||||
}
|
||||
_ = self.builder.buildRet(try self.resolveInst(inst.operand));
|
||||
const operand = try self.resolveInst(un_op);
|
||||
_ = self.builder.buildRet(operand);
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genCmp(self: *FuncGen, inst: *Inst.BinOp, op: math.CompareOperator) !?*const llvm.Value {
|
||||
const lhs = try self.resolveInst(inst.lhs);
|
||||
const rhs = try self.resolveInst(inst.rhs);
|
||||
fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
if (!inst.base.ty.isInt())
|
||||
if (inst.base.ty.tag() != .bool)
|
||||
return self.todo("implement 'genCmp' for type {}", .{inst.base.ty});
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = try self.resolveInst(bin_op.lhs);
|
||||
const rhs = try self.resolveInst(bin_op.rhs);
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
|
||||
const is_signed = inst.base.ty.isSignedInt();
|
||||
if (!inst_ty.isInt())
|
||||
if (inst_ty.tag() != .bool)
|
||||
return self.todo("implement 'airCmp' for type {}", .{inst_ty});
|
||||
|
||||
const is_signed = inst_ty.isSignedInt();
|
||||
const operation = switch (op) {
|
||||
.eq => .EQ,
|
||||
.neq => .NE,
|
||||
@ -780,32 +811,36 @@ pub const FuncGen = struct {
|
||||
return self.builder.buildICmp(operation, lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn genBlock(self: *FuncGen, inst: *Inst.Block) !?*const llvm.Value {
|
||||
fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const extra = self.air.extraData(Air.Block, ty_pl.payload);
|
||||
const body = self.air.extra[extra.end..][0..extra.data.body_len];
|
||||
const parent_bb = self.context().createBasicBlock("Block");
|
||||
|
||||
// 5 breaks to a block seems like a reasonable default.
|
||||
var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa(), 5);
|
||||
var break_vals = try BreakValues.initCapacity(self.gpa(), 5);
|
||||
try self.blocks.putNoClobber(self.gpa(), inst, .{
|
||||
var break_bbs = try BreakBasicBlocks.initCapacity(self.gpa, 5);
|
||||
var break_vals = try BreakValues.initCapacity(self.gpa, 5);
|
||||
try self.blocks.putNoClobber(self.gpa, inst, .{
|
||||
.parent_bb = parent_bb,
|
||||
.break_bbs = &break_bbs,
|
||||
.break_vals = &break_vals,
|
||||
});
|
||||
defer {
|
||||
assert(self.blocks.remove(inst));
|
||||
break_bbs.deinit(self.gpa());
|
||||
break_vals.deinit(self.gpa());
|
||||
break_bbs.deinit(self.gpa);
|
||||
break_vals.deinit(self.gpa);
|
||||
}
|
||||
|
||||
try self.genBody(inst.body);
|
||||
try self.genBody(body);
|
||||
|
||||
self.llvm_func.appendExistingBasicBlock(parent_bb);
|
||||
self.builder.positionBuilderAtEnd(parent_bb);
|
||||
|
||||
// If the block does not return a value, we dont have to create a phi node.
|
||||
if (!inst.base.ty.hasCodeGenBits()) return null;
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
if (!inst_ty.hasCodeGenBits()) return null;
|
||||
|
||||
const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst.base.ty), "");
|
||||
const phi_node = self.builder.buildPhi(try self.dg.getLLVMType(inst_ty), "");
|
||||
phi_node.addIncoming(
|
||||
break_vals.items.ptr,
|
||||
break_bbs.items.ptr,
|
||||
@ -814,35 +849,30 @@ pub const FuncGen = struct {
|
||||
return phi_node;
|
||||
}
|
||||
|
||||
fn genBr(self: *FuncGen, inst: *Inst.Br) !?*const llvm.Value {
|
||||
var block = self.blocks.get(inst.block).?;
|
||||
fn airBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const branch = self.air.instructions.items(.data)[inst].br;
|
||||
const block = self.blocks.get(branch.block_inst).?;
|
||||
|
||||
// If the break doesn't break a value, then we don't have to add
|
||||
// the values to the lists.
|
||||
if (!inst.operand.ty.hasCodeGenBits()) {
|
||||
// TODO: in astgen these instructions should turn into `br_void` instructions.
|
||||
_ = self.builder.buildBr(block.parent_bb);
|
||||
} else {
|
||||
const val = try self.resolveInst(inst.operand);
|
||||
if (self.air.typeOf(branch.operand).hasCodeGenBits()) {
|
||||
const val = try self.resolveInst(branch.operand);
|
||||
|
||||
// For the phi node, we need the basic blocks and the values of the
|
||||
// break instructions.
|
||||
try block.break_bbs.append(self.gpa(), self.builder.getInsertBlock());
|
||||
try block.break_vals.append(self.gpa(), val);
|
||||
|
||||
_ = self.builder.buildBr(block.parent_bb);
|
||||
try block.break_bbs.append(self.gpa, self.builder.getInsertBlock());
|
||||
try block.break_vals.append(self.gpa, val);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genBrVoid(self: *FuncGen, inst: *Inst.BrVoid) !?*const llvm.Value {
|
||||
var block = self.blocks.get(inst.block).?;
|
||||
_ = self.builder.buildBr(block.parent_bb);
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genCondBr(self: *FuncGen, inst: *Inst.CondBr) !?*const llvm.Value {
|
||||
const condition_value = try self.resolveInst(inst.condition);
|
||||
fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
||||
const cond = try self.resolveInst(pl_op.operand);
|
||||
const extra = self.air.extraData(Air.CondBr, pl_op.payload);
|
||||
const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
|
||||
const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
|
||||
|
||||
const then_block = self.context().appendBasicBlock(self.llvm_func, "Then");
|
||||
const else_block = self.context().appendBasicBlock(self.llvm_func, "Else");
|
||||
@ -851,38 +881,51 @@ pub const FuncGen = struct {
|
||||
defer self.builder.positionBuilderAtEnd(prev_block);
|
||||
|
||||
self.builder.positionBuilderAtEnd(then_block);
|
||||
try self.genBody(inst.then_body);
|
||||
try self.genBody(then_body);
|
||||
|
||||
self.builder.positionBuilderAtEnd(else_block);
|
||||
try self.genBody(inst.else_body);
|
||||
try self.genBody(else_body);
|
||||
}
|
||||
_ = self.builder.buildCondBr(condition_value, then_block, else_block);
|
||||
_ = self.builder.buildCondBr(cond, then_block, else_block);
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genLoop(self: *FuncGen, inst: *Inst.Loop) !?*const llvm.Value {
|
||||
fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const loop = self.air.extraData(Air.Block, ty_pl.payload);
|
||||
const body = self.air.extra[loop.end..][0..loop.data.body_len];
|
||||
const loop_block = self.context().appendBasicBlock(self.llvm_func, "Loop");
|
||||
_ = self.builder.buildBr(loop_block);
|
||||
|
||||
self.builder.positionBuilderAtEnd(loop_block);
|
||||
try self.genBody(inst.body);
|
||||
try self.genBody(body);
|
||||
|
||||
_ = self.builder.buildBr(loop_block);
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genNot(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
|
||||
return self.builder.buildNot(try self.resolveInst(inst.operand), "");
|
||||
fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const operand = try self.resolveInst(ty_op.operand);
|
||||
|
||||
return self.builder.buildNot(operand, "");
|
||||
}
|
||||
|
||||
fn genUnreach(self: *FuncGen, inst: *Inst.NoOp) ?*const llvm.Value {
|
||||
fn airUnreach(self: *FuncGen, inst: Air.Inst.Index) ?*const llvm.Value {
|
||||
_ = inst;
|
||||
_ = self.builder.buildUnreachable();
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genIsNonNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
|
||||
const operand = try self.resolveInst(inst.operand);
|
||||
fn airIsNonNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
||||
const operand = try self.resolveInst(un_op);
|
||||
|
||||
if (operand_is_ptr) {
|
||||
const index_type = self.context().intType(32);
|
||||
@ -898,12 +941,23 @@ pub const FuncGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn genIsNull(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
|
||||
return self.builder.buildNot((try self.genIsNonNull(inst, operand_is_ptr)).?, "");
|
||||
fn airIsNull(self: *FuncGen, inst: Air.Inst.Index, operand_is_ptr: bool) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
return self.builder.buildNot((try self.airIsNonNull(inst, operand_is_ptr)).?, "");
|
||||
}
|
||||
|
||||
fn genOptionalPayload(self: *FuncGen, inst: *Inst.UnOp, operand_is_ptr: bool) !?*const llvm.Value {
|
||||
const operand = try self.resolveInst(inst.operand);
|
||||
fn airOptionalPayload(
|
||||
self: *FuncGen,
|
||||
inst: Air.Inst.Index,
|
||||
operand_is_ptr: bool,
|
||||
) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const operand = try self.resolveInst(ty_op.operand);
|
||||
|
||||
if (operand_is_ptr) {
|
||||
const index_type = self.context().intType(32);
|
||||
@ -919,61 +973,83 @@ pub const FuncGen = struct {
|
||||
}
|
||||
}
|
||||
|
||||
fn genAdd(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
|
||||
const lhs = try self.resolveInst(inst.lhs);
|
||||
const rhs = try self.resolveInst(inst.rhs);
|
||||
fn airAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = try self.resolveInst(bin_op.lhs);
|
||||
const rhs = try self.resolveInst(bin_op.rhs);
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
|
||||
if (!inst.base.ty.isInt())
|
||||
return self.todo("implement 'genAdd' for type {}", .{inst.base.ty});
|
||||
if (!inst_ty.isInt())
|
||||
return self.todo("implement 'airAdd' for type {}", .{inst_ty});
|
||||
|
||||
return if (inst.base.ty.isSignedInt())
|
||||
return if (inst_ty.isSignedInt())
|
||||
self.builder.buildNSWAdd(lhs, rhs, "")
|
||||
else
|
||||
self.builder.buildNUWAdd(lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn genSub(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
|
||||
const lhs = try self.resolveInst(inst.lhs);
|
||||
const rhs = try self.resolveInst(inst.rhs);
|
||||
fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = try self.resolveInst(bin_op.lhs);
|
||||
const rhs = try self.resolveInst(bin_op.rhs);
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
|
||||
if (!inst.base.ty.isInt())
|
||||
return self.todo("implement 'genSub' for type {}", .{inst.base.ty});
|
||||
if (!inst_ty.isInt())
|
||||
return self.todo("implement 'airSub' for type {}", .{inst_ty});
|
||||
|
||||
return if (inst.base.ty.isSignedInt())
|
||||
return if (inst_ty.isSignedInt())
|
||||
self.builder.buildNSWSub(lhs, rhs, "")
|
||||
else
|
||||
self.builder.buildNUWSub(lhs, rhs, "");
|
||||
}
|
||||
|
||||
fn genIntCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
|
||||
const val = try self.resolveInst(inst.operand);
|
||||
fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
const signed = inst.base.ty.isSignedInt();
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const operand = try self.resolveInst(ty_op.operand);
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
|
||||
const signed = inst_ty.isSignedInt();
|
||||
// TODO: Should we use intcast here or just a simple bitcast?
|
||||
// LLVM does truncation vs bitcast (+signed extension) in the intcast depending on the sizes
|
||||
return self.builder.buildIntCast2(val, try self.dg.getLLVMType(inst.base.ty), llvm.Bool.fromBool(signed), "");
|
||||
return self.builder.buildIntCast2(operand, try self.dg.getLLVMType(inst_ty), llvm.Bool.fromBool(signed), "");
|
||||
}
|
||||
|
||||
fn genBitCast(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
|
||||
const val = try self.resolveInst(inst.operand);
|
||||
const dest_type = try self.dg.getLLVMType(inst.base.ty);
|
||||
fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
|
||||
return self.builder.buildBitCast(val, dest_type, "");
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const operand = try self.resolveInst(ty_op.operand);
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
const dest_type = try self.dg.getLLVMType(inst_ty);
|
||||
|
||||
return self.builder.buildBitCast(operand, dest_type, "");
|
||||
}
|
||||
|
||||
fn genArg(self: *FuncGen, inst: *Inst.Arg) !?*const llvm.Value {
|
||||
fn airArg(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const arg_val = self.args[self.arg_index];
|
||||
self.arg_index += 1;
|
||||
|
||||
const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst.base.ty));
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
const ptr_val = self.buildAlloca(try self.dg.getLLVMType(inst_ty));
|
||||
_ = self.builder.buildStore(arg_val, ptr_val);
|
||||
return self.builder.buildLoad(ptr_val, "");
|
||||
}
|
||||
|
||||
fn genAlloc(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value {
|
||||
fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
if (self.liveness.isUnused(inst))
|
||||
return null;
|
||||
// buildAlloca expects the pointee type, not the pointer type, so assert that
|
||||
// a Payload.PointerSimple is passed to the alloc instruction.
|
||||
const pointee_type = inst.base.ty.castPointer().?.data;
|
||||
const inst_ty = self.air.typeOfIndex(inst);
|
||||
const pointee_type = inst_ty.castPointer().?.data;
|
||||
|
||||
// TODO: figure out a way to get the name of the var decl.
|
||||
// TODO: set alignment and volatile
|
||||
@ -1004,19 +1080,26 @@ pub const FuncGen = struct {
|
||||
return val;
|
||||
}
|
||||
|
||||
fn genStore(self: *FuncGen, inst: *Inst.BinOp) !?*const llvm.Value {
|
||||
const val = try self.resolveInst(inst.rhs);
|
||||
const ptr = try self.resolveInst(inst.lhs);
|
||||
_ = self.builder.buildStore(val, ptr);
|
||||
fn airStore(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const dest_ptr = try self.resolveInst(bin_op.lhs);
|
||||
const src_operand = try self.resolveInst(bin_op.rhs);
|
||||
// TODO set volatile on this store properly
|
||||
_ = self.builder.buildStore(src_operand, dest_ptr);
|
||||
return null;
|
||||
}
|
||||
|
||||
fn genLoad(self: *FuncGen, inst: *Inst.UnOp) !?*const llvm.Value {
|
||||
const ptr_val = try self.resolveInst(inst.operand);
|
||||
return self.builder.buildLoad(ptr_val, "");
|
||||
fn airLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const is_volatile = self.air.typeOf(ty_op.operand).isVolatilePtr();
|
||||
if (!is_volatile and self.liveness.isUnused(inst))
|
||||
return null;
|
||||
const ptr = try self.resolveInst(ty_op.operand);
|
||||
// TODO set volatile on this load properly
|
||||
return self.builder.buildLoad(ptr, "");
|
||||
}
|
||||
|
||||
fn genBreakpoint(self: *FuncGen, inst: *Inst.NoOp) !?*const llvm.Value {
|
||||
fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
|
||||
_ = inst;
|
||||
const llvn_fn = self.getIntrinsic("llvm.debugtrap");
|
||||
_ = self.builder.buildCall(llvn_fn, null, 0, "");
|
||||
|
||||
@ -12,21 +12,21 @@ const Decl = Module.Decl;
|
||||
const Type = @import("../type.zig").Type;
|
||||
const Value = @import("../value.zig").Value;
|
||||
const LazySrcLoc = Module.LazySrcLoc;
|
||||
const ir = @import("../air.zig");
|
||||
const Inst = ir.Inst;
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
pub const Word = u32;
|
||||
pub const ResultId = u32;
|
||||
|
||||
pub const TypeMap = std.HashMap(Type, u32, Type.HashContext64, std.hash_map.default_max_load_percentage);
|
||||
pub const InstMap = std.AutoHashMap(*Inst, ResultId);
|
||||
pub const InstMap = std.AutoHashMap(Air.Inst.Index, ResultId);
|
||||
|
||||
const IncomingBlock = struct {
|
||||
src_label_id: ResultId,
|
||||
break_value_id: ResultId,
|
||||
};
|
||||
|
||||
pub const BlockMap = std.AutoHashMap(*Inst.Block, struct {
|
||||
pub const BlockMap = std.AutoHashMap(Air.Inst.Index, struct {
|
||||
label_id: ResultId,
|
||||
incoming_blocks: *std.ArrayListUnmanaged(IncomingBlock),
|
||||
});
|
||||
@ -160,7 +160,11 @@ pub const DeclGen = struct {
|
||||
/// The SPIR-V module code should be put in.
|
||||
spv: *SPIRVModule,
|
||||
|
||||
/// An array of function argument result-ids. Each index corresponds with the function argument of the same index.
|
||||
air: Air,
|
||||
liveness: Liveness,
|
||||
|
||||
/// An array of function argument result-ids. Each index corresponds with the
|
||||
/// function argument of the same index.
|
||||
args: std.ArrayList(ResultId),
|
||||
|
||||
/// A counter to keep track of how many `arg` instructions we've seen yet.
|
||||
@ -169,33 +173,35 @@ pub const DeclGen = struct {
|
||||
/// A map keeping track of which instruction generated which result-id.
|
||||
inst_results: InstMap,
|
||||
|
||||
/// We need to keep track of result ids for block labels, as well as the 'incoming' blocks for a block.
|
||||
/// We need to keep track of result ids for block labels, as well as the 'incoming'
|
||||
/// blocks for a block.
|
||||
blocks: BlockMap,
|
||||
|
||||
/// The label of the SPIR-V block we are currently generating.
|
||||
current_block_label_id: ResultId,
|
||||
|
||||
/// The actual instructions for this function. We need to declare all locals in the first block, and because we don't
|
||||
/// know which locals there are going to be, we're just going to generate everything after the locals-section in this array.
|
||||
/// Note: It will not contain OpFunction, OpFunctionParameter, OpVariable and the initial OpLabel. These will be generated
|
||||
/// into spv.binary.fn_decls directly.
|
||||
/// The actual instructions for this function. We need to declare all locals in
|
||||
/// the first block, and because we don't know which locals there are going to be,
|
||||
/// we're just going to generate everything after the locals-section in this array.
|
||||
/// Note: It will not contain OpFunction, OpFunctionParameter, OpVariable and the
|
||||
/// initial OpLabel. These will be generated into spv.binary.fn_decls directly.
|
||||
code: std.ArrayList(Word),
|
||||
|
||||
/// The decl we are currently generating code for.
|
||||
decl: *Decl,
|
||||
|
||||
/// If `gen` returned `Error.AnalysisFail`, this contains an explanatory message. Memory is owned by
|
||||
/// `module.gpa`.
|
||||
/// If `gen` returned `Error.AnalysisFail`, this contains an explanatory message.
|
||||
/// Memory is owned by `module.gpa`.
|
||||
error_msg: ?*Module.ErrorMsg,
|
||||
|
||||
/// Possible errors the `gen` function may return.
|
||||
const Error = error{ AnalysisFail, OutOfMemory };
|
||||
|
||||
/// This structure is used to return information about a type typically used for arithmetic operations.
|
||||
/// These types may either be integers, floats, or a vector of these. Most scalar operations also work on vectors,
|
||||
/// so we can easily represent those as arithmetic types.
|
||||
/// If the type is a scalar, 'inner type' refers to the scalar type. Otherwise, if its a vector, it refers
|
||||
/// to the vector's element type.
|
||||
/// This structure is used to return information about a type typically used for
|
||||
/// arithmetic operations. These types may either be integers, floats, or a vector
|
||||
/// of these. Most scalar operations also work on vectors, so we can easily represent
|
||||
/// those as arithmetic types. If the type is a scalar, 'inner type' refers to the
|
||||
/// scalar type. Otherwise, if its a vector, it refers to the vector's element type.
|
||||
const ArithmeticTypeInfo = struct {
|
||||
/// A classification of the inner type.
|
||||
const Class = enum {
|
||||
@ -207,13 +213,14 @@ pub const DeclGen = struct {
|
||||
/// the relevant capability is enabled).
|
||||
integer,
|
||||
|
||||
/// A regular float. These are all required to be natively supported. Floating points for
|
||||
/// which the relevant capability is not enabled are not emulated.
|
||||
/// A regular float. These are all required to be natively supported. Floating points
|
||||
/// for which the relevant capability is not enabled are not emulated.
|
||||
float,
|
||||
|
||||
/// An integer of a 'strange' size (which' bit size is not the same as its backing type. **Note**: this
|
||||
/// may **also** include power-of-2 integers for which the relevant capability is not enabled), but still
|
||||
/// within the limits of the largest natively supported integer type.
|
||||
/// An integer of a 'strange' size (which' bit size is not the same as its backing
|
||||
/// type. **Note**: this may **also** include power-of-2 integers for which the
|
||||
/// relevant capability is not enabled), but still within the limits of the largest
|
||||
/// natively supported integer type.
|
||||
strange_integer,
|
||||
|
||||
/// An integer with more bits than the largest natively supported integer type.
|
||||
@ -221,7 +228,7 @@ pub const DeclGen = struct {
|
||||
};
|
||||
|
||||
/// The number of bits in the inner type.
|
||||
/// Note: this is the actual number of bits of the type, not the size of the backing integer.
|
||||
/// This is the actual number of bits of the type, not the size of the backing integer.
|
||||
bits: u16,
|
||||
|
||||
/// Whether the type is a vector.
|
||||
@ -235,10 +242,13 @@ pub const DeclGen = struct {
|
||||
class: Class,
|
||||
};
|
||||
|
||||
/// Initialize the common resources of a DeclGen. Some fields are left uninitialized, only set when `gen` is called.
|
||||
/// Initialize the common resources of a DeclGen. Some fields are left uninitialized,
|
||||
/// only set when `gen` is called.
|
||||
pub fn init(spv: *SPIRVModule) DeclGen {
|
||||
return .{
|
||||
.spv = spv,
|
||||
.air = undefined,
|
||||
.liveness = undefined,
|
||||
.args = std.ArrayList(ResultId).init(spv.gpa),
|
||||
.next_arg_index = undefined,
|
||||
.inst_results = InstMap.init(spv.gpa),
|
||||
@ -251,10 +261,12 @@ pub const DeclGen = struct {
|
||||
}
|
||||
|
||||
/// Generate the code for `decl`. If a reportable error occured during code generation,
|
||||
/// a message is returned by this function. Callee owns the memory. If this function returns such
|
||||
/// a reportable error, it is valid to be called again for a different decl.
|
||||
pub fn gen(self: *DeclGen, decl: *Decl) !?*Module.ErrorMsg {
|
||||
/// a message is returned by this function. Callee owns the memory. If this function
|
||||
/// returns such a reportable error, it is valid to be called again for a different decl.
|
||||
pub fn gen(self: *DeclGen, decl: *Decl, air: Air, liveness: Liveness) !?*Module.ErrorMsg {
|
||||
// Reset internal resources, we don't want to re-allocate these.
|
||||
self.air = air;
|
||||
self.liveness = liveness;
|
||||
self.args.items.len = 0;
|
||||
self.next_arg_index = 0;
|
||||
self.inst_results.clearRetainingCapacity();
|
||||
@ -280,19 +292,20 @@ pub const DeclGen = struct {
|
||||
return self.spv.module.getTarget();
|
||||
}
|
||||
|
||||
fn fail(self: *DeclGen, src: LazySrcLoc, comptime format: []const u8, args: anytype) Error {
|
||||
fn fail(self: *DeclGen, comptime format: []const u8, args: anytype) Error {
|
||||
@setCold(true);
|
||||
const src: LazySrcLoc = .{ .node_offset = 0 };
|
||||
const src_loc = src.toSrcLocWithDecl(self.decl);
|
||||
self.error_msg = try Module.ErrorMsg.create(self.spv.module.gpa, src_loc, format, args);
|
||||
return error.AnalysisFail;
|
||||
}
|
||||
|
||||
fn resolve(self: *DeclGen, inst: *Inst) !ResultId {
|
||||
if (inst.value()) |val| {
|
||||
return self.genConstant(inst.src, inst.ty, val);
|
||||
fn resolve(self: *DeclGen, inst: Air.Inst.Ref) !ResultId {
|
||||
if (self.air.value(inst)) |val| {
|
||||
return self.genConstant(self.air.typeOf(inst), val);
|
||||
}
|
||||
|
||||
return self.inst_results.get(inst).?; // Instruction does not dominate all uses!
|
||||
const index = Air.refToIndex(inst).?;
|
||||
return self.inst_results.get(index).?; // Assertion means instruction does not dominate usage.
|
||||
}
|
||||
|
||||
fn beginSPIRVBlock(self: *DeclGen, label_id: ResultId) !void {
|
||||
@ -314,7 +327,7 @@ pub const DeclGen = struct {
|
||||
const target = self.getTarget();
|
||||
|
||||
// The backend will never be asked to compiler a 0-bit integer, so we won't have to handle those in this function.
|
||||
std.debug.assert(bits != 0);
|
||||
assert(bits != 0);
|
||||
|
||||
// 8, 16 and 64-bit integers require the Int8, Int16 and Inr64 capabilities respectively.
|
||||
// 32-bit integers are always supported (see spec, 2.16.1, Data rules).
|
||||
@ -388,19 +401,19 @@ pub const DeclGen = struct {
|
||||
.composite_integer };
|
||||
},
|
||||
// As of yet, there is no vector support in the self-hosted compiler.
|
||||
.Vector => self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
|
||||
.Vector => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for Vector", .{}),
|
||||
// TODO: For which types is this the case?
|
||||
else => self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}),
|
||||
else => self.fail("TODO: SPIR-V backend: implement arithmeticTypeInfo for {}", .{ty}),
|
||||
};
|
||||
}
|
||||
|
||||
/// Generate a constant representing `val`.
|
||||
/// TODO: Deduplication?
|
||||
fn genConstant(self: *DeclGen, src: LazySrcLoc, ty: Type, val: Value) Error!ResultId {
|
||||
fn genConstant(self: *DeclGen, ty: Type, val: Value) Error!ResultId {
|
||||
const target = self.getTarget();
|
||||
const code = &self.spv.binary.types_globals_constants;
|
||||
const result_id = self.spv.allocResultId();
|
||||
const result_type_id = try self.genType(src, ty);
|
||||
const result_type_id = try self.genType(ty);
|
||||
|
||||
if (val.isUndef()) {
|
||||
try writeInstruction(code, .OpUndef, &[_]Word{ result_type_id, result_id });
|
||||
@ -412,13 +425,13 @@ pub const DeclGen = struct {
|
||||
const int_info = ty.intInfo(target);
|
||||
const backing_bits = self.backingIntBits(int_info.bits) orelse {
|
||||
// Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
|
||||
return self.fail(src, "TODO: SPIR-V backend: implement composite int constants for {}", .{ty});
|
||||
return self.fail("TODO: SPIR-V backend: implement composite int constants for {}", .{ty});
|
||||
};
|
||||
|
||||
// We can just use toSignedInt/toUnsignedInt here as it returns u64 - a type large enough to hold any
|
||||
// SPIR-V native type (up to i/u64 with Int64). If SPIR-V ever supports native ints of a larger size, this
|
||||
// might need to be updated.
|
||||
std.debug.assert(self.largestSupportedIntBits() <= std.meta.bitCount(u64));
|
||||
assert(self.largestSupportedIntBits() <= std.meta.bitCount(u64));
|
||||
var int_bits = if (ty.isSignedInt()) @bitCast(u64, val.toSignedInt()) else val.toUnsignedInt();
|
||||
|
||||
// Mask the low bits which make up the actual integer. This is to make sure that negative values
|
||||
@ -470,13 +483,13 @@ pub const DeclGen = struct {
|
||||
}
|
||||
},
|
||||
.Void => unreachable,
|
||||
else => return self.fail(src, "TODO: SPIR-V backend: constant generation of type {}", .{ty}),
|
||||
else => return self.fail("TODO: SPIR-V backend: constant generation of type {}", .{ty}),
|
||||
}
|
||||
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn genType(self: *DeclGen, src: LazySrcLoc, ty: Type) Error!ResultId {
|
||||
fn genType(self: *DeclGen, ty: Type) Error!ResultId {
|
||||
// We can't use getOrPut here so we can recursively generate types.
|
||||
if (self.spv.types.get(ty)) |already_generated| {
|
||||
return already_generated;
|
||||
@ -493,7 +506,7 @@ pub const DeclGen = struct {
|
||||
const int_info = ty.intInfo(target);
|
||||
const backing_bits = self.backingIntBits(int_info.bits) orelse {
|
||||
// Integers too big for any native type are represented as "composite integers": An array of largestSupportedIntBits.
|
||||
return self.fail(src, "TODO: SPIR-V backend: implement composite int {}", .{ty});
|
||||
return self.fail("TODO: SPIR-V backend: implement composite int {}", .{ty});
|
||||
};
|
||||
|
||||
// TODO: If backing_bits != int_info.bits, a duplicate type might be generated here.
|
||||
@ -519,7 +532,7 @@ pub const DeclGen = struct {
|
||||
};
|
||||
|
||||
if (!supported) {
|
||||
return self.fail(src, "Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
|
||||
return self.fail("Floating point width of {} bits is not supported for the current SPIR-V feature set", .{bits});
|
||||
}
|
||||
|
||||
try writeInstruction(code, .OpTypeFloat, &[_]Word{ result_id, bits });
|
||||
@ -527,19 +540,19 @@ pub const DeclGen = struct {
|
||||
.Fn => {
|
||||
// We only support zig-calling-convention functions, no varargs.
|
||||
if (ty.fnCallingConvention() != .Unspecified)
|
||||
return self.fail(src, "Unsupported calling convention for SPIR-V", .{});
|
||||
return self.fail("Unsupported calling convention for SPIR-V", .{});
|
||||
if (ty.fnIsVarArgs())
|
||||
return self.fail(src, "VarArgs unsupported for SPIR-V", .{});
|
||||
return self.fail("VarArgs unsupported for SPIR-V", .{});
|
||||
|
||||
// In order to avoid a temporary here, first generate all the required types and then simply look them up
|
||||
// when generating the function type.
|
||||
const params = ty.fnParamLen();
|
||||
var i: usize = 0;
|
||||
while (i < params) : (i += 1) {
|
||||
_ = try self.genType(src, ty.fnParamType(i));
|
||||
_ = try self.genType(ty.fnParamType(i));
|
||||
}
|
||||
|
||||
const return_type_id = try self.genType(src, ty.fnReturnType());
|
||||
const return_type_id = try self.genType(ty.fnReturnType());
|
||||
|
||||
// result id + result type id + parameter type ids.
|
||||
try writeOpcode(code, .OpTypeFunction, 2 + @intCast(u16, ty.fnParamLen()));
|
||||
@ -552,7 +565,7 @@ pub const DeclGen = struct {
|
||||
}
|
||||
},
|
||||
// When recursively generating a type, we cannot infer the pointer's storage class. See genPointerType.
|
||||
.Pointer => return self.fail(src, "Cannot create pointer with unkown storage class", .{}),
|
||||
.Pointer => return self.fail("Cannot create pointer with unkown storage class", .{}),
|
||||
.Vector => {
|
||||
// Although not 100% the same, Zig vectors map quite neatly to SPIR-V vectors (including many integer and float operations
|
||||
// which work on them), so simply use those.
|
||||
@ -562,7 +575,7 @@ pub const DeclGen = struct {
|
||||
// is adequate at all for this.
|
||||
|
||||
// TODO: Vectors are not yet supported by the self-hosted compiler itself it seems.
|
||||
return self.fail(src, "TODO: SPIR-V backend: implement type Vector", .{});
|
||||
return self.fail("TODO: SPIR-V backend: implement type Vector", .{});
|
||||
},
|
||||
.Null,
|
||||
.Undefined,
|
||||
@ -574,7 +587,7 @@ pub const DeclGen = struct {
|
||||
|
||||
.BoundFn => unreachable, // this type will be deleted from the language.
|
||||
|
||||
else => |tag| return self.fail(src, "TODO: SPIR-V backend: implement type {}s", .{tag}),
|
||||
else => |tag| return self.fail("TODO: SPIR-V backend: implement type {}s", .{tag}),
|
||||
}
|
||||
|
||||
try self.spv.types.putNoClobber(ty, result_id);
|
||||
@ -583,8 +596,8 @@ pub const DeclGen = struct {
|
||||
|
||||
/// SPIR-V requires pointers to have a storage class (address space), and so we have a special function for that.
|
||||
/// TODO: The result of this needs to be cached.
|
||||
fn genPointerType(self: *DeclGen, src: LazySrcLoc, ty: Type, storage_class: spec.StorageClass) !ResultId {
|
||||
std.debug.assert(ty.zigTypeTag() == .Pointer);
|
||||
fn genPointerType(self: *DeclGen, ty: Type, storage_class: spec.StorageClass) !ResultId {
|
||||
assert(ty.zigTypeTag() == .Pointer);
|
||||
|
||||
const code = &self.spv.binary.types_globals_constants;
|
||||
const result_id = self.spv.allocResultId();
|
||||
@ -592,7 +605,7 @@ pub const DeclGen = struct {
|
||||
// TODO: There are many constraints which are ignored for now: We may only create pointers to certain types, and to other types
|
||||
// if more capabilities are enabled. For example, we may only create pointers to f16 if Float16Buffer is enabled.
|
||||
// These also relates to the pointer's address space.
|
||||
const child_id = try self.genType(src, ty.elemType());
|
||||
const child_id = try self.genType(ty.elemType());
|
||||
|
||||
try writeInstruction(code, .OpTypePointer, &[_]Word{ result_id, @enumToInt(storage_class), child_id });
|
||||
|
||||
@ -603,9 +616,9 @@ pub const DeclGen = struct {
|
||||
const decl = self.decl;
|
||||
const result_id = decl.fn_link.spirv.id;
|
||||
|
||||
if (decl.val.castTag(.function)) |func_payload| {
|
||||
std.debug.assert(decl.ty.zigTypeTag() == .Fn);
|
||||
const prototype_id = try self.genType(.{ .node_offset = 0 }, decl.ty);
|
||||
if (decl.val.castTag(.function)) |_| {
|
||||
assert(decl.ty.zigTypeTag() == .Fn);
|
||||
const prototype_id = try self.genType(decl.ty);
|
||||
try writeInstruction(&self.spv.binary.fn_decls, .OpFunction, &[_]Word{
|
||||
self.spv.types.get(decl.ty.fnReturnType()).?, // This type should be generated along with the prototype.
|
||||
result_id,
|
||||
@ -632,189 +645,171 @@ pub const DeclGen = struct {
|
||||
try writeInstruction(&self.spv.binary.fn_decls, .OpLabel, &[_]Word{root_block_id});
|
||||
self.current_block_label_id = root_block_id;
|
||||
|
||||
try self.genBody(func_payload.data.body);
|
||||
const main_body = self.air.getMainBody();
|
||||
try self.genBody(main_body);
|
||||
|
||||
// Append the actual code into the fn_decls section.
|
||||
try self.spv.binary.fn_decls.appendSlice(self.code.items);
|
||||
try writeInstruction(&self.spv.binary.fn_decls, .OpFunctionEnd, &[_]Word{});
|
||||
} else {
|
||||
return self.fail(.{ .node_offset = 0 }, "TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});
|
||||
return self.fail("TODO: SPIR-V backend: generate decl type {}", .{decl.ty.zigTypeTag()});
|
||||
}
|
||||
}
|
||||
|
||||
fn genBody(self: *DeclGen, body: ir.Body) Error!void {
|
||||
for (body.instructions) |inst| {
|
||||
fn genBody(self: *DeclGen, body: []const Air.Inst.Index) Error!void {
|
||||
for (body) |inst| {
|
||||
try self.genInst(inst);
|
||||
}
|
||||
}
|
||||
|
||||
fn genInst(self: *DeclGen, inst: *Inst) !void {
|
||||
const result_id = switch (inst.tag) {
|
||||
.add, .addwrap => try self.genBinOp(inst.castTag(.add).?),
|
||||
.sub, .subwrap => try self.genBinOp(inst.castTag(.sub).?),
|
||||
.mul, .mulwrap => try self.genBinOp(inst.castTag(.mul).?),
|
||||
.div => try self.genBinOp(inst.castTag(.div).?),
|
||||
.bit_and => try self.genBinOp(inst.castTag(.bit_and).?),
|
||||
.bit_or => try self.genBinOp(inst.castTag(.bit_or).?),
|
||||
.xor => try self.genBinOp(inst.castTag(.xor).?),
|
||||
.cmp_eq => try self.genCmp(inst.castTag(.cmp_eq).?),
|
||||
.cmp_neq => try self.genCmp(inst.castTag(.cmp_neq).?),
|
||||
.cmp_gt => try self.genCmp(inst.castTag(.cmp_gt).?),
|
||||
.cmp_gte => try self.genCmp(inst.castTag(.cmp_gte).?),
|
||||
.cmp_lt => try self.genCmp(inst.castTag(.cmp_lt).?),
|
||||
.cmp_lte => try self.genCmp(inst.castTag(.cmp_lte).?),
|
||||
.bool_and => try self.genBinOp(inst.castTag(.bool_and).?),
|
||||
.bool_or => try self.genBinOp(inst.castTag(.bool_or).?),
|
||||
.not => try self.genUnOp(inst.castTag(.not).?),
|
||||
.alloc => try self.genAlloc(inst.castTag(.alloc).?),
|
||||
.arg => self.genArg(),
|
||||
.block => (try self.genBlock(inst.castTag(.block).?)) orelse return,
|
||||
.br => return try self.genBr(inst.castTag(.br).?),
|
||||
.br_void => return try self.genBrVoid(inst.castTag(.br_void).?),
|
||||
// TODO: Breakpoints won't be supported in SPIR-V, but the compiler seems to insert them
|
||||
// throughout the IR.
|
||||
fn genInst(self: *DeclGen, inst: Air.Inst.Index) !void {
|
||||
const air_tags = self.air.instructions.items(.tag);
|
||||
const result_id = switch (air_tags[inst]) {
|
||||
// zig fmt: off
|
||||
.add, .addwrap => try self.airArithOp(inst, .{.OpFAdd, .OpIAdd, .OpIAdd}),
|
||||
.sub, .subwrap => try self.airArithOp(inst, .{.OpFSub, .OpISub, .OpISub}),
|
||||
.mul, .mulwrap => try self.airArithOp(inst, .{.OpFMul, .OpIMul, .OpIMul}),
|
||||
.div => try self.airArithOp(inst, .{.OpFDiv, .OpSDiv, .OpUDiv}),
|
||||
|
||||
.bit_and => try self.airBinOpSimple(inst, .OpBitwiseAnd),
|
||||
.bit_or => try self.airBinOpSimple(inst, .OpBitwiseOr),
|
||||
.xor => try self.airBinOpSimple(inst, .OpBitwiseXor),
|
||||
.bool_and => try self.airBinOpSimple(inst, .OpLogicalAnd),
|
||||
.bool_or => try self.airBinOpSimple(inst, .OpLogicalOr),
|
||||
|
||||
.not => try self.airNot(inst),
|
||||
|
||||
.cmp_eq => try self.airCmp(inst, .{.OpFOrdEqual, .OpLogicalEqual, .OpIEqual}),
|
||||
.cmp_neq => try self.airCmp(inst, .{.OpFOrdNotEqual, .OpLogicalNotEqual, .OpINotEqual}),
|
||||
.cmp_gt => try self.airCmp(inst, .{.OpFOrdGreaterThan, .OpSGreaterThan, .OpUGreaterThan}),
|
||||
.cmp_gte => try self.airCmp(inst, .{.OpFOrdGreaterThanEqual, .OpSGreaterThanEqual, .OpUGreaterThanEqual}),
|
||||
.cmp_lt => try self.airCmp(inst, .{.OpFOrdLessThan, .OpSLessThan, .OpULessThan}),
|
||||
.cmp_lte => try self.airCmp(inst, .{.OpFOrdLessThanEqual, .OpSLessThanEqual, .OpULessThanEqual}),
|
||||
|
||||
.arg => self.airArg(),
|
||||
.alloc => try self.airAlloc(inst),
|
||||
.block => (try self.airBlock(inst)) orelse return,
|
||||
.load => try self.airLoad(inst),
|
||||
|
||||
.br => return self.airBr(inst),
|
||||
.breakpoint => return,
|
||||
.condbr => return try self.genCondBr(inst.castTag(.condbr).?),
|
||||
.constant => unreachable,
|
||||
.dbg_stmt => return try self.genDbgStmt(inst.castTag(.dbg_stmt).?),
|
||||
.load => try self.genLoad(inst.castTag(.load).?),
|
||||
.loop => return try self.genLoop(inst.castTag(.loop).?),
|
||||
.ret => return try self.genRet(inst.castTag(.ret).?),
|
||||
.retvoid => return try self.genRetVoid(),
|
||||
.store => return try self.genStore(inst.castTag(.store).?),
|
||||
.unreach => return try self.genUnreach(),
|
||||
else => return self.fail(inst.src, "TODO: SPIR-V backend: implement inst {s}", .{@tagName(inst.tag)}),
|
||||
.cond_br => return self.airCondBr(inst),
|
||||
.constant => unreachable,
|
||||
.dbg_stmt => return self.airDbgStmt(inst),
|
||||
.loop => return self.airLoop(inst),
|
||||
.ret => return self.airRet(inst),
|
||||
.store => return self.airStore(inst),
|
||||
.unreach => return self.airUnreach(),
|
||||
// zig fmt: on
|
||||
|
||||
else => |tag| return self.fail("TODO: SPIR-V backend: implement AIR tag {s}", .{
|
||||
@tagName(tag),
|
||||
}),
|
||||
};
|
||||
|
||||
try self.inst_results.putNoClobber(inst, result_id);
|
||||
}
|
||||
|
||||
fn genBinOp(self: *DeclGen, inst: *Inst.BinOp) !ResultId {
|
||||
// TODO: Will lhs and rhs have the same type?
|
||||
const lhs_id = try self.resolve(inst.lhs);
|
||||
const rhs_id = try self.resolve(inst.rhs);
|
||||
fn airBinOpSimple(self: *DeclGen, inst: Air.Inst.Index, opcode: Opcode) !ResultId {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs_id = try self.resolve(bin_op.lhs);
|
||||
const rhs_id = try self.resolve(bin_op.rhs);
|
||||
const result_id = self.spv.allocResultId();
|
||||
const result_type_id = try self.genType(self.air.typeOfIndex(inst));
|
||||
try writeInstruction(&self.code, opcode, &[_]Word{
|
||||
result_type_id, result_id, lhs_id, rhs_id,
|
||||
});
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn airArithOp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {
|
||||
// LHS and RHS are guaranteed to have the same type, and AIR guarantees
|
||||
// the result to be the same as the LHS and RHS, which matches SPIR-V.
|
||||
const ty = self.air.typeOfIndex(inst);
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs_id = try self.resolve(bin_op.lhs);
|
||||
const rhs_id = try self.resolve(bin_op.rhs);
|
||||
|
||||
const result_id = self.spv.allocResultId();
|
||||
const result_type_id = try self.genType(inst.base.src, inst.base.ty);
|
||||
const result_type_id = try self.genType(ty);
|
||||
|
||||
// TODO: Is the result the same as the argument types?
|
||||
// This is supposed to be the case for SPIR-V.
|
||||
std.debug.assert(inst.rhs.ty.eql(inst.lhs.ty));
|
||||
std.debug.assert(inst.base.ty.tag() == .bool or inst.base.ty.eql(inst.lhs.ty));
|
||||
assert(self.air.typeOf(bin_op.lhs).eql(ty));
|
||||
assert(self.air.typeOf(bin_op.rhs).eql(ty));
|
||||
|
||||
// Binary operations are generally applicable to both scalar and vector operations in SPIR-V, but int and float
|
||||
// versions of operations require different opcodes.
|
||||
// For operations which produce bools, the information of inst.base.ty is not useful, so just pick either operand
|
||||
// instead.
|
||||
const info = try self.arithmeticTypeInfo(inst.lhs.ty);
|
||||
// Binary operations are generally applicable to both scalar and vector operations
|
||||
// in SPIR-V, but int and float versions of operations require different opcodes.
|
||||
const info = try self.arithmeticTypeInfo(ty);
|
||||
|
||||
if (info.class == .composite_integer) {
|
||||
return self.fail(inst.base.src, "TODO: SPIR-V backend: binary operations for composite integers", .{});
|
||||
} else if (info.class == .strange_integer) {
|
||||
return self.fail(inst.base.src, "TODO: SPIR-V backend: binary operations for strange integers", .{});
|
||||
}
|
||||
|
||||
const is_float = info.class == .float;
|
||||
const is_signed = info.signedness == .signed;
|
||||
// **Note**: All these operations must be valid for vectors as well!
|
||||
const opcode = switch (inst.base.tag) {
|
||||
// The regular integer operations are all defined for wrapping. Since theyre only relevant for integers,
|
||||
// we can just switch on both cases here.
|
||||
.add, .addwrap => if (is_float) Opcode.OpFAdd else Opcode.OpIAdd,
|
||||
.sub, .subwrap => if (is_float) Opcode.OpFSub else Opcode.OpISub,
|
||||
.mul, .mulwrap => if (is_float) Opcode.OpFMul else Opcode.OpIMul,
|
||||
// TODO: Trap if divisor is 0?
|
||||
// TODO: Figure out of OpSDiv for unsigned/OpUDiv for signed does anything useful.
|
||||
// => Those are probably for divTrunc and divFloor, though the compiler does not yet generate those.
|
||||
// => TODO: Figure out how those work on the SPIR-V side.
|
||||
// => TODO: Test these.
|
||||
.div => if (is_float) Opcode.OpFDiv else if (is_signed) Opcode.OpSDiv else Opcode.OpUDiv,
|
||||
// Only integer versions for these.
|
||||
.bit_and => Opcode.OpBitwiseAnd,
|
||||
.bit_or => Opcode.OpBitwiseOr,
|
||||
.xor => Opcode.OpBitwiseXor,
|
||||
// Bool -> bool operations.
|
||||
.bool_and => Opcode.OpLogicalAnd,
|
||||
.bool_or => Opcode.OpLogicalOr,
|
||||
const opcode_index: usize = switch (info.class) {
|
||||
.composite_integer => {
|
||||
return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});
|
||||
},
|
||||
.strange_integer => {
|
||||
return self.fail("TODO: SPIR-V backend: binary operations for strange integers", .{});
|
||||
},
|
||||
.integer => switch (info.signedness) {
|
||||
.signed => @as(usize, 1),
|
||||
.unsigned => @as(usize, 2),
|
||||
},
|
||||
.float => 0,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const opcode = ops[opcode_index];
|
||||
try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });
|
||||
|
||||
// TODO: Trap on overflow? Probably going to be annoying.
|
||||
// TODO: Look into SPV_KHR_no_integer_wrap_decoration which provides NoSignedWrap/NoUnsignedWrap.
|
||||
|
||||
if (info.class != .strange_integer)
|
||||
return result_id;
|
||||
|
||||
return self.fail(inst.base.src, "TODO: SPIR-V backend: strange integer operation mask", .{});
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn genCmp(self: *DeclGen, inst: *Inst.BinOp) !ResultId {
|
||||
const lhs_id = try self.resolve(inst.lhs);
|
||||
const rhs_id = try self.resolve(inst.rhs);
|
||||
|
||||
fn airCmp(self: *DeclGen, inst: Air.Inst.Index, ops: [3]Opcode) !ResultId {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs_id = try self.resolve(bin_op.lhs);
|
||||
const rhs_id = try self.resolve(bin_op.rhs);
|
||||
const result_id = self.spv.allocResultId();
|
||||
const result_type_id = try self.genType(inst.base.src, inst.base.ty);
|
||||
const result_type_id = try self.genType(Type.initTag(.bool));
|
||||
const op_ty = self.air.typeOf(bin_op.lhs);
|
||||
assert(op_ty.eql(self.air.typeOf(bin_op.rhs)));
|
||||
|
||||
// All of these operations should be 2 equal types -> bool
|
||||
std.debug.assert(inst.rhs.ty.eql(inst.lhs.ty));
|
||||
std.debug.assert(inst.base.ty.tag() == .bool);
|
||||
// Comparisons are generally applicable to both scalar and vector operations in SPIR-V,
|
||||
// but int and float versions of operations require different opcodes.
|
||||
const info = try self.arithmeticTypeInfo(op_ty);
|
||||
|
||||
// Comparisons are generally applicable to both scalar and vector operations in SPIR-V, but int and float
|
||||
// versions of operations require different opcodes.
|
||||
// Since inst.base.ty is always bool and so not very useful, and because both arguments must be the same, just get the info
|
||||
// from either of the operands.
|
||||
const info = try self.arithmeticTypeInfo(inst.lhs.ty);
|
||||
|
||||
if (info.class == .composite_integer) {
|
||||
return self.fail(inst.base.src, "TODO: SPIR-V backend: binary operations for composite integers", .{});
|
||||
} else if (info.class == .strange_integer) {
|
||||
return self.fail(inst.base.src, "TODO: SPIR-V backend: comparison for strange integers", .{});
|
||||
}
|
||||
|
||||
const is_bool = info.class == .bool;
|
||||
const is_float = info.class == .float;
|
||||
const is_signed = info.signedness == .signed;
|
||||
|
||||
// **Note**: All these operations must be valid for vectors as well!
|
||||
// For floating points, we generally want ordered operations (which return false if either operand is nan).
|
||||
const opcode = switch (inst.base.tag) {
|
||||
.cmp_eq => if (is_float) Opcode.OpFOrdEqual else if (is_bool) Opcode.OpLogicalEqual else Opcode.OpIEqual,
|
||||
.cmp_neq => if (is_float) Opcode.OpFOrdNotEqual else if (is_bool) Opcode.OpLogicalNotEqual else Opcode.OpINotEqual,
|
||||
// TODO: Verify that these OpFOrd type operations produce the right value.
|
||||
// TODO: Is there a more fundamental difference between OpU and OpS operations here than just the type?
|
||||
.cmp_gt => if (is_float) Opcode.OpFOrdGreaterThan else if (is_signed) Opcode.OpSGreaterThan else Opcode.OpUGreaterThan,
|
||||
.cmp_gte => if (is_float) Opcode.OpFOrdGreaterThanEqual else if (is_signed) Opcode.OpSGreaterThanEqual else Opcode.OpUGreaterThanEqual,
|
||||
.cmp_lt => if (is_float) Opcode.OpFOrdLessThan else if (is_signed) Opcode.OpSLessThan else Opcode.OpULessThan,
|
||||
.cmp_lte => if (is_float) Opcode.OpFOrdLessThanEqual else if (is_signed) Opcode.OpSLessThanEqual else Opcode.OpULessThanEqual,
|
||||
else => unreachable,
|
||||
const opcode_index: usize = switch (info.class) {
|
||||
.composite_integer => {
|
||||
return self.fail("TODO: SPIR-V backend: binary operations for composite integers", .{});
|
||||
},
|
||||
.strange_integer => {
|
||||
return self.fail("TODO: SPIR-V backend: comparison for strange integers", .{});
|
||||
},
|
||||
.float => 0,
|
||||
.bool => 1,
|
||||
.integer => switch (info.signedness) {
|
||||
.signed => @as(usize, 1),
|
||||
.unsigned => @as(usize, 2),
|
||||
},
|
||||
};
|
||||
const opcode = ops[opcode_index];
|
||||
|
||||
try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, lhs_id, rhs_id });
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn genUnOp(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
|
||||
const operand_id = try self.resolve(inst.operand);
|
||||
|
||||
fn airNot(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const operand_id = try self.resolve(ty_op.operand);
|
||||
const result_id = self.spv.allocResultId();
|
||||
const result_type_id = try self.genType(inst.base.src, inst.base.ty);
|
||||
|
||||
const opcode = switch (inst.base.tag) {
|
||||
// Bool -> bool
|
||||
.not => Opcode.OpLogicalNot,
|
||||
else => unreachable,
|
||||
};
|
||||
|
||||
const result_type_id = try self.genType(Type.initTag(.bool));
|
||||
const opcode: Opcode = .OpLogicalNot;
|
||||
try writeInstruction(&self.code, opcode, &[_]Word{ result_type_id, result_id, operand_id });
|
||||
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn genAlloc(self: *DeclGen, inst: *Inst.NoOp) !ResultId {
|
||||
fn airAlloc(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
|
||||
const ty = self.air.typeOfIndex(inst);
|
||||
const storage_class = spec.StorageClass.Function;
|
||||
const result_type_id = try self.genPointerType(inst.base.src, inst.base.ty, storage_class);
|
||||
const result_type_id = try self.genPointerType(ty, storage_class);
|
||||
const result_id = self.spv.allocResultId();
|
||||
|
||||
// Rather than generating into code here, we're just going to generate directly into the fn_decls section so that
|
||||
@ -824,12 +819,12 @@ pub const DeclGen = struct {
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn genArg(self: *DeclGen) ResultId {
|
||||
fn airArg(self: *DeclGen) ResultId {
|
||||
defer self.next_arg_index += 1;
|
||||
return self.args.items[self.next_arg_index];
|
||||
}
|
||||
|
||||
fn genBlock(self: *DeclGen, inst: *Inst.Block) !?ResultId {
|
||||
fn airBlock(self: *DeclGen, inst: Air.Inst.Index) !?ResultId {
|
||||
// In IR, a block doesn't really define an entry point like a block, but more like a scope that breaks can jump out of and
|
||||
// "return" a value from. This cannot be directly modelled in SPIR-V, so in a block instruction, we're going to split up
|
||||
// the current block by first generating the code of the block, then a label, and then generate the rest of the current
|
||||
@ -849,11 +844,16 @@ pub const DeclGen = struct {
|
||||
incoming_blocks.deinit(self.spv.gpa);
|
||||
}
|
||||
|
||||
try self.genBody(inst.body);
|
||||
const ty = self.air.typeOfIndex(inst);
|
||||
const inst_datas = self.air.instructions.items(.data);
|
||||
const extra = self.air.extraData(Air.Block, inst_datas[inst].ty_pl.payload);
|
||||
const body = self.air.extra[extra.end..][0..extra.data.body_len];
|
||||
|
||||
try self.genBody(body);
|
||||
try self.beginSPIRVBlock(label_id);
|
||||
|
||||
// If this block didn't produce a value, simply return here.
|
||||
if (!inst.base.ty.hasCodeGenBits())
|
||||
if (!ty.hasCodeGenBits())
|
||||
return null;
|
||||
|
||||
// Combine the result from the blocks using the Phi instruction.
|
||||
@ -863,7 +863,7 @@ pub const DeclGen = struct {
|
||||
// TODO: OpPhi is limited in the types that it may produce, such as pointers. Figure out which other types
|
||||
// are not allowed to be created from a phi node, and throw an error for those. For now, genType already throws
|
||||
// an error for pointers.
|
||||
const result_type_id = try self.genType(inst.base.src, inst.base.ty);
|
||||
const result_type_id = try self.genType(ty);
|
||||
_ = result_type_id;
|
||||
|
||||
try writeOpcode(&self.code, .OpPhi, 2 + @intCast(u16, incoming_blocks.items.len * 2)); // result type + result + variable/parent...
|
||||
@ -875,30 +875,26 @@ pub const DeclGen = struct {
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn genBr(self: *DeclGen, inst: *Inst.Br) !void {
|
||||
// TODO: This instruction needs to be the last in a block. Is that guaranteed?
|
||||
const target = self.blocks.get(inst.block).?;
|
||||
fn airBr(self: *DeclGen, inst: Air.Inst.Index) !void {
|
||||
const br = self.air.instructions.items(.data)[inst].br;
|
||||
const block = self.blocks.get(br.block_inst).?;
|
||||
const operand_ty = self.air.typeOf(br.operand);
|
||||
|
||||
// TODO: For some reason, br is emitted with void parameters.
|
||||
if (inst.operand.ty.hasCodeGenBits()) {
|
||||
const operand_id = try self.resolve(inst.operand);
|
||||
if (operand_ty.hasCodeGenBits()) {
|
||||
const operand_id = try self.resolve(br.operand);
|
||||
// current_block_label_id should not be undefined here, lest there is a br or br_void in the function's body.
|
||||
try target.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });
|
||||
try block.incoming_blocks.append(self.spv.gpa, .{ .src_label_id = self.current_block_label_id, .break_value_id = operand_id });
|
||||
}
|
||||
|
||||
try writeInstruction(&self.code, .OpBranch, &[_]Word{target.label_id});
|
||||
try writeInstruction(&self.code, .OpBranch, &[_]Word{block.label_id});
|
||||
}
|
||||
|
||||
fn genBrVoid(self: *DeclGen, inst: *Inst.BrVoid) !void {
|
||||
// TODO: This instruction needs to be the last in a block. Is that guaranteed?
|
||||
const target = self.blocks.get(inst.block).?;
|
||||
// Don't need to add this to the incoming block list, as there is no value to insert in the phi node anyway.
|
||||
try writeInstruction(&self.code, .OpBranch, &[_]Word{target.label_id});
|
||||
}
|
||||
|
||||
fn genCondBr(self: *DeclGen, inst: *Inst.CondBr) !void {
|
||||
// TODO: This instruction needs to be the last in a block. Is that guaranteed?
|
||||
const condition_id = try self.resolve(inst.condition);
|
||||
fn airCondBr(self: *DeclGen, inst: Air.Inst.Index) !void {
|
||||
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
||||
const cond_br = self.air.extraData(Air.CondBr, pl_op.payload);
|
||||
const then_body = self.air.extra[cond_br.end..][0..cond_br.data.then_body_len];
|
||||
const else_body = self.air.extra[cond_br.end + then_body.len ..][0..cond_br.data.else_body_len];
|
||||
const condition_id = try self.resolve(pl_op.operand);
|
||||
|
||||
// These will always generate a new SPIR-V block, since they are ir.Body and not ir.Block.
|
||||
const then_label_id = self.spv.allocResultId();
|
||||
@ -914,23 +910,26 @@ pub const DeclGen = struct {
|
||||
});
|
||||
|
||||
try self.beginSPIRVBlock(then_label_id);
|
||||
try self.genBody(inst.then_body);
|
||||
try self.genBody(then_body);
|
||||
try self.beginSPIRVBlock(else_label_id);
|
||||
try self.genBody(inst.else_body);
|
||||
try self.genBody(else_body);
|
||||
}
|
||||
|
||||
fn genDbgStmt(self: *DeclGen, inst: *Inst.DbgStmt) !void {
|
||||
fn airDbgStmt(self: *DeclGen, inst: Air.Inst.Index) !void {
|
||||
const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
|
||||
const src_fname_id = try self.spv.resolveSourceFileName(self.decl);
|
||||
try writeInstruction(&self.code, .OpLine, &[_]Word{ src_fname_id, inst.line, inst.column });
|
||||
try writeInstruction(&self.code, .OpLine, &[_]Word{ src_fname_id, dbg_stmt.line, dbg_stmt.column });
|
||||
}
|
||||
|
||||
fn genLoad(self: *DeclGen, inst: *Inst.UnOp) !ResultId {
|
||||
const operand_id = try self.resolve(inst.operand);
|
||||
fn airLoad(self: *DeclGen, inst: Air.Inst.Index) !ResultId {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const operand_id = try self.resolve(ty_op.operand);
|
||||
const ty = self.air.typeOfIndex(inst);
|
||||
|
||||
const result_type_id = try self.genType(inst.base.src, inst.base.ty);
|
||||
const result_type_id = try self.genType(ty);
|
||||
const result_id = self.spv.allocResultId();
|
||||
|
||||
const operands = if (inst.base.ty.isVolatilePtr())
|
||||
const operands = if (ty.isVolatilePtr())
|
||||
&[_]Word{ result_type_id, result_id, operand_id, @bitCast(u32, spec.MemoryAccess{ .Volatile = true }) }
|
||||
else
|
||||
&[_]Word{ result_type_id, result_id, operand_id };
|
||||
@ -940,8 +939,10 @@ pub const DeclGen = struct {
|
||||
return result_id;
|
||||
}
|
||||
|
||||
fn genLoop(self: *DeclGen, inst: *Inst.Loop) !void {
|
||||
// TODO: This instruction needs to be the last in a block. Is that guaranteed?
|
||||
fn airLoop(self: *DeclGen, inst: Air.Inst.Index) !void {
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const loop = self.air.extraData(Air.Block, ty_pl.payload);
|
||||
const body = self.air.extra[loop.end..][0..loop.data.body_len];
|
||||
const loop_label_id = self.spv.allocResultId();
|
||||
|
||||
// Jump to the loop entry point
|
||||
@ -950,27 +951,29 @@ pub const DeclGen = struct {
|
||||
// TODO: Look into OpLoopMerge.
|
||||
|
||||
try self.beginSPIRVBlock(loop_label_id);
|
||||
try self.genBody(inst.body);
|
||||
try self.genBody(body);
|
||||
|
||||
try writeInstruction(&self.code, .OpBranch, &[_]Word{loop_label_id});
|
||||
}
|
||||
|
||||
fn genRet(self: *DeclGen, inst: *Inst.UnOp) !void {
|
||||
const operand_id = try self.resolve(inst.operand);
|
||||
// TODO: This instruction needs to be the last in a block. Is that guaranteed?
|
||||
try writeInstruction(&self.code, .OpReturnValue, &[_]Word{operand_id});
|
||||
fn airRet(self: *DeclGen, inst: Air.Inst.Index) !void {
|
||||
const operand = self.air.instructions.items(.data)[inst].un_op;
|
||||
const operand_ty = self.air.typeOf(operand);
|
||||
if (operand_ty.hasCodeGenBits()) {
|
||||
const operand_id = try self.resolve(operand);
|
||||
try writeInstruction(&self.code, .OpReturnValue, &[_]Word{operand_id});
|
||||
} else {
|
||||
try writeInstruction(&self.code, .OpReturn, &[_]Word{});
|
||||
}
|
||||
}
|
||||
|
||||
fn genRetVoid(self: *DeclGen) !void {
|
||||
// TODO: This instruction needs to be the last in a block. Is that guaranteed?
|
||||
try writeInstruction(&self.code, .OpReturn, &[_]Word{});
|
||||
}
|
||||
fn airStore(self: *DeclGen, inst: Air.Inst.Index) !void {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const dst_ptr_id = try self.resolve(bin_op.lhs);
|
||||
const src_val_id = try self.resolve(bin_op.rhs);
|
||||
const lhs_ty = self.air.typeOf(bin_op.lhs);
|
||||
|
||||
fn genStore(self: *DeclGen, inst: *Inst.BinOp) !void {
|
||||
const dst_ptr_id = try self.resolve(inst.lhs);
|
||||
const src_val_id = try self.resolve(inst.rhs);
|
||||
|
||||
const operands = if (inst.lhs.ty.isVolatilePtr())
|
||||
const operands = if (lhs_ty.isVolatilePtr())
|
||||
&[_]Word{ dst_ptr_id, src_val_id, @bitCast(u32, spec.MemoryAccess{ .Volatile = true }) }
|
||||
else
|
||||
&[_]Word{ dst_ptr_id, src_val_id };
|
||||
@ -978,8 +981,7 @@ pub const DeclGen = struct {
|
||||
try writeInstruction(&self.code, .OpStore, operands);
|
||||
}
|
||||
|
||||
fn genUnreach(self: *DeclGen) !void {
|
||||
// TODO: This instruction needs to be the last in a block. Is that guaranteed?
|
||||
fn airUnreach(self: *DeclGen) !void {
|
||||
try writeInstruction(&self.code, .OpUnreachable, &[_]Word{});
|
||||
}
|
||||
};
|
||||
|
||||
@ -9,14 +9,14 @@ const wasm = std.wasm;
|
||||
|
||||
const Module = @import("../Module.zig");
|
||||
const Decl = Module.Decl;
|
||||
const ir = @import("../air.zig");
|
||||
const Inst = ir.Inst;
|
||||
const Type = @import("../type.zig").Type;
|
||||
const Value = @import("../value.zig").Value;
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const LazySrcLoc = Module.LazySrcLoc;
|
||||
const link = @import("../link.zig");
|
||||
const TypedValue = @import("../TypedValue.zig");
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
/// Wasm Value, created when generating an instruction
|
||||
const WValue = union(enum) {
|
||||
@ -24,8 +24,8 @@ const WValue = union(enum) {
|
||||
none: void,
|
||||
/// Index of the local variable
|
||||
local: u32,
|
||||
/// Instruction holding a constant `Value`
|
||||
constant: *Inst,
|
||||
/// Holds a memoized typed value
|
||||
constant: TypedValue,
|
||||
/// Offset position in the list of bytecode instructions
|
||||
code_offset: usize,
|
||||
/// Used for variables that create multiple locals on the stack when allocated
|
||||
@ -483,8 +483,8 @@ pub const Result = union(enum) {
|
||||
externally_managed: []const u8,
|
||||
};
|
||||
|
||||
/// Hashmap to store generated `WValue` for each `Inst`
|
||||
pub const ValueTable = std.AutoHashMapUnmanaged(*Inst, WValue);
|
||||
/// Hashmap to store generated `WValue` for each `Air.Inst.Ref`
|
||||
pub const ValueTable = std.AutoHashMapUnmanaged(Air.Inst.Index, WValue);
|
||||
|
||||
/// Code represents the `Code` section of wasm that
|
||||
/// belongs to a function
|
||||
@ -492,11 +492,13 @@ pub const Context = struct {
|
||||
/// Reference to the function declaration the code
|
||||
/// section belongs to
|
||||
decl: *Decl,
|
||||
air: Air,
|
||||
liveness: Liveness,
|
||||
gpa: *mem.Allocator,
|
||||
/// Table to save `WValue`'s generated by an `Inst`
|
||||
/// Table to save `WValue`'s generated by an `Air.Inst`
|
||||
values: ValueTable,
|
||||
/// Mapping from *Inst.Block to block ids
|
||||
blocks: std.AutoArrayHashMapUnmanaged(*Inst.Block, u32) = .{},
|
||||
/// Mapping from Air.Inst.Index to block ids
|
||||
blocks: std.AutoArrayHashMapUnmanaged(Air.Inst.Index, u32) = .{},
|
||||
/// `bytes` contains the wasm bytecode belonging to the 'code' section.
|
||||
code: ArrayList(u8),
|
||||
/// Contains the generated function type bytecode for the current function
|
||||
@ -536,7 +538,8 @@ pub const Context = struct {
|
||||
}
|
||||
|
||||
/// Sets `err_msg` on `Context` and returns `error.CodegemFail` which is caught in link/Wasm.zig
|
||||
fn fail(self: *Context, src: LazySrcLoc, comptime fmt: []const u8, args: anytype) InnerError {
|
||||
fn fail(self: *Context, comptime fmt: []const u8, args: anytype) InnerError {
|
||||
const src: LazySrcLoc = .{ .node_offset = 0 };
|
||||
const src_loc = src.toSrcLocWithDecl(self.decl);
|
||||
self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, fmt, args);
|
||||
return error.CodegenFail;
|
||||
@ -544,59 +547,66 @@ pub const Context = struct {
|
||||
|
||||
/// Resolves the `WValue` for the given instruction `inst`
|
||||
/// When the given instruction has a `Value`, it returns a constant instead
|
||||
fn resolveInst(self: Context, inst: *Inst) WValue {
|
||||
if (!inst.ty.hasCodeGenBits()) return .none;
|
||||
fn resolveInst(self: Context, ref: Air.Inst.Ref) WValue {
|
||||
const inst_index = Air.refToIndex(ref) orelse {
|
||||
const tv = Air.Inst.Ref.typed_value_map[@enumToInt(ref)];
|
||||
if (!tv.ty.hasCodeGenBits()) {
|
||||
return WValue.none;
|
||||
}
|
||||
return WValue{ .constant = tv };
|
||||
};
|
||||
|
||||
if (inst.value()) |_| {
|
||||
return WValue{ .constant = inst };
|
||||
const inst_type = self.air.typeOfIndex(inst_index);
|
||||
if (!inst_type.hasCodeGenBits()) return .none;
|
||||
|
||||
if (self.air.instructions.items(.tag)[inst_index] == .constant) {
|
||||
const ty_pl = self.air.instructions.items(.data)[inst_index].ty_pl;
|
||||
return WValue{ .constant = .{ .ty = inst_type, .val = self.air.values[ty_pl.payload] } };
|
||||
}
|
||||
|
||||
return self.values.get(inst).?; // Instruction does not dominate all uses!
|
||||
return self.values.get(inst_index).?; // Instruction does not dominate all uses!
|
||||
}
|
||||
|
||||
/// Using a given `Type`, returns the corresponding wasm Valtype
|
||||
fn typeToValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!wasm.Valtype {
|
||||
fn typeToValtype(self: *Context, ty: Type) InnerError!wasm.Valtype {
|
||||
return switch (ty.zigTypeTag()) {
|
||||
.Float => blk: {
|
||||
const bits = ty.floatBits(self.target);
|
||||
if (bits == 16 or bits == 32) break :blk wasm.Valtype.f32;
|
||||
if (bits == 64) break :blk wasm.Valtype.f64;
|
||||
return self.fail(src, "Float bit size not supported by wasm: '{d}'", .{bits});
|
||||
return self.fail("Float bit size not supported by wasm: '{d}'", .{bits});
|
||||
},
|
||||
.Int => blk: {
|
||||
const info = ty.intInfo(self.target);
|
||||
if (info.bits <= 32) break :blk wasm.Valtype.i32;
|
||||
if (info.bits > 32 and info.bits <= 64) break :blk wasm.Valtype.i64;
|
||||
return self.fail(src, "Integer bit size not supported by wasm: '{d}'", .{info.bits});
|
||||
return self.fail("Integer bit size not supported by wasm: '{d}'", .{info.bits});
|
||||
},
|
||||
.Enum => switch (ty.tag()) {
|
||||
.enum_simple => wasm.Valtype.i32,
|
||||
else => self.typeToValtype(
|
||||
src,
|
||||
ty.cast(Type.Payload.EnumFull).?.data.tag_ty,
|
||||
),
|
||||
else => self.typeToValtype(ty.cast(Type.Payload.EnumFull).?.data.tag_ty),
|
||||
},
|
||||
.Bool,
|
||||
.Pointer,
|
||||
.ErrorSet,
|
||||
=> wasm.Valtype.i32,
|
||||
.Struct, .ErrorUnion => unreachable, // Multi typed, must be handled individually.
|
||||
else => self.fail(src, "TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}),
|
||||
else => self.fail("TODO - Wasm valtype for type '{s}'", .{ty.zigTypeTag()}),
|
||||
};
|
||||
}
|
||||
|
||||
/// Using a given `Type`, returns the byte representation of its wasm value type
|
||||
fn genValtype(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
|
||||
return wasm.valtype(try self.typeToValtype(src, ty));
|
||||
fn genValtype(self: *Context, ty: Type) InnerError!u8 {
|
||||
return wasm.valtype(try self.typeToValtype(ty));
|
||||
}
|
||||
|
||||
/// Using a given `Type`, returns the corresponding wasm value type
|
||||
/// Differently from `genValtype` this also allows `void` to create a block
|
||||
/// with no return type
|
||||
fn genBlockType(self: *Context, src: LazySrcLoc, ty: Type) InnerError!u8 {
|
||||
fn genBlockType(self: *Context, ty: Type) InnerError!u8 {
|
||||
return switch (ty.tag()) {
|
||||
.void, .noreturn => wasm.block_empty,
|
||||
else => self.genValtype(src, ty),
|
||||
else => self.genValtype(ty),
|
||||
};
|
||||
}
|
||||
|
||||
@ -610,7 +620,7 @@ pub const Context = struct {
|
||||
try writer.writeByte(wasm.opcode(.local_get));
|
||||
try leb.writeULEB128(writer, idx);
|
||||
},
|
||||
.constant => |inst| try self.emitConstant(inst.src, inst.value().?, inst.ty), // creates a new constant onto the stack
|
||||
.constant => |tv| try self.emitConstant(tv.val, tv.ty), // Creates a new constant on the stack
|
||||
}
|
||||
}
|
||||
|
||||
@ -626,10 +636,7 @@ pub const Context = struct {
|
||||
const fields_len = @intCast(u32, struct_data.fields.count());
|
||||
try self.locals.ensureCapacity(self.gpa, self.locals.items.len + fields_len);
|
||||
for (struct_data.fields.values()) |*value| {
|
||||
const val_type = try self.genValtype(
|
||||
.{ .node_offset = struct_data.node_offset },
|
||||
value.ty,
|
||||
);
|
||||
const val_type = try self.genValtype(value.ty);
|
||||
self.locals.appendAssumeCapacity(val_type);
|
||||
self.local_index += 1;
|
||||
}
|
||||
@ -640,7 +647,7 @@ pub const Context = struct {
|
||||
},
|
||||
.ErrorUnion => {
|
||||
const payload_type = ty.errorUnionChild();
|
||||
const val_type = try self.genValtype(.{ .node_offset = 0 }, payload_type);
|
||||
const val_type = try self.genValtype(payload_type);
|
||||
|
||||
// we emit the error value as the first local, and the payload as the following.
|
||||
// The first local is also used to find the index of the error and payload.
|
||||
@ -657,7 +664,7 @@ pub const Context = struct {
|
||||
} };
|
||||
},
|
||||
else => {
|
||||
const valtype = try self.genValtype(.{ .node_offset = 0 }, ty);
|
||||
const valtype = try self.genValtype(ty);
|
||||
try self.locals.append(self.gpa, valtype);
|
||||
self.local_index += 1;
|
||||
return WValue{ .local = initial_index };
|
||||
@ -680,7 +687,7 @@ pub const Context = struct {
|
||||
ty.fnParamTypes(params);
|
||||
for (params) |param_type| {
|
||||
// Can we maybe get the source index of each param?
|
||||
const val_type = try self.genValtype(.{ .node_offset = 0 }, param_type);
|
||||
const val_type = try self.genValtype(param_type);
|
||||
try writer.writeByte(val_type);
|
||||
}
|
||||
}
|
||||
@ -689,13 +696,10 @@ pub const Context = struct {
|
||||
const return_type = ty.fnReturnType();
|
||||
switch (return_type.zigTypeTag()) {
|
||||
.Void, .NoReturn => try leb.writeULEB128(writer, @as(u32, 0)),
|
||||
.Struct => return self.fail(.{ .node_offset = 0 }, "TODO: Implement struct as return type for wasm", .{}),
|
||||
.Optional => return self.fail(.{ .node_offset = 0 }, "TODO: Implement optionals as return type for wasm", .{}),
|
||||
.Struct => return self.fail("TODO: Implement struct as return type for wasm", .{}),
|
||||
.Optional => return self.fail("TODO: Implement optionals as return type for wasm", .{}),
|
||||
.ErrorUnion => {
|
||||
const val_type = try self.genValtype(
|
||||
.{ .node_offset = 0 },
|
||||
return_type.errorUnionChild(),
|
||||
);
|
||||
const val_type = try self.genValtype(return_type.errorUnionChild());
|
||||
|
||||
// write down the amount of return values
|
||||
try leb.writeULEB128(writer, @as(u32, 2));
|
||||
@ -705,58 +709,57 @@ pub const Context = struct {
|
||||
else => {
|
||||
try leb.writeULEB128(writer, @as(u32, 1));
|
||||
// Can we maybe get the source index of the return type?
|
||||
const val_type = try self.genValtype(.{ .node_offset = 0 }, return_type);
|
||||
const val_type = try self.genValtype(return_type);
|
||||
try writer.writeByte(val_type);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates the wasm bytecode for the function declaration belonging to `Context`
|
||||
pub fn genFunc(self: *Context) InnerError!Result {
|
||||
try self.genFunctype();
|
||||
// TODO: check for and handle death of instructions
|
||||
|
||||
// Reserve space to write the size after generating the code as well as space for locals count
|
||||
try self.code.resize(10);
|
||||
|
||||
try self.genBody(self.air.getMainBody());
|
||||
|
||||
// finally, write our local types at the 'offset' position
|
||||
{
|
||||
leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
|
||||
|
||||
// offset into 'code' section where we will put our locals types
|
||||
var local_offset: usize = 10;
|
||||
|
||||
// emit the actual locals amount
|
||||
for (self.locals.items) |local| {
|
||||
var buf: [6]u8 = undefined;
|
||||
leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
|
||||
buf[5] = local;
|
||||
try self.code.insertSlice(local_offset, &buf);
|
||||
local_offset += 6;
|
||||
}
|
||||
}
|
||||
|
||||
const writer = self.code.writer();
|
||||
try writer.writeByte(wasm.opcode(.end));
|
||||
|
||||
// Fill in the size of the generated code to the reserved space at the
|
||||
// beginning of the buffer.
|
||||
const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5;
|
||||
leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
|
||||
|
||||
// codegen data has been appended to `code`
|
||||
return Result.appended;
|
||||
}
|
||||
|
||||
/// Generates the wasm bytecode for the declaration belonging to `Context`
|
||||
pub fn gen(self: *Context, typed_value: TypedValue) InnerError!Result {
|
||||
switch (typed_value.ty.zigTypeTag()) {
|
||||
.Fn => {
|
||||
try self.genFunctype();
|
||||
|
||||
// Write instructions
|
||||
// TODO: check for and handle death of instructions
|
||||
const mod_fn = blk: {
|
||||
if (typed_value.val.castTag(.function)) |func| break :blk func.data;
|
||||
if (typed_value.val.castTag(.extern_fn)) |_| return Result.appended; // don't need code body for extern functions
|
||||
unreachable;
|
||||
};
|
||||
|
||||
// Reserve space to write the size after generating the code as well as space for locals count
|
||||
try self.code.resize(10);
|
||||
|
||||
try self.genBody(mod_fn.body);
|
||||
|
||||
// finally, write our local types at the 'offset' position
|
||||
{
|
||||
leb.writeUnsignedFixed(5, self.code.items[5..10], @intCast(u32, self.locals.items.len));
|
||||
|
||||
// offset into 'code' section where we will put our locals types
|
||||
var local_offset: usize = 10;
|
||||
|
||||
// emit the actual locals amount
|
||||
for (self.locals.items) |local| {
|
||||
var buf: [6]u8 = undefined;
|
||||
leb.writeUnsignedFixed(5, buf[0..5], @as(u32, 1));
|
||||
buf[5] = local;
|
||||
try self.code.insertSlice(local_offset, &buf);
|
||||
local_offset += 6;
|
||||
}
|
||||
}
|
||||
|
||||
const writer = self.code.writer();
|
||||
try writer.writeByte(wasm.opcode(.end));
|
||||
|
||||
// Fill in the size of the generated code to the reserved space at the
|
||||
// beginning of the buffer.
|
||||
const size = self.code.items.len - 5 + self.decl.fn_link.wasm.idx_refs.items.len * 5;
|
||||
leb.writeUnsignedFixed(5, self.code.items[0..5], @intCast(u32, size));
|
||||
|
||||
// codegen data has been appended to `code`
|
||||
return Result.appended;
|
||||
if (typed_value.val.castTag(.extern_fn)) |_| return Result.appended; // don't need code body for extern functions
|
||||
return self.fail("TODO implement wasm codegen for function pointers", .{});
|
||||
},
|
||||
.Array => {
|
||||
if (typed_value.val.castTag(.bytes)) |payload| {
|
||||
@ -775,7 +778,7 @@ pub const Context = struct {
|
||||
}
|
||||
}
|
||||
return Result{ .externally_managed = payload.data };
|
||||
} else return self.fail(.{ .node_offset = 0 }, "TODO implement gen for more kinds of arrays", .{});
|
||||
} else return self.fail("TODO implement gen for more kinds of arrays", .{});
|
||||
},
|
||||
.Int => {
|
||||
const info = typed_value.ty.intInfo(self.target);
|
||||
@ -784,85 +787,91 @@ pub const Context = struct {
|
||||
try self.code.append(@intCast(u8, int_byte));
|
||||
return Result.appended;
|
||||
}
|
||||
return self.fail(.{ .node_offset = 0 }, "TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
|
||||
return self.fail("TODO: Implement codegen for int type: '{}'", .{typed_value.ty});
|
||||
},
|
||||
else => |tag| return self.fail(.{ .node_offset = 0 }, "TODO: Implement zig type codegen for type: '{s}'", .{tag}),
|
||||
else => |tag| return self.fail("TODO: Implement zig type codegen for type: '{s}'", .{tag}),
|
||||
}
|
||||
}
|
||||
|
||||
fn genInst(self: *Context, inst: *Inst) InnerError!WValue {
|
||||
return switch (inst.tag) {
|
||||
.add => self.genBinOp(inst.castTag(.add).?, .add),
|
||||
.alloc => self.genAlloc(inst.castTag(.alloc).?),
|
||||
.arg => self.genArg(inst.castTag(.arg).?),
|
||||
.bit_and => self.genBinOp(inst.castTag(.bit_and).?, .@"and"),
|
||||
.bitcast => self.genBitcast(inst.castTag(.bitcast).?),
|
||||
.bit_or => self.genBinOp(inst.castTag(.bit_or).?, .@"or"),
|
||||
.block => self.genBlock(inst.castTag(.block).?),
|
||||
.bool_and => self.genBinOp(inst.castTag(.bool_and).?, .@"and"),
|
||||
.bool_or => self.genBinOp(inst.castTag(.bool_or).?, .@"or"),
|
||||
.breakpoint => self.genBreakpoint(inst.castTag(.breakpoint).?),
|
||||
.br => self.genBr(inst.castTag(.br).?),
|
||||
.call => self.genCall(inst.castTag(.call).?),
|
||||
.cmp_eq => self.genCmp(inst.castTag(.cmp_eq).?, .eq),
|
||||
.cmp_gte => self.genCmp(inst.castTag(.cmp_gte).?, .gte),
|
||||
.cmp_gt => self.genCmp(inst.castTag(.cmp_gt).?, .gt),
|
||||
.cmp_lte => self.genCmp(inst.castTag(.cmp_lte).?, .lte),
|
||||
.cmp_lt => self.genCmp(inst.castTag(.cmp_lt).?, .lt),
|
||||
.cmp_neq => self.genCmp(inst.castTag(.cmp_neq).?, .neq),
|
||||
.condbr => self.genCondBr(inst.castTag(.condbr).?),
|
||||
fn genInst(self: *Context, inst: Air.Inst.Index) !WValue {
|
||||
const air_tags = self.air.instructions.items(.tag);
|
||||
return switch (air_tags[inst]) {
|
||||
.add => self.airBinOp(inst, .add),
|
||||
.sub => self.airBinOp(inst, .sub),
|
||||
.mul => self.airBinOp(inst, .mul),
|
||||
.div => self.airBinOp(inst, .div),
|
||||
.bit_and => self.airBinOp(inst, .@"and"),
|
||||
.bit_or => self.airBinOp(inst, .@"or"),
|
||||
.bool_and => self.airBinOp(inst, .@"and"),
|
||||
.bool_or => self.airBinOp(inst, .@"or"),
|
||||
.xor => self.airBinOp(inst, .xor),
|
||||
|
||||
.cmp_eq => self.airCmp(inst, .eq),
|
||||
.cmp_gte => self.airCmp(inst, .gte),
|
||||
.cmp_gt => self.airCmp(inst, .gt),
|
||||
.cmp_lte => self.airCmp(inst, .lte),
|
||||
.cmp_lt => self.airCmp(inst, .lt),
|
||||
.cmp_neq => self.airCmp(inst, .neq),
|
||||
|
||||
.alloc => self.airAlloc(inst),
|
||||
.arg => self.airArg(inst),
|
||||
.bitcast => self.airBitcast(inst),
|
||||
.block => self.airBlock(inst),
|
||||
.breakpoint => self.airBreakpoint(inst),
|
||||
.br => self.airBr(inst),
|
||||
.call => self.airCall(inst),
|
||||
.cond_br => self.airCondBr(inst),
|
||||
.constant => unreachable,
|
||||
.dbg_stmt => WValue.none,
|
||||
.div => self.genBinOp(inst.castTag(.div).?, .div),
|
||||
.is_err => self.genIsErr(inst.castTag(.is_err).?, .i32_ne),
|
||||
.is_non_err => self.genIsErr(inst.castTag(.is_non_err).?, .i32_eq),
|
||||
.load => self.genLoad(inst.castTag(.load).?),
|
||||
.loop => self.genLoop(inst.castTag(.loop).?),
|
||||
.mul => self.genBinOp(inst.castTag(.mul).?, .mul),
|
||||
.not => self.genNot(inst.castTag(.not).?),
|
||||
.ret => self.genRet(inst.castTag(.ret).?),
|
||||
.retvoid => WValue.none,
|
||||
.store => self.genStore(inst.castTag(.store).?),
|
||||
.struct_field_ptr => self.genStructFieldPtr(inst.castTag(.struct_field_ptr).?),
|
||||
.sub => self.genBinOp(inst.castTag(.sub).?, .sub),
|
||||
.switchbr => self.genSwitchBr(inst.castTag(.switchbr).?),
|
||||
.unreach => self.genUnreachable(inst.castTag(.unreach).?),
|
||||
.unwrap_errunion_payload => self.genUnwrapErrUnionPayload(inst.castTag(.unwrap_errunion_payload).?),
|
||||
.wrap_errunion_payload => self.genWrapErrUnionPayload(inst.castTag(.wrap_errunion_payload).?),
|
||||
.xor => self.genBinOp(inst.castTag(.xor).?, .xor),
|
||||
else => self.fail(.{ .node_offset = 0 }, "TODO: Implement wasm inst: {s}", .{inst.tag}),
|
||||
.is_err => self.airIsErr(inst, .i32_ne),
|
||||
.is_non_err => self.airIsErr(inst, .i32_eq),
|
||||
.load => self.airLoad(inst),
|
||||
.loop => self.airLoop(inst),
|
||||
.not => self.airNot(inst),
|
||||
.ret => self.airRet(inst),
|
||||
.store => self.airStore(inst),
|
||||
.struct_field_ptr => self.airStructFieldPtr(inst),
|
||||
.switch_br => self.airSwitchBr(inst),
|
||||
.unreach => self.airUnreachable(inst),
|
||||
.unwrap_errunion_payload => self.airUnwrapErrUnionPayload(inst),
|
||||
.wrap_errunion_payload => self.airWrapErrUnionPayload(inst),
|
||||
else => |tag| self.fail("TODO: Implement wasm inst: {s}", .{@tagName(tag)}),
|
||||
};
|
||||
}
|
||||
|
||||
fn genBody(self: *Context, body: ir.Body) InnerError!void {
|
||||
for (body.instructions) |inst| {
|
||||
fn genBody(self: *Context, body: []const Air.Inst.Index) InnerError!void {
|
||||
for (body) |inst| {
|
||||
const result = try self.genInst(inst);
|
||||
try self.values.putNoClobber(self.gpa, inst, result);
|
||||
}
|
||||
}
|
||||
|
||||
fn genRet(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
|
||||
// TODO: Implement tail calls
|
||||
const operand = self.resolveInst(inst.operand);
|
||||
fn airRet(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
||||
const operand = self.resolveInst(un_op);
|
||||
try self.emitWValue(operand);
|
||||
try self.code.append(wasm.opcode(.@"return"));
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genCall(self: *Context, inst: *Inst.Call) InnerError!WValue {
|
||||
const func_val = inst.func.value().?;
|
||||
fn airCall(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = self.air.extraData(Air.Call, pl_op.payload);
|
||||
const args = self.air.extra[extra.end..][0..extra.data.args_len];
|
||||
|
||||
const target: *Decl = blk: {
|
||||
const func_val = self.air.value(pl_op.operand).?;
|
||||
|
||||
if (func_val.castTag(.function)) |func| {
|
||||
break :blk func.data.owner_decl;
|
||||
} else if (func_val.castTag(.extern_fn)) |ext_fn| {
|
||||
break :blk ext_fn.data;
|
||||
}
|
||||
return self.fail(inst.base.src, "Expected a function, but instead found type '{s}'", .{func_val.tag()});
|
||||
return self.fail("Expected a function, but instead found type '{s}'", .{func_val.tag()});
|
||||
};
|
||||
|
||||
for (inst.args) |arg| {
|
||||
const arg_val = self.resolveInst(arg);
|
||||
for (args) |arg| {
|
||||
const arg_val = self.resolveInst(@intToEnum(Air.Inst.Ref, arg));
|
||||
try self.emitWValue(arg_val);
|
||||
}
|
||||
|
||||
@ -878,16 +887,17 @@ pub const Context = struct {
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genAlloc(self: *Context, inst: *Inst.NoOp) InnerError!WValue {
|
||||
const elem_type = inst.base.ty.elemType();
|
||||
fn airAlloc(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const elem_type = self.air.typeOfIndex(inst).elemType();
|
||||
return self.allocLocal(elem_type);
|
||||
}
|
||||
|
||||
fn genStore(self: *Context, inst: *Inst.BinOp) InnerError!WValue {
|
||||
fn airStore(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const writer = self.code.writer();
|
||||
|
||||
const lhs = self.resolveInst(inst.lhs);
|
||||
const rhs = self.resolveInst(inst.rhs);
|
||||
const lhs = self.resolveInst(bin_op.lhs);
|
||||
const rhs = self.resolveInst(bin_op.rhs);
|
||||
|
||||
switch (lhs) {
|
||||
.multi_value => |multi_value| switch (rhs) {
|
||||
@ -895,7 +905,7 @@ pub const Context = struct {
|
||||
// we simply assign the local_index to the rhs one.
|
||||
// This allows us to update struct fields without having to individually
|
||||
// set each local as each field's index will be calculated off the struct's base index
|
||||
.multi_value => self.values.put(self.gpa, inst.lhs, rhs) catch unreachable, // Instruction does not dominate all uses!
|
||||
.multi_value => self.values.put(self.gpa, Air.refToIndex(bin_op.lhs).?, rhs) catch unreachable, // Instruction does not dominate all uses!
|
||||
.constant, .none => {
|
||||
// emit all values onto the stack if constant
|
||||
try self.emitWValue(rhs);
|
||||
@ -921,20 +931,22 @@ pub const Context = struct {
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genLoad(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
|
||||
return self.resolveInst(inst.operand);
|
||||
fn airLoad(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
return self.resolveInst(ty_op.operand);
|
||||
}
|
||||
|
||||
fn genArg(self: *Context, inst: *Inst.Arg) InnerError!WValue {
|
||||
fn airArg(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
_ = inst;
|
||||
// arguments share the index with locals
|
||||
defer self.local_index += 1;
|
||||
return WValue{ .local = self.local_index };
|
||||
}
|
||||
|
||||
fn genBinOp(self: *Context, inst: *Inst.BinOp, op: Op) InnerError!WValue {
|
||||
const lhs = self.resolveInst(inst.lhs);
|
||||
const rhs = self.resolveInst(inst.rhs);
|
||||
fn airBinOp(self: *Context, inst: Air.Inst.Index, op: Op) InnerError!WValue {
|
||||
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
||||
const lhs = self.resolveInst(bin_op.lhs);
|
||||
const rhs = self.resolveInst(bin_op.rhs);
|
||||
|
||||
// it's possible for both lhs and/or rhs to return an offset as well,
|
||||
// in which case we return the first offset occurance we find.
|
||||
@ -947,23 +959,24 @@ pub const Context = struct {
|
||||
try self.emitWValue(lhs);
|
||||
try self.emitWValue(rhs);
|
||||
|
||||
const bin_ty = self.air.typeOf(bin_op.lhs);
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.op = op,
|
||||
.valtype1 = try self.typeToValtype(inst.base.src, inst.base.ty),
|
||||
.signedness = if (inst.base.ty.isSignedInt()) .signed else .unsigned,
|
||||
.valtype1 = try self.typeToValtype(bin_ty),
|
||||
.signedness = if (bin_ty.isSignedInt()) .signed else .unsigned,
|
||||
});
|
||||
try self.code.append(wasm.opcode(opcode));
|
||||
return WValue{ .code_offset = offset };
|
||||
}
|
||||
|
||||
fn emitConstant(self: *Context, src: LazySrcLoc, value: Value, ty: Type) InnerError!void {
|
||||
fn emitConstant(self: *Context, value: Value, ty: Type) InnerError!void {
|
||||
const writer = self.code.writer();
|
||||
switch (ty.zigTypeTag()) {
|
||||
.Int => {
|
||||
// write opcode
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.op = .@"const",
|
||||
.valtype1 = try self.typeToValtype(src, ty),
|
||||
.valtype1 = try self.typeToValtype(ty),
|
||||
});
|
||||
try writer.writeByte(wasm.opcode(opcode));
|
||||
// write constant
|
||||
@ -982,14 +995,14 @@ pub const Context = struct {
|
||||
// write opcode
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.op = .@"const",
|
||||
.valtype1 = try self.typeToValtype(src, ty),
|
||||
.valtype1 = try self.typeToValtype(ty),
|
||||
});
|
||||
try writer.writeByte(wasm.opcode(opcode));
|
||||
// write constant
|
||||
switch (ty.floatBits(self.target)) {
|
||||
0...32 => try writer.writeIntLittle(u32, @bitCast(u32, value.toFloat(f32))),
|
||||
64 => try writer.writeIntLittle(u64, @bitCast(u64, value.toFloat(f64))),
|
||||
else => |bits| return self.fail(src, "Wasm TODO: emitConstant for float with {d} bits", .{bits}),
|
||||
else => |bits| return self.fail("Wasm TODO: emitConstant for float with {d} bits", .{bits}),
|
||||
}
|
||||
},
|
||||
.Pointer => {
|
||||
@ -1006,7 +1019,7 @@ pub const Context = struct {
|
||||
try writer.writeByte(wasm.opcode(.i32_load));
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
} else return self.fail(src, "Wasm TODO: emitConstant for other const pointer tag {s}", .{value.tag()});
|
||||
} else return self.fail("Wasm TODO: emitConstant for other const pointer tag {s}", .{value.tag()});
|
||||
},
|
||||
.Void => {},
|
||||
.Enum => {
|
||||
@ -1020,7 +1033,7 @@ pub const Context = struct {
|
||||
const enum_full = ty.cast(Type.Payload.EnumFull).?.data;
|
||||
if (enum_full.values.count() != 0) {
|
||||
const tag_val = enum_full.values.keys()[field_index.data];
|
||||
try self.emitConstant(src, tag_val, enum_full.tag_ty);
|
||||
try self.emitConstant(tag_val, enum_full.tag_ty);
|
||||
} else {
|
||||
try writer.writeByte(wasm.opcode(.i32_const));
|
||||
try leb.writeULEB128(writer, field_index.data);
|
||||
@ -1031,7 +1044,7 @@ pub const Context = struct {
|
||||
} else {
|
||||
var int_tag_buffer: Type.Payload.Bits = undefined;
|
||||
const int_tag_ty = ty.intTagType(&int_tag_buffer);
|
||||
try self.emitConstant(src, value, int_tag_ty);
|
||||
try self.emitConstant(value, int_tag_ty);
|
||||
}
|
||||
},
|
||||
.ErrorSet => {
|
||||
@ -1045,12 +1058,12 @@ pub const Context = struct {
|
||||
const payload_type = ty.errorUnionChild();
|
||||
if (value.getError()) |_| {
|
||||
// write the error value
|
||||
try self.emitConstant(src, data, error_type);
|
||||
try self.emitConstant(data, error_type);
|
||||
|
||||
// no payload, so write a '0' const
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.op = .@"const",
|
||||
.valtype1 = try self.typeToValtype(src, payload_type),
|
||||
.valtype1 = try self.typeToValtype(payload_type),
|
||||
});
|
||||
try writer.writeByte(wasm.opcode(opcode));
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
@ -1059,21 +1072,24 @@ pub const Context = struct {
|
||||
try writer.writeByte(wasm.opcode(.i32_const));
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
// after the error code, we emit the payload
|
||||
try self.emitConstant(src, data, payload_type);
|
||||
try self.emitConstant(data, payload_type);
|
||||
}
|
||||
},
|
||||
else => |zig_type| return self.fail(src, "Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
|
||||
else => |zig_type| return self.fail("Wasm TODO: emitConstant for zigTypeTag {s}", .{zig_type}),
|
||||
}
|
||||
}
|
||||
|
||||
fn genBlock(self: *Context, block: *Inst.Block) InnerError!WValue {
|
||||
const block_ty = try self.genBlockType(block.base.src, block.base.ty);
|
||||
fn airBlock(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const block_ty = try self.genBlockType(self.air.getRefType(ty_pl.ty));
|
||||
const extra = self.air.extraData(Air.Block, ty_pl.payload);
|
||||
const body = self.air.extra[extra.end..][0..extra.data.body_len];
|
||||
|
||||
try self.startBlock(.block, block_ty, null);
|
||||
// Here we set the current block idx, so breaks know the depth to jump
|
||||
// to when breaking out.
|
||||
try self.blocks.putNoClobber(self.gpa, block, self.block_depth);
|
||||
try self.genBody(block.body);
|
||||
try self.blocks.putNoClobber(self.gpa, inst, self.block_depth);
|
||||
try self.genBody(body);
|
||||
try self.endBlock();
|
||||
|
||||
return .none;
|
||||
@ -1097,11 +1113,15 @@ pub const Context = struct {
|
||||
self.block_depth -= 1;
|
||||
}
|
||||
|
||||
fn genLoop(self: *Context, loop: *Inst.Loop) InnerError!WValue {
|
||||
const loop_ty = try self.genBlockType(loop.base.src, loop.base.ty);
|
||||
fn airLoop(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const loop = self.air.extraData(Air.Block, ty_pl.payload);
|
||||
const body = self.air.extra[loop.end..][0..loop.data.body_len];
|
||||
|
||||
try self.startBlock(.loop, loop_ty, null);
|
||||
try self.genBody(loop.body);
|
||||
// result type of loop is always 'noreturn', meaning we can always
|
||||
// emit the wasm type 'block_empty'.
|
||||
try self.startBlock(.loop, wasm.block_empty, null);
|
||||
try self.genBody(body);
|
||||
|
||||
// breaking to the index of a loop block will continue the loop instead
|
||||
try self.code.append(wasm.opcode(.br));
|
||||
@ -1112,8 +1132,12 @@ pub const Context = struct {
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genCondBr(self: *Context, condbr: *Inst.CondBr) InnerError!WValue {
|
||||
const condition = self.resolveInst(condbr.condition);
|
||||
fn airCondBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
||||
const condition = self.resolveInst(pl_op.operand);
|
||||
const extra = self.air.extraData(Air.CondBr, pl_op.payload);
|
||||
const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
|
||||
const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
|
||||
const writer = self.code.writer();
|
||||
|
||||
// TODO: Handle death instructions for then and else body
|
||||
@ -1128,8 +1152,9 @@ pub const Context = struct {
|
||||
break :blk offset;
|
||||
},
|
||||
};
|
||||
const block_ty = try self.genBlockType(condbr.base.src, condbr.base.ty);
|
||||
try self.startBlock(.block, block_ty, offset);
|
||||
|
||||
// result type is always noreturn, so use `block_empty` as type.
|
||||
try self.startBlock(.block, wasm.block_empty, offset);
|
||||
|
||||
// we inserted the block in front of the condition
|
||||
// so now check if condition matches. If not, break outside this block
|
||||
@ -1137,35 +1162,37 @@ pub const Context = struct {
|
||||
try writer.writeByte(wasm.opcode(.br_if));
|
||||
try leb.writeULEB128(writer, @as(u32, 0));
|
||||
|
||||
try self.genBody(condbr.else_body);
|
||||
try self.genBody(else_body);
|
||||
try self.endBlock();
|
||||
|
||||
// Outer block that matches the condition
|
||||
try self.genBody(condbr.then_body);
|
||||
try self.genBody(then_body);
|
||||
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genCmp(self: *Context, inst: *Inst.BinOp, op: std.math.CompareOperator) InnerError!WValue {
|
||||
fn airCmp(self: *Context, inst: Air.Inst.Index, op: std.math.CompareOperator) InnerError!WValue {
|
||||
// save offset, so potential conditions can insert blocks in front of
|
||||
// the comparison that we can later jump back to
|
||||
const offset = self.code.items.len;
|
||||
|
||||
const lhs = self.resolveInst(inst.lhs);
|
||||
const rhs = self.resolveInst(inst.rhs);
|
||||
const data: Air.Inst.Data = self.air.instructions.items(.data)[inst];
|
||||
const lhs = self.resolveInst(data.bin_op.lhs);
|
||||
const rhs = self.resolveInst(data.bin_op.rhs);
|
||||
const lhs_ty = self.air.typeOf(data.bin_op.lhs);
|
||||
|
||||
try self.emitWValue(lhs);
|
||||
try self.emitWValue(rhs);
|
||||
|
||||
const signedness: std.builtin.Signedness = blk: {
|
||||
// by default we tell the operand type is unsigned (i.e. bools and enum values)
|
||||
if (inst.lhs.ty.zigTypeTag() != .Int) break :blk .unsigned;
|
||||
if (lhs_ty.zigTypeTag() != .Int) break :blk .unsigned;
|
||||
|
||||
// incase of an actual integer, we emit the correct signedness
|
||||
break :blk inst.lhs.ty.intInfo(self.target).signedness;
|
||||
break :blk lhs_ty.intInfo(self.target).signedness;
|
||||
};
|
||||
const opcode: wasm.Opcode = buildOpcode(.{
|
||||
.valtype1 = try self.typeToValtype(inst.base.src, inst.lhs.ty),
|
||||
.valtype1 = try self.typeToValtype(lhs_ty),
|
||||
.op = switch (op) {
|
||||
.lt => .lt,
|
||||
.lte => .le,
|
||||
@ -1180,16 +1207,17 @@ pub const Context = struct {
|
||||
return WValue{ .code_offset = offset };
|
||||
}
|
||||
|
||||
fn genBr(self: *Context, br: *Inst.Br) InnerError!WValue {
|
||||
fn airBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const br = self.air.instructions.items(.data)[inst].br;
|
||||
|
||||
// if operand has codegen bits we should break with a value
|
||||
if (br.operand.ty.hasCodeGenBits()) {
|
||||
const operand = self.resolveInst(br.operand);
|
||||
try self.emitWValue(operand);
|
||||
if (self.air.typeOf(br.operand).hasCodeGenBits()) {
|
||||
try self.emitWValue(self.resolveInst(br.operand));
|
||||
}
|
||||
|
||||
// We map every block to its block index.
|
||||
// We then determine how far we have to jump to it by substracting it from current block depth
|
||||
const idx: u32 = self.block_depth - self.blocks.get(br.block).?;
|
||||
const idx: u32 = self.block_depth - self.blocks.get(br.block_inst).?;
|
||||
const writer = self.code.writer();
|
||||
try writer.writeByte(wasm.opcode(.br));
|
||||
try leb.writeULEB128(writer, idx);
|
||||
@ -1197,10 +1225,11 @@ pub const Context = struct {
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genNot(self: *Context, not: *Inst.UnOp) InnerError!WValue {
|
||||
fn airNot(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const offset = self.code.items.len;
|
||||
|
||||
const operand = self.resolveInst(not.operand);
|
||||
const operand = self.resolveInst(ty_op.operand);
|
||||
try self.emitWValue(operand);
|
||||
|
||||
// wasm does not have booleans nor the `not` instruction, therefore compare with 0
|
||||
@ -1214,73 +1243,93 @@ pub const Context = struct {
|
||||
return WValue{ .code_offset = offset };
|
||||
}
|
||||
|
||||
fn genBreakpoint(self: *Context, breakpoint: *Inst.NoOp) InnerError!WValue {
|
||||
fn airBreakpoint(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
_ = self;
|
||||
_ = breakpoint;
|
||||
_ = inst;
|
||||
// unsupported by wasm itself. Can be implemented once we support DWARF
|
||||
// for wasm
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genUnreachable(self: *Context, unreach: *Inst.NoOp) InnerError!WValue {
|
||||
_ = unreach;
|
||||
fn airUnreachable(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
_ = inst;
|
||||
try self.code.append(wasm.opcode(.@"unreachable"));
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn genBitcast(self: *Context, bitcast: *Inst.UnOp) InnerError!WValue {
|
||||
return self.resolveInst(bitcast.operand);
|
||||
fn airBitcast(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
return self.resolveInst(ty_op.operand);
|
||||
}
|
||||
|
||||
fn genStructFieldPtr(self: *Context, inst: *Inst.StructFieldPtr) InnerError!WValue {
|
||||
const struct_ptr = self.resolveInst(inst.struct_ptr);
|
||||
fn airStructFieldPtr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
||||
const extra = self.air.extraData(Air.StructField, ty_pl.payload);
|
||||
const struct_ptr = self.resolveInst(extra.data.struct_ptr);
|
||||
|
||||
return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, inst.field_index) };
|
||||
return WValue{ .local = struct_ptr.multi_value.index + @intCast(u32, extra.data.field_index) };
|
||||
}
|
||||
|
||||
fn genSwitchBr(self: *Context, inst: *Inst.SwitchBr) InnerError!WValue {
|
||||
const target = self.resolveInst(inst.target);
|
||||
const target_ty = inst.target.ty;
|
||||
const valtype = try self.typeToValtype(.{ .node_offset = 0 }, target_ty);
|
||||
const blocktype = try self.genBlockType(inst.base.src, inst.base.ty);
|
||||
fn airSwitchBr(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = self.air.extraData(Air.SwitchBr, pl_op.payload);
|
||||
const cases = self.air.extra[extra.end..][0..extra.data.cases_len];
|
||||
const else_body = self.air.extra[extra.end + cases.len ..][0..extra.data.else_body_len];
|
||||
|
||||
const signedness: std.builtin.Signedness = blk: {
|
||||
// by default we tell the operand type is unsigned (i.e. bools and enum values)
|
||||
if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
|
||||
const target = self.resolveInst(pl_op.operand);
|
||||
const target_ty = self.air.typeOf(pl_op.operand);
|
||||
const valtype = try self.typeToValtype(target_ty);
|
||||
// result type is always 'noreturn'
|
||||
const blocktype = wasm.block_empty;
|
||||
|
||||
// incase of an actual integer, we emit the correct signedness
|
||||
break :blk target_ty.intInfo(self.target).signedness;
|
||||
};
|
||||
for (inst.cases) |case| {
|
||||
// create a block for each case, when the condition does not match we break out of it
|
||||
try self.startBlock(.block, blocktype, null);
|
||||
try self.emitWValue(target);
|
||||
try self.emitConstant(.{ .node_offset = 0 }, case.item, target_ty);
|
||||
const opcode = buildOpcode(.{
|
||||
.valtype1 = valtype,
|
||||
.op = .ne, // not equal because we jump out the block if it does not match the condition
|
||||
.signedness = signedness,
|
||||
});
|
||||
try self.code.append(wasm.opcode(opcode));
|
||||
try self.code.append(wasm.opcode(.br_if));
|
||||
try leb.writeULEB128(self.code.writer(), @as(u32, 0));
|
||||
_ = valtype;
|
||||
_ = blocktype;
|
||||
_ = target;
|
||||
_ = else_body;
|
||||
return self.fail("TODO implement wasm codegen for switch", .{});
|
||||
//const signedness: std.builtin.Signedness = blk: {
|
||||
// // by default we tell the operand type is unsigned (i.e. bools and enum values)
|
||||
// if (target_ty.zigTypeTag() != .Int) break :blk .unsigned;
|
||||
|
||||
// emit our block code
|
||||
try self.genBody(case.body);
|
||||
// // incase of an actual integer, we emit the correct signedness
|
||||
// break :blk target_ty.intInfo(self.target).signedness;
|
||||
//};
|
||||
//for (cases) |case_idx| {
|
||||
// const case = self.air.extraData(Air.SwitchBr.Case, case_idx);
|
||||
// const case_body = self.air.extra[case.end..][0..case.data.body_len];
|
||||
|
||||
// end the block we created earlier
|
||||
try self.endBlock();
|
||||
}
|
||||
// // create a block for each case, when the condition does not match we break out of it
|
||||
// try self.startBlock(.block, blocktype, null);
|
||||
// try self.emitWValue(target);
|
||||
|
||||
// finally, emit the else case if it exists. Here we will not have to
|
||||
// check for a condition, so also no need to emit a block.
|
||||
try self.genBody(inst.else_body);
|
||||
// const val = self.air.value(case.data.item).?;
|
||||
// try self.emitConstant(val, target_ty);
|
||||
// const opcode = buildOpcode(.{
|
||||
// .valtype1 = valtype,
|
||||
// .op = .ne, // not equal because we jump out the block if it does not match the condition
|
||||
// .signedness = signedness,
|
||||
// });
|
||||
// try self.code.append(wasm.opcode(opcode));
|
||||
// try self.code.append(wasm.opcode(.br_if));
|
||||
// try leb.writeULEB128(self.code.writer(), @as(u32, 0));
|
||||
|
||||
return .none;
|
||||
// // emit our block code
|
||||
// try self.genBody(case_body);
|
||||
|
||||
// // end the block we created earlier
|
||||
// try self.endBlock();
|
||||
//}
|
||||
|
||||
//// finally, emit the else case if it exists. Here we will not have to
|
||||
//// check for a condition, so also no need to emit a block.
|
||||
//try self.genBody(else_body);
|
||||
|
||||
//return .none;
|
||||
}
|
||||
|
||||
fn genIsErr(self: *Context, inst: *Inst.UnOp, opcode: wasm.Opcode) InnerError!WValue {
|
||||
const operand = self.resolveInst(inst.operand);
|
||||
fn airIsErr(self: *Context, inst: Air.Inst.Index, opcode: wasm.Opcode) InnerError!WValue {
|
||||
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
||||
const operand = self.resolveInst(un_op);
|
||||
const offset = self.code.items.len;
|
||||
const writer = self.code.writer();
|
||||
|
||||
@ -1295,8 +1344,9 @@ pub const Context = struct {
|
||||
return WValue{ .code_offset = offset };
|
||||
}
|
||||
|
||||
fn genUnwrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
|
||||
const operand = self.resolveInst(inst.operand);
|
||||
fn airUnwrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
const operand = self.resolveInst(ty_op.operand);
|
||||
// The index of multi_value contains the error code. To get the initial index of the payload we get
|
||||
// the following index. Next, convert it to a `WValue.local`
|
||||
//
|
||||
@ -1304,7 +1354,8 @@ pub const Context = struct {
|
||||
return WValue{ .local = operand.multi_value.index + 1 };
|
||||
}
|
||||
|
||||
fn genWrapErrUnionPayload(self: *Context, inst: *Inst.UnOp) InnerError!WValue {
|
||||
return self.resolveInst(inst.operand);
|
||||
fn airWrapErrUnionPayload(self: *Context, inst: Air.Inst.Index) InnerError!WValue {
|
||||
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
||||
return self.resolveInst(ty_op.operand);
|
||||
}
|
||||
};
|
||||
|
||||
34
src/link.zig
34
src/link.zig
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const fs = std.fs;
|
||||
@ -14,8 +15,10 @@ const Cache = @import("Cache.zig");
|
||||
const build_options = @import("build_options");
|
||||
const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
|
||||
const wasi_libc = @import("wasi_libc.zig");
|
||||
const Air = @import("Air.zig");
|
||||
const Liveness = @import("Liveness.zig");
|
||||
|
||||
pub const producer_string = if (std.builtin.is_test) "zig test" else "zig " ++ build_options.version;
|
||||
pub const producer_string = if (builtin.is_test) "zig test" else "zig " ++ build_options.version;
|
||||
|
||||
pub const Emit = struct {
|
||||
/// Where the output will go.
|
||||
@ -313,13 +316,34 @@ pub const File = struct {
|
||||
log.debug("updateDecl {*} ({s}), type={}", .{ decl, decl.name, decl.ty });
|
||||
assert(decl.has_tv);
|
||||
switch (base.tag) {
|
||||
.coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl),
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
|
||||
// zig fmt: off
|
||||
.coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl),
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
|
||||
.c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
|
||||
.c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
|
||||
.spirv => return @fieldParentPtr(SpirV, "base", base).updateDecl(module, decl),
|
||||
.plan9 => return @fieldParentPtr(Plan9, "base", base).updateDecl(module, decl),
|
||||
// zig fmt: on
|
||||
}
|
||||
}
|
||||
|
||||
/// May be called before or after updateDeclExports but must be called
|
||||
/// after allocateDeclIndexes for any given Decl.
|
||||
pub fn updateFunc(base: *File, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
log.debug("updateFunc {*} ({s}), type={}", .{
|
||||
func.owner_decl, func.owner_decl.name, func.owner_decl.ty,
|
||||
});
|
||||
switch (base.tag) {
|
||||
// zig fmt: off
|
||||
.coff => return @fieldParentPtr(Coff, "base", base).updateFunc(module, func, air, liveness),
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).updateFunc(module, func, air, liveness),
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).updateFunc(module, func, air, liveness),
|
||||
.c => return @fieldParentPtr(C, "base", base).updateFunc(module, func, air, liveness),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).updateFunc(module, func, air, liveness),
|
||||
.spirv => return @fieldParentPtr(SpirV, "base", base).updateFunc(module, func, air, liveness),
|
||||
.plan9 => return @fieldParentPtr(Plan9, "base", base).updateFunc(module, func, air, liveness),
|
||||
// zig fmt: on
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,14 +2,17 @@ const std = @import("std");
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const fs = std.fs;
|
||||
|
||||
const C = @This();
|
||||
const Module = @import("../Module.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const fs = std.fs;
|
||||
const codegen = @import("../codegen/c.zig");
|
||||
const link = @import("../link.zig");
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const C = @This();
|
||||
const Type = @import("../type.zig").Type;
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
pub const base_tag: link.File.Tag = .c;
|
||||
pub const zig_h = @embedFile("C/zig.h");
|
||||
@ -95,10 +98,7 @@ fn deinitDecl(gpa: *Allocator, decl: *Module.Decl) void {
|
||||
decl.fn_link.c.typedefs.deinit(gpa);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
pub fn finishUpdateDecl(self: *C, module: *Module, decl: *Module.Decl, air: Air, liveness: Liveness) !void {
|
||||
// Keep track of all decls so we can iterate over them on flush().
|
||||
_ = try self.decl_table.getOrPut(self.base.allocator, decl);
|
||||
|
||||
@ -126,6 +126,8 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
.code = code.toManaged(module.gpa),
|
||||
.value_map = codegen.CValueMap.init(module.gpa),
|
||||
.indent_writer = undefined, // set later so we can get a pointer to object.code
|
||||
.air = air,
|
||||
.liveness = liveness,
|
||||
};
|
||||
object.indent_writer = .{ .underlying_writer = object.code.writer() };
|
||||
defer {
|
||||
@ -157,6 +159,20 @@ pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
code.shrinkAndFree(module.gpa, code.items.len);
|
||||
}
|
||||
|
||||
pub fn updateFunc(self: *C, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
return self.finishUpdateDecl(module, func.owner_decl, air, liveness);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
return self.finishUpdateDecl(module, decl, undefined, undefined);
|
||||
}
|
||||
|
||||
pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
// The C backend does not have the ability to fix line numbers without re-generating
|
||||
// the entire Decl.
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const Coff = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const log = std.log.scoped(.link);
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
@ -17,6 +18,8 @@ const build_options = @import("build_options");
|
||||
const Cache = @import("../Cache.zig");
|
||||
const mingw = @import("../mingw.zig");
|
||||
const llvm_backend = @import("../codegen/llvm.zig");
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
const allocation_padding = 4 / 3;
|
||||
const minimum_text_block_size = 64 * allocation_padding;
|
||||
@ -653,19 +656,63 @@ fn writeOffsetTableEntry(self: *Coff, index: usize) !void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
|
||||
// TODO COFF/PE debug information
|
||||
// TODO Implement exports
|
||||
pub fn updateFunc(self: *Coff, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
if (build_options.skip_non_native and
|
||||
builtin.object_format != .coff and
|
||||
builtin.object_format != .pe)
|
||||
{
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| {
|
||||
return llvm_object.updateFunc(module, func, air, liveness);
|
||||
}
|
||||
}
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
if (build_options.have_llvm)
|
||||
if (self.llvm_object) |llvm_object| return try llvm_object.updateDecl(module, decl);
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
const decl = func.owner_decl;
|
||||
const res = try codegen.generateFunction(
|
||||
&self.base,
|
||||
decl.srcLoc(),
|
||||
func,
|
||||
air,
|
||||
liveness,
|
||||
&code_buffer,
|
||||
.none,
|
||||
);
|
||||
const code = switch (res) {
|
||||
.appended => code_buffer.items,
|
||||
.fail => |em| {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
return self.finishUpdateDecl(module, func.owner_decl, code);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .coff and builtin.object_format != .pe) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(module, decl);
|
||||
}
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
if (decl.val.tag() == .extern_fn) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
|
||||
// TODO COFF/PE debug information
|
||||
// TODO Implement exports
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
@ -683,6 +730,10 @@ pub fn updateDecl(self: *Coff, module: *Module, decl: *Module.Decl) !void {
|
||||
},
|
||||
};
|
||||
|
||||
return self.finishUpdateDecl(module, decl, code);
|
||||
}
|
||||
|
||||
fn finishUpdateDecl(self: *Coff, module: *Module, decl: *Module.Decl, code: []const u8) !void {
|
||||
const required_alignment = decl.ty.abiAlignment(self.base.options.target);
|
||||
const curr_size = decl.link.coff.size;
|
||||
if (curr_size != 0) {
|
||||
|
||||
562
src/link/Elf.zig
562
src/link/Elf.zig
@ -1,6 +1,7 @@
|
||||
const Elf = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
@ -10,7 +11,6 @@ const log = std.log.scoped(.link);
|
||||
const DW = std.dwarf;
|
||||
const leb128 = std.leb;
|
||||
|
||||
const ir = @import("../air.zig");
|
||||
const Module = @import("../Module.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const codegen = @import("../codegen.zig");
|
||||
@ -26,6 +26,8 @@ const glibc = @import("../glibc.zig");
|
||||
const musl = @import("../musl.zig");
|
||||
const Cache = @import("../Cache.zig");
|
||||
const llvm_backend = @import("../codegen/llvm.zig");
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
const default_entry_addr = 0x8000000;
|
||||
|
||||
@ -2155,138 +2157,17 @@ pub fn freeDecl(self: *Elf, decl: *Module.Decl) void {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
if (build_options.have_llvm)
|
||||
if (self.llvm_object) |llvm_object| return try llvm_object.updateDecl(module, decl);
|
||||
|
||||
if (decl.val.tag() == .extern_fn) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
if (decl.val.castTag(.variable)) |payload| {
|
||||
const variable = payload.data;
|
||||
if (variable.is_extern) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
fn deinitRelocs(gpa: *Allocator, table: *File.DbgInfoTypeRelocsTable) void {
|
||||
var it = table.valueIterator();
|
||||
while (it.next()) |value| {
|
||||
value.relocs.deinit(gpa);
|
||||
}
|
||||
table.deinit(gpa);
|
||||
}
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer dbg_line_buffer.deinit();
|
||||
|
||||
var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer dbg_info_buffer.deinit();
|
||||
|
||||
var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
|
||||
defer {
|
||||
var it = dbg_info_type_relocs.valueIterator();
|
||||
while (it.next()) |value| {
|
||||
value.relocs.deinit(self.base.allocator);
|
||||
}
|
||||
dbg_info_type_relocs.deinit(self.base.allocator);
|
||||
}
|
||||
|
||||
const is_fn: bool = switch (decl.ty.zigTypeTag()) {
|
||||
.Fn => true,
|
||||
else => false,
|
||||
};
|
||||
if (is_fn) {
|
||||
// For functions we need to add a prologue to the debug line program.
|
||||
try dbg_line_buffer.ensureCapacity(26);
|
||||
|
||||
const func = decl.val.castTag(.function).?.data;
|
||||
const line_off = @intCast(u28, decl.src_line + func.lbrace_line);
|
||||
|
||||
const ptr_width_bytes = self.ptrWidthBytes();
|
||||
dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
|
||||
DW.LNS_extended_op,
|
||||
ptr_width_bytes + 1,
|
||||
DW.LNE_set_address,
|
||||
});
|
||||
// This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`.
|
||||
assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len);
|
||||
dbg_line_buffer.items.len += ptr_width_bytes;
|
||||
|
||||
dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line);
|
||||
// This is the "relocatable" relative line offset from the previous function's end curly
|
||||
// to this function's begin curly.
|
||||
assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len);
|
||||
// Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later.
|
||||
leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off);
|
||||
|
||||
dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file);
|
||||
assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len);
|
||||
// Once we support more than one source file, this will have the ability to be more
|
||||
// than one possible value.
|
||||
const file_index = 1;
|
||||
leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index);
|
||||
|
||||
// Emit a line for the begin curly with prologue_end=false. The codegen will
|
||||
// do the work of setting prologue_end=true and epilogue_begin=true.
|
||||
dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy);
|
||||
|
||||
// .debug_info subprogram
|
||||
const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1];
|
||||
try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 25 + decl_name_with_null.len);
|
||||
|
||||
const fn_ret_type = decl.ty.fnReturnType();
|
||||
const fn_ret_has_bits = fn_ret_type.hasCodeGenBits();
|
||||
if (fn_ret_has_bits) {
|
||||
dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram);
|
||||
} else {
|
||||
dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid);
|
||||
}
|
||||
// These get overwritten after generating the machine code. These values are
|
||||
// "relocations" and have to be in this fixed place so that functions can be
|
||||
// moved in virtual address space.
|
||||
assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len);
|
||||
dbg_info_buffer.items.len += ptr_width_bytes; // DW.AT_low_pc, DW.FORM_addr
|
||||
assert(self.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len);
|
||||
dbg_info_buffer.items.len += 4; // DW.AT_high_pc, DW.FORM_data4
|
||||
if (fn_ret_has_bits) {
|
||||
const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, fn_ret_type);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = .{
|
||||
.off = undefined,
|
||||
.relocs = .{},
|
||||
};
|
||||
}
|
||||
try gop.value_ptr.relocs.append(self.base.allocator, @intCast(u32, dbg_info_buffer.items.len));
|
||||
dbg_info_buffer.items.len += 4; // DW.AT_type, DW.FORM_ref4
|
||||
}
|
||||
dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string
|
||||
} else {
|
||||
// TODO implement .debug_info for global variables
|
||||
}
|
||||
const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
|
||||
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
|
||||
.ty = decl.ty,
|
||||
.val = decl_val,
|
||||
}, &code_buffer, .{
|
||||
.dwarf = .{
|
||||
.dbg_line = &dbg_line_buffer,
|
||||
.dbg_info = &dbg_info_buffer,
|
||||
.dbg_info_type_relocs = &dbg_info_type_relocs,
|
||||
},
|
||||
});
|
||||
const code = switch (res) {
|
||||
.externally_managed => |x| x,
|
||||
.appended => code_buffer.items,
|
||||
.fail => |em| {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
fn updateDeclCode(self: *Elf, decl: *Module.Decl, code: []const u8, stt_bits: u8) !*elf.Elf64_Sym {
|
||||
const required_alignment = decl.ty.abiAlignment(self.base.options.target);
|
||||
|
||||
const stt_bits: u8 = if (is_fn) elf.STT_FUNC else elf.STT_OBJECT;
|
||||
|
||||
assert(decl.link.elf.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()
|
||||
const local_sym = &self.local_symbols.items[decl.link.elf.local_sym_index];
|
||||
if (local_sym.st_size != 0) {
|
||||
@ -2338,128 +2219,16 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
|
||||
const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset;
|
||||
try self.base.file.?.pwriteAll(code, file_offset);
|
||||
|
||||
const target_endian = self.base.options.target.cpu.arch.endian();
|
||||
|
||||
const text_block = &decl.link.elf;
|
||||
|
||||
// If the Decl is a function, we need to update the .debug_line program.
|
||||
if (is_fn) {
|
||||
// Perform the relocations based on vaddr.
|
||||
switch (self.ptr_width) {
|
||||
.p32 => {
|
||||
{
|
||||
const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4];
|
||||
mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian);
|
||||
}
|
||||
{
|
||||
const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..4];
|
||||
mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian);
|
||||
}
|
||||
},
|
||||
.p64 => {
|
||||
{
|
||||
const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8];
|
||||
mem.writeInt(u64, ptr, local_sym.st_value, target_endian);
|
||||
}
|
||||
{
|
||||
const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8];
|
||||
mem.writeInt(u64, ptr, local_sym.st_value, target_endian);
|
||||
}
|
||||
},
|
||||
}
|
||||
{
|
||||
const ptr = dbg_info_buffer.items[self.getRelocDbgInfoSubprogramHighPC()..][0..4];
|
||||
mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_size), target_endian);
|
||||
}
|
||||
|
||||
try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence });
|
||||
|
||||
// Now we have the full contents and may allocate a region to store it.
|
||||
|
||||
// This logic is nearly identical to the logic below in `updateDeclDebugInfoAllocation` for
|
||||
// `TextBlock` and the .debug_info. If you are editing this logic, you
|
||||
// probably need to edit that logic too.
|
||||
|
||||
const debug_line_sect = &self.sections.items[self.debug_line_section_index.?];
|
||||
const src_fn = &decl.fn_link.elf;
|
||||
src_fn.len = @intCast(u32, dbg_line_buffer.items.len);
|
||||
if (self.dbg_line_fn_last) |last| not_first: {
|
||||
if (src_fn.next) |next| {
|
||||
// Update existing function - non-last item.
|
||||
if (src_fn.off + src_fn.len + min_nop_size > next.off) {
|
||||
// It grew too big, so we move it to a new location.
|
||||
if (src_fn.prev) |prev| {
|
||||
self.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {};
|
||||
prev.next = src_fn.next;
|
||||
}
|
||||
assert(src_fn.prev != next);
|
||||
next.prev = src_fn.prev;
|
||||
src_fn.next = null;
|
||||
// Populate where it used to be with NOPs.
|
||||
const file_pos = debug_line_sect.sh_offset + src_fn.off;
|
||||
try self.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos);
|
||||
// TODO Look at the free list before appending at the end.
|
||||
src_fn.prev = last;
|
||||
last.next = src_fn;
|
||||
self.dbg_line_fn_last = src_fn;
|
||||
|
||||
src_fn.off = last.off + padToIdeal(last.len);
|
||||
}
|
||||
} else if (src_fn.prev == null) {
|
||||
if (src_fn == last) {
|
||||
// Special case: there is only 1 function and it is being updated.
|
||||
// In this case there is nothing to do. The function's length has
|
||||
// already been updated, and the logic below takes care of
|
||||
// resizing the .debug_line section.
|
||||
break :not_first;
|
||||
}
|
||||
// Append new function.
|
||||
// TODO Look at the free list before appending at the end.
|
||||
src_fn.prev = last;
|
||||
last.next = src_fn;
|
||||
self.dbg_line_fn_last = src_fn;
|
||||
|
||||
src_fn.off = last.off + padToIdeal(last.len);
|
||||
}
|
||||
} else {
|
||||
// This is the first function of the Line Number Program.
|
||||
self.dbg_line_fn_first = src_fn;
|
||||
self.dbg_line_fn_last = src_fn;
|
||||
|
||||
src_fn.off = padToIdeal(self.dbgLineNeededHeaderBytes());
|
||||
}
|
||||
|
||||
const last_src_fn = self.dbg_line_fn_last.?;
|
||||
const needed_size = last_src_fn.off + last_src_fn.len;
|
||||
if (needed_size != debug_line_sect.sh_size) {
|
||||
if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) {
|
||||
const new_offset = self.findFreeSpace(needed_size, 1);
|
||||
const existing_size = last_src_fn.off;
|
||||
log.debug("moving .debug_line section: {d} bytes from 0x{x} to 0x{x}", .{
|
||||
existing_size,
|
||||
debug_line_sect.sh_offset,
|
||||
new_offset,
|
||||
});
|
||||
const amt = try self.base.file.?.copyRangeAll(debug_line_sect.sh_offset, self.base.file.?, new_offset, existing_size);
|
||||
if (amt != existing_size) return error.InputOutput;
|
||||
debug_line_sect.sh_offset = new_offset;
|
||||
}
|
||||
debug_line_sect.sh_size = needed_size;
|
||||
self.shdr_table_dirty = true; // TODO look into making only the one section dirty
|
||||
self.debug_line_header_dirty = true;
|
||||
}
|
||||
const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0;
|
||||
const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0;
|
||||
|
||||
// We only have support for one compilation unit so far, so the offsets are directly
|
||||
// from the .debug_line section.
|
||||
const file_pos = debug_line_sect.sh_offset + src_fn.off;
|
||||
try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos);
|
||||
|
||||
// .debug_info - End the TAG_subprogram children.
|
||||
try dbg_info_buffer.append(0);
|
||||
}
|
||||
return local_sym;
|
||||
}
|
||||
|
||||
fn finishUpdateDecl(
|
||||
self: *Elf,
|
||||
module: *Module,
|
||||
decl: *Module.Decl,
|
||||
dbg_info_type_relocs: *File.DbgInfoTypeRelocsTable,
|
||||
dbg_info_buffer: *std.ArrayList(u8),
|
||||
) !void {
|
||||
// Now we emit the .debug_info types of the Decl. These will count towards the size of
|
||||
// the buffer, so we have to do it before computing the offset, and we can't perform the actual
|
||||
// relocations yet.
|
||||
@ -2467,12 +2236,15 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
|
||||
var it = dbg_info_type_relocs.iterator();
|
||||
while (it.next()) |entry| {
|
||||
entry.value_ptr.off = @intCast(u32, dbg_info_buffer.items.len);
|
||||
try self.addDbgInfoType(entry.key_ptr.*, &dbg_info_buffer);
|
||||
try self.addDbgInfoType(entry.key_ptr.*, dbg_info_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
const text_block = &decl.link.elf;
|
||||
try self.updateDeclDebugInfoAllocation(text_block, @intCast(u32, dbg_info_buffer.items.len));
|
||||
|
||||
const target_endian = self.base.options.target.cpu.arch.endian();
|
||||
|
||||
{
|
||||
// Now that we have the offset assigned we can finally perform type relocations.
|
||||
var it = dbg_info_type_relocs.valueIterator();
|
||||
@ -2495,6 +2267,292 @@ pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
|
||||
return self.updateDeclExports(module, decl, decl_exports);
|
||||
}
|
||||
|
||||
pub fn updateFunc(self: *Elf, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .elf) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(module, func, air, liveness);
|
||||
}
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer dbg_line_buffer.deinit();
|
||||
|
||||
var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer dbg_info_buffer.deinit();
|
||||
|
||||
var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
|
||||
defer deinitRelocs(self.base.allocator, &dbg_info_type_relocs);
|
||||
|
||||
// For functions we need to add a prologue to the debug line program.
|
||||
try dbg_line_buffer.ensureCapacity(26);
|
||||
|
||||
const decl = func.owner_decl;
|
||||
const line_off = @intCast(u28, decl.src_line + func.lbrace_line);
|
||||
|
||||
const ptr_width_bytes = self.ptrWidthBytes();
|
||||
dbg_line_buffer.appendSliceAssumeCapacity(&[_]u8{
|
||||
DW.LNS_extended_op,
|
||||
ptr_width_bytes + 1,
|
||||
DW.LNE_set_address,
|
||||
});
|
||||
// This is the "relocatable" vaddr, corresponding to `code_buffer` index `0`.
|
||||
assert(dbg_line_vaddr_reloc_index == dbg_line_buffer.items.len);
|
||||
dbg_line_buffer.items.len += ptr_width_bytes;
|
||||
|
||||
dbg_line_buffer.appendAssumeCapacity(DW.LNS_advance_line);
|
||||
// This is the "relocatable" relative line offset from the previous function's end curly
|
||||
// to this function's begin curly.
|
||||
assert(self.getRelocDbgLineOff() == dbg_line_buffer.items.len);
|
||||
// Here we use a ULEB128-fixed-4 to make sure this field can be overwritten later.
|
||||
leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), line_off);
|
||||
|
||||
dbg_line_buffer.appendAssumeCapacity(DW.LNS_set_file);
|
||||
assert(self.getRelocDbgFileIndex() == dbg_line_buffer.items.len);
|
||||
// Once we support more than one source file, this will have the ability to be more
|
||||
// than one possible value.
|
||||
const file_index = 1;
|
||||
leb128.writeUnsignedFixed(4, dbg_line_buffer.addManyAsArrayAssumeCapacity(4), file_index);
|
||||
|
||||
// Emit a line for the begin curly with prologue_end=false. The codegen will
|
||||
// do the work of setting prologue_end=true and epilogue_begin=true.
|
||||
dbg_line_buffer.appendAssumeCapacity(DW.LNS_copy);
|
||||
|
||||
// .debug_info subprogram
|
||||
const decl_name_with_null = decl.name[0 .. mem.lenZ(decl.name) + 1];
|
||||
try dbg_info_buffer.ensureCapacity(dbg_info_buffer.items.len + 25 + decl_name_with_null.len);
|
||||
|
||||
const fn_ret_type = decl.ty.fnReturnType();
|
||||
const fn_ret_has_bits = fn_ret_type.hasCodeGenBits();
|
||||
if (fn_ret_has_bits) {
|
||||
dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram);
|
||||
} else {
|
||||
dbg_info_buffer.appendAssumeCapacity(abbrev_subprogram_retvoid);
|
||||
}
|
||||
// These get overwritten after generating the machine code. These values are
|
||||
// "relocations" and have to be in this fixed place so that functions can be
|
||||
// moved in virtual address space.
|
||||
assert(dbg_info_low_pc_reloc_index == dbg_info_buffer.items.len);
|
||||
dbg_info_buffer.items.len += ptr_width_bytes; // DW.AT_low_pc, DW.FORM_addr
|
||||
assert(self.getRelocDbgInfoSubprogramHighPC() == dbg_info_buffer.items.len);
|
||||
dbg_info_buffer.items.len += 4; // DW.AT_high_pc, DW.FORM_data4
|
||||
if (fn_ret_has_bits) {
|
||||
const gop = try dbg_info_type_relocs.getOrPut(self.base.allocator, fn_ret_type);
|
||||
if (!gop.found_existing) {
|
||||
gop.value_ptr.* = .{
|
||||
.off = undefined,
|
||||
.relocs = .{},
|
||||
};
|
||||
}
|
||||
try gop.value_ptr.relocs.append(self.base.allocator, @intCast(u32, dbg_info_buffer.items.len));
|
||||
dbg_info_buffer.items.len += 4; // DW.AT_type, DW.FORM_ref4
|
||||
}
|
||||
dbg_info_buffer.appendSliceAssumeCapacity(decl_name_with_null); // DW.AT_name, DW.FORM_string
|
||||
|
||||
const res = try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{
|
||||
.dwarf = .{
|
||||
.dbg_line = &dbg_line_buffer,
|
||||
.dbg_info = &dbg_info_buffer,
|
||||
.dbg_info_type_relocs = &dbg_info_type_relocs,
|
||||
},
|
||||
});
|
||||
const code = switch (res) {
|
||||
.appended => code_buffer.items,
|
||||
.fail => |em| {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
const local_sym = try self.updateDeclCode(decl, code, elf.STT_FUNC);
|
||||
|
||||
const target_endian = self.base.options.target.cpu.arch.endian();
|
||||
|
||||
// Since the Decl is a function, we need to update the .debug_line program.
|
||||
// Perform the relocations based on vaddr.
|
||||
switch (self.ptr_width) {
|
||||
.p32 => {
|
||||
{
|
||||
const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..4];
|
||||
mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian);
|
||||
}
|
||||
{
|
||||
const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..4];
|
||||
mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_value), target_endian);
|
||||
}
|
||||
},
|
||||
.p64 => {
|
||||
{
|
||||
const ptr = dbg_line_buffer.items[dbg_line_vaddr_reloc_index..][0..8];
|
||||
mem.writeInt(u64, ptr, local_sym.st_value, target_endian);
|
||||
}
|
||||
{
|
||||
const ptr = dbg_info_buffer.items[dbg_info_low_pc_reloc_index..][0..8];
|
||||
mem.writeInt(u64, ptr, local_sym.st_value, target_endian);
|
||||
}
|
||||
},
|
||||
}
|
||||
{
|
||||
const ptr = dbg_info_buffer.items[self.getRelocDbgInfoSubprogramHighPC()..][0..4];
|
||||
mem.writeInt(u32, ptr, @intCast(u32, local_sym.st_size), target_endian);
|
||||
}
|
||||
|
||||
try dbg_line_buffer.appendSlice(&[_]u8{ DW.LNS_extended_op, 1, DW.LNE_end_sequence });
|
||||
|
||||
// Now we have the full contents and may allocate a region to store it.
|
||||
|
||||
// This logic is nearly identical to the logic below in `updateDeclDebugInfoAllocation` for
|
||||
// `TextBlock` and the .debug_info. If you are editing this logic, you
|
||||
// probably need to edit that logic too.
|
||||
|
||||
const debug_line_sect = &self.sections.items[self.debug_line_section_index.?];
|
||||
const src_fn = &decl.fn_link.elf;
|
||||
src_fn.len = @intCast(u32, dbg_line_buffer.items.len);
|
||||
if (self.dbg_line_fn_last) |last| not_first: {
|
||||
if (src_fn.next) |next| {
|
||||
// Update existing function - non-last item.
|
||||
if (src_fn.off + src_fn.len + min_nop_size > next.off) {
|
||||
// It grew too big, so we move it to a new location.
|
||||
if (src_fn.prev) |prev| {
|
||||
self.dbg_line_fn_free_list.put(self.base.allocator, prev, {}) catch {};
|
||||
prev.next = src_fn.next;
|
||||
}
|
||||
assert(src_fn.prev != next);
|
||||
next.prev = src_fn.prev;
|
||||
src_fn.next = null;
|
||||
// Populate where it used to be with NOPs.
|
||||
const file_pos = debug_line_sect.sh_offset + src_fn.off;
|
||||
try self.pwriteDbgLineNops(0, &[0]u8{}, src_fn.len, file_pos);
|
||||
// TODO Look at the free list before appending at the end.
|
||||
src_fn.prev = last;
|
||||
last.next = src_fn;
|
||||
self.dbg_line_fn_last = src_fn;
|
||||
|
||||
src_fn.off = last.off + padToIdeal(last.len);
|
||||
}
|
||||
} else if (src_fn.prev == null) {
|
||||
if (src_fn == last) {
|
||||
// Special case: there is only 1 function and it is being updated.
|
||||
// In this case there is nothing to do. The function's length has
|
||||
// already been updated, and the logic below takes care of
|
||||
// resizing the .debug_line section.
|
||||
break :not_first;
|
||||
}
|
||||
// Append new function.
|
||||
// TODO Look at the free list before appending at the end.
|
||||
src_fn.prev = last;
|
||||
last.next = src_fn;
|
||||
self.dbg_line_fn_last = src_fn;
|
||||
|
||||
src_fn.off = last.off + padToIdeal(last.len);
|
||||
}
|
||||
} else {
|
||||
// This is the first function of the Line Number Program.
|
||||
self.dbg_line_fn_first = src_fn;
|
||||
self.dbg_line_fn_last = src_fn;
|
||||
|
||||
src_fn.off = padToIdeal(self.dbgLineNeededHeaderBytes());
|
||||
}
|
||||
|
||||
const last_src_fn = self.dbg_line_fn_last.?;
|
||||
const needed_size = last_src_fn.off + last_src_fn.len;
|
||||
if (needed_size != debug_line_sect.sh_size) {
|
||||
if (needed_size > self.allocatedSize(debug_line_sect.sh_offset)) {
|
||||
const new_offset = self.findFreeSpace(needed_size, 1);
|
||||
const existing_size = last_src_fn.off;
|
||||
log.debug("moving .debug_line section: {d} bytes from 0x{x} to 0x{x}", .{
|
||||
existing_size,
|
||||
debug_line_sect.sh_offset,
|
||||
new_offset,
|
||||
});
|
||||
const amt = try self.base.file.?.copyRangeAll(debug_line_sect.sh_offset, self.base.file.?, new_offset, existing_size);
|
||||
if (amt != existing_size) return error.InputOutput;
|
||||
debug_line_sect.sh_offset = new_offset;
|
||||
}
|
||||
debug_line_sect.sh_size = needed_size;
|
||||
self.shdr_table_dirty = true; // TODO look into making only the one section dirty
|
||||
self.debug_line_header_dirty = true;
|
||||
}
|
||||
const prev_padding_size: u32 = if (src_fn.prev) |prev| src_fn.off - (prev.off + prev.len) else 0;
|
||||
const next_padding_size: u32 = if (src_fn.next) |next| next.off - (src_fn.off + src_fn.len) else 0;
|
||||
|
||||
// We only have support for one compilation unit so far, so the offsets are directly
|
||||
// from the .debug_line section.
|
||||
const file_pos = debug_line_sect.sh_offset + src_fn.off;
|
||||
try self.pwriteDbgLineNops(prev_padding_size, dbg_line_buffer.items, next_padding_size, file_pos);
|
||||
|
||||
// .debug_info - End the TAG_subprogram children.
|
||||
try dbg_info_buffer.append(0);
|
||||
|
||||
return self.finishUpdateDecl(module, decl, &dbg_info_type_relocs, &dbg_info_buffer);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .elf) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(module, decl);
|
||||
}
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
if (decl.val.tag() == .extern_fn) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
if (decl.val.castTag(.variable)) |payload| {
|
||||
const variable = payload.data;
|
||||
if (variable.is_extern) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
}
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
var dbg_line_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer dbg_line_buffer.deinit();
|
||||
|
||||
var dbg_info_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer dbg_info_buffer.deinit();
|
||||
|
||||
var dbg_info_type_relocs: File.DbgInfoTypeRelocsTable = .{};
|
||||
defer deinitRelocs(self.base.allocator, &dbg_info_type_relocs);
|
||||
|
||||
// TODO implement .debug_info for global variables
|
||||
const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
|
||||
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
|
||||
.ty = decl.ty,
|
||||
.val = decl_val,
|
||||
}, &code_buffer, .{
|
||||
.dwarf = .{
|
||||
.dbg_line = &dbg_line_buffer,
|
||||
.dbg_info = &dbg_info_buffer,
|
||||
.dbg_info_type_relocs = &dbg_info_type_relocs,
|
||||
},
|
||||
});
|
||||
const code = switch (res) {
|
||||
.externally_managed => |x| x,
|
||||
.appended => code_buffer.items,
|
||||
.fail => |em| {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
_ = try self.updateDeclCode(decl, code, elf.STT_OBJECT);
|
||||
return self.finishUpdateDecl(module, decl, &dbg_info_type_relocs, &dbg_info_buffer);
|
||||
}
|
||||
|
||||
/// Asserts the type has codegen bits.
|
||||
fn addDbgInfoType(self: *Elf, ty: Type, dbg_info_buffer: *std.ArrayList(u8)) !void {
|
||||
switch (ty.zigTypeTag()) {
|
||||
@ -3022,7 +3080,7 @@ fn pwriteDbgLineNops(
|
||||
|
||||
const page_of_nops = [1]u8{DW.LNS_negate_stmt} ** 4096;
|
||||
const three_byte_nop = [3]u8{ DW.LNS_advance_pc, 0b1000_0000, 0 };
|
||||
var vecs: [256]std.os.iovec_const = undefined;
|
||||
var vecs: [512]std.os.iovec_const = undefined;
|
||||
var vec_index: usize = 0;
|
||||
{
|
||||
var padding_left = prev_padding_size;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const MachO = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
const fmt = std.fmt;
|
||||
@ -22,11 +23,14 @@ const link = @import("../link.zig");
|
||||
const File = link.File;
|
||||
const Cache = @import("../Cache.zig");
|
||||
const target_util = @import("../target.zig");
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
const DebugSymbols = @import("MachO/DebugSymbols.zig");
|
||||
const Trie = @import("MachO/Trie.zig");
|
||||
const CodeSignature = @import("MachO/CodeSignature.zig");
|
||||
const Zld = @import("MachO/Zld.zig");
|
||||
const llvm_backend = @import("../codegen/llvm.zig");
|
||||
|
||||
usingnamespace @import("MachO/commands.zig");
|
||||
|
||||
@ -34,6 +38,9 @@ pub const base_tag: File.Tag = File.Tag.macho;
|
||||
|
||||
base: File,
|
||||
|
||||
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
|
||||
llvm_object: ?*llvm_backend.Object = null,
|
||||
|
||||
/// Debug symbols bundle (or dSym).
|
||||
d_sym: ?DebugSymbols = null,
|
||||
|
||||
@ -344,8 +351,13 @@ pub const SrcFn = struct {
|
||||
pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*MachO {
|
||||
assert(options.object_format == .macho);
|
||||
|
||||
if (options.use_llvm) return error.LLVM_BackendIsTODO_ForMachO; // TODO
|
||||
if (options.use_lld) return error.LLD_LinkingIsTODO_ForMachO; // TODO
|
||||
if (build_options.have_llvm and options.use_llvm) {
|
||||
const self = try createEmpty(allocator, options);
|
||||
errdefer self.base.destroy();
|
||||
|
||||
self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
|
||||
return self;
|
||||
}
|
||||
|
||||
const file = try options.emit.?.directory.handle.createFile(sub_path, .{
|
||||
.truncate = false,
|
||||
@ -1132,20 +1144,28 @@ pub fn allocateDeclIndexes(self: *MachO, decl: *Module.Decl) !void {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
|
||||
pub fn updateFunc(self: *MachO, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .macho) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(module, func, air, liveness);
|
||||
}
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
if (decl.val.tag() == .extern_fn) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
const decl = func.owner_decl;
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
var debug_buffers = if (self.d_sym) |*ds| try ds.initDeclDebugBuffers(self.base.allocator, module, decl) else null;
|
||||
var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
|
||||
const debug_buffers = if (self.d_sym) |*ds| blk: {
|
||||
debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
|
||||
break :blk &debug_buffers_buf;
|
||||
} else null;
|
||||
defer {
|
||||
if (debug_buffers) |*dbg| {
|
||||
if (debug_buffers) |dbg| {
|
||||
dbg.dbg_line_buffer.deinit();
|
||||
dbg.dbg_info_buffer.deinit();
|
||||
var it = dbg.dbg_info_type_relocs.valueIterator();
|
||||
@ -1156,7 +1176,155 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
|
||||
}
|
||||
}
|
||||
|
||||
const res = if (debug_buffers) |*dbg|
|
||||
const res = if (debug_buffers) |dbg|
|
||||
try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{
|
||||
.dwarf = .{
|
||||
.dbg_line = &dbg.dbg_line_buffer,
|
||||
.dbg_info = &dbg.dbg_info_buffer,
|
||||
.dbg_info_type_relocs = &dbg.dbg_info_type_relocs,
|
||||
},
|
||||
})
|
||||
else
|
||||
try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .none);
|
||||
switch (res) {
|
||||
.appended => {},
|
||||
.fail => |em| {
|
||||
// Clear any PIE fixups for this decl.
|
||||
self.pie_fixups.shrinkRetainingCapacity(0);
|
||||
// Clear any stub fixups for this decl.
|
||||
self.stub_fixups.shrinkRetainingCapacity(0);
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
}
|
||||
const symbol = try self.placeDecl(decl, code_buffer.items.len);
|
||||
|
||||
// Calculate displacements to target addr (if any).
|
||||
while (self.pie_fixups.popOrNull()) |fixup| {
|
||||
assert(fixup.size == 4);
|
||||
const this_addr = symbol.n_value + fixup.offset;
|
||||
const target_addr = fixup.target_addr;
|
||||
|
||||
switch (self.base.options.target.cpu.arch) {
|
||||
.x86_64 => {
|
||||
const displacement = try math.cast(u32, target_addr - this_addr - 4);
|
||||
mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement);
|
||||
},
|
||||
.aarch64 => {
|
||||
// TODO optimize instruction based on jump length (use ldr(literal) + nop if possible).
|
||||
{
|
||||
const inst = code_buffer.items[fixup.offset..][0..4];
|
||||
const parsed = mem.bytesAsValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.pc_relative_address,
|
||||
), inst);
|
||||
const this_page = @intCast(i32, this_addr >> 12);
|
||||
const target_page = @intCast(i32, target_addr >> 12);
|
||||
const pages = @bitCast(u21, @intCast(i21, target_page - this_page));
|
||||
parsed.immhi = @truncate(u19, pages >> 2);
|
||||
parsed.immlo = @truncate(u2, pages);
|
||||
}
|
||||
{
|
||||
const inst = code_buffer.items[fixup.offset + 4 ..][0..4];
|
||||
const parsed = mem.bytesAsValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.load_store_register,
|
||||
), inst);
|
||||
const narrowed = @truncate(u12, target_addr);
|
||||
const offset = try math.divExact(u12, narrowed, 8);
|
||||
parsed.offset = offset;
|
||||
}
|
||||
},
|
||||
else => unreachable, // unsupported target architecture
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve stubs (if any)
|
||||
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
|
||||
const stubs = text_segment.sections.items[self.stubs_section_index.?];
|
||||
for (self.stub_fixups.items) |fixup| {
|
||||
const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2;
|
||||
const text_addr = symbol.n_value + fixup.start;
|
||||
switch (self.base.options.target.cpu.arch) {
|
||||
.x86_64 => {
|
||||
assert(stub_addr >= text_addr + fixup.len);
|
||||
const displacement = try math.cast(u32, stub_addr - text_addr - fixup.len);
|
||||
const placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)];
|
||||
mem.writeIntSliceLittle(u32, placeholder, displacement);
|
||||
},
|
||||
.aarch64 => {
|
||||
assert(stub_addr >= text_addr);
|
||||
const displacement = try math.cast(i28, stub_addr - text_addr);
|
||||
const placeholder = code_buffer.items[fixup.start..][0..fixup.len];
|
||||
mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(displacement).toU32());
|
||||
},
|
||||
else => unreachable, // unsupported target architecture
|
||||
}
|
||||
if (!fixup.already_defined) {
|
||||
try self.writeStub(fixup.symbol);
|
||||
try self.writeStubInStubHelper(fixup.symbol);
|
||||
try self.writeLazySymbolPointer(fixup.symbol);
|
||||
|
||||
self.rebase_info_dirty = true;
|
||||
self.lazy_binding_info_dirty = true;
|
||||
}
|
||||
}
|
||||
self.stub_fixups.shrinkRetainingCapacity(0);
|
||||
|
||||
try self.writeCode(symbol, code_buffer.items);
|
||||
|
||||
if (debug_buffers) |db| {
|
||||
try self.d_sym.?.commitDeclDebugInfo(
|
||||
self.base.allocator,
|
||||
module,
|
||||
decl,
|
||||
db,
|
||||
self.base.options.target,
|
||||
);
|
||||
}
|
||||
|
||||
// Since we updated the vaddr and the size, each corresponding export symbol also
|
||||
// needs to be updated.
|
||||
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
|
||||
try self.updateDeclExports(module, decl, decl_exports);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .macho) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(module, decl);
|
||||
}
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
if (decl.val.tag() == .extern_fn) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
|
||||
var debug_buffers_buf: DebugSymbols.DeclDebugBuffers = undefined;
|
||||
const debug_buffers = if (self.d_sym) |*ds| blk: {
|
||||
debug_buffers_buf = try ds.initDeclDebugBuffers(self.base.allocator, module, decl);
|
||||
break :blk &debug_buffers_buf;
|
||||
} else null;
|
||||
defer {
|
||||
if (debug_buffers) |dbg| {
|
||||
dbg.dbg_line_buffer.deinit();
|
||||
dbg.dbg_info_buffer.deinit();
|
||||
var it = dbg.dbg_info_type_relocs.valueIterator();
|
||||
while (it.next()) |value| {
|
||||
value.relocs.deinit(self.base.allocator);
|
||||
}
|
||||
dbg.dbg_info_type_relocs.deinit(self.base.allocator);
|
||||
}
|
||||
}
|
||||
|
||||
const res = if (debug_buffers) |dbg|
|
||||
try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
|
||||
.ty = decl.ty,
|
||||
.val = decl.val,
|
||||
@ -1177,25 +1345,33 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
|
||||
.externally_managed => |x| x,
|
||||
.appended => code_buffer.items,
|
||||
.fail => |em| {
|
||||
// Clear any PIE fixups for this decl.
|
||||
self.pie_fixups.shrinkRetainingCapacity(0);
|
||||
// Clear any stub fixups for this decl.
|
||||
self.stub_fixups.shrinkRetainingCapacity(0);
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
};
|
||||
const symbol = try self.placeDecl(decl, code.len);
|
||||
assert(self.pie_fixups.items.len == 0);
|
||||
assert(self.stub_fixups.items.len == 0);
|
||||
|
||||
try self.writeCode(symbol, code);
|
||||
|
||||
// Since we updated the vaddr and the size, each corresponding export symbol also
|
||||
// needs to be updated.
|
||||
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
|
||||
try self.updateDeclExports(module, decl, decl_exports);
|
||||
}
|
||||
|
||||
fn placeDecl(self: *MachO, decl: *Module.Decl, code_len: usize) !*macho.nlist_64 {
|
||||
const required_alignment = decl.ty.abiAlignment(self.base.options.target);
|
||||
assert(decl.link.macho.local_sym_index != 0); // Caller forgot to call allocateDeclIndexes()
|
||||
const symbol = &self.locals.items[decl.link.macho.local_sym_index];
|
||||
|
||||
if (decl.link.macho.size != 0) {
|
||||
const capacity = decl.link.macho.capacity(self.*);
|
||||
const need_realloc = code.len > capacity or !mem.isAlignedGeneric(u64, symbol.n_value, required_alignment);
|
||||
const need_realloc = code_len > capacity or !mem.isAlignedGeneric(u64, symbol.n_value, required_alignment);
|
||||
if (need_realloc) {
|
||||
const vaddr = try self.growTextBlock(&decl.link.macho, code.len, required_alignment);
|
||||
const vaddr = try self.growTextBlock(&decl.link.macho, code_len, required_alignment);
|
||||
|
||||
log.debug("growing {s} and moving from 0x{x} to 0x{x}", .{ decl.name, symbol.n_value, vaddr });
|
||||
|
||||
@ -1210,10 +1386,10 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
|
||||
}
|
||||
|
||||
symbol.n_value = vaddr;
|
||||
} else if (code.len < decl.link.macho.size) {
|
||||
self.shrinkTextBlock(&decl.link.macho, code.len);
|
||||
} else if (code_len < decl.link.macho.size) {
|
||||
self.shrinkTextBlock(&decl.link.macho, code_len);
|
||||
}
|
||||
decl.link.macho.size = code.len;
|
||||
decl.link.macho.size = code_len;
|
||||
|
||||
const new_name = try std.fmt.allocPrint(self.base.allocator, "_{s}", .{mem.spanZ(decl.name)});
|
||||
defer self.base.allocator.free(new_name);
|
||||
@ -1231,7 +1407,7 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
|
||||
defer self.base.allocator.free(decl_name);
|
||||
|
||||
const name_str_index = try self.makeString(decl_name);
|
||||
const addr = try self.allocateTextBlock(&decl.link.macho, code.len, required_alignment);
|
||||
const addr = try self.allocateTextBlock(&decl.link.macho, code_len, required_alignment);
|
||||
|
||||
log.debug("allocated text block for {s} at 0x{x}", .{ decl_name, addr });
|
||||
|
||||
@ -1256,96 +1432,15 @@ pub fn updateDecl(self: *MachO, module: *Module, decl: *Module.Decl) !void {
|
||||
try self.writeOffsetTableEntry(decl.link.macho.offset_table_index);
|
||||
}
|
||||
|
||||
// Calculate displacements to target addr (if any).
|
||||
while (self.pie_fixups.popOrNull()) |fixup| {
|
||||
assert(fixup.size == 4);
|
||||
const this_addr = symbol.n_value + fixup.offset;
|
||||
const target_addr = fixup.target_addr;
|
||||
|
||||
switch (self.base.options.target.cpu.arch) {
|
||||
.x86_64 => {
|
||||
const displacement = try math.cast(u32, target_addr - this_addr - 4);
|
||||
mem.writeIntLittle(u32, code_buffer.items[fixup.offset..][0..4], displacement);
|
||||
},
|
||||
.aarch64 => {
|
||||
// TODO optimize instruction based on jump length (use ldr(literal) + nop if possible).
|
||||
{
|
||||
const inst = code_buffer.items[fixup.offset..][0..4];
|
||||
var parsed = mem.bytesAsValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.pc_relative_address,
|
||||
), inst);
|
||||
const this_page = @intCast(i32, this_addr >> 12);
|
||||
const target_page = @intCast(i32, target_addr >> 12);
|
||||
const pages = @bitCast(u21, @intCast(i21, target_page - this_page));
|
||||
parsed.immhi = @truncate(u19, pages >> 2);
|
||||
parsed.immlo = @truncate(u2, pages);
|
||||
}
|
||||
{
|
||||
const inst = code_buffer.items[fixup.offset + 4 ..][0..4];
|
||||
var parsed = mem.bytesAsValue(meta.TagPayload(
|
||||
aarch64.Instruction,
|
||||
aarch64.Instruction.load_store_register,
|
||||
), inst);
|
||||
const narrowed = @truncate(u12, target_addr);
|
||||
const offset = try math.divExact(u12, narrowed, 8);
|
||||
parsed.offset = offset;
|
||||
}
|
||||
},
|
||||
else => unreachable, // unsupported target architecture
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve stubs (if any)
|
||||
const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment;
|
||||
const stubs = text_segment.sections.items[self.stubs_section_index.?];
|
||||
for (self.stub_fixups.items) |fixup| {
|
||||
const stub_addr = stubs.addr + fixup.symbol * stubs.reserved2;
|
||||
const text_addr = symbol.n_value + fixup.start;
|
||||
switch (self.base.options.target.cpu.arch) {
|
||||
.x86_64 => {
|
||||
assert(stub_addr >= text_addr + fixup.len);
|
||||
const displacement = try math.cast(u32, stub_addr - text_addr - fixup.len);
|
||||
var placeholder = code_buffer.items[fixup.start + fixup.len - @sizeOf(u32) ..][0..@sizeOf(u32)];
|
||||
mem.writeIntSliceLittle(u32, placeholder, displacement);
|
||||
},
|
||||
.aarch64 => {
|
||||
assert(stub_addr >= text_addr);
|
||||
const displacement = try math.cast(i28, stub_addr - text_addr);
|
||||
var placeholder = code_buffer.items[fixup.start..][0..fixup.len];
|
||||
mem.writeIntSliceLittle(u32, placeholder, aarch64.Instruction.bl(displacement).toU32());
|
||||
},
|
||||
else => unreachable, // unsupported target architecture
|
||||
}
|
||||
if (!fixup.already_defined) {
|
||||
try self.writeStub(fixup.symbol);
|
||||
try self.writeStubInStubHelper(fixup.symbol);
|
||||
try self.writeLazySymbolPointer(fixup.symbol);
|
||||
|
||||
self.rebase_info_dirty = true;
|
||||
self.lazy_binding_info_dirty = true;
|
||||
}
|
||||
}
|
||||
self.stub_fixups.shrinkRetainingCapacity(0);
|
||||
return symbol;
|
||||
}
|
||||
|
||||
fn writeCode(self: *MachO, symbol: *macho.nlist_64, code: []const u8) !void {
|
||||
const text_segment = &self.load_commands.items[self.text_segment_cmd_index.?].Segment;
|
||||
const text_section = text_segment.sections.items[self.text_section_index.?];
|
||||
const section_offset = symbol.n_value - text_section.addr;
|
||||
const file_offset = text_section.offset + section_offset;
|
||||
try self.base.file.?.pwriteAll(code, file_offset);
|
||||
|
||||
if (debug_buffers) |*db| {
|
||||
try self.d_sym.?.commitDeclDebugInfo(
|
||||
self.base.allocator,
|
||||
module,
|
||||
decl,
|
||||
db,
|
||||
self.base.options.target,
|
||||
);
|
||||
}
|
||||
|
||||
// Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
|
||||
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
|
||||
try self.updateDeclExports(module, decl, decl_exports);
|
||||
}
|
||||
|
||||
pub fn updateDeclLineNumber(self: *MachO, module: *Module, decl: *const Module.Decl) !void {
|
||||
|
||||
@ -2,18 +2,21 @@
|
||||
//! would be to add incremental linking in a similar way as ELF does.
|
||||
|
||||
const Plan9 = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const link = @import("../link.zig");
|
||||
const Module = @import("../Module.zig");
|
||||
const Compilation = @import("../Compilation.zig");
|
||||
const aout = @import("Plan9/aout.zig");
|
||||
const codegen = @import("../codegen.zig");
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const mem = std.mem;
|
||||
const File = link.File;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const build_options = @import("build_options");
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const log = std.log.scoped(.link);
|
||||
const assert = std.debug.assert;
|
||||
|
||||
@ -22,20 +25,22 @@ sixtyfour_bit: bool,
|
||||
error_flags: File.ErrorFlags = File.ErrorFlags{},
|
||||
bases: Bases,
|
||||
|
||||
decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{},
|
||||
/// is just casted down when 32 bit
|
||||
/// A symbol's value is just casted down when compiling
|
||||
/// for a 32 bit target.
|
||||
syms: std.ArrayListUnmanaged(aout.Sym) = .{},
|
||||
text_buf: std.ArrayListUnmanaged(u8) = .{},
|
||||
data_buf: std.ArrayListUnmanaged(u8) = .{},
|
||||
|
||||
fn_decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, []const u8) = .{},
|
||||
data_decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, []const u8) = .{},
|
||||
|
||||
hdr: aout.ExecHdr = undefined,
|
||||
|
||||
entry_decl: ?*Module.Decl = null,
|
||||
entry_val: ?u64 = null,
|
||||
|
||||
got_len: u64 = 0,
|
||||
|
||||
got: std.ArrayListUnmanaged(u64) = .{},
|
||||
const Bases = struct {
|
||||
text: u64,
|
||||
/// the addr of the got
|
||||
/// the Global Offset Table starts at the beginning of the data section
|
||||
data: u64,
|
||||
};
|
||||
|
||||
@ -46,14 +51,6 @@ fn getAddr(self: Plan9, addr: u64, t: aout.Sym.Type) u64 {
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
/// opposite of getAddr
|
||||
fn takeAddr(self: Plan9, addr: u64, t: aout.Sym.Type) u64 {
|
||||
return addr - switch (t) {
|
||||
.T, .t, .l, .L => self.bases.text,
|
||||
.D, .d, .B, .b => self.bases.data,
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
|
||||
fn getSymAddr(self: Plan9, s: aout.Sym) u64 {
|
||||
return self.getAddr(s.value, s.type);
|
||||
@ -120,9 +117,84 @@ pub fn createEmpty(gpa: *Allocator, options: link.Options) !*Plan9 {
|
||||
return self;
|
||||
}
|
||||
|
||||
pub fn updateFunc(self: *Plan9, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .plan9) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
|
||||
const decl = func.owner_decl;
|
||||
log.debug("codegen decl {*} ({s})", .{ decl, decl.name });
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
const res = try codegen.generateFunction(&self.base, decl.srcLoc(), func, air, liveness, &code_buffer, .{ .none = .{} });
|
||||
const code = switch (res) {
|
||||
.appended => code_buffer.toOwnedSlice(),
|
||||
.fail => |em| {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
};
|
||||
try self.fn_decl_table.put(self.base.allocator, decl, code);
|
||||
return self.updateFinish(decl);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *Plan9, module: *Module, decl: *Module.Decl) !void {
|
||||
_ = module;
|
||||
_ = try self.decl_table.getOrPut(self.base.allocator, decl);
|
||||
if (decl.val.tag() == .extern_fn) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
if (decl.val.castTag(.variable)) |payload| {
|
||||
const variable = payload.data;
|
||||
if (variable.is_extern) {
|
||||
return; // TODO Should we do more when front-end analyzed extern decl?
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("codegen decl {*} ({s})", .{ decl, decl.name });
|
||||
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
const decl_val = if (decl.val.castTag(.variable)) |payload| payload.data.init else decl.val;
|
||||
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
|
||||
.ty = decl.ty,
|
||||
.val = decl_val,
|
||||
}, &code_buffer, .{ .none = .{} });
|
||||
const code = switch (res) {
|
||||
.externally_managed => |x| x,
|
||||
.appended => code_buffer.items,
|
||||
.fail => |em| {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
return;
|
||||
},
|
||||
};
|
||||
var duped_code = try std.mem.dupe(self.base.allocator, u8, code);
|
||||
errdefer self.base.allocator.free(duped_code);
|
||||
try self.data_decl_table.put(self.base.allocator, decl, duped_code);
|
||||
return self.updateFinish(decl);
|
||||
}
|
||||
/// called at the end of update{Decl,Func}
|
||||
fn updateFinish(self: *Plan9, decl: *Module.Decl) !void {
|
||||
const is_fn = (decl.ty.zigTypeTag() == .Fn);
|
||||
log.debug("update the symbol table and got for decl {*} ({s})", .{ decl, decl.name });
|
||||
const sym_t: aout.Sym.Type = if (is_fn) .t else .d;
|
||||
// write the internal linker metadata
|
||||
decl.link.plan9.type = sym_t;
|
||||
// write the symbol
|
||||
// we already have the got index because that got allocated in allocateDeclIndexes
|
||||
const sym: aout.Sym = .{
|
||||
.value = undefined, // the value of stuff gets filled in in flushModule
|
||||
.type = decl.link.plan9.type,
|
||||
.name = mem.span(decl.name),
|
||||
};
|
||||
|
||||
if (decl.link.plan9.sym_index) |s| {
|
||||
self.syms.items[s] = sym;
|
||||
} else {
|
||||
try self.syms.append(self.base.allocator, sym);
|
||||
decl.link.plan9.sym_index = self.syms.items.len - 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(self: *Plan9, comp: *Compilation) !void {
|
||||
@ -138,6 +210,10 @@ pub fn flush(self: *Plan9, comp: *Compilation) !void {
|
||||
}
|
||||
|
||||
pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .plan9) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
|
||||
_ = comp;
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
@ -146,160 +222,147 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
|
||||
|
||||
defer assert(self.hdr.entry != 0x0);
|
||||
|
||||
const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
|
||||
const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
|
||||
|
||||
self.text_buf.items.len = 0;
|
||||
self.data_buf.items.len = 0;
|
||||
// ensure space to write the got later
|
||||
assert(self.got.items.len == self.decl_table.count());
|
||||
try self.data_buf.appendNTimes(self.base.allocator, 0x69, self.got.items.len * if (!self.sixtyfour_bit) @as(u32, 4) else 8);
|
||||
// temporary buffer
|
||||
var code_buffer = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer code_buffer.deinit();
|
||||
assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count());
|
||||
const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8;
|
||||
var got_table = try self.base.allocator.alloc(u8, got_size);
|
||||
defer self.base.allocator.free(got_table);
|
||||
|
||||
// + 2 for header, got, symbols
|
||||
var iovecs = try self.base.allocator.alloc(std.os.iovec_const, self.fn_decl_table.count() + self.data_decl_table.count() + 3);
|
||||
defer self.base.allocator.free(iovecs);
|
||||
|
||||
const file = self.base.file.?;
|
||||
|
||||
var hdr_buf: [40]u8 = undefined;
|
||||
// account for the fat header
|
||||
const hdr_size = if (self.sixtyfour_bit) @as(usize, 40) else 32;
|
||||
const hdr_slice: []u8 = hdr_buf[0..hdr_size];
|
||||
var foff = hdr_size;
|
||||
iovecs[0] = .{ .iov_base = hdr_slice.ptr, .iov_len = hdr_slice.len };
|
||||
var iovecs_i: u64 = 1;
|
||||
var text_i: u64 = 0;
|
||||
// text
|
||||
{
|
||||
for (self.decl_table.keys()) |decl| {
|
||||
if (!decl.has_tv) continue;
|
||||
const is_fn = (decl.ty.zigTypeTag() == .Fn);
|
||||
|
||||
log.debug("update the symbol table and got for decl {*} ({s})", .{ decl, decl.name });
|
||||
decl.link.plan9 = if (is_fn) .{
|
||||
.offset = self.getAddr(self.text_buf.items.len, .t),
|
||||
.type = .t,
|
||||
.sym_index = decl.link.plan9.sym_index,
|
||||
.got_index = decl.link.plan9.got_index,
|
||||
} else .{
|
||||
.offset = self.getAddr(self.data_buf.items.len, .d),
|
||||
.type = .d,
|
||||
.sym_index = decl.link.plan9.sym_index,
|
||||
.got_index = decl.link.plan9.got_index,
|
||||
};
|
||||
self.got.items[decl.link.plan9.got_index.?] = decl.link.plan9.offset.?;
|
||||
if (decl.link.plan9.sym_index) |s| {
|
||||
self.syms.items[s] = .{
|
||||
.value = decl.link.plan9.offset.?,
|
||||
.type = decl.link.plan9.type,
|
||||
.name = mem.span(decl.name),
|
||||
};
|
||||
var it = self.fn_decl_table.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const decl = entry.key_ptr.*;
|
||||
const code = entry.value_ptr.*;
|
||||
log.debug("write text decl {*} ({s})", .{ decl, decl.name });
|
||||
foff += code.len;
|
||||
iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len };
|
||||
iovecs_i += 1;
|
||||
const off = self.getAddr(text_i, .t);
|
||||
text_i += code.len;
|
||||
decl.link.plan9.offset = off;
|
||||
if (!self.sixtyfour_bit) {
|
||||
mem.writeIntNative(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off));
|
||||
mem.writeInt(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off), self.base.options.target.cpu.arch.endian());
|
||||
} else {
|
||||
try self.syms.append(self.base.allocator, .{
|
||||
.value = decl.link.plan9.offset.?,
|
||||
.type = decl.link.plan9.type,
|
||||
.name = mem.span(decl.name),
|
||||
});
|
||||
decl.link.plan9.sym_index = self.syms.items.len - 1;
|
||||
mem.writeInt(u64, got_table[decl.link.plan9.got_index.? * 8 ..][0..8], off, self.base.options.target.cpu.arch.endian());
|
||||
}
|
||||
|
||||
if (module.decl_exports.get(decl)) |exports| {
|
||||
for (exports) |exp| {
|
||||
// plan9 does not support custom sections
|
||||
if (exp.options.section) |section_name| {
|
||||
if (!mem.eql(u8, section_name, ".text") or !mem.eql(u8, section_name, ".data")) {
|
||||
try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "plan9 does not support extra sections", .{}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (std.mem.eql(u8, exp.options.name, "_start")) {
|
||||
std.debug.assert(decl.link.plan9.type == .t); // we tried to link a non-function as the entry
|
||||
self.entry_decl = decl;
|
||||
}
|
||||
if (exp.link.plan9) |i| {
|
||||
self.syms.items[i] = .{
|
||||
.value = decl.link.plan9.offset.?,
|
||||
.type = decl.link.plan9.type.toGlobal(),
|
||||
.name = exp.options.name,
|
||||
};
|
||||
} else {
|
||||
try self.syms.append(self.base.allocator, .{
|
||||
.value = decl.link.plan9.offset.?,
|
||||
.type = decl.link.plan9.type.toGlobal(),
|
||||
.name = exp.options.name,
|
||||
});
|
||||
exp.link.plan9 = self.syms.items.len - 1;
|
||||
}
|
||||
}
|
||||
self.syms.items[decl.link.plan9.sym_index.?].value = off;
|
||||
if (mod.decl_exports.get(decl)) |exports| {
|
||||
try self.addDeclExports(mod, decl, exports);
|
||||
}
|
||||
}
|
||||
// etext symbol
|
||||
self.syms.items[2].value = self.getAddr(text_i, .t);
|
||||
}
|
||||
// global offset table is in data
|
||||
iovecs[iovecs_i] = .{ .iov_base = got_table.ptr, .iov_len = got_table.len };
|
||||
iovecs_i += 1;
|
||||
// data
|
||||
var data_i: u64 = got_size;
|
||||
{
|
||||
var it = self.data_decl_table.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const decl = entry.key_ptr.*;
|
||||
const code = entry.value_ptr.*;
|
||||
log.debug("write data decl {*} ({s})", .{ decl, decl.name });
|
||||
|
||||
log.debug("codegen decl {*} ({s})", .{ decl, decl.name });
|
||||
const res = try codegen.generateSymbol(&self.base, decl.srcLoc(), .{
|
||||
.ty = decl.ty,
|
||||
.val = decl.val,
|
||||
}, &code_buffer, .{ .none = {} });
|
||||
const code = switch (res) {
|
||||
.externally_managed => |x| x,
|
||||
.appended => code_buffer.items,
|
||||
.fail => |em| {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, em);
|
||||
// TODO try to do more decls
|
||||
return;
|
||||
},
|
||||
};
|
||||
if (is_fn) {
|
||||
try self.text_buf.appendSlice(self.base.allocator, code);
|
||||
code_buffer.items.len = 0;
|
||||
foff += code.len;
|
||||
iovecs[iovecs_i] = .{ .iov_base = code.ptr, .iov_len = code.len };
|
||||
iovecs_i += 1;
|
||||
const off = self.getAddr(data_i, .d);
|
||||
data_i += code.len;
|
||||
decl.link.plan9.offset = off;
|
||||
if (!self.sixtyfour_bit) {
|
||||
mem.writeInt(u32, got_table[decl.link.plan9.got_index.? * 4 ..][0..4], @intCast(u32, off), self.base.options.target.cpu.arch.endian());
|
||||
} else {
|
||||
try self.data_buf.appendSlice(self.base.allocator, code);
|
||||
code_buffer.items.len = 0;
|
||||
mem.writeInt(u64, got_table[decl.link.plan9.got_index.? * 8 ..][0..8], off, self.base.options.target.cpu.arch.endian());
|
||||
}
|
||||
self.syms.items[decl.link.plan9.sym_index.?].value = off;
|
||||
if (mod.decl_exports.get(decl)) |exports| {
|
||||
try self.addDeclExports(mod, decl, exports);
|
||||
}
|
||||
}
|
||||
// edata symbol
|
||||
self.syms.items[0].value = self.getAddr(data_i, .b);
|
||||
}
|
||||
|
||||
// write the got
|
||||
if (!self.sixtyfour_bit) {
|
||||
for (self.got.items) |p, i| {
|
||||
mem.writeInt(u32, self.data_buf.items[i * 4 ..][0..4], @intCast(u32, p), self.base.options.target.cpu.arch.endian());
|
||||
}
|
||||
} else {
|
||||
for (self.got.items) |p, i| {
|
||||
mem.writeInt(u64, self.data_buf.items[i * 8 ..][0..8], p, self.base.options.target.cpu.arch.endian());
|
||||
}
|
||||
}
|
||||
|
||||
self.hdr.entry = @truncate(u32, self.entry_decl.?.link.plan9.offset.?);
|
||||
|
||||
// edata, end, etext
|
||||
self.syms.items[0].value = self.getAddr(0x0, .b);
|
||||
// edata
|
||||
self.syms.items[1].value = self.getAddr(0x0, .b);
|
||||
self.syms.items[2].value = self.getAddr(self.text_buf.items.len, .t);
|
||||
|
||||
var sym_buf = std.ArrayList(u8).init(self.base.allocator);
|
||||
defer sym_buf.deinit();
|
||||
try self.writeSyms(&sym_buf);
|
||||
|
||||
assert(2 + self.fn_decl_table.count() + self.data_decl_table.count() == iovecs_i); // we didn't write all the decls
|
||||
iovecs[iovecs_i] = .{ .iov_base = sym_buf.items.ptr, .iov_len = sym_buf.items.len };
|
||||
iovecs_i += 1;
|
||||
// generate the header
|
||||
self.hdr = .{
|
||||
.magic = try aout.magicFromArch(self.base.options.target.cpu.arch),
|
||||
.text = @intCast(u32, self.text_buf.items.len),
|
||||
.data = @intCast(u32, self.data_buf.items.len),
|
||||
.text = @intCast(u32, text_i),
|
||||
.data = @intCast(u32, data_i),
|
||||
.syms = @intCast(u32, sym_buf.items.len),
|
||||
.bss = 0,
|
||||
.pcsz = 0,
|
||||
.spsz = 0,
|
||||
.entry = self.hdr.entry,
|
||||
.entry = @intCast(u32, self.entry_val.?),
|
||||
};
|
||||
|
||||
const file = self.base.file.?;
|
||||
|
||||
var hdr_buf = self.hdr.toU8s();
|
||||
const hdr_slice: []const u8 = &hdr_buf;
|
||||
// account for the fat header
|
||||
const hdr_size: u8 = if (!self.sixtyfour_bit) 32 else 40;
|
||||
std.mem.copy(u8, hdr_slice, self.hdr.toU8s()[0..hdr_size]);
|
||||
// write the fat header for 64 bit entry points
|
||||
if (self.sixtyfour_bit) {
|
||||
mem.writeIntSliceBig(u64, hdr_buf[32..40], self.hdr.entry);
|
||||
mem.writeIntSliceBig(u64, hdr_buf[32..40], self.entry_val.?);
|
||||
}
|
||||
// write it all!
|
||||
var vectors: [4]std.os.iovec_const = .{
|
||||
.{ .iov_base = hdr_slice.ptr, .iov_len = hdr_size },
|
||||
.{ .iov_base = self.text_buf.items.ptr, .iov_len = self.text_buf.items.len },
|
||||
.{ .iov_base = self.data_buf.items.ptr, .iov_len = self.data_buf.items.len },
|
||||
.{ .iov_base = sym_buf.items.ptr, .iov_len = sym_buf.items.len },
|
||||
// TODO spsz, pcsz
|
||||
};
|
||||
try file.pwritevAll(&vectors, 0);
|
||||
try file.pwritevAll(iovecs, 0);
|
||||
}
|
||||
fn addDeclExports(
|
||||
self: *Plan9,
|
||||
module: *Module,
|
||||
decl: *Module.Decl,
|
||||
exports: []const *Module.Export,
|
||||
) !void {
|
||||
for (exports) |exp| {
|
||||
// plan9 does not support custom sections
|
||||
if (exp.options.section) |section_name| {
|
||||
if (!mem.eql(u8, section_name, ".text") or !mem.eql(u8, section_name, ".data")) {
|
||||
try module.failed_exports.put(module.gpa, exp, try Module.ErrorMsg.create(self.base.allocator, decl.srcLoc(), "plan9 does not support extra sections", .{}));
|
||||
break;
|
||||
}
|
||||
}
|
||||
const sym = .{
|
||||
.value = decl.link.plan9.offset.?,
|
||||
.type = decl.link.plan9.type.toGlobal(),
|
||||
.name = exp.options.name,
|
||||
};
|
||||
|
||||
if (exp.link.plan9) |i| {
|
||||
self.syms.items[i] = sym;
|
||||
} else {
|
||||
try self.syms.append(self.base.allocator, sym);
|
||||
exp.link.plan9 = self.syms.items.len - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void {
|
||||
assert(self.decl_table.swapRemove(decl));
|
||||
const is_fn = (decl.ty.zigTypeTag() == .Fn);
|
||||
if (is_fn)
|
||||
assert(self.fn_decl_table.swapRemove(decl))
|
||||
else
|
||||
assert(self.data_decl_table.swapRemove(decl));
|
||||
}
|
||||
|
||||
pub fn updateDeclExports(
|
||||
@ -315,11 +378,17 @@ pub fn updateDeclExports(
|
||||
_ = exports;
|
||||
}
|
||||
pub fn deinit(self: *Plan9) void {
|
||||
self.decl_table.deinit(self.base.allocator);
|
||||
var itf = self.fn_decl_table.iterator();
|
||||
while (itf.next()) |entry| {
|
||||
self.base.allocator.free(entry.value_ptr.*);
|
||||
}
|
||||
self.fn_decl_table.deinit(self.base.allocator);
|
||||
var itd = self.data_decl_table.iterator();
|
||||
while (itd.next()) |entry| {
|
||||
self.base.allocator.free(entry.value_ptr.*);
|
||||
}
|
||||
self.data_decl_table.deinit(self.base.allocator);
|
||||
self.syms.deinit(self.base.allocator);
|
||||
self.text_buf.deinit(self.base.allocator);
|
||||
self.data_buf.deinit(self.base.allocator);
|
||||
self.got.deinit(self.base.allocator);
|
||||
}
|
||||
|
||||
pub const Export = ?usize;
|
||||
@ -366,18 +435,24 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
|
||||
pub fn writeSyms(self: *Plan9, buf: *std.ArrayList(u8)) !void {
|
||||
const writer = buf.writer();
|
||||
for (self.syms.items) |sym| {
|
||||
log.debug("sym.name: {s}", .{sym.name});
|
||||
log.debug("sym.value: {x}", .{sym.value});
|
||||
if (mem.eql(u8, sym.name, "_start"))
|
||||
self.entry_val = sym.value;
|
||||
if (!self.sixtyfour_bit) {
|
||||
try writer.writeIntBig(u32, @intCast(u32, sym.value));
|
||||
} else {
|
||||
try writer.writeIntBig(u64, sym.value);
|
||||
}
|
||||
try writer.writeByte(@enumToInt(sym.type));
|
||||
try writer.writeAll(std.mem.span(sym.name));
|
||||
try writer.writeAll(sym.name);
|
||||
try writer.writeByte(0);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allocateDeclIndexes(self: *Plan9, decl: *Module.Decl) !void {
|
||||
try self.got.append(self.base.allocator, 0xdeadbeef);
|
||||
decl.link.plan9.got_index = self.got.items.len - 1;
|
||||
if (decl.link.plan9.got_index == null) {
|
||||
self.got_len += 1;
|
||||
decl.link.plan9.got_index = self.got_len - 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -36,6 +36,8 @@ const ResultId = codegen.ResultId;
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const build_options = @import("build_options");
|
||||
const spec = @import("../codegen/spirv/spec.zig");
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
// TODO: Should this struct be used at all rather than just a hashmap of aux data for every decl?
|
||||
pub const FnData = struct {
|
||||
@ -49,7 +51,12 @@ base: link.File,
|
||||
/// This linker backend does not try to incrementally link output SPIR-V code.
|
||||
/// Instead, it tracks all declarations in this table, and iterates over it
|
||||
/// in the flush function.
|
||||
decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, void) = .{},
|
||||
decl_table: std.AutoArrayHashMapUnmanaged(*Module.Decl, DeclGenContext) = .{},
|
||||
|
||||
const DeclGenContext = struct {
|
||||
air: Air,
|
||||
liveness: Liveness,
|
||||
};
|
||||
|
||||
pub fn createEmpty(gpa: *Allocator, options: link.Options) !*SpirV {
|
||||
const spirv = try gpa.create(SpirV);
|
||||
@ -101,7 +108,23 @@ pub fn deinit(self: *SpirV) void {
|
||||
self.decl_table.deinit(self.base.allocator);
|
||||
}
|
||||
|
||||
pub fn updateFunc(self: *SpirV, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
if (build_options.skip_non_native) {
|
||||
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
||||
}
|
||||
_ = module;
|
||||
// Keep track of all decls so we can iterate over them on flush().
|
||||
_ = try self.decl_table.getOrPut(self.base.allocator, func.owner_decl);
|
||||
|
||||
_ = air;
|
||||
_ = liveness;
|
||||
@panic("TODO SPIR-V needs to keep track of Air and Liveness so it can use them later");
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *SpirV, module: *Module, decl: *Module.Decl) !void {
|
||||
if (build_options.skip_non_native) {
|
||||
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
||||
}
|
||||
_ = module;
|
||||
// Keep track of all decls so we can iterate over them on flush().
|
||||
_ = try self.decl_table.getOrPut(self.base.allocator, decl);
|
||||
@ -132,6 +155,10 @@ pub fn flush(self: *SpirV, comp: *Compilation) !void {
|
||||
}
|
||||
|
||||
pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
|
||||
if (build_options.skip_non_native) {
|
||||
@panic("Attempted to compile for architecture that was disabled by build configuration");
|
||||
}
|
||||
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
@ -159,10 +186,15 @@ pub fn flushModule(self: *SpirV, comp: *Compilation) !void {
|
||||
var decl_gen = codegen.DeclGen.init(&spv);
|
||||
defer decl_gen.deinit();
|
||||
|
||||
for (self.decl_table.keys()) |decl| {
|
||||
var it = self.decl_table.iterator();
|
||||
while (it.next()) |entry| {
|
||||
const decl = entry.key_ptr.*;
|
||||
if (!decl.has_tv) continue;
|
||||
|
||||
if (try decl_gen.gen(decl)) |msg| {
|
||||
const air = entry.value_ptr.air;
|
||||
const liveness = entry.value_ptr.liveness;
|
||||
|
||||
if (try decl_gen.gen(decl, air, liveness)) |msg| {
|
||||
try module.failed_decls.put(module.gpa, decl, msg);
|
||||
return; // TODO: Attempt to generate more decls?
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
const Wasm = @This();
|
||||
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const mem = std.mem;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const assert = std.debug.assert;
|
||||
@ -18,10 +19,15 @@ const build_options = @import("build_options");
|
||||
const wasi_libc = @import("../wasi_libc.zig");
|
||||
const Cache = @import("../Cache.zig");
|
||||
const TypedValue = @import("../TypedValue.zig");
|
||||
const llvm_backend = @import("../codegen/llvm.zig");
|
||||
const Air = @import("../Air.zig");
|
||||
const Liveness = @import("../Liveness.zig");
|
||||
|
||||
pub const base_tag = link.File.Tag.wasm;
|
||||
|
||||
base: link.File,
|
||||
/// If this is not null, an object file is created by LLVM and linked with LLD afterwards.
|
||||
llvm_object: ?*llvm_backend.Object = null,
|
||||
/// List of all function Decls to be written to the output file. The index of
|
||||
/// each Decl in this list at the time of writing the binary is used as the
|
||||
/// function index. In the event where ext_funcs' size is not 0, the index of
|
||||
@ -111,8 +117,13 @@ pub const DeclBlock = struct {
|
||||
pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*Wasm {
|
||||
assert(options.object_format == .wasm);
|
||||
|
||||
if (options.use_llvm) return error.LLVM_BackendIsTODO_ForWasm; // TODO
|
||||
if (options.use_lld) return error.LLD_LinkingIsTODO_ForWasm; // TODO
|
||||
if (build_options.have_llvm and options.use_llvm) {
|
||||
const self = try createEmpty(allocator, options);
|
||||
errdefer self.base.destroy();
|
||||
|
||||
self.llvm_object = try llvm_backend.Object.create(allocator, sub_path, options);
|
||||
return self;
|
||||
}
|
||||
|
||||
// TODO: read the file and keep valid parts instead of truncating
|
||||
const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true });
|
||||
@ -186,10 +197,15 @@ pub fn allocateDeclIndexes(self: *Wasm, decl: *Module.Decl) !void {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate code for the Decl, storing it in memory to be later written to
|
||||
// the file on flush().
|
||||
pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
|
||||
std.debug.assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
|
||||
pub fn updateFunc(self: *Wasm, module: *Module, func: *Module.Fn, air: Air, liveness: Liveness) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .wasm) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateFunc(module, func, air, liveness);
|
||||
}
|
||||
const decl = func.owner_decl;
|
||||
assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
|
||||
|
||||
const fn_data = &decl.fn_link.wasm;
|
||||
fn_data.functype.items.len = 0;
|
||||
@ -198,6 +214,52 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
|
||||
|
||||
var context = codegen.Context{
|
||||
.gpa = self.base.allocator,
|
||||
.air = air,
|
||||
.liveness = liveness,
|
||||
.values = .{},
|
||||
.code = fn_data.code.toManaged(self.base.allocator),
|
||||
.func_type_data = fn_data.functype.toManaged(self.base.allocator),
|
||||
.decl = decl,
|
||||
.err_msg = undefined,
|
||||
.locals = .{},
|
||||
.target = self.base.options.target,
|
||||
.global_error_set = self.base.options.module.?.global_error_set,
|
||||
};
|
||||
defer context.deinit();
|
||||
|
||||
// generate the 'code' section for the function declaration
|
||||
const result = context.genFunc() catch |err| switch (err) {
|
||||
error.CodegenFail => {
|
||||
decl.analysis = .codegen_failure;
|
||||
try module.failed_decls.put(module.gpa, decl, context.err_msg);
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
return self.finishUpdateDecl(decl, result, &context);
|
||||
}
|
||||
|
||||
// Generate code for the Decl, storing it in memory to be later written to
|
||||
// the file on flush().
|
||||
pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
|
||||
if (build_options.skip_non_native and builtin.object_format != .wasm) {
|
||||
@panic("Attempted to compile for object format that was disabled by build configuration");
|
||||
}
|
||||
if (build_options.have_llvm) {
|
||||
if (self.llvm_object) |llvm_object| return llvm_object.updateDecl(module, decl);
|
||||
}
|
||||
assert(decl.link.wasm.init); // Must call allocateDeclIndexes()
|
||||
|
||||
// TODO don't use this for non-functions
|
||||
const fn_data = &decl.fn_link.wasm;
|
||||
fn_data.functype.items.len = 0;
|
||||
fn_data.code.items.len = 0;
|
||||
fn_data.idx_refs.items.len = 0;
|
||||
|
||||
var context = codegen.Context{
|
||||
.gpa = self.base.allocator,
|
||||
.air = undefined,
|
||||
.liveness = undefined,
|
||||
.values = .{},
|
||||
.code = fn_data.code.toManaged(self.base.allocator),
|
||||
.func_type_data = fn_data.functype.toManaged(self.base.allocator),
|
||||
@ -219,14 +281,20 @@ pub fn updateDecl(self: *Wasm, module: *Module, decl: *Module.Decl) !void {
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
const code: []const u8 = switch (result) {
|
||||
.appended => @as([]const u8, context.code.items),
|
||||
.externally_managed => |payload| payload,
|
||||
};
|
||||
return self.finishUpdateDecl(decl, result, &context);
|
||||
}
|
||||
|
||||
fn finishUpdateDecl(self: *Wasm, decl: *Module.Decl, result: codegen.Result, context: *codegen.Context) !void {
|
||||
const fn_data: *FnData = &decl.fn_link.wasm;
|
||||
|
||||
fn_data.code = context.code.toUnmanaged();
|
||||
fn_data.functype = context.func_type_data.toUnmanaged();
|
||||
|
||||
const code: []const u8 = switch (result) {
|
||||
.appended => @as([]const u8, fn_data.code.items),
|
||||
.externally_managed => |payload| payload,
|
||||
};
|
||||
|
||||
const block = &decl.link.wasm;
|
||||
if (decl.ty.zigTypeTag() == .Fn) {
|
||||
// as locals are patched afterwards, the offsets of funcidx's are off,
|
||||
@ -521,7 +589,7 @@ pub fn flushModule(self: *Wasm, comp: *Compilation) !void {
|
||||
var data_offset = offset_table_size;
|
||||
while (cur) |cur_block| : (cur = cur_block.next) {
|
||||
if (cur_block.size == 0) continue;
|
||||
std.debug.assert(cur_block.init);
|
||||
assert(cur_block.init);
|
||||
|
||||
const offset = (cur_block.offset_index) * ptr_width;
|
||||
var buf: [4]u8 = undefined;
|
||||
|
||||
254
src/liveness.zig
254
src/liveness.zig
@ -1,254 +0,0 @@
|
||||
const std = @import("std");
|
||||
const ir = @import("air.zig");
|
||||
const trace = @import("tracy.zig").trace;
|
||||
const log = std.log.scoped(.liveness);
|
||||
const assert = std.debug.assert;
|
||||
|
||||
/// Perform Liveness Analysis over the `Body`. Each `Inst` will have its `deaths` field populated.
|
||||
pub fn analyze(
|
||||
/// Used for temporary storage during the analysis.
|
||||
gpa: *std.mem.Allocator,
|
||||
/// Used to tack on extra allocations in the same lifetime as the existing instructions.
|
||||
arena: *std.mem.Allocator,
|
||||
body: ir.Body,
|
||||
) error{OutOfMemory}!void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
var table = std.AutoHashMap(*ir.Inst, void).init(gpa);
|
||||
defer table.deinit();
|
||||
try table.ensureCapacity(@intCast(u32, body.instructions.len));
|
||||
try analyzeWithTable(arena, &table, null, body);
|
||||
}
|
||||
|
||||
fn analyzeWithTable(
|
||||
arena: *std.mem.Allocator,
|
||||
table: *std.AutoHashMap(*ir.Inst, void),
|
||||
new_set: ?*std.AutoHashMap(*ir.Inst, void),
|
||||
body: ir.Body,
|
||||
) error{OutOfMemory}!void {
|
||||
var i: usize = body.instructions.len;
|
||||
|
||||
if (new_set) |ns| {
|
||||
// We are only interested in doing this for instructions which are born
|
||||
// before a conditional branch, so after obtaining the new set for
|
||||
// each branch we prune the instructions which were born within.
|
||||
while (i != 0) {
|
||||
i -= 1;
|
||||
const base = body.instructions[i];
|
||||
_ = ns.remove(base);
|
||||
try analyzeInst(arena, table, new_set, base);
|
||||
}
|
||||
} else {
|
||||
while (i != 0) {
|
||||
i -= 1;
|
||||
const base = body.instructions[i];
|
||||
try analyzeInst(arena, table, new_set, base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn analyzeInst(
|
||||
arena: *std.mem.Allocator,
|
||||
table: *std.AutoHashMap(*ir.Inst, void),
|
||||
new_set: ?*std.AutoHashMap(*ir.Inst, void),
|
||||
base: *ir.Inst,
|
||||
) error{OutOfMemory}!void {
|
||||
if (table.contains(base)) {
|
||||
base.deaths = 0;
|
||||
} else {
|
||||
// No tombstone for this instruction means it is never referenced,
|
||||
// and its birth marks its own death. Very metal 🤘
|
||||
base.deaths = 1 << ir.Inst.unreferenced_bit_index;
|
||||
}
|
||||
|
||||
switch (base.tag) {
|
||||
.constant => return,
|
||||
.block => {
|
||||
const inst = base.castTag(.block).?;
|
||||
try analyzeWithTable(arena, table, new_set, inst.body);
|
||||
// We let this continue so that it can possibly mark the block as
|
||||
// unreferenced below.
|
||||
},
|
||||
.loop => {
|
||||
const inst = base.castTag(.loop).?;
|
||||
try analyzeWithTable(arena, table, new_set, inst.body);
|
||||
return; // Loop has no operands and it is always unreferenced.
|
||||
},
|
||||
.condbr => {
|
||||
const inst = base.castTag(.condbr).?;
|
||||
|
||||
// Each death that occurs inside one branch, but not the other, needs
|
||||
// to be added as a death immediately upon entering the other branch.
|
||||
|
||||
var then_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
|
||||
defer then_table.deinit();
|
||||
try analyzeWithTable(arena, table, &then_table, inst.then_body);
|
||||
|
||||
// Reset the table back to its state from before the branch.
|
||||
{
|
||||
var it = then_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
assert(table.remove(key.*));
|
||||
}
|
||||
}
|
||||
|
||||
var else_table = std.AutoHashMap(*ir.Inst, void).init(table.allocator);
|
||||
defer else_table.deinit();
|
||||
try analyzeWithTable(arena, table, &else_table, inst.else_body);
|
||||
|
||||
var then_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);
|
||||
defer then_entry_deaths.deinit();
|
||||
var else_entry_deaths = std.ArrayList(*ir.Inst).init(table.allocator);
|
||||
defer else_entry_deaths.deinit();
|
||||
|
||||
{
|
||||
var it = else_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
const else_death = key.*;
|
||||
if (!then_table.contains(else_death)) {
|
||||
try then_entry_deaths.append(else_death);
|
||||
}
|
||||
}
|
||||
}
|
||||
// This loop is the same, except it's for the then branch, and it additionally
|
||||
// has to put its items back into the table to undo the reset.
|
||||
{
|
||||
var it = then_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
const then_death = key.*;
|
||||
if (!else_table.contains(then_death)) {
|
||||
try else_entry_deaths.append(then_death);
|
||||
}
|
||||
try table.put(then_death, {});
|
||||
}
|
||||
}
|
||||
// Now we have to correctly populate new_set.
|
||||
if (new_set) |ns| {
|
||||
try ns.ensureCapacity(@intCast(u32, ns.count() + then_table.count() + else_table.count()));
|
||||
var it = then_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
_ = ns.putAssumeCapacity(key.*, {});
|
||||
}
|
||||
it = else_table.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
_ = ns.putAssumeCapacity(key.*, {});
|
||||
}
|
||||
}
|
||||
inst.then_death_count = std.math.cast(@TypeOf(inst.then_death_count), then_entry_deaths.items.len) catch return error.OutOfMemory;
|
||||
inst.else_death_count = std.math.cast(@TypeOf(inst.else_death_count), else_entry_deaths.items.len) catch return error.OutOfMemory;
|
||||
const allocated_slice = try arena.alloc(*ir.Inst, then_entry_deaths.items.len + else_entry_deaths.items.len);
|
||||
inst.deaths = allocated_slice.ptr;
|
||||
std.mem.copy(*ir.Inst, inst.thenDeaths(), then_entry_deaths.items);
|
||||
std.mem.copy(*ir.Inst, inst.elseDeaths(), else_entry_deaths.items);
|
||||
|
||||
// Continue on with the instruction analysis. The following code will find the condition
|
||||
// instruction, and the deaths flag for the CondBr instruction will indicate whether the
|
||||
// condition's lifetime ends immediately before entering any branch.
|
||||
},
|
||||
.switchbr => {
|
||||
const inst = base.castTag(.switchbr).?;
|
||||
|
||||
const Table = std.AutoHashMap(*ir.Inst, void);
|
||||
const case_tables = try table.allocator.alloc(Table, inst.cases.len + 1); // +1 for else
|
||||
defer table.allocator.free(case_tables);
|
||||
|
||||
std.mem.set(Table, case_tables, Table.init(table.allocator));
|
||||
defer for (case_tables) |*ct| ct.deinit();
|
||||
|
||||
for (inst.cases) |case, i| {
|
||||
try analyzeWithTable(arena, table, &case_tables[i], case.body);
|
||||
|
||||
// Reset the table back to its state from before the case.
|
||||
var it = case_tables[i].keyIterator();
|
||||
while (it.next()) |key| {
|
||||
assert(table.remove(key.*));
|
||||
}
|
||||
}
|
||||
{ // else
|
||||
try analyzeWithTable(arena, table, &case_tables[case_tables.len - 1], inst.else_body);
|
||||
|
||||
// Reset the table back to its state from before the case.
|
||||
var it = case_tables[case_tables.len - 1].keyIterator();
|
||||
while (it.next()) |key| {
|
||||
assert(table.remove(key.*));
|
||||
}
|
||||
}
|
||||
|
||||
const List = std.ArrayList(*ir.Inst);
|
||||
const case_deaths = try table.allocator.alloc(List, case_tables.len); // +1 for else
|
||||
defer table.allocator.free(case_deaths);
|
||||
|
||||
std.mem.set(List, case_deaths, List.init(table.allocator));
|
||||
defer for (case_deaths) |*cd| cd.deinit();
|
||||
|
||||
var total_deaths: u32 = 0;
|
||||
for (case_tables) |*ct, i| {
|
||||
total_deaths += ct.count();
|
||||
var it = ct.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
const case_death = key.*;
|
||||
for (case_tables) |*ct_inner, j| {
|
||||
if (i == j) continue;
|
||||
if (!ct_inner.contains(case_death)) {
|
||||
// instruction is not referenced in this case
|
||||
try case_deaths[j].append(case_death);
|
||||
}
|
||||
}
|
||||
// undo resetting the table
|
||||
try table.put(case_death, {});
|
||||
}
|
||||
}
|
||||
|
||||
// Now we have to correctly populate new_set.
|
||||
if (new_set) |ns| {
|
||||
try ns.ensureCapacity(@intCast(u32, ns.count() + total_deaths));
|
||||
for (case_tables) |*ct| {
|
||||
var it = ct.keyIterator();
|
||||
while (it.next()) |key| {
|
||||
_ = ns.putAssumeCapacity(key.*, {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total_deaths = 0;
|
||||
for (case_deaths[0 .. case_deaths.len - 1]) |*ct, i| {
|
||||
inst.cases[i].index = total_deaths;
|
||||
const len = std.math.cast(@TypeOf(inst.else_deaths), ct.items.len) catch return error.OutOfMemory;
|
||||
inst.cases[i].deaths = len;
|
||||
total_deaths += len;
|
||||
}
|
||||
{ // else
|
||||
const else_deaths = std.math.cast(@TypeOf(inst.else_deaths), case_deaths[case_deaths.len - 1].items.len) catch return error.OutOfMemory;
|
||||
inst.else_index = total_deaths;
|
||||
inst.else_deaths = else_deaths;
|
||||
total_deaths += else_deaths;
|
||||
}
|
||||
|
||||
const allocated_slice = try arena.alloc(*ir.Inst, total_deaths);
|
||||
inst.deaths = allocated_slice.ptr;
|
||||
for (case_deaths[0 .. case_deaths.len - 1]) |*cd, i| {
|
||||
std.mem.copy(*ir.Inst, inst.caseDeaths(i), cd.items);
|
||||
}
|
||||
std.mem.copy(*ir.Inst, inst.elseDeaths(), case_deaths[case_deaths.len - 1].items);
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
const needed_bits = base.operandCount();
|
||||
if (needed_bits <= ir.Inst.deaths_bits) {
|
||||
var bit_i: ir.Inst.DeathsBitIndex = 0;
|
||||
while (base.getOperand(bit_i)) |operand| : (bit_i += 1) {
|
||||
const prev = try table.fetchPut(operand, {});
|
||||
if (prev == null) {
|
||||
// Death.
|
||||
base.deaths |= @as(ir.Inst.DeathsInt, 1) << bit_i;
|
||||
if (new_set) |ns| try ns.putNoClobber(operand, {});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@panic("Handle liveness analysis for instructions with many parameters");
|
||||
}
|
||||
|
||||
log.debug("analyze {}: 0b{b}\n", .{ base.tag, base.deaths });
|
||||
}
|
||||
@ -365,6 +365,7 @@ const usage_build_generic =
|
||||
\\ coff Common Object File Format (Windows)
|
||||
\\ macho macOS relocatables
|
||||
\\ spirv Standard, Portable Intermediate Representation V (SPIR-V)
|
||||
\\ plan9 Plan 9 from Bell Labs object format
|
||||
\\ hex (planned) Intel IHEX
|
||||
\\ raw (planned) Dump machine code directly
|
||||
\\ -dirafter [dir] Add directory to AFTER include search path
|
||||
|
||||
413
src/print_air.zig
Normal file
413
src/print_air.zig
Normal file
@ -0,0 +1,413 @@
|
||||
const std = @import("std");
|
||||
const Allocator = std.mem.Allocator;
|
||||
const fmtIntSizeBin = std.fmt.fmtIntSizeBin;
|
||||
|
||||
const Module = @import("Module.zig");
|
||||
const Value = @import("value.zig").Value;
|
||||
const Zir = @import("Zir.zig");
|
||||
const Air = @import("Air.zig");
|
||||
const Liveness = @import("Liveness.zig");
|
||||
|
||||
pub fn dump(gpa: *Allocator, air: Air, zir: Zir, liveness: Liveness) void {
|
||||
const instruction_bytes = air.instructions.len *
|
||||
// Here we don't use @sizeOf(Air.Inst.Data) because it would include
|
||||
// the debug safety tag but we want to measure release size.
|
||||
(@sizeOf(Air.Inst.Tag) + 8);
|
||||
const extra_bytes = air.extra.len * @sizeOf(u32);
|
||||
const values_bytes = air.values.len * @sizeOf(Value);
|
||||
const variables_bytes = air.variables.len * @sizeOf(*Module.Var);
|
||||
const tomb_bytes = liveness.tomb_bits.len * @sizeOf(usize);
|
||||
const liveness_extra_bytes = liveness.extra.len * @sizeOf(u32);
|
||||
const liveness_special_bytes = liveness.special.count() * 8;
|
||||
const total_bytes = @sizeOf(Air) + instruction_bytes + extra_bytes +
|
||||
values_bytes * variables_bytes + @sizeOf(Liveness) + liveness_extra_bytes +
|
||||
liveness_special_bytes + tomb_bytes;
|
||||
|
||||
// zig fmt: off
|
||||
std.debug.print(
|
||||
\\# Total AIR+Liveness bytes: {}
|
||||
\\# AIR Instructions: {d} ({})
|
||||
\\# AIR Extra Data: {d} ({})
|
||||
\\# AIR Values Bytes: {d} ({})
|
||||
\\# AIR Variables Bytes: {d} ({})
|
||||
\\# Liveness tomb_bits: {}
|
||||
\\# Liveness Extra Data: {d} ({})
|
||||
\\# Liveness special table: {d} ({})
|
||||
\\
|
||||
, .{
|
||||
fmtIntSizeBin(total_bytes),
|
||||
air.instructions.len, fmtIntSizeBin(instruction_bytes),
|
||||
air.extra.len, fmtIntSizeBin(extra_bytes),
|
||||
air.values.len, fmtIntSizeBin(values_bytes),
|
||||
air.variables.len, fmtIntSizeBin(variables_bytes),
|
||||
fmtIntSizeBin(tomb_bytes),
|
||||
liveness.extra.len, fmtIntSizeBin(liveness_extra_bytes),
|
||||
liveness.special.count(), fmtIntSizeBin(liveness_special_bytes),
|
||||
});
|
||||
// zig fmt: on
|
||||
var arena = std.heap.ArenaAllocator.init(gpa);
|
||||
defer arena.deinit();
|
||||
|
||||
var writer: Writer = .{
|
||||
.gpa = gpa,
|
||||
.arena = &arena.allocator,
|
||||
.air = air,
|
||||
.zir = zir,
|
||||
.liveness = liveness,
|
||||
.indent = 2,
|
||||
};
|
||||
const stream = std.io.getStdErr().writer();
|
||||
writer.writeAllConstants(stream) catch return;
|
||||
stream.writeByte('\n') catch return;
|
||||
writer.writeBody(stream, air.getMainBody()) catch return;
|
||||
}
|
||||
|
||||
const Writer = struct {
|
||||
gpa: *Allocator,
|
||||
arena: *Allocator,
|
||||
air: Air,
|
||||
zir: Zir,
|
||||
liveness: Liveness,
|
||||
indent: usize,
|
||||
|
||||
fn writeAllConstants(w: *Writer, s: anytype) @TypeOf(s).Error!void {
|
||||
for (w.air.instructions.items(.tag)) |tag, i| {
|
||||
const inst = @intCast(u32, i);
|
||||
switch (tag) {
|
||||
.constant, .const_ty => {
|
||||
try s.writeByteNTimes(' ', w.indent);
|
||||
try s.print("%{d} ", .{inst});
|
||||
try w.writeInst(s, inst);
|
||||
try s.writeAll(")\n");
|
||||
},
|
||||
else => continue,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn writeBody(w: *Writer, s: anytype, body: []const Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
for (body) |inst| {
|
||||
try s.writeByteNTimes(' ', w.indent);
|
||||
if (w.liveness.isUnused(inst)) {
|
||||
try s.print("%{d}!", .{inst});
|
||||
} else {
|
||||
try s.print("%{d} ", .{inst});
|
||||
}
|
||||
try w.writeInst(s, inst);
|
||||
try s.writeAll(")\n");
|
||||
}
|
||||
}
|
||||
|
||||
fn writeInst(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const tags = w.air.instructions.items(.tag);
|
||||
const tag = tags[inst];
|
||||
try s.print("= {s}(", .{@tagName(tags[inst])});
|
||||
switch (tag) {
|
||||
.arg => try w.writeTyStr(s, inst),
|
||||
|
||||
.add,
|
||||
.addwrap,
|
||||
.sub,
|
||||
.subwrap,
|
||||
.mul,
|
||||
.mulwrap,
|
||||
.div,
|
||||
.bit_and,
|
||||
.bit_or,
|
||||
.xor,
|
||||
.cmp_lt,
|
||||
.cmp_lte,
|
||||
.cmp_eq,
|
||||
.cmp_gte,
|
||||
.cmp_gt,
|
||||
.cmp_neq,
|
||||
.bool_and,
|
||||
.bool_or,
|
||||
.store,
|
||||
=> try w.writeBinOp(s, inst),
|
||||
|
||||
.is_null,
|
||||
.is_non_null,
|
||||
.is_null_ptr,
|
||||
.is_non_null_ptr,
|
||||
.is_err,
|
||||
.is_non_err,
|
||||
.is_err_ptr,
|
||||
.is_non_err_ptr,
|
||||
.ptrtoint,
|
||||
.ret,
|
||||
=> try w.writeUnOp(s, inst),
|
||||
|
||||
.breakpoint,
|
||||
.unreach,
|
||||
=> try w.writeNoOp(s, inst),
|
||||
|
||||
.const_ty,
|
||||
.alloc,
|
||||
=> try w.writeTy(s, inst),
|
||||
|
||||
.not,
|
||||
.bitcast,
|
||||
.load,
|
||||
.ref,
|
||||
.floatcast,
|
||||
.intcast,
|
||||
.optional_payload,
|
||||
.optional_payload_ptr,
|
||||
.wrap_optional,
|
||||
.unwrap_errunion_payload,
|
||||
.unwrap_errunion_err,
|
||||
.unwrap_errunion_payload_ptr,
|
||||
.unwrap_errunion_err_ptr,
|
||||
.wrap_errunion_payload,
|
||||
.wrap_errunion_err,
|
||||
=> try w.writeTyOp(s, inst),
|
||||
|
||||
.block,
|
||||
.loop,
|
||||
=> try w.writeBlock(s, inst),
|
||||
|
||||
.struct_field_ptr => try w.writeStructFieldPtr(s, inst),
|
||||
.varptr => try w.writeVarPtr(s, inst),
|
||||
.constant => try w.writeConstant(s, inst),
|
||||
.assembly => try w.writeAssembly(s, inst),
|
||||
.dbg_stmt => try w.writeDbgStmt(s, inst),
|
||||
.call => try w.writeCall(s, inst),
|
||||
.br => try w.writeBr(s, inst),
|
||||
.cond_br => try w.writeCondBr(s, inst),
|
||||
.switch_br => try w.writeSwitchBr(s, inst),
|
||||
}
|
||||
}
|
||||
|
||||
fn writeTyStr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const ty_str = w.air.instructions.items(.data)[inst].ty_str;
|
||||
const name = w.zir.nullTerminatedString(ty_str.str);
|
||||
try s.print("\"{}\", {}", .{ std.zig.fmtEscapes(name), ty_str.ty });
|
||||
}
|
||||
|
||||
fn writeBinOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const bin_op = w.air.instructions.items(.data)[inst].bin_op;
|
||||
try w.writeOperand(s, inst, 0, bin_op.lhs);
|
||||
try s.writeAll(", ");
|
||||
try w.writeOperand(s, inst, 1, bin_op.rhs);
|
||||
}
|
||||
|
||||
fn writeUnOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const un_op = w.air.instructions.items(.data)[inst].un_op;
|
||||
try w.writeOperand(s, inst, 0, un_op);
|
||||
}
|
||||
|
||||
fn writeNoOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
_ = w;
|
||||
_ = inst;
|
||||
_ = s;
|
||||
// no-op, no argument to write
|
||||
}
|
||||
|
||||
fn writeTy(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const ty = w.air.instructions.items(.data)[inst].ty;
|
||||
try s.print("{}", .{ty});
|
||||
}
|
||||
|
||||
fn writeTyOp(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const ty_op = w.air.instructions.items(.data)[inst].ty_op;
|
||||
try s.print("{}, ", .{w.air.getRefType(ty_op.ty)});
|
||||
try w.writeOperand(s, inst, 0, ty_op.operand);
|
||||
}
|
||||
|
||||
fn writeBlock(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
|
||||
const extra = w.air.extraData(Air.Block, ty_pl.payload);
|
||||
const body = w.air.extra[extra.end..][0..extra.data.body_len];
|
||||
|
||||
try s.writeAll("{\n");
|
||||
const old_indent = w.indent;
|
||||
w.indent += 2;
|
||||
try w.writeBody(s, body);
|
||||
w.indent = old_indent;
|
||||
try s.writeByteNTimes(' ', w.indent);
|
||||
try s.writeAll("}");
|
||||
}
|
||||
|
||||
fn writeStructFieldPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
|
||||
const extra = w.air.extraData(Air.StructField, ty_pl.payload);
|
||||
|
||||
try w.writeOperand(s, inst, 0, extra.data.struct_ptr);
|
||||
try s.print(", {d}", .{extra.data.field_index});
|
||||
}
|
||||
|
||||
fn writeVarPtr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
_ = w;
|
||||
_ = inst;
|
||||
try s.writeAll("TODO");
|
||||
}
|
||||
|
||||
fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
|
||||
const val = w.air.values[ty_pl.payload];
|
||||
try s.print("{}, {}", .{ w.air.getRefType(ty_pl.ty), val });
|
||||
}
|
||||
|
||||
fn writeAssembly(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
_ = w;
|
||||
_ = inst;
|
||||
try s.writeAll("TODO");
|
||||
}
|
||||
|
||||
fn writeDbgStmt(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const dbg_stmt = w.air.instructions.items(.data)[inst].dbg_stmt;
|
||||
try s.print("{d}:{d}", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 });
|
||||
}
|
||||
|
||||
fn writeCall(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const pl_op = w.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = w.air.extraData(Air.Call, pl_op.payload);
|
||||
const args = @bitCast([]const Air.Inst.Ref, w.air.extra[extra.end..][0..extra.data.args_len]);
|
||||
try w.writeOperand(s, inst, 0, pl_op.operand);
|
||||
try s.writeAll(", [");
|
||||
for (args) |arg, i| {
|
||||
if (i != 0) try s.writeAll(", ");
|
||||
try w.writeOperand(s, inst, 1 + i, arg);
|
||||
}
|
||||
try s.writeAll("]");
|
||||
}
|
||||
|
||||
fn writeBr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const br = w.air.instructions.items(.data)[inst].br;
|
||||
try w.writeInstIndex(s, br.block_inst, false);
|
||||
try s.writeAll(", ");
|
||||
try w.writeOperand(s, inst, 0, br.operand);
|
||||
}
|
||||
|
||||
fn writeCondBr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const pl_op = w.air.instructions.items(.data)[inst].pl_op;
|
||||
const extra = w.air.extraData(Air.CondBr, pl_op.payload);
|
||||
const then_body = w.air.extra[extra.end..][0..extra.data.then_body_len];
|
||||
const else_body = w.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
|
||||
const liveness_condbr = w.liveness.getCondBr(inst);
|
||||
|
||||
try w.writeOperand(s, inst, 0, pl_op.operand);
|
||||
try s.writeAll(", {\n");
|
||||
const old_indent = w.indent;
|
||||
w.indent += 2;
|
||||
|
||||
if (liveness_condbr.then_deaths.len != 0) {
|
||||
try s.writeByteNTimes(' ', w.indent);
|
||||
for (liveness_condbr.then_deaths) |operand, i| {
|
||||
if (i != 0) try s.writeAll(" ");
|
||||
try s.print("%{d}!", .{operand});
|
||||
}
|
||||
try s.writeAll("\n");
|
||||
}
|
||||
|
||||
try w.writeBody(s, then_body);
|
||||
try s.writeByteNTimes(' ', old_indent);
|
||||
try s.writeAll("}, {\n");
|
||||
|
||||
if (liveness_condbr.else_deaths.len != 0) {
|
||||
try s.writeByteNTimes(' ', w.indent);
|
||||
for (liveness_condbr.else_deaths) |operand, i| {
|
||||
if (i != 0) try s.writeAll(" ");
|
||||
try s.print("%{d}!", .{operand});
|
||||
}
|
||||
try s.writeAll("\n");
|
||||
}
|
||||
|
||||
try w.writeBody(s, else_body);
|
||||
w.indent = old_indent;
|
||||
|
||||
try s.writeByteNTimes(' ', old_indent);
|
||||
try s.writeAll("}");
|
||||
}
|
||||
|
||||
fn writeSwitchBr(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
|
||||
const pl_op = w.air.instructions.items(.data)[inst].pl_op;
|
||||
const switch_br = w.air.extraData(Air.SwitchBr, pl_op.payload);
|
||||
var extra_index: usize = switch_br.end;
|
||||
var case_i: u32 = 0;
|
||||
|
||||
try w.writeOperand(s, inst, 0, pl_op.operand);
|
||||
const old_indent = w.indent;
|
||||
w.indent += 2;
|
||||
|
||||
while (case_i < switch_br.data.cases_len) : (case_i += 1) {
|
||||
const case = w.air.extraData(Air.SwitchBr.Case, extra_index);
|
||||
const items = @bitCast([]const Air.Inst.Ref, w.air.extra[case.end..][0..case.data.items_len]);
|
||||
const case_body = w.air.extra[case.end + items.len ..][0..case.data.body_len];
|
||||
extra_index = case.end + case.data.items_len + case_body.len;
|
||||
|
||||
try s.writeAll(", [");
|
||||
for (items) |item, item_i| {
|
||||
if (item_i != 0) try s.writeAll(", ");
|
||||
try w.writeInstRef(s, item, false);
|
||||
}
|
||||
try s.writeAll("] => {\n");
|
||||
w.indent += 2;
|
||||
try w.writeBody(s, case_body);
|
||||
w.indent -= 2;
|
||||
try s.writeByteNTimes(' ', w.indent);
|
||||
try s.writeAll("}");
|
||||
}
|
||||
|
||||
const else_body = w.air.extra[extra_index..][0..switch_br.data.else_body_len];
|
||||
if (else_body.len != 0) {
|
||||
try s.writeAll(", else => {\n");
|
||||
w.indent += 2;
|
||||
try w.writeBody(s, else_body);
|
||||
w.indent -= 2;
|
||||
try s.writeByteNTimes(' ', w.indent);
|
||||
try s.writeAll("}");
|
||||
}
|
||||
|
||||
try s.writeAll("\n");
|
||||
try s.writeByteNTimes(' ', old_indent);
|
||||
try s.writeAll("}");
|
||||
}
|
||||
|
||||
fn writeOperand(
|
||||
w: *Writer,
|
||||
s: anytype,
|
||||
inst: Air.Inst.Index,
|
||||
op_index: usize,
|
||||
operand: Air.Inst.Ref,
|
||||
) @TypeOf(s).Error!void {
|
||||
const dies = if (op_index < Liveness.bpi - 1)
|
||||
w.liveness.operandDies(inst, @intCast(Liveness.OperandInt, op_index))
|
||||
else blk: {
|
||||
// TODO
|
||||
break :blk false;
|
||||
};
|
||||
return w.writeInstRef(s, operand, dies);
|
||||
}
|
||||
|
||||
fn writeInstRef(
|
||||
w: *Writer,
|
||||
s: anytype,
|
||||
operand: Air.Inst.Ref,
|
||||
dies: bool,
|
||||
) @TypeOf(s).Error!void {
|
||||
var i: usize = @enumToInt(operand);
|
||||
|
||||
if (i < Air.Inst.Ref.typed_value_map.len) {
|
||||
return s.print("@{}", .{operand});
|
||||
}
|
||||
i -= Air.Inst.Ref.typed_value_map.len;
|
||||
|
||||
return w.writeInstIndex(s, @intCast(Air.Inst.Index, i), dies);
|
||||
}
|
||||
|
||||
fn writeInstIndex(
|
||||
w: *Writer,
|
||||
s: anytype,
|
||||
inst: Air.Inst.Index,
|
||||
dies: bool,
|
||||
) @TypeOf(s).Error!void {
|
||||
_ = w;
|
||||
if (dies) {
|
||||
try s.print("%{d}!", .{inst});
|
||||
} else {
|
||||
try s.print("%{d}", .{inst});
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -3,7 +3,7 @@ const math = std.math;
|
||||
const mem = std.mem;
|
||||
const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ir = @import("air.zig");
|
||||
const Air = @import("Air.zig");
|
||||
const Type = @import("type.zig").Type;
|
||||
const Module = @import("Module.zig");
|
||||
const LazySrcLoc = Module.LazySrcLoc;
|
||||
@ -20,7 +20,7 @@ pub fn RegisterManager(
|
||||
) type {
|
||||
return struct {
|
||||
/// The key must be canonical register.
|
||||
registers: [callee_preserved_regs.len]?*ir.Inst = [_]?*ir.Inst{null} ** callee_preserved_regs.len,
|
||||
registers: [callee_preserved_regs.len]?Air.Inst.Index = [_]?Air.Inst.Index{null} ** callee_preserved_regs.len,
|
||||
free_registers: FreeRegInt = math.maxInt(FreeRegInt),
|
||||
/// Tracks all registers allocated in the course of this function
|
||||
allocated_registers: FreeRegInt = 0,
|
||||
@ -75,7 +75,7 @@ pub fn RegisterManager(
|
||||
pub fn tryAllocRegs(
|
||||
self: *Self,
|
||||
comptime count: comptime_int,
|
||||
insts: [count]?*ir.Inst,
|
||||
insts: [count]?Air.Inst.Index,
|
||||
exceptions: []const Register,
|
||||
) ?[count]Register {
|
||||
comptime if (callee_preserved_regs.len == 0) return null;
|
||||
@ -113,7 +113,7 @@ pub fn RegisterManager(
|
||||
/// Allocates a register and optionally tracks it with a
|
||||
/// corresponding instruction. Returns `null` if all registers
|
||||
/// are allocated.
|
||||
pub fn tryAllocReg(self: *Self, inst: ?*ir.Inst, exceptions: []const Register) ?Register {
|
||||
pub fn tryAllocReg(self: *Self, inst: ?Air.Inst.Index, exceptions: []const Register) ?Register {
|
||||
return if (tryAllocRegs(self, 1, .{inst}, exceptions)) |regs| regs[0] else null;
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ pub fn RegisterManager(
|
||||
pub fn allocRegs(
|
||||
self: *Self,
|
||||
comptime count: comptime_int,
|
||||
insts: [count]?*ir.Inst,
|
||||
insts: [count]?Air.Inst.Index,
|
||||
exceptions: []const Register,
|
||||
) ![count]Register {
|
||||
comptime assert(count > 0 and count <= callee_preserved_regs.len);
|
||||
@ -147,14 +147,14 @@ pub fn RegisterManager(
|
||||
self.markRegUsed(reg);
|
||||
} else {
|
||||
const spilled_inst = self.registers[index].?;
|
||||
try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
}
|
||||
self.registers[index] = inst;
|
||||
} else {
|
||||
// Don't track the register
|
||||
if (!self.isRegFree(reg)) {
|
||||
const spilled_inst = self.registers[index].?;
|
||||
try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
self.freeReg(reg);
|
||||
}
|
||||
}
|
||||
@ -168,14 +168,14 @@ pub fn RegisterManager(
|
||||
|
||||
/// Allocates a register and optionally tracks it with a
|
||||
/// corresponding instruction.
|
||||
pub fn allocReg(self: *Self, inst: ?*ir.Inst, exceptions: []const Register) !Register {
|
||||
pub fn allocReg(self: *Self, inst: ?Air.Inst.Index, exceptions: []const Register) !Register {
|
||||
return (try self.allocRegs(1, .{inst}, exceptions))[0];
|
||||
}
|
||||
|
||||
/// Spills the register if it is currently allocated. If a
|
||||
/// corresponding instruction is passed, will also track this
|
||||
/// register.
|
||||
pub fn getReg(self: *Self, reg: Register, inst: ?*ir.Inst) !void {
|
||||
pub fn getReg(self: *Self, reg: Register, inst: ?Air.Inst.Index) !void {
|
||||
const index = reg.allocIndex() orelse return;
|
||||
|
||||
if (inst) |tracked_inst|
|
||||
@ -184,7 +184,7 @@ pub fn RegisterManager(
|
||||
// stack allocation.
|
||||
const spilled_inst = self.registers[index].?;
|
||||
self.registers[index] = tracked_inst;
|
||||
try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
} else {
|
||||
self.getRegAssumeFree(reg, tracked_inst);
|
||||
}
|
||||
@ -193,7 +193,7 @@ pub fn RegisterManager(
|
||||
// Move the instruction that was previously there to a
|
||||
// stack allocation.
|
||||
const spilled_inst = self.registers[index].?;
|
||||
try self.getFunction().spillInstruction(spilled_inst.src, reg, spilled_inst);
|
||||
try self.getFunction().spillInstruction(reg, spilled_inst);
|
||||
self.freeReg(reg);
|
||||
}
|
||||
}
|
||||
@ -202,7 +202,7 @@ pub fn RegisterManager(
|
||||
/// Allocates the specified register with the specified
|
||||
/// instruction. Asserts that the register is free and no
|
||||
/// spilling is necessary.
|
||||
pub fn getRegAssumeFree(self: *Self, reg: Register, inst: *ir.Inst) void {
|
||||
pub fn getRegAssumeFree(self: *Self, reg: Register, inst: Air.Inst.Index) void {
|
||||
const index = reg.allocIndex() orelse return;
|
||||
|
||||
assert(self.registers[index] == null);
|
||||
@ -264,8 +264,7 @@ fn MockFunction(comptime Register: type) type {
|
||||
self.spilled.deinit(self.allocator);
|
||||
}
|
||||
|
||||
pub fn spillInstruction(self: *Self, src: LazySrcLoc, reg: Register, inst: *ir.Inst) !void {
|
||||
_ = src;
|
||||
pub fn spillInstruction(self: *Self, reg: Register, inst: Air.Inst.Index) !void {
|
||||
_ = inst;
|
||||
try self.spilled.append(self.allocator, reg);
|
||||
}
|
||||
@ -297,15 +296,11 @@ test "tryAllocReg: no spilling" {
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
var mock_instruction = ir.Inst{
|
||||
.tag = .breakpoint,
|
||||
.ty = Type.initTag(.void),
|
||||
.src = .unneeded,
|
||||
};
|
||||
const mock_instruction: Air.Inst.Index = 1;
|
||||
|
||||
try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(&mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r2), function.register_manager.tryAllocReg(mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r3), function.register_manager.tryAllocReg(mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, null), function.register_manager.tryAllocReg(mock_instruction, &.{}));
|
||||
|
||||
try expect(function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
@ -329,28 +324,24 @@ test "allocReg: spilling" {
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
var mock_instruction = ir.Inst{
|
||||
.tag = .breakpoint,
|
||||
.ty = Type.initTag(.void),
|
||||
.src = .unneeded,
|
||||
};
|
||||
const mock_instruction: Air.Inst.Index = 1;
|
||||
|
||||
try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(&mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(&mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, &.{}));
|
||||
|
||||
// Spill a register
|
||||
try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(&mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r2), try function.register_manager.allocReg(mock_instruction, &.{}));
|
||||
try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
|
||||
|
||||
// No spilling necessary
|
||||
function.register_manager.freeReg(.r3);
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(&mock_instruction, &.{}));
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, &.{}));
|
||||
try expectEqualSlices(MockRegister1, &[_]MockRegister1{.r2}, function.spilled.items);
|
||||
|
||||
// Exceptions
|
||||
function.register_manager.freeReg(.r2);
|
||||
function.register_manager.freeReg(.r3);
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(&mock_instruction, &.{.r2}));
|
||||
try expectEqual(@as(?MockRegister1, .r3), try function.register_manager.allocReg(mock_instruction, &.{.r2}));
|
||||
}
|
||||
|
||||
test "tryAllocRegs" {
|
||||
@ -378,16 +369,12 @@ test "allocRegs" {
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
var mock_instruction = ir.Inst{
|
||||
.tag = .breakpoint,
|
||||
.ty = Type.initTag(.void),
|
||||
.src = .unneeded,
|
||||
};
|
||||
const mock_instruction: Air.Inst.Index = 1;
|
||||
|
||||
try expectEqual([_]MockRegister2{ .r0, .r1, .r2 }, try function.register_manager.allocRegs(3, .{
|
||||
&mock_instruction,
|
||||
&mock_instruction,
|
||||
&mock_instruction,
|
||||
mock_instruction,
|
||||
mock_instruction,
|
||||
mock_instruction,
|
||||
}, &.{}));
|
||||
|
||||
// Exceptions
|
||||
@ -403,13 +390,9 @@ test "getReg" {
|
||||
};
|
||||
defer function.deinit();
|
||||
|
||||
var mock_instruction = ir.Inst{
|
||||
.tag = .breakpoint,
|
||||
.ty = Type.initTag(.void),
|
||||
.src = .unneeded,
|
||||
};
|
||||
const mock_instruction: Air.Inst.Index = 1;
|
||||
|
||||
try function.register_manager.getReg(.r3, &mock_instruction);
|
||||
try function.register_manager.getReg(.r3, mock_instruction);
|
||||
|
||||
try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
@ -417,7 +400,7 @@ test "getReg" {
|
||||
try expect(!function.register_manager.isRegFree(.r3));
|
||||
|
||||
// Spill r3
|
||||
try function.register_manager.getReg(.r3, &mock_instruction);
|
||||
try function.register_manager.getReg(.r3, mock_instruction);
|
||||
|
||||
try expect(!function.register_manager.isRegAllocated(.r2));
|
||||
try expect(function.register_manager.isRegAllocated(.r3));
|
||||
|
||||
11
src/test.zig
11
src/test.zig
@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const link = @import("link.zig");
|
||||
const Compilation = @import("Compilation.zig");
|
||||
const Allocator = std.mem.Allocator;
|
||||
@ -610,7 +611,7 @@ pub const TestContext = struct {
|
||||
|
||||
fn run(self: *TestContext) !void {
|
||||
var progress = std.Progress{};
|
||||
const root_node = try progress.start("tests", self.cases.items.len);
|
||||
const root_node = try progress.start("compiler", self.cases.items.len);
|
||||
defer root_node.end();
|
||||
|
||||
var zig_lib_directory = try introspect.findZigLibDir(std.testing.allocator);
|
||||
@ -640,8 +641,12 @@ pub const TestContext = struct {
|
||||
var fail_count: usize = 0;
|
||||
|
||||
for (self.cases.items) |case| {
|
||||
if (build_options.skip_non_native and case.target.getCpuArch() != std.Target.current.cpu.arch)
|
||||
continue;
|
||||
if (build_options.skip_non_native) {
|
||||
if (case.target.getCpuArch() != builtin.cpu.arch)
|
||||
continue;
|
||||
if (case.target.getObjectFormat() != builtin.object_format)
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip tests that require LLVM backend when it is not available
|
||||
if (!build_options.have_llvm and case.backend == .llvm)
|
||||
|
||||
@ -7,7 +7,7 @@ const BigIntMutable = std.math.big.int.Mutable;
|
||||
const Target = std.Target;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Module = @import("Module.zig");
|
||||
const ir = @import("air.zig");
|
||||
const Air = @import("Air.zig");
|
||||
|
||||
/// This is the raw data, with no bookkeeping, no memory awareness,
|
||||
/// no de-duplication, and no type system awareness.
|
||||
@ -573,7 +573,7 @@ pub const Value = extern union {
|
||||
.int_i64 => return std.fmt.formatIntValue(val.castTag(.int_i64).?.data, "", options, out_stream),
|
||||
.int_big_positive => return out_stream.print("{}", .{val.castTag(.int_big_positive).?.asBigInt()}),
|
||||
.int_big_negative => return out_stream.print("{}", .{val.castTag(.int_big_negative).?.asBigInt()}),
|
||||
.function => return out_stream.writeAll("(function)"),
|
||||
.function => return out_stream.print("(function '{s}')", .{val.castTag(.function).?.data.owner_decl.name}),
|
||||
.extern_fn => return out_stream.writeAll("(extern function)"),
|
||||
.variable => return out_stream.writeAll("(variable)"),
|
||||
.ref_val => {
|
||||
@ -1700,7 +1700,7 @@ pub const Value = extern union {
|
||||
/// peer type resolution. This is stored in a separate list so that
|
||||
/// the items are contiguous in memory and thus can be passed to
|
||||
/// `Module.resolvePeerTypes`.
|
||||
stored_inst_list: std.ArrayListUnmanaged(*ir.Inst) = .{},
|
||||
stored_inst_list: std.ArrayListUnmanaged(Air.Inst.Ref) = .{},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@ -479,66 +479,68 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
, "30\n");
|
||||
}
|
||||
|
||||
{
|
||||
var case = ctx.exe("wasm switch", wasi);
|
||||
// This test case is disabled until the codegen for switch is reworked
|
||||
// to take advantage of br_table rather than a series of br_if opcodes.
|
||||
//{
|
||||
// var case = ctx.exe("wasm switch", wasi);
|
||||
|
||||
case.addCompareOutput(
|
||||
\\pub export fn _start() u32 {
|
||||
\\ var val: u32 = 1;
|
||||
\\ var a: u32 = switch (val) {
|
||||
\\ 0, 1 => 2,
|
||||
\\ 2 => 3,
|
||||
\\ 3 => 4,
|
||||
\\ else => 5,
|
||||
\\ };
|
||||
\\
|
||||
\\ return a;
|
||||
\\}
|
||||
, "2\n");
|
||||
// case.addCompareOutput(
|
||||
// \\pub export fn _start() u32 {
|
||||
// \\ var val: u32 = 1;
|
||||
// \\ var a: u32 = switch (val) {
|
||||
// \\ 0, 1 => 2,
|
||||
// \\ 2 => 3,
|
||||
// \\ 3 => 4,
|
||||
// \\ else => 5,
|
||||
// \\ };
|
||||
// \\
|
||||
// \\ return a;
|
||||
// \\}
|
||||
// , "2\n");
|
||||
|
||||
case.addCompareOutput(
|
||||
\\pub export fn _start() u32 {
|
||||
\\ var val: u32 = 2;
|
||||
\\ var a: u32 = switch (val) {
|
||||
\\ 0, 1 => 2,
|
||||
\\ 2 => 3,
|
||||
\\ 3 => 4,
|
||||
\\ else => 5,
|
||||
\\ };
|
||||
\\
|
||||
\\ return a;
|
||||
\\}
|
||||
, "3\n");
|
||||
// case.addCompareOutput(
|
||||
// \\pub export fn _start() u32 {
|
||||
// \\ var val: u32 = 2;
|
||||
// \\ var a: u32 = switch (val) {
|
||||
// \\ 0, 1 => 2,
|
||||
// \\ 2 => 3,
|
||||
// \\ 3 => 4,
|
||||
// \\ else => 5,
|
||||
// \\ };
|
||||
// \\
|
||||
// \\ return a;
|
||||
// \\}
|
||||
// , "3\n");
|
||||
|
||||
case.addCompareOutput(
|
||||
\\pub export fn _start() u32 {
|
||||
\\ var val: u32 = 10;
|
||||
\\ var a: u32 = switch (val) {
|
||||
\\ 0, 1 => 2,
|
||||
\\ 2 => 3,
|
||||
\\ 3 => 4,
|
||||
\\ else => 5,
|
||||
\\ };
|
||||
\\
|
||||
\\ return a;
|
||||
\\}
|
||||
, "5\n");
|
||||
// case.addCompareOutput(
|
||||
// \\pub export fn _start() u32 {
|
||||
// \\ var val: u32 = 10;
|
||||
// \\ var a: u32 = switch (val) {
|
||||
// \\ 0, 1 => 2,
|
||||
// \\ 2 => 3,
|
||||
// \\ 3 => 4,
|
||||
// \\ else => 5,
|
||||
// \\ };
|
||||
// \\
|
||||
// \\ return a;
|
||||
// \\}
|
||||
// , "5\n");
|
||||
|
||||
case.addCompareOutput(
|
||||
\\const MyEnum = enum { One, Two, Three };
|
||||
\\
|
||||
\\pub export fn _start() u32 {
|
||||
\\ var val: MyEnum = .Two;
|
||||
\\ var a: u32 = switch (val) {
|
||||
\\ .One => 1,
|
||||
\\ .Two => 2,
|
||||
\\ .Three => 3,
|
||||
\\ };
|
||||
\\
|
||||
\\ return a;
|
||||
\\}
|
||||
, "2\n");
|
||||
}
|
||||
// case.addCompareOutput(
|
||||
// \\const MyEnum = enum { One, Two, Three };
|
||||
// \\
|
||||
// \\pub export fn _start() u32 {
|
||||
// \\ var val: MyEnum = .Two;
|
||||
// \\ var a: u32 = switch (val) {
|
||||
// \\ .One => 1,
|
||||
// \\ .Two => 2,
|
||||
// \\ .Three => 3,
|
||||
// \\ };
|
||||
// \\
|
||||
// \\ return a;
|
||||
// \\}
|
||||
// , "2\n");
|
||||
//}
|
||||
|
||||
{
|
||||
var case = ctx.exe("wasm error unions", wasi);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user