Merge pull request #9353 from ziglang/stage2-air

stage2: rework AIR memory layout
This commit is contained in:
Andrew Kelley 2021-07-21 03:18:39 -04:00 committed by GitHub
commit 26984852bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 7595 additions and 6280 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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" {

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

@ -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?
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) = .{},
},
};

View File

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