commit e8545db9d4ced8978c5594c637d9bf76dc26209d (tree)
parent b5e72c0148e40df418bdc8e1770b1bd42e76732e
Author: Andrew Kelley <andrew@ziglang.org>
Date: Wed, 22 Apr 2020 14:42:46 -0400
Merge pull request #5130 from ziglang/stage2-ir
beginnings of non-LLVM self-hosted backend
Diffstat:
14 files changed, 3067 insertions(+), 4267 deletions(-)
diff --git a/lib/std/fs.zig b/lib/std/fs.zig
@@ -1012,25 +1012,27 @@ pub const Dir = struct {
/// On success, caller owns returned buffer.
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 {
- return self.readFileAllocAligned(allocator, file_path, max_bytes, @alignOf(u8));
+ return self.readFileAllocOptions(allocator, file_path, max_bytes, @alignOf(u8), null);
}
/// On success, caller owns returned buffer.
/// If the file is larger than `max_bytes`, returns `error.FileTooBig`.
- pub fn readFileAllocAligned(
+ /// Allows specifying alignment and a sentinel value.
+ pub fn readFileAllocOptions(
self: Dir,
allocator: *mem.Allocator,
file_path: []const u8,
max_bytes: usize,
- comptime A: u29,
- ) ![]align(A) u8 {
+ comptime alignment: u29,
+ comptime optional_sentinel: ?u8,
+ ) !(if (optional_sentinel) |s| [:s]align(alignment) u8 else []align(alignment) u8) {
var file = try self.openFile(file_path, .{});
defer file.close();
const size = math.cast(usize, try file.getEndPos()) catch math.maxInt(usize);
if (size > max_bytes) return error.FileTooBig;
- const buf = try allocator.alignedAlloc(u8, A, size);
+ const buf = try allocator.allocWithOptions(u8, size, alignment, optional_sentinel);
errdefer allocator.free(buf);
try file.inStream().readNoEof(buf);
diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig
@@ -143,12 +143,15 @@ pub const Int = struct {
/// Clones an Int and returns a new Int with the same value. The new Int is a deep copy and
/// can be modified separately from the original.
pub fn clone(other: Int) !Int {
- other.assertWritable();
+ return other.clone2(other.allocator.?);
+ }
+
+ pub fn clone2(other: Int, allocator: *Allocator) !Int {
return Int{
- .allocator = other.allocator,
+ .allocator = allocator,
.metadata = other.metadata,
.limbs = block: {
- var limbs = try other.allocator.?.alloc(Limb, other.len());
+ var limbs = try allocator.alloc(Limb, other.len());
mem.copy(Limb, limbs[0..], other.limbs[0..other.len()]);
break :block limbs;
},
@@ -237,7 +240,7 @@ pub const Int = struct {
return bits;
}
- fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool {
+ pub fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool {
if (self.eqZero()) {
return true;
}
@@ -470,8 +473,8 @@ pub const Int = struct {
break;
}
}
- } // Non power-of-two: batch divisions per word size.
- else {
+ } else {
+ // Non power-of-two: batch divisions per word size.
const digits_per_limb = math.log(Limb, base, maxInt(Limb));
var limb_base: Limb = 1;
var j: usize = 0;
@@ -479,7 +482,7 @@ pub const Int = struct {
limb_base *= base;
}
- var q = try self.clone();
+ var q = try self.clone2(allocator);
defer q.deinit();
q.abs();
var r = try Int.init(allocator);
@@ -522,15 +525,13 @@ pub const Int = struct {
/// To allow `std.fmt.printf` to work with Int.
/// TODO make this non-allocating
+ /// TODO support read-only fixed integers
pub fn format(
self: Int,
comptime fmt: []const u8,
options: std.fmt.FormatOptions,
out_stream: var,
) !void {
- self.assertWritable();
- // TODO support read-only fixed integers
-
comptime var radix = 10;
comptime var uppercase = false;
@@ -550,8 +551,9 @@ pub const Int = struct {
@compileError("Unknown format string: '" ++ fmt ++ "'");
}
- const str = self.toString(self.allocator.?, radix, uppercase) catch @panic("TODO make this non allocating");
- defer self.allocator.?.free(str);
+ var buf: [4096]u8 = undefined;
+ var fba = std.heap.FixedBufferAllocator.init(&buf);
+ const str = self.toString(&fba.allocator, radix, uppercase) catch @panic("TODO make this non allocating");
return out_stream.writeAll(str);
}
diff --git a/lib/std/mem.zig b/lib/std/mem.zig
@@ -105,6 +105,31 @@ pub const Allocator = struct {
return self.alignedAlloc(T, null, n);
}
+ pub fn allocWithOptions(
+ self: *Allocator,
+ comptime Elem: type,
+ n: usize,
+ /// null means naturally aligned
+ comptime optional_alignment: ?u29,
+ comptime optional_sentinel: ?Elem,
+ ) Error!AllocWithOptionsPayload(Elem, optional_alignment, optional_sentinel) {
+ if (optional_sentinel) |sentinel| {
+ const ptr = try self.alignedAlloc(Elem, optional_alignment, n + 1);
+ ptr[n] = sentinel;
+ return ptr[0..n :sentinel];
+ } else {
+ return self.alignedAlloc(Elem, optional_alignment, n);
+ }
+ }
+
+ fn AllocWithOptionsPayload(comptime Elem: type, comptime alignment: ?u29, comptime sentinel: ?Elem) type {
+ if (sentinel) |s| {
+ return [:s]align(alignment orelse @alignOf(T)) Elem;
+ } else {
+ return []align(alignment orelse @alignOf(T)) Elem;
+ }
+ }
+
/// Allocates an array of `n + 1` items of type `T` and sets the first `n`
/// items to `undefined` and the last item to `sentinel`. Depending on the
/// Allocator implementation, it may be required to call `free` once the
@@ -113,10 +138,10 @@ pub const Allocator = struct {
/// call `free` when done.
///
/// For allocating a single item, see `create`.
+ ///
+ /// Deprecated; use `allocWithOptions`.
pub fn allocSentinel(self: *Allocator, comptime Elem: type, n: usize, comptime sentinel: Elem) Error![:sentinel]Elem {
- var ptr = try self.alloc(Elem, n + 1);
- ptr[n] = sentinel;
- return ptr[0..n :sentinel];
+ return self.allocWithOptions(Elem, n, null, sentinel);
}
pub fn alignedAlloc(
diff --git a/lib/std/target.zig b/lib/std/target.zig
@@ -761,7 +761,7 @@ pub const Target = struct {
};
}
- pub fn ptrBitWidth(arch: Arch) u32 {
+ pub fn ptrBitWidth(arch: Arch) u16 {
switch (arch) {
.avr,
.msp430,
diff --git a/lib/std/zig.zig b/lib/std/zig.zig
@@ -2,8 +2,9 @@ const tokenizer = @import("zig/tokenizer.zig");
pub const Token = tokenizer.Token;
pub const Tokenizer = tokenizer.Tokenizer;
pub const parse = @import("zig/parse.zig").parse;
-pub const parseStringLiteral = @import("zig/parse_string_literal.zig").parseStringLiteral;
+pub const parseStringLiteral = @import("zig/string_literal.zig").parse;
pub const render = @import("zig/render.zig").render;
+pub const renderStringLiteral = @import("zig/string_literal.zig").render;
pub const ast = @import("zig/ast.zig");
pub const system = @import("zig/system.zig");
pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;
diff --git a/lib/std/zig/parse_string_literal.zig b/lib/std/zig/parse_string_literal.zig
@@ -1,125 +0,0 @@
-const std = @import("../std.zig");
-const assert = std.debug.assert;
-
-const State = enum {
- Start,
- Backslash,
-};
-
-pub const ParseStringLiteralError = error{
- OutOfMemory,
-
- /// When this is returned, index will be the position of the character.
- InvalidCharacter,
-};
-
-/// caller owns returned memory
-pub fn parseStringLiteral(
- allocator: *std.mem.Allocator,
- bytes: []const u8,
- bad_index: *usize, // populated if error.InvalidCharacter is returned
-) ParseStringLiteralError![]u8 {
- assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"');
-
- var list = std.ArrayList(u8).init(allocator);
- errdefer list.deinit();
-
- const slice = bytes[1..];
- try list.ensureCapacity(slice.len - 1);
-
- var state = State.Start;
- var index: usize = 0;
- while (index < slice.len) : (index += 1) {
- const b = slice[index];
-
- switch (state) {
- State.Start => switch (b) {
- '\\' => state = State.Backslash,
- '\n' => {
- bad_index.* = index;
- return error.InvalidCharacter;
- },
- '"' => return list.toOwnedSlice(),
- else => try list.append(b),
- },
- State.Backslash => switch (b) {
- 'n' => {
- try list.append('\n');
- state = State.Start;
- },
- 'r' => {
- try list.append('\r');
- state = State.Start;
- },
- '\\' => {
- try list.append('\\');
- state = State.Start;
- },
- 't' => {
- try list.append('\t');
- state = State.Start;
- },
- '\'' => {
- try list.append('\'');
- state = State.Start;
- },
- '"' => {
- try list.append('"');
- state = State.Start;
- },
- 'x' => {
- // TODO: add more/better/broader tests for this.
- const index_continue = index + 3;
- if (slice.len >= index_continue)
- if (std.fmt.parseUnsigned(u8, slice[index + 1 .. index_continue], 16)) |char| {
- try list.append(char);
- state = State.Start;
- index = index_continue - 1; // loop-header increments again
- continue;
- } else |_| {};
-
- bad_index.* = index;
- return error.InvalidCharacter;
- },
- 'u' => {
- // TODO: add more/better/broader tests for this.
- if (slice.len > index + 2 and slice[index + 1] == '{')
- if (std.mem.indexOfScalarPos(u8, slice[0..std.math.min(index + 9, slice.len)], index + 3, '}')) |index_end| {
- const hex_str = slice[index + 2 .. index_end];
- if (std.fmt.parseUnsigned(u32, hex_str, 16)) |uint| {
- if (uint <= 0x10ffff) {
- try list.appendSlice(std.mem.toBytes(uint)[0..]);
- state = State.Start;
- index = index_end; // loop-header increments
- continue;
- }
- } else |_| {}
- };
-
- bad_index.* = index;
- return error.InvalidCharacter;
- },
- else => {
- bad_index.* = index;
- return error.InvalidCharacter;
- },
- },
- else => unreachable,
- }
- }
- unreachable;
-}
-
-test "parseStringLiteral" {
- const expect = std.testing.expect;
- const eql = std.mem.eql;
-
- var fixed_buf_mem: [32]u8 = undefined;
- var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(fixed_buf_mem[0..]);
- var alloc = &fixed_buf_alloc.allocator;
- var bad_index: usize = undefined;
-
- expect(eql(u8, "foo", try parseStringLiteral(alloc, "\"foo\"", &bad_index)));
- expect(eql(u8, "foo", try parseStringLiteral(alloc, "\"f\x6f\x6f\"", &bad_index)));
- expect(eql(u8, "f💯", try parseStringLiteral(alloc, "\"f\u{1f4af}\"", &bad_index)));
-}
diff --git a/lib/std/zig/string_literal.zig b/lib/std/zig/string_literal.zig
@@ -0,0 +1,155 @@
+const std = @import("../std.zig");
+const assert = std.debug.assert;
+
+const State = enum {
+ Start,
+ Backslash,
+};
+
+pub const ParseError = error{
+ OutOfMemory,
+
+ /// When this is returned, index will be the position of the character.
+ InvalidCharacter,
+};
+
+/// caller owns returned memory
+pub fn parse(
+ allocator: *std.mem.Allocator,
+ bytes: []const u8,
+ bad_index: *usize, // populated if error.InvalidCharacter is returned
+) ParseError![]u8 {
+ assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"');
+
+ var list = std.ArrayList(u8).init(allocator);
+ errdefer list.deinit();
+
+ const slice = bytes[1..];
+ try list.ensureCapacity(slice.len - 1);
+
+ var state = State.Start;
+ var index: usize = 0;
+ while (index < slice.len) : (index += 1) {
+ const b = slice[index];
+
+ switch (state) {
+ State.Start => switch (b) {
+ '\\' => state = State.Backslash,
+ '\n' => {
+ bad_index.* = index;
+ return error.InvalidCharacter;
+ },
+ '"' => return list.toOwnedSlice(),
+ else => try list.append(b),
+ },
+ State.Backslash => switch (b) {
+ 'n' => {
+ try list.append('\n');
+ state = State.Start;
+ },
+ 'r' => {
+ try list.append('\r');
+ state = State.Start;
+ },
+ '\\' => {
+ try list.append('\\');
+ state = State.Start;
+ },
+ 't' => {
+ try list.append('\t');
+ state = State.Start;
+ },
+ '\'' => {
+ try list.append('\'');
+ state = State.Start;
+ },
+ '"' => {
+ try list.append('"');
+ state = State.Start;
+ },
+ 'x' => {
+ // TODO: add more/better/broader tests for this.
+ const index_continue = index + 3;
+ if (slice.len >= index_continue)
+ if (std.fmt.parseUnsigned(u8, slice[index + 1 .. index_continue], 16)) |char| {
+ try list.append(char);
+ state = State.Start;
+ index = index_continue - 1; // loop-header increments again
+ continue;
+ } else |_| {};
+
+ bad_index.* = index;
+ return error.InvalidCharacter;
+ },
+ 'u' => {
+ // TODO: add more/better/broader tests for this.
+ if (slice.len > index + 2 and slice[index + 1] == '{')
+ if (std.mem.indexOfScalarPos(u8, slice[0..std.math.min(index + 9, slice.len)], index + 3, '}')) |index_end| {
+ const hex_str = slice[index + 2 .. index_end];
+ if (std.fmt.parseUnsigned(u32, hex_str, 16)) |uint| {
+ if (uint <= 0x10ffff) {
+ try list.appendSlice(std.mem.toBytes(uint)[0..]);
+ state = State.Start;
+ index = index_end; // loop-header increments
+ continue;
+ }
+ } else |_| {}
+ };
+
+ bad_index.* = index;
+ return error.InvalidCharacter;
+ },
+ else => {
+ bad_index.* = index;
+ return error.InvalidCharacter;
+ },
+ },
+ else => unreachable,
+ }
+ }
+ unreachable;
+}
+
+test "parse" {
+ const expect = std.testing.expect;
+ const eql = std.mem.eql;
+
+ var fixed_buf_mem: [32]u8 = undefined;
+ var fixed_buf_alloc = std.heap.FixedBufferAllocator.init(fixed_buf_mem[0..]);
+ var alloc = &fixed_buf_alloc.allocator;
+ var bad_index: usize = undefined;
+
+ expect(eql(u8, "foo", try parse(alloc, "\"foo\"", &bad_index)));
+ expect(eql(u8, "foo", try parse(alloc, "\"f\x6f\x6f\"", &bad_index)));
+ expect(eql(u8, "f💯", try parse(alloc, "\"f\u{1f4af}\"", &bad_index)));
+}
+
+/// Writes a Zig-syntax escaped string literal to the stream. Includes the double quotes.
+pub fn render(utf8: []const u8, out_stream: var) !void {
+ try out_stream.writeByte('"');
+ for (utf8) |byte| switch (byte) {
+ '\n' => try out_stream.writeAll("\\n"),
+ '\r' => try out_stream.writeAll("\\r"),
+ '\t' => try out_stream.writeAll("\\t"),
+ '\\' => try out_stream.writeAll("\\\\"),
+ '"' => try out_stream.writeAll("\\\""),
+ ' ', '!', '#'...'[', ']'...'~' => try out_stream.writeByte(byte),
+ else => try out_stream.print("\\x{x:0>2}", .{byte}),
+ };
+ try out_stream.writeByte('"');
+}
+
+test "render" {
+ const expect = std.testing.expect;
+ const eql = std.mem.eql;
+
+ var fixed_buf_mem: [32]u8 = undefined;
+
+ {
+ var fbs = std.io.fixedBufferStream(&fixed_buf_mem);
+ try render(" \\ hi \x07 \x11 \" derp", fbs.outStream());
+ expect(eql(u8,
+ \\" \\ hi \x07 \x11 \" derp"
+ , fbs.getWritten()));
+ }
+}
diff --git a/src-self-hosted/c_int.zig b/src-self-hosted/c_int.zig
@@ -1,169 +0,0 @@
-const Target = @import("std").Target;
-
-pub const CInt = struct {
- id: Id,
- zig_name: []const u8,
- c_name: []const u8,
- is_signed: bool,
-
- pub const Id = enum {
- Short,
- UShort,
- Int,
- UInt,
- Long,
- ULong,
- LongLong,
- ULongLong,
- };
-
- pub const list = [_]CInt{
- CInt{
- .id = .Short,
- .zig_name = "c_short",
- .c_name = "short",
- .is_signed = true,
- },
- CInt{
- .id = .UShort,
- .zig_name = "c_ushort",
- .c_name = "unsigned short",
- .is_signed = false,
- },
- CInt{
- .id = .Int,
- .zig_name = "c_int",
- .c_name = "int",
- .is_signed = true,
- },
- CInt{
- .id = .UInt,
- .zig_name = "c_uint",
- .c_name = "unsigned int",
- .is_signed = false,
- },
- CInt{
- .id = .Long,
- .zig_name = "c_long",
- .c_name = "long",
- .is_signed = true,
- },
- CInt{
- .id = .ULong,
- .zig_name = "c_ulong",
- .c_name = "unsigned long",
- .is_signed = false,
- },
- CInt{
- .id = .LongLong,
- .zig_name = "c_longlong",
- .c_name = "long long",
- .is_signed = true,
- },
- CInt{
- .id = .ULongLong,
- .zig_name = "c_ulonglong",
- .c_name = "unsigned long long",
- .is_signed = false,
- },
- };
-
- pub fn sizeInBits(cint: CInt, self: Target) u32 {
- const arch = self.cpu.arch;
- switch (self.os.tag) {
- .freestanding, .other => switch (self.cpu.arch) {
- .msp430 => switch (cint.id) {
- .Short,
- .UShort,
- .Int,
- .UInt,
- => return 16,
- .Long,
- .ULong,
- => return 32,
- .LongLong,
- .ULongLong,
- => return 64,
- },
- else => switch (cint.id) {
- .Short,
- .UShort,
- => return 16,
- .Int,
- .UInt,
- => return 32,
- .Long,
- .ULong,
- => return self.cpu.arch.ptrBitWidth(),
- .LongLong,
- .ULongLong,
- => return 64,
- },
- },
-
- .linux,
- .macosx,
- .freebsd,
- .openbsd,
- => switch (cint.id) {
- .Short,
- .UShort,
- => return 16,
- .Int,
- .UInt,
- => return 32,
- .Long,
- .ULong,
- => return self.cpu.arch.ptrBitWidth(),
- .LongLong,
- .ULongLong,
- => return 64,
- },
-
- .windows, .uefi => switch (cint.id) {
- .Short,
- .UShort,
- => return 16,
- .Int,
- .UInt,
- => return 32,
- .Long,
- .ULong,
- .LongLong,
- .ULongLong,
- => return 64,
- },
-
- .ananas,
- .cloudabi,
- .dragonfly,
- .fuchsia,
- .ios,
- .kfreebsd,
- .lv2,
- .netbsd,
- .solaris,
- .haiku,
- .minix,
- .rtems,
- .nacl,
- .cnk,
- .aix,
- .cuda,
- .nvcl,
- .amdhsa,
- .ps4,
- .elfiamcu,
- .tvos,
- .watchos,
- .mesa3d,
- .contiki,
- .amdpal,
- .hermit,
- .hurd,
- .wasi,
- .emscripten,
- => @panic("TODO specify the C integer type sizes for this OS"),
- }
- }
-};
diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig
@@ -1,2590 +1,740 @@
const std = @import("std");
-const Compilation = @import("compilation.zig").Compilation;
-const Scope = @import("scope.zig").Scope;
-const ast = std.zig.ast;
+const mem = std.mem;
const Allocator = std.mem.Allocator;
const Value = @import("value.zig").Value;
-const Type = Value.Type;
+const Type = @import("type.zig").Type;
const assert = std.debug.assert;
-const Token = std.zig.Token;
-const Span = @import("errmsg.zig").Span;
-const llvm = @import("llvm.zig");
-const codegen = @import("codegen.zig");
-const ObjectFile = codegen.ObjectFile;
-const Decl = @import("decl.zig").Decl;
-const mem = std.mem;
-
-pub const LVal = enum {
- None,
- Ptr,
-};
-
-pub const IrVal = union(enum) {
- Unknown,
- KnownType: *Type,
- KnownValue: *Value,
-
- const Init = enum {
- Unknown,
- NoReturn,
- Void,
- };
-
- pub fn dump(self: IrVal) void {
- switch (self) {
- .Unknown => std.debug.warn("Unknown", .{}),
- .KnownType => |typ| {
- std.debug.warn("KnownType(", .{});
- typ.dump();
- std.debug.warn(")", .{});
- },
- .KnownValue => |value| {
- std.debug.warn("KnownValue(", .{});
- value.dump();
- std.debug.warn(")", .{});
- },
- }
- }
-};
-
+const text = @import("ir/text.zig");
+const BigInt = std.math.big.Int;
+const Target = std.Target;
+
+/// These are in-memory, analyzed instructions. See `text.Inst` for the representation
+/// of instructions that correspond to the ZIR text format.
+/// This struct owns the `Value` and `Type` memory. When the struct is deallocated,
+/// so are the `Value` and `Type`. The value of a constant must be copied into
+/// a memory location for the value to survive after a const instruction.
pub const Inst = struct {
- id: Id,
- scope: *Scope,
- debug_id: usize,
- val: IrVal,
- ref_count: usize,
- span: Span,
- owner_bb: *BasicBlock,
-
- /// true if this instruction was generated by zig and not from user code
- is_generated: bool,
-
- /// the instruction that is derived from this one in analysis
- child: ?*Inst,
-
- /// the instruction that this one derives from in analysis
- parent: ?*Inst,
-
- /// populated durign codegen
- llvm_value: ?*llvm.Value,
+ tag: Tag,
+ ty: Type,
+ /// Byte offset into the source.
+ src: usize,
+
+ pub const Tag = enum {
+ unreach,
+ constant,
+ assembly,
+ ptrtoint,
+ };
pub fn cast(base: *Inst, comptime T: type) ?*T {
- if (base.id == comptime typeToId(T)) {
- return @fieldParentPtr(T, "base", base);
- }
- return null;
- }
-
- pub fn typeToId(comptime T: type) Id {
- inline for (@typeInfo(Id).Enum.fields) |f| {
- if (T == @field(Inst, f.name)) {
- return @field(Id, f.name);
- }
- }
- unreachable;
- }
-
- pub fn dump(base: *const Inst) void {
- inline for (@typeInfo(Id).Enum.fields) |f| {
- if (base.id == @field(Id, f.name)) {
- const T = @field(Inst, f.name);
- std.debug.warn("#{} = {}(", .{ base.debug_id, @tagName(base.id) });
- @fieldParentPtr(T, "base", base).dump();
- std.debug.warn(")", .{});
- return;
- }
- }
- unreachable;
- }
-
- pub fn hasSideEffects(base: *const Inst) bool {
- inline for (@typeInfo(Id).Enum.fields) |f| {
- if (base.id == @field(Id, f.name)) {
- const T = @field(Inst, f.name);
- return @fieldParentPtr(T, "base", base).hasSideEffects();
- }
- }
- unreachable;
- }
-
- pub fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst {
- switch (base.id) {
- .Return => return @fieldParentPtr(Return, "base", base).analyze(ira),
- .Const => return @fieldParentPtr(Const, "base", base).analyze(ira),
- .Call => return @fieldParentPtr(Call, "base", base).analyze(ira),
- .DeclRef => return @fieldParentPtr(DeclRef, "base", base).analyze(ira),
- .Ref => return @fieldParentPtr(Ref, "base", base).analyze(ira),
- .DeclVar => return @fieldParentPtr(DeclVar, "base", base).analyze(ira),
- .CheckVoidStmt => return @fieldParentPtr(CheckVoidStmt, "base", base).analyze(ira),
- .Phi => return @fieldParentPtr(Phi, "base", base).analyze(ira),
- .Br => return @fieldParentPtr(Br, "base", base).analyze(ira),
- .AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira),
- .PtrType => return @fieldParentPtr(PtrType, "base", base).analyze(ira),
- .VarPtr => return @fieldParentPtr(VarPtr, "base", base).analyze(ira),
- .LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).analyze(ira),
- }
- }
-
- pub fn render(base: *Inst, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?*llvm.Value) {
- switch (base.id) {
- .Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
- .Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
- .Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val),
- .VarPtr => return @fieldParentPtr(VarPtr, "base", base).render(ofile, fn_val),
- .LoadPtr => return @fieldParentPtr(LoadPtr, "base", base).render(ofile, fn_val),
- .DeclRef => unreachable,
- .PtrType => unreachable,
- .Ref => @panic("TODO"),
- .DeclVar => @panic("TODO"),
- .CheckVoidStmt => @panic("TODO"),
- .Phi => @panic("TODO"),
- .Br => @panic("TODO"),
- .AddImplicitReturnType => @panic("TODO"),
- }
- }
-
- fn ref(base: *Inst, builder: *Builder) void {
- base.ref_count += 1;
- if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) {
- base.owner_bb.ref(builder);
- }
- }
-
- fn copyVal(base: *Inst, comp: *Compilation) !*Value {
- if (base.parent.?.ref_count == 0) {
- return base.val.KnownValue.derefAndCopy(comp);
- }
- return base.val.KnownValue.copy(comp);
- }
-
- fn getAsParam(param: *Inst) !*Inst {
- param.ref_count -= 1;
- const child = param.child orelse return error.SemanticAnalysisFailed;
- switch (child.val) {
- .Unknown => return error.SemanticAnalysisFailed,
- else => return child,
- }
- }
-
- fn getConstVal(self: *Inst, ira: *Analyze) !*Value {
- if (self.isCompTime()) {
- return self.val.KnownValue;
- } else {
- try ira.addCompileError(self.span, "unable to evaluate constant expression", .{});
- return error.SemanticAnalysisFailed;
- }
- }
-
- fn getAsConstType(param: *Inst, ira: *Analyze) !*Type {
- const meta_type = Type.MetaType.get(ira.irb.comp);
- meta_type.base.base.deref(ira.irb.comp);
-
- const inst = try param.getAsParam();
- const casted = try ira.implicitCast(inst, &meta_type.base);
- const val = try casted.getConstVal(ira);
- return val.cast(Value.Type).?;
- }
-
- fn getAsConstAlign(param: *Inst, ira: *Analyze) !u32 {
- return error.Unimplemented;
- //const align_type = Type.Int.get_align(ira.irb.comp);
- //align_type.base.base.deref(ira.irb.comp);
-
- //const inst = try param.getAsParam();
- //const casted = try ira.implicitCast(inst, align_type);
- //const val = try casted.getConstVal(ira);
-
- //uint32_t align_bytes = bigint_as_unsigned(&const_val->data.x_bigint);
- //if (align_bytes == 0) {
- // ir_add_error(ira, value, buf_sprintf("alignment must be >= 1"));
- // return false;
- //}
-
- //if (!is_power_of_2(align_bytes)) {
- // ir_add_error(ira, value, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes));
- // return false;
- //}
- }
-
- /// asserts that the type is known
- fn getKnownType(self: *Inst) *Type {
- switch (self.val) {
- .KnownType => |typ| return typ,
- .KnownValue => |value| return value.typ,
- .Unknown => unreachable,
- }
- }
+ if (base.tag != T.base_tag)
+ return null;
- pub fn setGenerated(base: *Inst) void {
- base.is_generated = true;
+ return @fieldParentPtr(T, "base", base);
}
- pub fn isNoReturn(base: *const Inst) bool {
- switch (base.val) {
- .Unknown => return false,
- .KnownValue => |x| return x.typ.id == .NoReturn,
- .KnownType => |typ| return typ.id == .NoReturn,
- }
+ pub fn Args(comptime T: type) type {
+ return std.meta.fieldInfo(T, "args").field_type;
}
- pub fn isCompTime(base: *const Inst) bool {
- return base.val == .KnownValue;
- }
+ /// Returns `null` if runtime-known.
+ pub fn value(base: *Inst) ?Value {
+ return switch (base.tag) {
+ .unreach => Value.initTag(.noreturn_value),
+ .constant => base.cast(Constant).?.val,
- pub fn linkToParent(self: *Inst, parent: *Inst) void {
- assert(self.parent == null);
- assert(parent.child == null);
- self.parent = parent;
- parent.child = self;
+ .assembly,
+ .ptrtoint,
+ => null,
+ };
}
- pub const Id = enum {
- Return,
- Const,
- Ref,
- DeclVar,
- CheckVoidStmt,
- Phi,
- Br,
- AddImplicitReturnType,
- Call,
- DeclRef,
- PtrType,
- VarPtr,
- LoadPtr,
- };
-
- pub const Call = struct {
+ pub const Unreach = struct {
+ pub const base_tag = Tag.unreach;
base: Inst,
- params: Params,
-
- const Params = struct {
- fn_ref: *Inst,
- args: []*Inst,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(self: *const Call) void {
- std.debug.warn("#{}(", .{self.params.fn_ref.debug_id});
- for (self.params.args) |arg| {
- std.debug.warn("#{},", .{arg.debug_id});
- }
- std.debug.warn(")", .{});
- }
-
- pub fn hasSideEffects(self: *const Call) bool {
- return true;
- }
-
- pub fn analyze(self: *const Call, ira: *Analyze) !*Inst {
- const fn_ref = try self.params.fn_ref.getAsParam();
- const fn_ref_type = fn_ref.getKnownType();
- const fn_type = fn_ref_type.cast(Type.Fn) orelse {
- try ira.addCompileError(fn_ref.span, "type '{}' not a function", .{fn_ref_type.name});
- return error.SemanticAnalysisFailed;
- };
-
- const fn_type_param_count = fn_type.paramCount();
-
- if (fn_type_param_count != self.params.args.len) {
- try ira.addCompileError(self.base.span, "expected {} arguments, found {}", .{
- fn_type_param_count,
- self.params.args.len,
- });
- return error.SemanticAnalysisFailed;
- }
-
- const args = try ira.irb.arena().alloc(*Inst, self.params.args.len);
- for (self.params.args) |arg, i| {
- args[i] = try arg.getAsParam();
- }
- const new_inst = try ira.irb.build(Call, self.base.scope, self.base.span, Params{
- .fn_ref = fn_ref,
- .args = args,
- });
- new_inst.val = IrVal{ .KnownType = fn_type.key.data.Normal.return_type };
- return new_inst;
- }
-
- pub fn render(self: *Call, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value {
- const fn_ref = self.params.fn_ref.llvm_value.?;
-
- const args = try ofile.arena.alloc(*llvm.Value, self.params.args.len);
- for (self.params.args) |arg, i| {
- args[i] = arg.llvm_value.?;
- }
-
- const llvm_cc = llvm.CCallConv;
- const call_attr = llvm.CallAttr.Auto;
-
- return llvm.BuildCall(
- ofile.builder,
- fn_ref,
- args.ptr,
- @intCast(c_uint, args.len),
- llvm_cc,
- call_attr,
- "",
- ) orelse error.OutOfMemory;
- }
+ args: void,
};
- pub const Const = struct {
+ pub const Constant = struct {
+ pub const base_tag = Tag.constant;
base: Inst,
- params: Params,
-
- const Params = struct {};
-
- // Use Builder.buildConst* methods, or, after building a Const instruction,
- // manually set the ir_val field.
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(self: *const Const) void {
- self.base.val.KnownValue.dump();
- }
-
- pub fn hasSideEffects(self: *const Const) bool {
- return false;
- }
-
- pub fn analyze(self: *const Const, ira: *Analyze) !*Inst {
- const new_inst = try ira.irb.build(Const, self.base.scope, self.base.span, Params{});
- new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() };
- return new_inst;
- }
- pub fn render(self: *Const, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value {
- return self.base.val.KnownValue.getLlvmConst(ofile);
- }
+ val: Value,
};
- pub const Return = struct {
+ pub const Assembly = struct {
+ pub const base_tag = Tag.assembly;
base: Inst,
- params: Params,
-
- const Params = struct {
- return_value: *Inst,
- };
-
- const ir_val_init = IrVal.Init.NoReturn;
-
- pub fn dump(self: *const Return) void {
- std.debug.warn("#{}", .{self.params.return_value.debug_id});
- }
-
- pub fn hasSideEffects(self: *const Return) bool {
- return true;
- }
-
- pub fn analyze(self: *const Return, ira: *Analyze) !*Inst {
- const value = try self.params.return_value.getAsParam();
- const casted_value = try ira.implicitCast(value, ira.explicit_return_type);
-
- // TODO detect returning local variable address
-
- return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value });
- }
- pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value {
- const value = self.params.return_value.llvm_value;
- const return_type = self.params.return_value.getKnownType();
-
- if (return_type.handleIsPtr()) {
- @panic("TODO");
- } else {
- _ = llvm.BuildRet(ofile.builder, value) orelse return error.OutOfMemory;
- }
- return null;
- }
+ args: struct {
+ asm_source: []const u8,
+ is_volatile: bool,
+ output: ?[]const u8,
+ inputs: []const []const u8,
+ clobbers: []const []const u8,
+ args: []const *Inst,
+ },
};
- pub const Ref = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- target: *Inst,
- mut: Type.Pointer.Mut,
- volatility: Type.Pointer.Vol,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
+ pub const PtrToInt = struct {
+ pub const base_tag = Tag.ptrtoint;
- pub fn dump(inst: *const Ref) void {}
-
- pub fn hasSideEffects(inst: *const Ref) bool {
- return false;
- }
-
- pub fn analyze(self: *const Ref, ira: *Analyze) !*Inst {
- const target = try self.params.target.getAsParam();
-
- if (ira.getCompTimeValOrNullUndefOk(target)) |val| {
- return ira.getCompTimeRef(
- val,
- Value.Ptr.Mut.CompTimeConst,
- self.params.mut,
- self.params.volatility,
- );
- }
-
- const new_inst = try ira.irb.build(Ref, self.base.scope, self.base.span, Params{
- .target = target,
- .mut = self.params.mut,
- .volatility = self.params.volatility,
- });
- const elem_type = target.getKnownType();
- const ptr_type = try Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
- .child_type = elem_type,
- .mut = self.params.mut,
- .vol = self.params.volatility,
- .size = .One,
- .alignment = .Abi,
- });
- // TODO: potentially set the hint that this is a stack pointer. But it might not be - this
- // could be a ref of a global, for example
- new_inst.val = IrVal{ .KnownType = &ptr_type.base };
- // TODO potentially add an alloca entry here
- return new_inst;
- }
- };
-
- pub const DeclRef = struct {
base: Inst,
- params: Params,
-
- const Params = struct {
- decl: *Decl,
- lval: LVal,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const DeclRef) void {}
-
- pub fn hasSideEffects(inst: *const DeclRef) bool {
- return false;
- }
-
- pub fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst {
- (ira.irb.comp.resolveDecl(self.params.decl)) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- else => return error.SemanticAnalysisFailed,
- };
- switch (self.params.decl.id) {
- .CompTime => unreachable,
- .Var => return error.Unimplemented,
- .Fn => {
- const fn_decl = @fieldParentPtr(Decl.Fn, "base", self.params.decl);
- const decl_val = switch (fn_decl.value) {
- .Unresolved => unreachable,
- .Fn => |fn_val| &fn_val.base,
- .FnProto => |fn_proto| &fn_proto.base,
- };
- switch (self.params.lval) {
- .None => {
- return ira.irb.buildConstValue(self.base.scope, self.base.span, decl_val);
- },
- .Ptr => return error.Unimplemented,
- }
- },
- }
- }
+ args: struct {
+ ptr: *Inst,
+ },
};
+};
- pub const VarPtr = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- var_scope: *Scope.Var,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const VarPtr) void {
- std.debug.warn("{}", .{inst.params.var_scope.name});
- }
-
- pub fn hasSideEffects(inst: *const VarPtr) bool {
- return false;
- }
+pub const TypedValue = struct {
+ ty: Type,
+ val: Value,
+};
- pub fn analyze(self: *const VarPtr, ira: *Analyze) !*Inst {
- switch (self.params.var_scope.data) {
- .Const => @panic("TODO"),
- .Param => |param| {
- const new_inst = try ira.irb.build(
- Inst.VarPtr,
- self.base.scope,
- self.base.span,
- Inst.VarPtr.Params{ .var_scope = self.params.var_scope },
- );
- const ptr_type = try Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
- .child_type = param.typ,
- .mut = .Const,
- .vol = .Non,
- .size = .One,
- .alignment = .Abi,
- });
- new_inst.val = IrVal{ .KnownType = &ptr_type.base };
- return new_inst;
- },
- }
- }
+pub const Module = struct {
+ exports: []Export,
+ errors: []ErrorMsg,
+ arena: std.heap.ArenaAllocator,
+ fns: []Fn,
- pub fn render(self: *VarPtr, ofile: *ObjectFile, fn_val: *Value.Fn) *llvm.Value {
- switch (self.params.var_scope.data) {
- .Const => unreachable, // turned into Inst.Const in analyze pass
- .Param => |param| return param.llvm_value,
- }
- }
+ pub const Export = struct {
+ name: []const u8,
+ typed_value: TypedValue,
+ src: usize,
};
- pub const LoadPtr = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- target: *Inst,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const LoadPtr) void {}
-
- pub fn hasSideEffects(inst: *const LoadPtr) bool {
- return false;
- }
-
- pub fn analyze(self: *const LoadPtr, ira: *Analyze) !*Inst {
- const target = try self.params.target.getAsParam();
- const target_type = target.getKnownType();
- if (target_type.id != .Pointer) {
- try ira.addCompileError(self.base.span, "dereference of non pointer type '{}'", .{target_type.name});
- return error.SemanticAnalysisFailed;
- }
- const ptr_type = @fieldParentPtr(Type.Pointer, "base", target_type);
- // if (instr_is_comptime(ptr)) {
- // if (ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst ||
- // ptr->value.data.x_ptr.mut == ConstPtrMutComptimeVar)
- // {
- // ConstExprValue *pointee = const_ptr_pointee(ira->codegen, &ptr->value);
- // if (pointee->special != ConstValSpecialRuntime) {
- // IrInstruction *result = ir_create_const(&ira->new_irb, source_instruction->scope,
- // source_instruction->source_node, child_type);
- // copy_const_val(&result->value, pointee, ptr->value.data.x_ptr.mut == ConstPtrMutComptimeConst);
- // result->value.type = child_type;
- // return result;
- // }
- // }
- // }
- const new_inst = try ira.irb.build(
- Inst.LoadPtr,
- self.base.scope,
- self.base.span,
- Inst.LoadPtr.Params{ .target = target },
- );
- new_inst.val = IrVal{ .KnownType = ptr_type.key.child_type };
- return new_inst;
- }
-
- pub fn render(self: *LoadPtr, ofile: *ObjectFile, fn_val: *Value.Fn) !?*llvm.Value {
- const child_type = self.base.getKnownType();
- if (!child_type.hasBits()) {
- return null;
- }
- const ptr = self.params.target.llvm_value.?;
- const ptr_type = self.params.target.getKnownType().cast(Type.Pointer).?;
-
- return try codegen.getHandleValue(ofile, ptr, ptr_type);
-
- //uint32_t unaligned_bit_count = ptr_type->data.pointer.unaligned_bit_count;
- //if (unaligned_bit_count == 0)
- // return get_handle_value(g, ptr, child_type, ptr_type);
-
- //bool big_endian = g->is_big_endian;
-
- //assert(!handle_is_ptr(child_type));
- //LLVMValueRef containing_int = gen_load(g, ptr, ptr_type, "");
-
- //uint32_t bit_offset = ptr_type->data.pointer.bit_offset;
- //uint32_t host_bit_count = LLVMGetIntTypeWidth(LLVMTypeOf(containing_int));
- //uint32_t shift_amt = big_endian ? host_bit_count - bit_offset - unaligned_bit_count : bit_offset;
-
- //LLVMValueRef shift_amt_val = LLVMConstInt(LLVMTypeOf(containing_int), shift_amt, false);
- //LLVMValueRef shifted_value = LLVMBuildLShr(g->builder, containing_int, shift_amt_val, "");
-
- //return LLVMBuildTrunc(g->builder, shifted_value, child_type->type_ref, "");
- }
+ pub const Fn = struct {
+ analysis_status: enum { in_progress, failure, success },
+ body: []*Inst,
+ fn_type: Type,
};
- pub const PtrType = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- child_type: *Inst,
- mut: Type.Pointer.Mut,
- vol: Type.Pointer.Vol,
- size: Type.Pointer.Size,
- alignment: ?*Inst,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const PtrType) void {}
-
- pub fn hasSideEffects(inst: *const PtrType) bool {
- return false;
- }
+ pub fn deinit(self: *Module, allocator: *Allocator) void {
+ allocator.free(self.exports);
+ allocator.free(self.errors);
+ self.arena.deinit();
+ self.* = undefined;
+ }
+};
- pub fn analyze(self: *const PtrType, ira: *Analyze) !*Inst {
- const child_type = try self.params.child_type.getAsConstType(ira);
- // if (child_type->id == TypeTableEntryIdUnreachable) {
- // ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed"));
- // return ira->codegen->builtin_types.entry_invalid;
- // } else if (child_type->id == TypeTableEntryIdOpaque && instruction->ptr_len == PtrLenUnknown) {
- // ir_add_error(ira, &instruction->base, buf_sprintf("unknown-length pointer to opaque"));
- // return ira->codegen->builtin_types.entry_invalid;
- // }
- const alignment = if (self.params.alignment) |align_inst| blk: {
- const amt = try align_inst.getAsConstAlign(ira);
- break :blk Type.Pointer.Align{ .Override = amt };
- } else blk: {
- break :blk .Abi;
- };
- const ptr_type = try Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
- .child_type = child_type,
- .mut = self.params.mut,
- .vol = self.params.vol,
- .size = self.params.size,
- .alignment = alignment,
- });
- ptr_type.base.base.deref(ira.irb.comp);
+pub const ErrorMsg = struct {
+ byte_offset: usize,
+ msg: []const u8,
+};
- return ira.irb.buildConstValue(self.base.scope, self.base.span, &ptr_type.base.base);
- }
+pub fn analyze(allocator: *Allocator, old_module: text.Module) !Module {
+ const native_info = try std.zig.system.NativeTargetInfo.detect(allocator, .{});
+
+ var ctx = Analyze{
+ .allocator = allocator,
+ .arena = std.heap.ArenaAllocator.init(allocator),
+ .old_module = &old_module,
+ .errors = std.ArrayList(ErrorMsg).init(allocator),
+ .decl_table = std.AutoHashMap(*text.Inst, Analyze.NewDecl).init(allocator),
+ .exports = std.ArrayList(Module.Export).init(allocator),
+ .fns = std.ArrayList(Module.Fn).init(allocator),
+ .target = native_info.target,
};
-
- pub const DeclVar = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- variable: *Variable,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const DeclVar) void {}
-
- pub fn hasSideEffects(inst: *const DeclVar) bool {
- return true;
- }
-
- pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Inst {
- return error.Unimplemented; // TODO
- }
+ defer ctx.errors.deinit();
+ defer ctx.decl_table.deinit();
+ defer ctx.exports.deinit();
+ defer ctx.fns.deinit();
+ errdefer ctx.arena.deinit();
+
+ ctx.analyzeRoot() catch |err| switch (err) {
+ error.AnalysisFail => {
+ assert(ctx.errors.items.len != 0);
+ },
+ else => |e| return e,
};
-
- pub const CheckVoidStmt = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- target: *Inst,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(self: *const CheckVoidStmt) void {
- std.debug.warn("#{}", .{self.params.target.debug_id});
- }
-
- pub fn hasSideEffects(inst: *const CheckVoidStmt) bool {
- return true;
- }
-
- pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst {
- const target = try self.params.target.getAsParam();
- if (target.getKnownType().id != .Void) {
- try ira.addCompileError(self.base.span, "expression value is ignored", .{});
- return error.SemanticAnalysisFailed;
- }
- return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
- }
+ return Module{
+ .exports = ctx.exports.toOwnedSlice(),
+ .errors = ctx.errors.toOwnedSlice(),
+ .fns = ctx.fns.toOwnedSlice(),
+ .arena = ctx.arena,
};
+}
- pub const Phi = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- incoming_blocks: []*BasicBlock,
- incoming_values: []*Inst,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const Phi) void {}
-
- pub fn hasSideEffects(inst: *const Phi) bool {
- return false;
- }
-
- pub fn analyze(self: *const Phi, ira: *Analyze) !*Inst {
- return error.Unimplemented; // TODO
- }
+const Analyze = struct {
+ allocator: *Allocator,
+ arena: std.heap.ArenaAllocator,
+ old_module: *const text.Module,
+ errors: std.ArrayList(ErrorMsg),
+ decl_table: std.AutoHashMap(*text.Inst, NewDecl),
+ exports: std.ArrayList(Module.Export),
+ fns: std.ArrayList(Module.Fn),
+ target: Target,
+
+ const NewDecl = struct {
+ /// null means a semantic analysis error happened
+ ptr: ?*Inst,
};
- pub const Br = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- dest_block: *BasicBlock,
- is_comptime: *Inst,
- };
-
- const ir_val_init = IrVal.Init.NoReturn;
-
- pub fn dump(inst: *const Br) void {}
-
- pub fn hasSideEffects(inst: *const Br) bool {
- return true;
- }
-
- pub fn analyze(self: *const Br, ira: *Analyze) !*Inst {
- return error.Unimplemented; // TODO
- }
+ const NewInst = struct {
+ /// null means a semantic analysis error happened
+ ptr: ?*Inst,
};
- pub const CondBr = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {
- condition: *Inst,
- then_block: *BasicBlock,
- else_block: *BasicBlock,
- is_comptime: *Inst,
- };
-
- const ir_val_init = IrVal.Init.NoReturn;
-
- pub fn dump(inst: *const CondBr) void {}
-
- pub fn hasSideEffects(inst: *const CondBr) bool {
- return true;
- }
-
- pub fn analyze(self: *const CondBr, ira: *Analyze) !*Inst {
- return error.Unimplemented; // TODO
- }
+ const Fn = struct {
+ body: std.ArrayList(*Inst),
+ inst_table: std.AutoHashMap(*text.Inst, NewInst),
+ /// Index into Module fns array
+ fn_index: usize,
};
- pub const AddImplicitReturnType = struct {
- base: Inst,
- params: Params,
-
- pub const Params = struct {
- target: *Inst,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
+ const InnerError = error{ OutOfMemory, AnalysisFail };
- pub fn dump(inst: *const AddImplicitReturnType) void {
- std.debug.warn("#{}", .{inst.params.target.debug_id});
- }
-
- pub fn hasSideEffects(inst: *const AddImplicitReturnType) bool {
- return true;
- }
-
- pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Inst {
- const target = try self.params.target.getAsParam();
- try ira.src_implicit_return_type_list.append(target);
- return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
- }
- };
-
- pub const TestErr = struct {
- base: Inst,
- params: Params,
-
- pub const Params = struct {
- target: *Inst,
- };
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const TestErr) void {
- std.debug.warn("#{}", .{inst.params.target.debug_id});
+ fn analyzeRoot(self: *Analyze) !void {
+ for (self.old_module.decls) |decl| {
+ if (decl.cast(text.Inst.Export)) |export_inst| {
+ try analyzeExport(self, null, export_inst);
+ }
}
+ }
- pub fn hasSideEffects(inst: *const TestErr) bool {
- return false;
+ fn resolveInst(self: *Analyze, opt_func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst {
+ if (opt_func) |func| {
+ if (func.inst_table.get(old_inst)) |kv| {
+ return kv.value.ptr orelse return error.AnalysisFail;
+ }
}
- pub fn analyze(self: *const TestErr, ira: *Analyze) !*Inst {
- const target = try self.params.target.getAsParam();
- const target_type = target.getKnownType();
- switch (target_type.id) {
- .ErrorUnion => {
- return error.Unimplemented;
- // if (instr_is_comptime(value)) {
- // ConstExprValue *err_union_val = ir_resolve_const(ira, value, UndefBad);
- // if (!err_union_val)
- // return ira->codegen->builtin_types.entry_invalid;
-
- // if (err_union_val->special != ConstValSpecialRuntime) {
- // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
- // out_val->data.x_bool = (err_union_val->data.x_err_union.err != nullptr);
- // return ira->codegen->builtin_types.entry_bool;
- // }
- // }
-
- // TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type;
- // if (!resolve_inferred_error_set(ira->codegen, err_set_type, instruction->base.source_node)) {
- // return ira->codegen->builtin_types.entry_invalid;
- // }
- // if (!type_is_global_error_set(err_set_type) &&
- // err_set_type->data.error_set.err_count == 0)
- // {
- // assert(err_set_type->data.error_set.infer_fn == nullptr);
- // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
- // out_val->data.x_bool = false;
- // return ira->codegen->builtin_types.entry_bool;
- // }
-
- // ir_build_test_err_from(&ira->new_irb, &instruction->base, value);
- // return ira->codegen->builtin_types.entry_bool;
- },
- .ErrorSet => {
- return ira.irb.buildConstBool(self.base.scope, self.base.span, true);
- },
- else => {
- return ira.irb.buildConstBool(self.base.scope, self.base.span, false);
+ if (self.decl_table.get(old_inst)) |kv| {
+ return kv.value.ptr orelse return error.AnalysisFail;
+ } else {
+ const new_inst = self.analyzeInst(null, old_inst) catch |err| switch (err) {
+ error.AnalysisFail => {
+ try self.decl_table.putNoClobber(old_inst, .{ .ptr = null });
+ return error.AnalysisFail;
},
- }
+ else => |e| return e,
+ };
+ try self.decl_table.putNoClobber(old_inst, .{ .ptr = new_inst });
+ return new_inst;
}
- };
+ }
- pub const TestCompTime = struct {
- base: Inst,
- params: Params,
+ fn requireFunctionBody(self: *Analyze, func: ?*Fn, src: usize) !*Fn {
+ return func orelse return self.fail(src, "instruction illegal outside function body", .{});
+ }
- pub const Params = struct {
- target: *Inst,
+ fn resolveInstConst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!TypedValue {
+ const new_inst = try self.resolveInst(func, old_inst);
+ const val = try self.resolveConstValue(new_inst);
+ return TypedValue{
+ .ty = new_inst.ty,
+ .val = val,
};
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const TestCompTime) void {
- std.debug.warn("#{}", .{inst.params.target.debug_id});
- }
-
- pub fn hasSideEffects(inst: *const TestCompTime) bool {
- return false;
- }
-
- pub fn analyze(self: *const TestCompTime, ira: *Analyze) !*Inst {
- const target = try self.params.target.getAsParam();
- return ira.irb.buildConstBool(self.base.scope, self.base.span, target.isCompTime());
- }
- };
-
- pub const SaveErrRetAddr = struct {
- base: Inst,
- params: Params,
-
- const Params = struct {};
-
- const ir_val_init = IrVal.Init.Unknown;
-
- pub fn dump(inst: *const SaveErrRetAddr) void {}
-
- pub fn hasSideEffects(inst: *const SaveErrRetAddr) bool {
- return true;
- }
-
- pub fn analyze(self: *const SaveErrRetAddr, ira: *Analyze) !*Inst {
- return ira.irb.build(Inst.SaveErrRetAddr, self.base.scope, self.base.span, Params{});
- }
- };
-};
-
-pub const Variable = struct {
- child_scope: *Scope,
-};
-
-pub const BasicBlock = struct {
- ref_count: usize,
- name_hint: [*:0]const u8,
- debug_id: usize,
- scope: *Scope,
- instruction_list: std.ArrayList(*Inst),
- ref_instruction: ?*Inst,
-
- /// for codegen
- llvm_block: *llvm.BasicBlock,
- llvm_exit_block: *llvm.BasicBlock,
-
- /// the basic block that is derived from this one in analysis
- child: ?*BasicBlock,
-
- /// the basic block that this one derives from in analysis
- parent: ?*BasicBlock,
-
- pub fn ref(self: *BasicBlock, builder: *Builder) void {
- self.ref_count += 1;
}
- pub fn linkToParent(self: *BasicBlock, parent: *BasicBlock) void {
- assert(self.parent == null);
- assert(parent.child == null);
- self.parent = parent;
- parent.child = self;
+ fn resolveConstValue(self: *Analyze, base: *Inst) !Value {
+ return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{});
}
-};
-/// Stuff that survives longer than Builder
-pub const Code = struct {
- basic_block_list: std.ArrayList(*BasicBlock),
- arena: std.heap.ArenaAllocator,
- return_type: ?*Type,
- tree_scope: *Scope.AstTree,
+ fn resolveConstString(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) ![]u8 {
+ const new_inst = try self.resolveInst(func, old_inst);
+ const wanted_type = Type.initTag(.const_slice_u8);
+ const coerced_inst = try self.coerce(wanted_type, new_inst);
+ const val = try self.resolveConstValue(coerced_inst);
+ return val.toAllocatedBytes(&self.arena.allocator);
+ }
- /// allocator is comp.gpa()
- pub fn destroy(self: *Code, allocator: *Allocator) void {
- self.arena.deinit();
- allocator.destroy(self);
+ fn resolveType(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) !Type {
+ const new_inst = try self.resolveInst(func, old_inst);
+ const wanted_type = Type.initTag(.@"type");
+ const coerced_inst = try self.coerce(wanted_type, new_inst);
+ const val = try self.resolveConstValue(coerced_inst);
+ return val.toType();
}
- pub fn dump(self: *Code) void {
- var bb_i: usize = 0;
- for (self.basic_block_list.span()) |bb| {
- std.debug.warn("{s}_{}:\n", .{ bb.name_hint, bb.debug_id });
- for (bb.instruction_list.span()) |instr| {
- std.debug.warn(" ", .{});
- instr.dump();
- std.debug.warn("\n", .{});
- }
+ fn analyzeExport(self: *Analyze, func: ?*Fn, export_inst: *text.Inst.Export) !void {
+ const symbol_name = try self.resolveConstString(func, export_inst.positionals.symbol_name);
+ const typed_value = try self.resolveInstConst(func, export_inst.positionals.value);
+
+ switch (typed_value.ty.zigTypeTag()) {
+ .Fn => {},
+ else => return self.fail(
+ export_inst.positionals.value.src,
+ "unable to export type '{}'",
+ .{typed_value.ty},
+ ),
}
+ try self.exports.append(.{
+ .name = symbol_name,
+ .typed_value = typed_value,
+ .src = export_inst.base.src,
+ });
}
- /// returns a ref-incremented value, or adds a compile error
- pub fn getCompTimeResult(self: *Code, comp: *Compilation) !*Value {
- const bb = self.basic_block_list.at(0);
- for (bb.instruction_list.span()) |inst| {
- if (inst.cast(Inst.Return)) |ret_inst| {
- const ret_value = ret_inst.params.return_value;
- if (ret_value.isCompTime()) {
- return ret_value.val.KnownValue.getRef();
- }
- try comp.addCompileError(
- self.tree_scope,
- ret_value.span,
- "unable to evaluate constant expression",
- .{},
- );
- return error.SemanticAnalysisFailed;
- } else if (inst.hasSideEffects()) {
- try comp.addCompileError(
- self.tree_scope,
- inst.span,
- "unable to evaluate constant expression",
- .{},
- );
- return error.SemanticAnalysisFailed;
- }
- }
- unreachable;
+ /// TODO should not need the cast on the last parameter at the callsites
+ fn addNewInstArgs(
+ self: *Analyze,
+ func: *Fn,
+ src: usize,
+ ty: Type,
+ comptime T: type,
+ args: Inst.Args(T),
+ ) !*Inst {
+ const inst = try self.addNewInst(func, src, ty, T);
+ inst.args = args;
+ return &inst.base;
}
-};
-pub const Builder = struct {
- comp: *Compilation,
- code: *Code,
- current_basic_block: *BasicBlock,
- next_debug_id: usize,
- is_comptime: bool,
- is_async: bool,
- begin_scope: ?*Scope,
-
- pub const Error = Analyze.Error;
-
- pub fn init(comp: *Compilation, tree_scope: *Scope.AstTree, begin_scope: ?*Scope) !Builder {
- const code = try comp.gpa().create(Code);
- code.* = Code{
- .basic_block_list = undefined,
- .arena = std.heap.ArenaAllocator.init(comp.gpa()),
- .return_type = null,
- .tree_scope = tree_scope,
- };
- code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator);
- errdefer code.destroy(comp.gpa());
-
- return Builder{
- .comp = comp,
- .current_basic_block = undefined,
- .code = code,
- .next_debug_id = 0,
- .is_comptime = false,
- .is_async = false,
- .begin_scope = begin_scope,
+ fn addNewInst(self: *Analyze, func: *Fn, src: usize, ty: Type, comptime T: type) !*T {
+ const inst = try self.arena.allocator.create(T);
+ inst.* = .{
+ .base = .{
+ .tag = T.base_tag,
+ .ty = ty,
+ .src = src,
+ },
+ .args = undefined,
};
+ try func.body.append(&inst.base);
+ return inst;
}
- pub fn abort(self: *Builder) void {
- self.code.destroy(self.comp.gpa());
+ fn constInst(self: *Analyze, src: usize, typed_value: TypedValue) !*Inst {
+ const const_inst = try self.arena.allocator.create(Inst.Constant);
+ const_inst.* = .{
+ .base = .{
+ .tag = Inst.Constant.base_tag,
+ .ty = typed_value.ty,
+ .src = src,
+ },
+ .val = typed_value.val,
+ };
+ return &const_inst.base;
}
- /// Call code.destroy() when done
- pub fn finish(self: *Builder) *Code {
- return self.code;
- }
+ fn constStr(self: *Analyze, src: usize, str: []const u8) !*Inst {
+ const array_payload = try self.arena.allocator.create(Type.Payload.Array_u8_Sentinel0);
+ array_payload.* = .{ .len = str.len };
- /// No need to clean up resources thanks to the arena allocator.
- pub fn createBasicBlock(self: *Builder, scope: *Scope, name_hint: [*:0]const u8) !*BasicBlock {
- const basic_block = try self.arena().create(BasicBlock);
- basic_block.* = BasicBlock{
- .ref_count = 0,
- .name_hint = name_hint,
- .debug_id = self.next_debug_id,
- .scope = scope,
- .instruction_list = std.ArrayList(*Inst).init(self.arena()),
- .child = null,
- .parent = null,
- .ref_instruction = null,
- .llvm_block = undefined,
- .llvm_exit_block = undefined,
- };
- self.next_debug_id += 1;
- return basic_block;
- }
+ const ty_payload = try self.arena.allocator.create(Type.Payload.SingleConstPointer);
+ ty_payload.* = .{ .pointee_type = Type.initPayload(&array_payload.base) };
- pub fn setCursorAtEndAndAppendBlock(self: *Builder, basic_block: *BasicBlock) !void {
- try self.code.basic_block_list.append(basic_block);
- self.setCursorAtEnd(basic_block);
- }
+ const bytes_payload = try self.arena.allocator.create(Value.Payload.Bytes);
+ bytes_payload.* = .{ .data = str };
- pub fn setCursorAtEnd(self: *Builder, basic_block: *BasicBlock) void {
- self.current_basic_block = basic_block;
+ return self.constInst(src, .{
+ .ty = Type.initPayload(&ty_payload.base),
+ .val = Value.initPayload(&bytes_payload.base),
+ });
}
- pub fn genNodeRecursive(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst {
- const alloc = irb.comp.gpa();
- var frame = try alloc.create(@Frame(genNode));
- defer alloc.destroy(frame);
- frame.* = async irb.genNode(node, scope, lval);
- return await frame;
+ fn constType(self: *Analyze, src: usize, ty: Type) !*Inst {
+ return self.constInst(src, .{
+ .ty = Type.initTag(.type),
+ .val = try ty.toValue(&self.arena.allocator),
+ });
}
- pub async fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst {
- switch (node.id) {
- .Root => unreachable,
- .Use => unreachable,
- .TestDecl => unreachable,
- .VarDecl => return error.Unimplemented,
- .Defer => return error.Unimplemented,
- .InfixOp => return error.Unimplemented,
- .PrefixOp => {
- const prefix_op = @fieldParentPtr(ast.Node.PrefixOp, "base", node);
- switch (prefix_op.op) {
- .AddressOf => return error.Unimplemented,
- .ArrayType => |n| return error.Unimplemented,
- .Await => return error.Unimplemented,
- .BitNot => return error.Unimplemented,
- .BoolNot => return error.Unimplemented,
- .OptionalType => return error.Unimplemented,
- .Negation => return error.Unimplemented,
- .NegationWrap => return error.Unimplemented,
- .Resume => return error.Unimplemented,
- .PtrType => |ptr_info| {
- const inst = try irb.genPtrType(prefix_op, ptr_info, scope);
- return irb.lvalWrap(scope, inst, lval);
- },
- .SliceType => |ptr_info| return error.Unimplemented,
- .Try => return error.Unimplemented,
- }
- },
- .SuffixOp => {
- const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", node);
- switch (suffix_op.op) {
- .Call => |*call| {
- const inst = try irb.genCall(suffix_op, call, scope);
- return irb.lvalWrap(scope, inst, lval);
- },
- .ArrayAccess => |n| return error.Unimplemented,
- .Slice => |slice| return error.Unimplemented,
- .ArrayInitializer => |init_list| return error.Unimplemented,
- .StructInitializer => |init_list| return error.Unimplemented,
- .Deref => return error.Unimplemented,
- .UnwrapOptional => return error.Unimplemented,
- }
- },
- .Switch => return error.Unimplemented,
- .While => return error.Unimplemented,
- .For => return error.Unimplemented,
- .If => return error.Unimplemented,
- .ControlFlowExpression => {
- const control_flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", node);
- return irb.genControlFlowExpr(control_flow_expr, scope, lval);
- },
- .Suspend => return error.Unimplemented,
- .VarType => return error.Unimplemented,
- .ErrorType => return error.Unimplemented,
- .FnProto => return error.Unimplemented,
- .AnyFrameType => return error.Unimplemented,
- .IntegerLiteral => {
- const int_lit = @fieldParentPtr(ast.Node.IntegerLiteral, "base", node);
- return irb.lvalWrap(scope, try irb.genIntLit(int_lit, scope), lval);
- },
- .FloatLiteral => return error.Unimplemented,
- .StringLiteral => {
- const str_lit = @fieldParentPtr(ast.Node.StringLiteral, "base", node);
- const inst = try irb.genStrLit(str_lit, scope);
- return irb.lvalWrap(scope, inst, lval);
- },
- .MultilineStringLiteral => return error.Unimplemented,
- .CharLiteral => return error.Unimplemented,
- .BoolLiteral => return error.Unimplemented,
- .NullLiteral => return error.Unimplemented,
- .UndefinedLiteral => return error.Unimplemented,
- .Unreachable => return error.Unimplemented,
- .Identifier => {
- const identifier = @fieldParentPtr(ast.Node.Identifier, "base", node);
- return irb.genIdentifier(identifier, scope, lval);
- },
- .GroupedExpression => {
- const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node);
- return irb.genNodeRecursive(grouped_expr.expr, scope, lval);
- },
- .BuiltinCall => return error.Unimplemented,
- .ErrorSetDecl => return error.Unimplemented,
- .ContainerDecl => return error.Unimplemented,
- .Asm => return error.Unimplemented,
- .Comptime => return error.Unimplemented,
- .Block => {
- const block = @fieldParentPtr(ast.Node.Block, "base", node);
- const inst = try irb.genBlock(block, scope);
- return irb.lvalWrap(scope, inst, lval);
- },
- .DocComment => return error.Unimplemented,
- .SwitchCase => return error.Unimplemented,
- .SwitchElse => return error.Unimplemented,
- .Else => return error.Unimplemented,
- .Payload => return error.Unimplemented,
- .PointerPayload => return error.Unimplemented,
- .PointerIndexPayload => return error.Unimplemented,
- .ContainerField => return error.Unimplemented,
- .ErrorTag => return error.Unimplemented,
- .AsmInput => return error.Unimplemented,
- .AsmOutput => return error.Unimplemented,
- .ParamDecl => return error.Unimplemented,
- .FieldInitializer => return error.Unimplemented,
- .EnumLiteral => return error.Unimplemented,
- .Noasync => return error.Unimplemented,
- }
+ fn constVoid(self: *Analyze, src: usize) !*Inst {
+ return self.constInst(src, .{
+ .ty = Type.initTag(.void),
+ .val = Value.initTag(.void_value),
+ });
}
- fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst {
- const fn_ref = try irb.genNodeRecursive(suffix_op.lhs.node, scope, .None);
-
- const args = try irb.arena().alloc(*Inst, call.params.len);
- var it = call.params.iterator(0);
- var i: usize = 0;
- while (it.next()) |arg_node_ptr| : (i += 1) {
- args[i] = try irb.genNodeRecursive(arg_node_ptr.*, scope, .None);
- }
+ fn constIntUnsigned(self: *Analyze, src: usize, ty: Type, int: u64) !*Inst {
+ const int_payload = try self.arena.allocator.create(Value.Payload.Int_u64);
+ int_payload.* = .{ .int = int };
- //bool is_async = node->data.fn_call_expr.is_async;
- //IrInstruction *async_allocator = nullptr;
- //if (is_async) {
- // if (node->data.fn_call_expr.async_allocator) {
- // async_allocator = ir_gen_node(irb, node->data.fn_call_expr.async_allocator, scope);
- // if (async_allocator == irb->codegen->invalid_instruction)
- // return async_allocator;
- // }
- //}
-
- return irb.build(Inst.Call, scope, Span.token(suffix_op.rtoken), Inst.Call.Params{
- .fn_ref = fn_ref,
- .args = args,
+ return self.constInst(src, .{
+ .ty = ty,
+ .val = Value.initPayload(&int_payload.base),
});
- //IrInstruction *fn_call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto, is_async, async_allocator, nullptr);
- //return ir_lval_wrap(irb, scope, fn_call, lval);
}
- fn genPtrType(
- irb: *Builder,
- prefix_op: *ast.Node.PrefixOp,
- ptr_info: ast.Node.PrefixOp.PtrInfo,
- scope: *Scope,
- ) !*Inst {
- // TODO port more logic
-
- //assert(node->type == NodeTypePointerType);
- //PtrLen ptr_len = (node->data.pointer_type.star_token->id == TokenIdStar ||
- // node->data.pointer_type.star_token->id == TokenIdStarStar) ? PtrLenSingle : PtrLenUnknown;
- //bool is_const = node->data.pointer_type.is_const;
- //bool is_volatile = node->data.pointer_type.is_volatile;
- //AstNode *expr_node = node->data.pointer_type.op_expr;
- //AstNode *align_expr = node->data.pointer_type.align_expr;
-
- //IrInstruction *align_value;
- //if (align_expr != nullptr) {
- // align_value = ir_gen_node(irb, align_expr, scope);
- // if (align_value == irb->codegen->invalid_instruction)
- // return align_value;
- //} else {
- // align_value = nullptr;
- //}
- const child_type = try irb.genNodeRecursive(prefix_op.rhs, scope, .None);
-
- //uint32_t bit_offset_start = 0;
- //if (node->data.pointer_type.bit_offset_start != nullptr) {
- // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_start, 32, false)) {
- // Buf *val_buf = buf_alloc();
- // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_start, 10);
- // exec_add_error_node(irb->codegen, irb->exec, node,
- // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf)));
- // return irb->codegen->invalid_instruction;
- // }
- // bit_offset_start = bigint_as_unsigned(node->data.pointer_type.bit_offset_start);
- //}
-
- //uint32_t bit_offset_end = 0;
- //if (node->data.pointer_type.bit_offset_end != nullptr) {
- // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_end, 32, false)) {
- // Buf *val_buf = buf_alloc();
- // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_end, 10);
- // exec_add_error_node(irb->codegen, irb->exec, node,
- // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf)));
- // return irb->codegen->invalid_instruction;
- // }
- // bit_offset_end = bigint_as_unsigned(node->data.pointer_type.bit_offset_end);
- //}
-
- //if ((bit_offset_start != 0 || bit_offset_end != 0) && bit_offset_start >= bit_offset_end) {
- // exec_add_error_node(irb->codegen, irb->exec, node,
- // buf_sprintf("bit offset start must be less than bit offset end"));
- // return irb->codegen->invalid_instruction;
- //}
-
- return irb.build(Inst.PtrType, scope, Span.node(&prefix_op.base), Inst.PtrType.Params{
- .child_type = child_type,
- .mut = .Mut,
- .vol = .Non,
- .size = .Many,
- .alignment = null,
- });
- }
+ fn constIntSigned(self: *Analyze, src: usize, ty: Type, int: i64) !*Inst {
+ const int_payload = try self.arena.allocator.create(Value.Payload.Int_i64);
+ int_payload.* = .{ .int = int };
- fn isCompTime(irb: *Builder, target_scope: *Scope) bool {
- if (irb.is_comptime)
- return true;
-
- var scope = target_scope;
- while (true) {
- switch (scope.id) {
- .CompTime => return true,
- .FnDef => return false,
- .Decls => unreachable,
- .Root => unreachable,
- .AstTree => unreachable,
- .Block,
- .Defer,
- .DeferExpr,
- .Var,
- => scope = scope.parent.?,
- }
- }
+ return self.constInst(src, .{
+ .ty = ty,
+ .val = Value.initPayload(&int_payload.base),
+ });
}
- pub fn genIntLit(irb: *Builder, int_lit: *ast.Node.IntegerLiteral, scope: *Scope) !*Inst {
- const int_token = irb.code.tree_scope.tree.tokenSlice(int_lit.token);
-
- var base: u8 = undefined;
- var rest: []const u8 = undefined;
- if (int_token.len >= 3 and int_token[0] == '0') {
- rest = int_token[2..];
- switch (int_token[1]) {
- 'b' => base = 2,
- 'o' => base = 8,
- 'x' => base = 16,
- else => {
- base = 10;
- rest = int_token;
- },
+ fn constIntBig(self: *Analyze, src: usize, ty: Type, big_int: BigInt) !*Inst {
+ if (big_int.isPositive()) {
+ if (big_int.to(u64)) |x| {
+ return self.constIntUnsigned(src, ty, x);
+ } else |err| switch (err) {
+ error.NegativeIntoUnsigned => unreachable,
+ error.TargetTooSmall => {}, // handled below
}
} else {
- base = 10;
- rest = int_token;
+ if (big_int.to(i64)) |x| {
+ return self.constIntSigned(src, ty, x);
+ } else |err| switch (err) {
+ error.NegativeIntoUnsigned => unreachable,
+ error.TargetTooSmall => {}, // handled below
+ }
}
- const comptime_int_type = Type.ComptimeInt.get(irb.comp);
- defer comptime_int_type.base.base.deref(irb.comp);
-
- const int_val = Value.Int.createFromString(
- irb.comp,
- &comptime_int_type.base,
- base,
- rest,
- ) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.InvalidBase => unreachable,
- error.InvalidCharForDigit => unreachable,
- error.DigitTooLargeForBase => unreachable,
- };
- errdefer int_val.base.deref(irb.comp);
+ const big_int_payload = try self.arena.allocator.create(Value.Payload.IntBig);
+ big_int_payload.* = .{ .big_int = big_int };
- const inst = try irb.build(Inst.Const, scope, Span.token(int_lit.token), Inst.Const.Params{});
- inst.val = IrVal{ .KnownValue = &int_val.base };
- return inst;
+ return self.constInst(src, .{
+ .ty = ty,
+ .val = Value.initPayload(&big_int_payload.base),
+ });
}
- pub fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst {
- const str_token = irb.code.tree_scope.tree.tokenSlice(str_lit.token);
- const src_span = Span.token(str_lit.token);
-
- var bad_index: usize = undefined;
- var buf = std.zig.parseStringLiteral(irb.comp.gpa(), str_token, &bad_index) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.InvalidCharacter => {
- try irb.comp.addCompileError(
- irb.code.tree_scope,
- src_span,
- "invalid character in string literal: '{c}'",
- .{str_token[bad_index]},
- );
- return error.SemanticAnalysisFailed;
+ fn analyzeInst(self: *Analyze, func: ?*Fn, old_inst: *text.Inst) InnerError!*Inst {
+ switch (old_inst.tag) {
+ .str => {
+ // We can use this reference because Inst.Const's Value is arena-allocated.
+ // The value would get copied to a MemoryCell before the `text.Inst.Str` lifetime ends.
+ const bytes = old_inst.cast(text.Inst.Str).?.positionals.bytes;
+ return self.constStr(old_inst.src, bytes);
},
- };
- var buf_cleaned = false;
- errdefer if (!buf_cleaned) irb.comp.gpa().free(buf);
-
- if (str_token[0] == 'c') {
- // first we add a null
- buf = try irb.comp.gpa().realloc(buf, buf.len + 1);
- buf[buf.len - 1] = 0;
-
- // next make an array value
- const array_val = try Value.Array.createOwnedBuffer(irb.comp, buf);
- buf_cleaned = true;
- defer array_val.base.deref(irb.comp);
-
- // then make a pointer value pointing at the first element
- const ptr_val = try Value.Ptr.createArrayElemPtr(
- irb.comp,
- array_val,
- .Const,
- .Many,
- 0,
- );
- defer ptr_val.base.deref(irb.comp);
-
- return irb.buildConstValue(scope, src_span, &ptr_val.base);
- } else {
- const array_val = try Value.Array.createOwnedBuffer(irb.comp, buf);
- buf_cleaned = true;
- defer array_val.base.deref(irb.comp);
-
- return irb.buildConstValue(scope, src_span, &array_val.base);
+ .int => {
+ const big_int = old_inst.cast(text.Inst.Int).?.positionals.int;
+ return self.constIntBig(old_inst.src, Type.initTag(.comptime_int), big_int);
+ },
+ .ptrtoint => return self.analyzeInstPtrToInt(func, old_inst.cast(text.Inst.PtrToInt).?),
+ .fieldptr => return self.analyzeInstFieldPtr(func, old_inst.cast(text.Inst.FieldPtr).?),
+ .deref => return self.analyzeInstDeref(func, old_inst.cast(text.Inst.Deref).?),
+ .as => return self.analyzeInstAs(func, old_inst.cast(text.Inst.As).?),
+ .@"asm" => return self.analyzeInstAsm(func, old_inst.cast(text.Inst.Asm).?),
+ .@"unreachable" => return self.analyzeInstUnreachable(func, old_inst.cast(text.Inst.Unreachable).?),
+ .@"fn" => return self.analyzeInstFn(func, old_inst.cast(text.Inst.Fn).?),
+ .@"export" => {
+ try self.analyzeExport(func, old_inst.cast(text.Inst.Export).?);
+ return self.constVoid(old_inst.src);
+ },
+ .primitive => return self.analyzeInstPrimitive(func, old_inst.cast(text.Inst.Primitive).?),
+ .fntype => return self.analyzeInstFnType(func, old_inst.cast(text.Inst.FnType).?),
+ .intcast => return self.analyzeInstIntCast(func, old_inst.cast(text.Inst.IntCast).?),
}
}
- pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst {
- const block_scope = try Scope.Block.create(irb.comp, parent_scope);
+ fn analyzeInstFn(self: *Analyze, opt_func: ?*Fn, fn_inst: *text.Inst.Fn) InnerError!*Inst {
+ const fn_type = try self.resolveType(opt_func, fn_inst.positionals.fn_type);
- const outer_block_scope = &block_scope.base;
- var child_scope = outer_block_scope;
-
- if (parent_scope.findFnDef()) |fndef_scope| {
- if (fndef_scope.fn_val.?.block_scope == null) {
- fndef_scope.fn_val.?.block_scope = block_scope;
- }
- }
-
- if (block.statements.len == 0) {
- // {}
- return irb.buildConstVoid(child_scope, Span.token(block.lbrace), false);
- }
+ var new_func: Fn = .{
+ .body = std.ArrayList(*Inst).init(self.allocator),
+ .inst_table = std.AutoHashMap(*text.Inst, NewInst).init(self.allocator),
+ .fn_index = self.fns.items.len,
+ };
+ defer new_func.body.deinit();
+ defer new_func.inst_table.deinit();
+ // Don't hang on to a reference to this when analyzing body instructions, since the memory
+ // could become invalid.
+ (try self.fns.addOne()).* = .{
+ .analysis_status = .in_progress,
+ .fn_type = fn_type,
+ .body = undefined,
+ };
- if (block.label) |label| {
- block_scope.incoming_values = std.ArrayList(*Inst).init(irb.arena());
- block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena());
- block_scope.end_block = try irb.createBasicBlock(parent_scope, "BlockEnd");
- block_scope.is_comptime = try irb.buildConstBool(
- parent_scope,
- Span.token(block.lbrace),
- irb.isCompTime(parent_scope),
- );
+ for (fn_inst.positionals.body.instructions) |src_inst| {
+ const new_inst = self.analyzeInst(&new_func, src_inst) catch |err| {
+ self.fns.items[new_func.fn_index].analysis_status = .failure;
+ try new_func.inst_table.putNoClobber(src_inst, .{ .ptr = null });
+ return err;
+ };
+ try new_func.inst_table.putNoClobber(src_inst, .{ .ptr = new_inst });
}
- var is_continuation_unreachable = false;
- var noreturn_return_value: ?*Inst = null;
-
- var stmt_it = block.statements.iterator(0);
- while (stmt_it.next()) |statement_node_ptr| {
- const statement_node = statement_node_ptr.*;
-
- if (statement_node.cast(ast.Node.Defer)) |defer_node| {
- // defer starts a new scope
- const defer_token = irb.code.tree_scope.tree.tokens.at(defer_node.defer_token);
- const kind = switch (defer_token.id) {
- Token.Id.Keyword_defer => Scope.Defer.Kind.ScopeExit,
- Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit,
- else => unreachable,
- };
- const defer_expr_scope = try Scope.DeferExpr.create(irb.comp, parent_scope, defer_node.expr);
- const defer_child_scope = try Scope.Defer.create(irb.comp, parent_scope, kind, defer_expr_scope);
- child_scope = &defer_child_scope.base;
- continue;
- }
- const statement_value = try irb.genNodeRecursive(statement_node, child_scope, .None);
+ const f = &self.fns.items[new_func.fn_index];
+ f.analysis_status = .success;
+ f.body = new_func.body.toOwnedSlice();
- is_continuation_unreachable = statement_value.isNoReturn();
- if (is_continuation_unreachable) {
- // keep the last noreturn statement value around in case we need to return it
- noreturn_return_value = statement_value;
- }
+ const fn_payload = try self.arena.allocator.create(Value.Payload.Function);
+ fn_payload.* = .{ .index = new_func.fn_index };
- if (statement_value.cast(Inst.DeclVar)) |decl_var| {
- // variable declarations start a new scope
- child_scope = decl_var.params.variable.child_scope;
- } else if (!is_continuation_unreachable) {
- // this statement's value must be void
- _ = try irb.build(
- Inst.CheckVoidStmt,
- child_scope,
- Span{
- .first = statement_node.firstToken(),
- .last = statement_node.lastToken(),
- },
- Inst.CheckVoidStmt.Params{ .target = statement_value },
- );
- }
- }
+ return self.constInst(fn_inst.base.src, .{
+ .ty = fn_type,
+ .val = Value.initPayload(&fn_payload.base),
+ });
+ }
- if (is_continuation_unreachable) {
- assert(noreturn_return_value != null);
- if (block.label == null or block_scope.incoming_blocks.len == 0) {
- return noreturn_return_value.?;
- }
+ fn analyzeInstFnType(self: *Analyze, func: ?*Fn, fntype: *text.Inst.FnType) InnerError!*Inst {
+ const return_type = try self.resolveType(func, fntype.positionals.return_type);
- try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
- return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
- .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
- .incoming_values = block_scope.incoming_values.toOwnedSlice(),
- });
+ if (return_type.zigTypeTag() == .NoReturn and
+ fntype.positionals.param_types.len == 0 and
+ fntype.kw_args.cc == .Naked)
+ {
+ return self.constType(fntype.base.src, Type.initTag(.fn_naked_noreturn_no_args));
}
- if (block.label) |label| {
- try block_scope.incoming_blocks.append(irb.current_basic_block);
- try block_scope.incoming_values.append(
- try irb.buildConstVoid(parent_scope, Span.token(block.rbrace), true),
- );
- _ = try irb.genDefersForBlock(child_scope, outer_block_scope, .ScopeExit);
+ return self.fail(fntype.base.src, "TODO implement fntype instruction more", .{});
+ }
- _ = try irb.buildGen(Inst.Br, parent_scope, Span.token(block.rbrace), Inst.Br.Params{
- .dest_block = block_scope.end_block,
- .is_comptime = block_scope.is_comptime,
- });
+ fn analyzeInstPrimitive(self: *Analyze, func: ?*Fn, primitive: *text.Inst.Primitive) InnerError!*Inst {
+ return self.constType(primitive.base.src, primitive.positionals.tag.toType());
+ }
- try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
+ fn analyzeInstAs(self: *Analyze, func: ?*Fn, as: *text.Inst.As) InnerError!*Inst {
+ const dest_type = try self.resolveType(func, as.positionals.dest_type);
+ const new_inst = try self.resolveInst(func, as.positionals.value);
+ return self.coerce(dest_type, new_inst);
+ }
- return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
- .incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
- .incoming_values = block_scope.incoming_values.toOwnedSlice(),
- });
+ fn analyzeInstPtrToInt(self: *Analyze, func: ?*Fn, ptrtoint: *text.Inst.PtrToInt) InnerError!*Inst {
+ const ptr = try self.resolveInst(func, ptrtoint.positionals.ptr);
+ if (ptr.ty.zigTypeTag() != .Pointer) {
+ return self.fail(ptrtoint.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty});
}
-
- _ = try irb.genDefersForBlock(child_scope, outer_block_scope, .ScopeExit);
- return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true);
+ // TODO handle known-pointer-address
+ const f = try self.requireFunctionBody(func, ptrtoint.base.src);
+ const ty = Type.initTag(.usize);
+ return self.addNewInstArgs(f, ptrtoint.base.src, ty, Inst.PtrToInt, Inst.Args(Inst.PtrToInt){ .ptr = ptr });
}
- pub fn genControlFlowExpr(
- irb: *Builder,
- control_flow_expr: *ast.Node.ControlFlowExpression,
- scope: *Scope,
- lval: LVal,
- ) !*Inst {
- switch (control_flow_expr.kind) {
- .Break => |arg| return error.Unimplemented,
- .Continue => |arg| return error.Unimplemented,
- .Return => {
- const src_span = Span.token(control_flow_expr.ltoken);
- if (scope.findFnDef() == null) {
- try irb.comp.addCompileError(
- irb.code.tree_scope,
- src_span,
- "return expression outside function definition",
- .{},
- );
- return error.SemanticAnalysisFailed;
- }
-
- if (scope.findDeferExpr()) |scope_defer_expr| {
- if (!scope_defer_expr.reported_err) {
- try irb.comp.addCompileError(
- irb.code.tree_scope,
- src_span,
- "cannot return from defer expression",
- .{},
- );
- scope_defer_expr.reported_err = true;
- }
- return error.SemanticAnalysisFailed;
- }
-
- const outer_scope = irb.begin_scope.?;
- const return_value = if (control_flow_expr.rhs) |rhs| blk: {
- break :blk try irb.genNodeRecursive(rhs, scope, .None);
- } else blk: {
- break :blk try irb.buildConstVoid(scope, src_span, true);
- };
-
- const defer_counts = irb.countDefers(scope, outer_scope);
- const have_err_defers = defer_counts.error_exit != 0;
- if (have_err_defers or irb.comp.have_err_ret_tracing) {
- const err_block = try irb.createBasicBlock(scope, "ErrRetErr");
- const ok_block = try irb.createBasicBlock(scope, "ErrRetOk");
- if (!have_err_defers) {
- _ = try irb.genDefersForBlock(scope, outer_scope, .ScopeExit);
- }
-
- const is_err = try irb.build(
- Inst.TestErr,
- scope,
- src_span,
- Inst.TestErr.Params{ .target = return_value },
- );
-
- const err_is_comptime = try irb.buildTestCompTime(scope, src_span, is_err);
-
- _ = try irb.buildGen(Inst.CondBr, scope, src_span, Inst.CondBr.Params{
- .condition = is_err,
- .then_block = err_block,
- .else_block = ok_block,
- .is_comptime = err_is_comptime,
- });
+ fn analyzeInstFieldPtr(self: *Analyze, func: ?*Fn, fieldptr: *text.Inst.FieldPtr) InnerError!*Inst {
+ const object_ptr = try self.resolveInst(func, fieldptr.positionals.object_ptr);
+ const field_name = try self.resolveConstString(func, fieldptr.positionals.field_name);
- const ret_stmt_block = try irb.createBasicBlock(scope, "RetStmt");
-
- try irb.setCursorAtEndAndAppendBlock(err_block);
- if (have_err_defers) {
- _ = try irb.genDefersForBlock(scope, outer_scope, .ErrorExit);
- }
- if (irb.comp.have_err_ret_tracing and !irb.isCompTime(scope)) {
- _ = try irb.build(Inst.SaveErrRetAddr, scope, src_span, Inst.SaveErrRetAddr.Params{});
- }
- _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
- .dest_block = ret_stmt_block,
- .is_comptime = err_is_comptime,
- });
-
- try irb.setCursorAtEndAndAppendBlock(ok_block);
- if (have_err_defers) {
- _ = try irb.genDefersForBlock(scope, outer_scope, .ScopeExit);
- }
- _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
- .dest_block = ret_stmt_block,
- .is_comptime = err_is_comptime,
+ const elem_ty = switch (object_ptr.ty.zigTypeTag()) {
+ .Pointer => object_ptr.ty.elemType(),
+ else => return self.fail(fieldptr.positionals.object_ptr.src, "expected pointer, found '{}'", .{object_ptr.ty}),
+ };
+ switch (elem_ty.zigTypeTag()) {
+ .Array => {
+ if (mem.eql(u8, field_name, "len")) {
+ const len_payload = try self.arena.allocator.create(Value.Payload.Int_u64);
+ len_payload.* = .{ .int = elem_ty.arrayLen() };
+
+ const ref_payload = try self.arena.allocator.create(Value.Payload.RefVal);
+ ref_payload.* = .{ .val = Value.initPayload(&len_payload.base) };
+
+ return self.constInst(fieldptr.base.src, .{
+ .ty = Type.initTag(.single_const_pointer_to_comptime_int),
+ .val = Value.initPayload(&ref_payload.base),
});
-
- try irb.setCursorAtEndAndAppendBlock(ret_stmt_block);
- return irb.genAsyncReturn(scope, src_span, return_value, false);
} else {
- _ = try irb.genDefersForBlock(scope, outer_scope, .ScopeExit);
- return irb.genAsyncReturn(scope, src_span, return_value, false);
- }
- },
- }
- }
-
- pub fn genIdentifier(irb: *Builder, identifier: *ast.Node.Identifier, scope: *Scope, lval: LVal) !*Inst {
- const src_span = Span.token(identifier.token);
- const name = irb.code.tree_scope.tree.tokenSlice(identifier.token);
-
- //if (buf_eql_str(variable_name, "_") && lval == LValPtr) {
- // IrInstructionConst *const_instruction = ir_build_instruction<IrInstructionConst>(irb, scope, node);
- // const_instruction->base.value.type = get_pointer_to_type(irb->codegen,
- // irb->codegen->builtin_types.entry_void, false);
- // const_instruction->base.value.special = ConstValSpecialStatic;
- // const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialDiscard;
- // return &const_instruction->base;
- //}
-
- if (irb.comp.getPrimitiveType(name)) |result| {
- if (result) |primitive_type| {
- defer primitive_type.base.deref(irb.comp);
- switch (lval) {
- // if (lval == LValPtr) {
- // return ir_build_ref(irb, scope, node, value, false, false);
- .Ptr => return error.Unimplemented,
- .None => return irb.buildConstValue(scope, src_span, &primitive_type.base),
- }
- }
- } else |err| switch (err) {
- error.Overflow => {
- try irb.comp.addCompileError(irb.code.tree_scope, src_span, "integer too large", .{});
- return error.SemanticAnalysisFailed;
- },
- error.OutOfMemory => return error.OutOfMemory,
- }
-
- switch (irb.findIdent(scope, name)) {
- .Decl => |decl| {
- return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{
- .decl = decl,
- .lval = lval,
- });
- },
- .VarScope => |var_scope| {
- const var_ptr = try irb.build(Inst.VarPtr, scope, src_span, Inst.VarPtr.Params{ .var_scope = var_scope });
- switch (lval) {
- .Ptr => return var_ptr,
- .None => {
- return irb.build(Inst.LoadPtr, scope, src_span, Inst.LoadPtr.Params{ .target = var_ptr });
- },
+ return self.fail(
+ fieldptr.positionals.field_name.src,
+ "no member named '{}' in '{}'",
+ .{ field_name, elem_ty },
+ );
}
},
- .NotFound => {},
+ else => return self.fail(fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}),
}
-
- //if (node->owner->any_imports_failed) {
- // // skip the error message since we had a failing import in this file
- // // if an import breaks we don't need redundant undeclared identifier errors
- // return irb->codegen->invalid_instruction;
- //}
-
- // TODO put a variable of same name with invalid type in global scope
- // so that future references to this same name will find a variable with an invalid type
-
- try irb.comp.addCompileError(irb.code.tree_scope, src_span, "unknown identifier '{}'", .{name});
- return error.SemanticAnalysisFailed;
}
- const DeferCounts = struct {
- scope_exit: usize,
- error_exit: usize,
- };
+ fn analyzeInstIntCast(self: *Analyze, func: ?*Fn, intcast: *text.Inst.IntCast) InnerError!*Inst {
+ const dest_type = try self.resolveType(func, intcast.positionals.dest_type);
+ const new_inst = try self.resolveInst(func, intcast.positionals.value);
- fn countDefers(irb: *Builder, inner_scope: *Scope, outer_scope: *Scope) DeferCounts {
- var result = DeferCounts{ .scope_exit = 0, .error_exit = 0 };
-
- var scope = inner_scope;
- while (scope != outer_scope) {
- switch (scope.id) {
- .Defer => {
- const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope);
- switch (defer_scope.kind) {
- .ScopeExit => result.scope_exit += 1,
- .ErrorExit => result.error_exit += 1,
- }
- scope = scope.parent orelse break;
+ const dest_is_comptime_int = switch (dest_type.zigTypeTag()) {
+ .ComptimeInt => true,
+ .Int => false,
+ else => return self.fail(
+ intcast.positionals.dest_type.src,
+ "expected integer type, found '{}'",
+ .{
+ dest_type,
},
- .FnDef => break,
-
- .CompTime,
- .Block,
- .Decls,
- .Root,
- .Var,
- => scope = scope.parent orelse break,
-
- .DeferExpr => unreachable,
- .AstTree => unreachable,
- }
- }
- return result;
- }
+ ),
+ };
- fn genDefersForBlock(
- irb: *Builder,
- inner_scope: *Scope,
- outer_scope: *Scope,
- gen_kind: Scope.Defer.Kind,
- ) !bool {
- var scope = inner_scope;
- var is_noreturn = false;
- while (true) {
- switch (scope.id) {
- .Defer => {
- const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope);
- const generate = switch (defer_scope.kind) {
- .ScopeExit => true,
- .ErrorExit => gen_kind == .ErrorExit,
- };
- if (generate) {
- const defer_expr_scope = defer_scope.defer_expr_scope;
- const instruction = try irb.genNodeRecursive(
- defer_expr_scope.expr_node,
- &defer_expr_scope.base,
- .None,
- );
- if (instruction.isNoReturn()) {
- is_noreturn = true;
- } else {
- _ = try irb.build(
- Inst.CheckVoidStmt,
- &defer_expr_scope.base,
- Span.token(defer_expr_scope.expr_node.lastToken()),
- Inst.CheckVoidStmt.Params{ .target = instruction },
- );
- }
- }
- },
- .FnDef,
- .Decls,
- .Root,
- => return is_noreturn,
-
- .CompTime,
- .Block,
- .Var,
- => scope = scope.parent orelse return is_noreturn,
-
- .DeferExpr => unreachable,
- .AstTree => unreachable,
- }
+ switch (new_inst.ty.zigTypeTag()) {
+ .ComptimeInt, .Int => {},
+ else => return self.fail(
+ intcast.positionals.value.src,
+ "expected integer type, found '{}'",
+ .{new_inst.ty},
+ ),
}
- }
- pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Inst, lval: LVal) !*Inst {
- switch (lval) {
- .None => return instruction,
- .Ptr => {
- // We needed a pointer to a value, but we got a value. So we create
- // an instruction which just makes a const pointer of it.
- return irb.build(Inst.Ref, scope, instruction.span, Inst.Ref.Params{
- .target = instruction,
- .mut = .Const,
- .volatility = .Non,
- });
- },
+ if (dest_is_comptime_int or new_inst.value() != null) {
+ return self.coerce(dest_type, new_inst);
}
- }
- fn arena(self: *Builder) *Allocator {
- return &self.code.arena.allocator;
+ return self.fail(intcast.base.src, "TODO implement analyze widen or shorten int", .{});
}
- fn buildExtra(
- self: *Builder,
- comptime I: type,
- scope: *Scope,
- span: Span,
- params: I.Params,
- is_generated: bool,
- ) !*Inst {
- const inst = try self.arena().create(I);
- inst.* = I{
- .base = Inst{
- .id = Inst.typeToId(I),
- .is_generated = is_generated,
- .scope = scope,
- .debug_id = self.next_debug_id,
- .val = switch (I.ir_val_init) {
- .Unknown => IrVal.Unknown,
- .NoReturn => IrVal{ .KnownValue = &Value.NoReturn.get(self.comp).base },
- .Void => IrVal{ .KnownValue = &Value.Void.get(self.comp).base },
- },
- .ref_count = 0,
- .span = span,
- .child = null,
- .parent = null,
- .llvm_value = undefined,
- .owner_bb = self.current_basic_block,
- },
- .params = params,
+ fn analyzeInstDeref(self: *Analyze, func: ?*Fn, deref: *text.Inst.Deref) InnerError!*Inst {
+ const ptr = try self.resolveInst(func, deref.positionals.ptr);
+ const elem_ty = switch (ptr.ty.zigTypeTag()) {
+ .Pointer => ptr.ty.elemType(),
+ else => return self.fail(deref.positionals.ptr.src, "expected pointer, found '{}'", .{ptr.ty}),
};
-
- // Look at the params and ref() other instructions
- inline for (@typeInfo(I.Params).Struct.fields) |f| {
- switch (f.field_type) {
- *Inst => @field(inst.params, f.name).ref(self),
- *BasicBlock => @field(inst.params, f.name).ref(self),
- ?*Inst => if (@field(inst.params, f.name)) |other| other.ref(self),
- []*Inst => {
- // TODO https://github.com/ziglang/zig/issues/1269
- for (@field(inst.params, f.name)) |other|
- other.ref(self);
- },
- []*BasicBlock => {
- // TODO https://github.com/ziglang/zig/issues/1269
- for (@field(inst.params, f.name)) |other|
- other.ref(self);
- },
- Type.Pointer.Mut,
- Type.Pointer.Vol,
- Type.Pointer.Size,
- LVal,
- *Decl,
- *Scope.Var,
- => {},
- // it's ok to add more types here, just make sure that
- // any instructions and basic blocks are ref'd appropriately
- else => @compileError("unrecognized type in Params: " ++ @typeName(f.field_type)),
- }
+ if (ptr.value()) |val| {
+ return self.constInst(deref.base.src, .{
+ .ty = elem_ty,
+ .val = val.pointerDeref(),
+ });
}
- self.next_debug_id += 1;
- try self.current_basic_block.instruction_list.append(&inst.base);
- return &inst.base;
- }
-
- fn build(
- self: *Builder,
- comptime I: type,
- scope: *Scope,
- span: Span,
- params: I.Params,
- ) !*Inst {
- return self.buildExtra(I, scope, span, params, false);
- }
-
- fn buildGen(
- self: *Builder,
- comptime I: type,
- scope: *Scope,
- span: Span,
- params: I.Params,
- ) !*Inst {
- return self.buildExtra(I, scope, span, params, true);
- }
-
- fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Inst {
- const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{});
- inst.val = IrVal{ .KnownValue = &Value.Bool.get(self.comp, x).base };
- return inst;
+ return self.fail(deref.base.src, "TODO implement runtime deref", .{});
}
- fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Inst {
- const inst = try self.buildExtra(Inst.Const, scope, span, Inst.Const.Params{}, is_generated);
- inst.val = IrVal{ .KnownValue = &Value.Void.get(self.comp).base };
- return inst;
- }
+ fn analyzeInstAsm(self: *Analyze, func: ?*Fn, assembly: *text.Inst.Asm) InnerError!*Inst {
+ const return_type = try self.resolveType(func, assembly.positionals.return_type);
+ const asm_source = try self.resolveConstString(func, assembly.positionals.asm_source);
+ const output = if (assembly.kw_args.output) |o| try self.resolveConstString(func, o) else null;
- fn buildConstValue(self: *Builder, scope: *Scope, span: Span, v: *Value) !*Inst {
- const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{});
- inst.val = IrVal{ .KnownValue = v.getRef() };
- return inst;
- }
+ const inputs = try self.arena.allocator.alloc([]const u8, assembly.kw_args.inputs.len);
+ const clobbers = try self.arena.allocator.alloc([]const u8, assembly.kw_args.clobbers.len);
+ const args = try self.arena.allocator.alloc(*Inst, assembly.kw_args.args.len);
- /// If the code is explicitly set to be comptime, then builds a const bool,
- /// otherwise builds a TestCompTime instruction.
- fn buildTestCompTime(self: *Builder, scope: *Scope, span: Span, target: *Inst) !*Inst {
- if (self.isCompTime(scope)) {
- return self.buildConstBool(scope, span, true);
- } else {
- return self.build(
- Inst.TestCompTime,
- scope,
- span,
- Inst.TestCompTime.Params{ .target = target },
- );
+ for (inputs) |*elem, i| {
+ elem.* = try self.resolveConstString(func, assembly.kw_args.inputs[i]);
}
- }
-
- fn genAsyncReturn(irb: *Builder, scope: *Scope, span: Span, result: *Inst, is_gen: bool) !*Inst {
- _ = try irb.buildGen(
- Inst.AddImplicitReturnType,
- scope,
- span,
- Inst.AddImplicitReturnType.Params{ .target = result },
- );
-
- if (!irb.is_async) {
- return irb.buildExtra(
- Inst.Return,
- scope,
- span,
- Inst.Return.Params{ .return_value = result },
- is_gen,
- );
+ for (clobbers) |*elem, i| {
+ elem.* = try self.resolveConstString(func, assembly.kw_args.clobbers[i]);
}
- return error.Unimplemented;
- }
-
- const Ident = union(enum) {
- NotFound,
- Decl: *Decl,
- VarScope: *Scope.Var,
- };
-
- fn findIdent(irb: *Builder, scope: *Scope, name: []const u8) Ident {
- var s = scope;
- while (true) {
- switch (s.id) {
- .Root => return .NotFound,
- .Decls => {
- const decls = @fieldParentPtr(Scope.Decls, "base", s);
- const locked_table = decls.table.acquireRead();
- defer locked_table.release();
- if (locked_table.value.get(name)) |entry| {
- return Ident{ .Decl = entry.value };
- }
- },
- .Var => {
- const var_scope = @fieldParentPtr(Scope.Var, "base", s);
- if (mem.eql(u8, var_scope.name, name)) {
- return Ident{ .VarScope = var_scope };
- }
- },
- else => {},
- }
- s = s.parent.?;
+ for (args) |*elem, i| {
+ elem.* = try self.resolveInst(func, assembly.kw_args.args[i]);
}
- }
-};
-
-const Analyze = struct {
- irb: Builder,
- old_bb_index: usize,
- const_predecessor_bb: ?*BasicBlock,
- parent_basic_block: *BasicBlock,
- instruction_index: usize,
- src_implicit_return_type_list: std.ArrayList(*Inst),
- explicit_return_type: ?*Type,
-
- pub const Error = error{
- /// This is only for when we have already reported a compile error. It is the poison value.
- SemanticAnalysisFailed,
-
- /// This is a placeholder - it is useful to use instead of panicking but once the compiler is
- /// done this error code will be removed.
- Unimplemented,
-
- OutOfMemory,
- };
- pub fn init(comp: *Compilation, tree_scope: *Scope.AstTree, explicit_return_type: ?*Type) !Analyze {
- var irb = try Builder.init(comp, tree_scope, null);
- errdefer irb.abort();
-
- return Analyze{
- .irb = irb,
- .old_bb_index = 0,
- .const_predecessor_bb = null,
- .parent_basic_block = undefined, // initialized with startBasicBlock
- .instruction_index = undefined, // initialized with startBasicBlock
- .src_implicit_return_type_list = std.ArrayList(*Inst).init(irb.arena()),
- .explicit_return_type = explicit_return_type,
- };
+ const f = try self.requireFunctionBody(func, assembly.base.src);
+ return self.addNewInstArgs(f, assembly.base.src, return_type, Inst.Assembly, Inst.Args(Inst.Assembly){
+ .asm_source = asm_source,
+ .is_volatile = assembly.kw_args.@"volatile",
+ .output = output,
+ .inputs = inputs,
+ .clobbers = clobbers,
+ .args = args,
+ });
}
- pub fn abort(self: *Analyze) void {
- self.irb.abort();
+ fn analyzeInstUnreachable(self: *Analyze, func: ?*Fn, unreach: *text.Inst.Unreachable) InnerError!*Inst {
+ const f = try self.requireFunctionBody(func, unreach.base.src);
+ return self.addNewInstArgs(f, unreach.base.src, Type.initTag(.noreturn), Inst.Unreach, {});
}
- pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Inst) !*BasicBlock {
- if (old_bb.child) |child| {
- if (ref_old_instruction == null or child.ref_instruction != ref_old_instruction)
- return child;
+ fn coerce(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst {
+ const in_memory_result = coerceInMemoryAllowed(dest_type, inst.ty);
+ if (in_memory_result == .ok) {
+ return self.bitcast(dest_type, inst);
}
- const new_bb = try self.irb.createBasicBlock(old_bb.scope, old_bb.name_hint);
- new_bb.linkToParent(old_bb);
- new_bb.ref_instruction = ref_old_instruction;
- return new_bb;
- }
-
- pub fn startBasicBlock(self: *Analyze, old_bb: *BasicBlock, const_predecessor_bb: ?*BasicBlock) void {
- self.instruction_index = 0;
- self.parent_basic_block = old_bb;
- self.const_predecessor_bb = const_predecessor_bb;
- }
-
- pub fn finishBasicBlock(ira: *Analyze, old_code: *Code) !void {
- try ira.irb.code.basic_block_list.append(ira.irb.current_basic_block);
- ira.instruction_index += 1;
-
- while (ira.instruction_index < ira.parent_basic_block.instruction_list.len) {
- const next_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index);
-
- if (!next_instruction.is_generated) {
- try ira.addCompileError(next_instruction.span, "unreachable code", .{});
- break;
+ // *[N]T to []T
+ if (inst.ty.isSinglePointer() and dest_type.isSlice() and
+ (!inst.ty.pointerIsConst() or dest_type.pointerIsConst()))
+ {
+ const array_type = inst.ty.elemType();
+ const dst_elem_type = dest_type.elemType();
+ if (array_type.zigTypeTag() == .Array and
+ coerceInMemoryAllowed(dst_elem_type, array_type.elemType()) == .ok)
+ {
+ return self.coerceArrayPtrToSlice(dest_type, inst);
}
- ira.instruction_index += 1;
}
- ira.old_bb_index += 1;
-
- var need_repeat = true;
- while (true) {
- while (ira.old_bb_index < old_code.basic_block_list.len) {
- const old_bb = old_code.basic_block_list.at(ira.old_bb_index);
- const new_bb = old_bb.child orelse {
- ira.old_bb_index += 1;
- continue;
- };
- if (new_bb.instruction_list.len != 0) {
- ira.old_bb_index += 1;
- continue;
- }
- ira.irb.current_basic_block = new_bb;
-
- ira.startBasicBlock(old_bb, null);
- return;
+ // comptime_int to fixed-width integer
+ if (inst.ty.zigTypeTag() == .ComptimeInt and dest_type.zigTypeTag() == .Int) {
+ // The representation is already correct; we only need to make sure it fits in the destination type.
+ const val = inst.value().?; // comptime_int always has comptime known value
+ if (!val.intFitsInType(dest_type, self.target)) {
+ return self.fail(inst.src, "type {} cannot represent integer value {}", .{ inst.ty, val });
}
- if (!need_repeat)
- return;
- need_repeat = false;
- ira.old_bb_index = 0;
- continue;
+ return self.constInst(inst.src, .{ .ty = dest_type, .val = val });
}
- }
-
- fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: var) !void {
- return self.irb.comp.addCompileError(self.irb.code.tree_scope, span, fmt, args);
- }
- fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Inst) Analyze.Error!*Type {
- // TODO actual implementation
- return &Type.Void.get(self.irb.comp).base;
+ return self.fail(inst.src, "TODO implement type coercion", .{});
}
- fn implicitCast(self: *Analyze, target: *Inst, optional_dest_type: ?*Type) Analyze.Error!*Inst {
- const dest_type = optional_dest_type orelse return target;
- const from_type = target.getKnownType();
- if (from_type == dest_type or from_type.id == .NoReturn) return target;
- return self.analyzeCast(target, target, dest_type);
+ fn bitcast(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst {
+ if (inst.value()) |val| {
+ // Keep the comptime Value representation; take the new type.
+ return self.constInst(inst.src, .{ .ty = dest_type, .val = val });
+ }
+ return self.fail(inst.src, "TODO implement runtime bitcast", .{});
}
- fn analyzeCast(ira: *Analyze, source_instr: *Inst, target: *Inst, dest_type: *Type) !*Inst {
- const from_type = target.getKnownType();
-
- //if (type_is_invalid(wanted_type) || type_is_invalid(actual_type)) {
- // return ira->codegen->invalid_instruction;
- //}
-
- //// perfect match or non-const to const
- //ConstCastOnly const_cast_result = types_match_const_cast_only(ira, wanted_type, actual_type,
- // source_node, false);
- //if (const_cast_result.id == ConstCastResultIdOk) {
- // return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop, false);
- //}
-
- //// widening conversion
- //if (wanted_type->id == TypeTableEntryIdInt &&
- // actual_type->id == TypeTableEntryIdInt &&
- // wanted_type->data.integral.is_signed == actual_type->data.integral.is_signed &&
- // wanted_type->data.integral.bit_count >= actual_type->data.integral.bit_count)
- //{
- // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
- //}
-
- //// small enough unsigned ints can get casted to large enough signed ints
- //if (wanted_type->id == TypeTableEntryIdInt && wanted_type->data.integral.is_signed &&
- // actual_type->id == TypeTableEntryIdInt && !actual_type->data.integral.is_signed &&
- // wanted_type->data.integral.bit_count > actual_type->data.integral.bit_count)
- //{
- // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
- //}
-
- //// float widening conversion
- //if (wanted_type->id == TypeTableEntryIdFloat &&
- // actual_type->id == TypeTableEntryIdFloat &&
- // wanted_type->data.floating.bit_count >= actual_type->data.floating.bit_count)
- //{
- // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
- //}
-
- //// cast from [N]T to []const T
- //if (is_slice(wanted_type) && actual_type->id == TypeTableEntryIdArray) {
- // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
- // assert(ptr_type->id == TypeTableEntryIdPointer);
- // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
- // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
- // source_node, false).id == ConstCastResultIdOk)
- // {
- // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type);
- // }
- //}
-
- //// cast from *const [N]T to []const T
- //if (is_slice(wanted_type) &&
- // actual_type->id == TypeTableEntryIdPointer &&
- // actual_type->data.pointer.is_const &&
- // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray)
- //{
- // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
- // assert(ptr_type->id == TypeTableEntryIdPointer);
-
- // TypeTableEntry *array_type = actual_type->data.pointer.child_type;
-
- // if ((ptr_type->data.pointer.is_const || array_type->data.array.len == 0) &&
- // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, array_type->data.array.child_type,
- // source_node, false).id == ConstCastResultIdOk)
- // {
- // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type);
- // }
- //}
-
- //// cast from [N]T to *const []const T
- //if (wanted_type->id == TypeTableEntryIdPointer &&
- // wanted_type->data.pointer.is_const &&
- // is_slice(wanted_type->data.pointer.child_type) &&
- // actual_type->id == TypeTableEntryIdArray)
- //{
- // TypeTableEntry *ptr_type =
- // wanted_type->data.pointer.child_type->data.structure.fields[slice_ptr_index].type_entry;
- // assert(ptr_type->id == TypeTableEntryIdPointer);
- // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
- // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
- // source_node, false).id == ConstCastResultIdOk)
- // {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // }
- //}
-
- //// cast from [N]T to ?[]const T
- //if (wanted_type->id == TypeTableEntryIdOptional &&
- // is_slice(wanted_type->data.maybe.child_type) &&
- // actual_type->id == TypeTableEntryIdArray)
- //{
- // TypeTableEntry *ptr_type =
- // wanted_type->data.maybe.child_type->data.structure.fields[slice_ptr_index].type_entry;
- // assert(ptr_type->id == TypeTableEntryIdPointer);
- // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
- // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
- // source_node, false).id == ConstCastResultIdOk)
- // {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.maybe.child_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // }
- //}
-
- //// *[N]T to [*]T
- //if (wanted_type->id == TypeTableEntryIdPointer &&
- // wanted_type->data.pointer.ptr_len == PtrLenUnknown &&
- // actual_type->id == TypeTableEntryIdPointer &&
- // actual_type->data.pointer.ptr_len == PtrLenSingle &&
- // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray &&
- // actual_type->data.pointer.alignment >= wanted_type->data.pointer.alignment &&
- // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
- // actual_type->data.pointer.child_type->data.array.child_type, source_node,
- // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
- //{
- // return ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, source_instr, value, wanted_type);
- //}
-
- //// *[N]T to []T
- //if (is_slice(wanted_type) &&
- // actual_type->id == TypeTableEntryIdPointer &&
- // actual_type->data.pointer.ptr_len == PtrLenSingle &&
- // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray)
- //{
- // TypeTableEntry *slice_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
- // assert(slice_ptr_type->id == TypeTableEntryIdPointer);
- // if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,
- // actual_type->data.pointer.child_type->data.array.child_type, source_node,
- // !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk)
- // {
- // return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type);
- // }
- //}
-
- //// cast from T to ?T
- //// note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism
- //if (wanted_type->id == TypeTableEntryIdOptional) {
- // TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type;
- // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node,
- // false).id == ConstCastResultIdOk)
- // {
- // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type);
- // } else if (actual_type->id == TypeTableEntryIdComptimeInt ||
- // actual_type->id == TypeTableEntryIdComptimeFloat)
- // {
- // if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) {
- // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type);
- // } else {
- // return ira->codegen->invalid_instruction;
- // }
- // } else if (wanted_child_type->id == TypeTableEntryIdPointer &&
- // wanted_child_type->data.pointer.is_const &&
- // (actual_type->id == TypeTableEntryIdPointer || is_container(actual_type)))
- // {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // }
- //}
-
- //// cast from null literal to maybe type
- //if (wanted_type->id == TypeTableEntryIdOptional &&
- // actual_type->id == TypeTableEntryIdNull)
- //{
- // return ir_analyze_null_to_maybe(ira, source_instr, value, wanted_type);
- //}
-
- //// cast from child type of error type to error type
- //if (wanted_type->id == TypeTableEntryIdErrorUnion) {
- // if (types_match_const_cast_only(ira, wanted_type->data.error_union.payload_type, actual_type,
- // source_node, false).id == ConstCastResultIdOk)
- // {
- // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type);
- // } else if (actual_type->id == TypeTableEntryIdComptimeInt ||
- // actual_type->id == TypeTableEntryIdComptimeFloat)
- // {
- // if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.error_union.payload_type, true)) {
- // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type);
- // } else {
- // return ira->codegen->invalid_instruction;
- // }
- // }
- //}
-
- //// cast from [N]T to E![]const T
- //if (wanted_type->id == TypeTableEntryIdErrorUnion &&
- // is_slice(wanted_type->data.error_union.payload_type) &&
- // actual_type->id == TypeTableEntryIdArray)
- //{
- // TypeTableEntry *ptr_type =
- // wanted_type->data.error_union.payload_type->data.structure.fields[slice_ptr_index].type_entry;
- // assert(ptr_type->id == TypeTableEntryIdPointer);
- // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
- // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
- // source_node, false).id == ConstCastResultIdOk)
- // {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // }
- //}
-
- //// cast from error set to error union type
- //if (wanted_type->id == TypeTableEntryIdErrorUnion &&
- // actual_type->id == TypeTableEntryIdErrorSet)
- //{
- // return ir_analyze_err_wrap_code(ira, source_instr, value, wanted_type);
- //}
-
- //// cast from T to E!?T
- //if (wanted_type->id == TypeTableEntryIdErrorUnion &&
- // wanted_type->data.error_union.payload_type->id == TypeTableEntryIdOptional &&
- // actual_type->id != TypeTableEntryIdOptional)
- //{
- // TypeTableEntry *wanted_child_type = wanted_type->data.error_union.payload_type->data.maybe.child_type;
- // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node, false).id == ConstCastResultIdOk ||
- // actual_type->id == TypeTableEntryIdNull ||
- // actual_type->id == TypeTableEntryIdComptimeInt ||
- // actual_type->id == TypeTableEntryIdComptimeFloat)
- // {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // }
- //}
-
- // cast from comptime-known integer to another integer where the value fits
- if (target.isCompTime() and (from_type.id == .Int or from_type.id == .ComptimeInt)) cast: {
- const target_val = target.val.KnownValue;
- const from_int = &target_val.cast(Value.Int).?.big_int;
- const fits = fits: {
- if (dest_type.cast(Type.ComptimeInt)) |ctint| {
- break :fits true;
- }
- if (dest_type.cast(Type.Int)) |int| {
- break :fits from_int.fitsInTwosComp(int.key.is_signed, int.key.bit_count);
- }
- break :cast;
- };
- if (!fits) {
- try ira.addCompileError(source_instr.span, "integer value '{}' cannot be stored in type '{}'", .{
- from_int,
- dest_type.name,
- });
- return error.SemanticAnalysisFailed;
- }
-
- const new_val = try target.copyVal(ira.irb.comp);
- new_val.setType(dest_type, ira.irb.comp);
- return ira.irb.buildConstValue(source_instr.scope, source_instr.span, new_val);
+ fn coerceArrayPtrToSlice(self: *Analyze, dest_type: Type, inst: *Inst) !*Inst {
+ if (inst.value()) |val| {
+ // The comptime Value representation is compatible with both types.
+ return self.constInst(inst.src, .{ .ty = dest_type, .val = val });
}
-
- // cast from number literal to another type
- // cast from number literal to *const integer
- //if (actual_type->id == TypeTableEntryIdComptimeFloat ||
- // actual_type->id == TypeTableEntryIdComptimeInt)
- //{
- // ensure_complete_type(ira->codegen, wanted_type);
- // if (type_is_invalid(wanted_type))
- // return ira->codegen->invalid_instruction;
- // if (wanted_type->id == TypeTableEntryIdEnum) {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.enumeration.tag_int_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // } else if (wanted_type->id == TypeTableEntryIdPointer &&
- // wanted_type->data.pointer.is_const)
- // {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // } else if (ir_num_lit_fits_in_other_type(ira, value, wanted_type, true)) {
- // CastOp op;
- // if ((actual_type->id == TypeTableEntryIdComptimeFloat &&
- // wanted_type->id == TypeTableEntryIdFloat) ||
- // (actual_type->id == TypeTableEntryIdComptimeInt &&
- // wanted_type->id == TypeTableEntryIdInt))
- // {
- // op = CastOpNumLitToConcrete;
- // } else if (wanted_type->id == TypeTableEntryIdInt) {
- // op = CastOpFloatToInt;
- // } else if (wanted_type->id == TypeTableEntryIdFloat) {
- // op = CastOpIntToFloat;
- // } else {
- // zig_unreachable();
- // }
- // return ir_resolve_cast(ira, source_instr, value, wanted_type, op, false);
- // } else {
- // return ira->codegen->invalid_instruction;
- // }
- //}
-
- //// cast from typed number to integer or float literal.
- //// works when the number is known at compile time
- //if (instr_is_comptime(value) &&
- // ((actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdComptimeInt) ||
- // (actual_type->id == TypeTableEntryIdFloat && wanted_type->id == TypeTableEntryIdComptimeFloat)))
- //{
- // return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type);
- //}
-
- //// cast from union to the enum type of the union
- //if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) {
- // type_ensure_zero_bits_known(ira->codegen, actual_type);
- // if (type_is_invalid(actual_type))
- // return ira->codegen->invalid_instruction;
-
- // if (actual_type->data.unionation.tag_type == wanted_type) {
- // return ir_analyze_union_to_tag(ira, source_instr, value, wanted_type);
- // }
- //}
-
- //// enum to union which has the enum as the tag type
- //if (wanted_type->id == TypeTableEntryIdUnion && actual_type->id == TypeTableEntryIdEnum &&
- // (wanted_type->data.unionation.decl_node->data.container_decl.auto_enum ||
- // wanted_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr))
- //{
- // type_ensure_zero_bits_known(ira->codegen, wanted_type);
- // if (wanted_type->data.unionation.tag_type == actual_type) {
- // return ir_analyze_enum_to_union(ira, source_instr, value, wanted_type);
- // }
- //}
-
- //// enum to &const union which has the enum as the tag type
- //if (actual_type->id == TypeTableEntryIdEnum && wanted_type->id == TypeTableEntryIdPointer) {
- // TypeTableEntry *union_type = wanted_type->data.pointer.child_type;
- // if (union_type->data.unionation.decl_node->data.container_decl.auto_enum ||
- // union_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)
- // {
- // type_ensure_zero_bits_known(ira->codegen, union_type);
- // if (union_type->data.unionation.tag_type == actual_type) {
- // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, union_type, value);
- // if (type_is_invalid(cast1->value.type))
- // return ira->codegen->invalid_instruction;
-
- // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
- // if (type_is_invalid(cast2->value.type))
- // return ira->codegen->invalid_instruction;
-
- // return cast2;
- // }
- // }
- //}
-
- //// cast from *T to *[1]T
- //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle &&
- // actual_type->id == TypeTableEntryIdPointer && actual_type->data.pointer.ptr_len == PtrLenSingle)
- //{
- // TypeTableEntry *array_type = wanted_type->data.pointer.child_type;
- // if (array_type->id == TypeTableEntryIdArray && array_type->data.array.len == 1 &&
- // types_match_const_cast_only(ira, array_type->data.array.child_type,
- // actual_type->data.pointer.child_type, source_node,
- // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
- // {
- // if (wanted_type->data.pointer.alignment > actual_type->data.pointer.alignment) {
- // ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("cast increases pointer alignment"));
- // add_error_note(ira->codegen, msg, value->source_node,
- // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&actual_type->name),
- // actual_type->data.pointer.alignment));
- // add_error_note(ira->codegen, msg, source_instr->source_node,
- // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&wanted_type->name),
- // wanted_type->data.pointer.alignment));
- // return ira->codegen->invalid_instruction;
- // }
- // return ir_analyze_ptr_to_array(ira, source_instr, value, wanted_type);
- // }
- //}
-
- //// cast from T to *T where T is zero bits
- //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle &&
- // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
- // actual_type, source_node, !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
- //{
- // type_ensure_zero_bits_known(ira->codegen, actual_type);
- // if (type_is_invalid(actual_type)) {
- // return ira->codegen->invalid_instruction;
- // }
- // if (!type_has_bits(actual_type)) {
- // return ir_get_ref(ira, source_instr, value, false, false);
- // }
- //}
-
- //// cast from undefined to anything
- //if (actual_type->id == TypeTableEntryIdUndefined) {
- // return ir_analyze_undefined_to_anything(ira, source_instr, value, wanted_type);
- //}
-
- //// cast from something to const pointer of it
- //if (!type_requires_comptime(actual_type)) {
- // TypeTableEntry *const_ptr_actual = get_pointer_to_type(ira->codegen, actual_type, true);
- // if (types_match_const_cast_only(ira, wanted_type, const_ptr_actual, source_node, false).id == ConstCastResultIdOk) {
- // return ir_analyze_cast_ref(ira, source_instr, value, wanted_type);
- // }
- //}
-
- try ira.addCompileError(source_instr.span, "expected type '{}', found '{}'", .{
- dest_type.name,
- from_type.name,
- });
- //ErrorMsg *parent_msg = ir_add_error_node(ira, source_instr->source_node,
- // buf_sprintf("expected type '%s', found '%s'",
- // buf_ptr(&wanted_type->name),
- // buf_ptr(&actual_type->name)));
- //report_recursive_error(ira, source_instr->source_node, &const_cast_result, parent_msg);
- return error.SemanticAnalysisFailed;
+ return self.fail(inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{});
}
- fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Inst) ?*Value {
- @panic("TODO");
+ fn fail(self: *Analyze, src: usize, comptime format: []const u8, args: var) InnerError {
+ @setCold(true);
+ const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args);
+ (try self.errors.addOne()).* = .{
+ .byte_offset = src,
+ .msg = msg,
+ };
+ return error.AnalysisFail;
}
- fn getCompTimeRef(
- self: *Analyze,
- value: *Value,
- ptr_mut: Value.Ptr.Mut,
- mut: Type.Pointer.Mut,
- volatility: Type.Pointer.Vol,
- ) Analyze.Error!*Inst {
- return error.Unimplemented;
- }
-};
+ const InMemoryCoercionResult = enum {
+ ok,
+ no_match,
+ };
-pub fn gen(
- comp: *Compilation,
- body_node: *ast.Node,
- tree_scope: *Scope.AstTree,
- scope: *Scope,
-) !*Code {
- var irb = try Builder.init(comp, tree_scope, scope);
- errdefer irb.abort();
-
- const entry_block = try irb.createBasicBlock(scope, "Entry");
- entry_block.ref(&irb); // Entry block gets a reference because we enter it to begin.
- try irb.setCursorAtEndAndAppendBlock(entry_block);
-
- const result = try irb.genNode(body_node, scope, .None);
- if (!result.isNoReturn()) {
- // no need for save_err_ret_addr because this cannot return error
- _ = try irb.genAsyncReturn(scope, Span.token(body_node.lastToken()), result, true);
- }
+ fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult {
+ if (dest_type.eql(src_type))
+ return .ok;
- return irb.finish();
-}
+ // TODO: implement more of this function
-pub fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code {
- const old_entry_bb = old_code.basic_block_list.at(0);
+ return .no_match;
+ }
+};
- var ira = try Analyze.init(comp, old_code.tree_scope, expected_type);
- errdefer ira.abort();
+pub fn main() anyerror!void {
+ var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
+ defer arena.deinit();
+ const allocator = if (std.builtin.link_libc) std.heap.c_allocator else &arena.allocator;
- const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null);
- new_entry_bb.ref(&ira.irb);
+ const args = try std.process.argsAlloc(allocator);
- ira.irb.current_basic_block = new_entry_bb;
+ const src_path = args[1];
+ const debug_error_trace = true;
- ira.startBasicBlock(old_entry_bb, null);
+ const source = try std.fs.cwd().readFileAllocOptions(allocator, src_path, std.math.maxInt(u32), 1, 0);
- while (ira.old_bb_index < old_code.basic_block_list.len) {
- const old_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index);
+ var zir_module = try text.parse(allocator, source);
+ defer zir_module.deinit(allocator);
- if (old_instruction.ref_count == 0 and !old_instruction.hasSideEffects()) {
- ira.instruction_index += 1;
- continue;
+ if (zir_module.errors.len != 0) {
+ for (zir_module.errors) |err_msg| {
+ const loc = findLineColumn(source, err_msg.byte_offset);
+ std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
}
+ if (debug_error_trace) return error.ParseFailure;
+ std.process.exit(1);
+ }
- const return_inst = try old_instruction.analyze(&ira);
- assert(return_inst.val != IrVal.Unknown); // at least the type should be known at this point
- return_inst.linkToParent(old_instruction);
- // Note: if we ever modify the above to handle error.CompileError by continuing analysis,
- // then here we want to check if ira.isCompTime() and return early if true
+ var analyzed_module = try analyze(allocator, zir_module);
+ defer analyzed_module.deinit(allocator);
- if (return_inst.isNoReturn()) {
- try ira.finishBasicBlock(old_code);
- continue;
+ if (analyzed_module.errors.len != 0) {
+ for (analyzed_module.errors) |err_msg| {
+ const loc = findLineColumn(source, err_msg.byte_offset);
+ std.debug.warn("{}:{}:{}: error: {}\n", .{ src_path, loc.line + 1, loc.column + 1, err_msg.msg });
}
-
- ira.instruction_index += 1;
+ if (debug_error_trace) return error.ParseFailure;
+ std.process.exit(1);
}
- if (ira.src_implicit_return_type_list.len == 0) {
- ira.irb.code.return_type = &Type.NoReturn.get(comp).base;
- return ira.irb.finish();
- }
+ var new_zir_module = try text.emit_zir(allocator, analyzed_module);
+ defer new_zir_module.deinit(allocator);
- ira.irb.code.return_type = try ira.resolvePeerTypes(expected_type, ira.src_implicit_return_type_list.span());
- return ira.irb.finish();
+ var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream());
+ try new_zir_module.writeToStream(allocator, bos.outStream());
+ try bos.flush();
}
+
+fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } {
+ var line: usize = 0;
+ var column: usize = 0;
+ for (source[0..byte_offset]) |byte| {
+ switch (byte) {
+ '\n' => {
+ line += 1;
+ column = 0;
+ },
+ else => {
+ column += 1;
+ },
+ }
+ }
+ return .{ .line = line, .column = column };
+}
+
+// Performance optimization ideas:
+// * when analyzing use a field in the Inst instead of HashMap to track corresponding instructions
diff --git a/src-self-hosted/ir/text.zig b/src-self-hosted/ir/text.zig
@@ -0,0 +1,1065 @@
+//! This file has to do with parsing and rendering the ZIR text format.
+
+const std = @import("std");
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
+const BigInt = std.math.big.Int;
+const Type = @import("../type.zig").Type;
+const Value = @import("../value.zig").Value;
+const ir = @import("../ir.zig");
+
+/// These are instructions that correspond to the ZIR text format. See `ir.Inst` for
+/// in-memory, analyzed instructions with types and values.
+pub const Inst = struct {
+ tag: Tag,
+ /// Byte offset into the source.
+ src: usize,
+
+ /// These names are used directly as the instruction names in the text format.
+ pub const Tag = enum {
+ str,
+ int,
+ ptrtoint,
+ fieldptr,
+ deref,
+ as,
+ @"asm",
+ @"unreachable",
+ @"fn",
+ @"export",
+ primitive,
+ fntype,
+ intcast,
+ };
+
+ pub fn TagToType(tag: Tag) type {
+ return switch (tag) {
+ .str => Str,
+ .int => Int,
+ .ptrtoint => PtrToInt,
+ .fieldptr => FieldPtr,
+ .deref => Deref,
+ .as => As,
+ .@"asm" => Asm,
+ .@"unreachable" => Unreachable,
+ .@"fn" => Fn,
+ .@"export" => Export,
+ .primitive => Primitive,
+ .fntype => FnType,
+ .intcast => IntCast,
+ };
+ }
+
+ pub fn cast(base: *Inst, comptime T: type) ?*T {
+ if (base.tag != T.base_tag)
+ return null;
+
+ return @fieldParentPtr(T, "base", base);
+ }
+
+ pub const Str = struct {
+ pub const base_tag = Tag.str;
+ base: Inst,
+
+ positionals: struct {
+ bytes: []const u8,
+ },
+ kw_args: struct {},
+ };
+
+ pub const Int = struct {
+ pub const base_tag = Tag.int;
+ base: Inst,
+
+ positionals: struct {
+ int: BigInt,
+ },
+ kw_args: struct {},
+ };
+
+ pub const PtrToInt = struct {
+ pub const base_tag = Tag.ptrtoint;
+ base: Inst,
+
+ positionals: struct {
+ ptr: *Inst,
+ },
+ kw_args: struct {},
+ };
+
+ pub const FieldPtr = struct {
+ pub const base_tag = Tag.fieldptr;
+ base: Inst,
+
+ positionals: struct {
+ object_ptr: *Inst,
+ field_name: *Inst,
+ },
+ kw_args: struct {},
+ };
+
+ pub const Deref = struct {
+ pub const base_tag = Tag.deref;
+ base: Inst,
+
+ positionals: struct {
+ ptr: *Inst,
+ },
+ kw_args: struct {},
+ };
+
+ pub const As = struct {
+ pub const base_tag = Tag.as;
+ base: Inst,
+
+ positionals: struct {
+ dest_type: *Inst,
+ value: *Inst,
+ },
+ kw_args: struct {},
+ };
+
+ pub const Asm = struct {
+ pub const base_tag = Tag.@"asm";
+ base: Inst,
+
+ positionals: struct {
+ asm_source: *Inst,
+ return_type: *Inst,
+ },
+ kw_args: struct {
+ @"volatile": bool = false,
+ output: ?*Inst = null,
+ inputs: []*Inst = &[0]*Inst{},
+ clobbers: []*Inst = &[0]*Inst{},
+ args: []*Inst = &[0]*Inst{},
+ },
+ };
+
+ pub const Unreachable = struct {
+ pub const base_tag = Tag.@"unreachable";
+ base: Inst,
+
+ positionals: struct {},
+ kw_args: struct {},
+ };
+
+ pub const Fn = struct {
+ pub const base_tag = Tag.@"fn";
+ base: Inst,
+
+ positionals: struct {
+ fn_type: *Inst,
+ body: Body,
+ },
+ kw_args: struct {},
+
+ pub const Body = struct {
+ instructions: []*Inst,
+ };
+ };
+
+ pub const Export = struct {
+ pub const base_tag = Tag.@"export";
+ base: Inst,
+
+ positionals: struct {
+ symbol_name: *Inst,
+ value: *Inst,
+ },
+ kw_args: struct {},
+ };
+
+ pub const Primitive = struct {
+ pub const base_tag = Tag.primitive;
+ base: Inst,
+
+ positionals: struct {
+ tag: BuiltinType,
+ },
+ kw_args: struct {},
+
+ pub const BuiltinType = enum {
+ @"isize",
+ @"usize",
+ @"c_short",
+ @"c_ushort",
+ @"c_int",
+ @"c_uint",
+ @"c_long",
+ @"c_ulong",
+ @"c_longlong",
+ @"c_ulonglong",
+ @"c_longdouble",
+ @"c_void",
+ @"f16",
+ @"f32",
+ @"f64",
+ @"f128",
+ @"bool",
+ @"void",
+ @"noreturn",
+ @"type",
+ @"anyerror",
+ @"comptime_int",
+ @"comptime_float",
+
+ fn toType(self: BuiltinType) Type {
+ return switch (self) {
+ .@"isize" => Type.initTag(.@"isize"),
+ .@"usize" => Type.initTag(.@"usize"),
+ .@"c_short" => Type.initTag(.@"c_short"),
+ .@"c_ushort" => Type.initTag(.@"c_ushort"),
+ .@"c_int" => Type.initTag(.@"c_int"),
+ .@"c_uint" => Type.initTag(.@"c_uint"),
+ .@"c_long" => Type.initTag(.@"c_long"),
+ .@"c_ulong" => Type.initTag(.@"c_ulong"),
+ .@"c_longlong" => Type.initTag(.@"c_longlong"),
+ .@"c_ulonglong" => Type.initTag(.@"c_ulonglong"),
+ .@"c_longdouble" => Type.initTag(.@"c_longdouble"),
+ .@"c_void" => Type.initTag(.@"c_void"),
+ .@"f16" => Type.initTag(.@"f16"),
+ .@"f32" => Type.initTag(.@"f32"),
+ .@"f64" => Type.initTag(.@"f64"),
+ .@"f128" => Type.initTag(.@"f128"),
+ .@"bool" => Type.initTag(.@"bool"),
+ .@"void" => Type.initTag(.@"void"),
+ .@"noreturn" => Type.initTag(.@"noreturn"),
+ .@"type" => Type.initTag(.@"type"),
+ .@"anyerror" => Type.initTag(.@"anyerror"),
+ .@"comptime_int" => Type.initTag(.@"comptime_int"),
+ .@"comptime_float" => Type.initTag(.@"comptime_float"),
+ };
+ }
+ };
+ };
+
+ pub const FnType = struct {
+ pub const base_tag = Tag.fntype;
+ base: Inst,
+
+ positionals: struct {
+ param_types: []*Inst,
+ return_type: *Inst,
+ },
+ kw_args: struct {
+ cc: std.builtin.CallingConvention = .Unspecified,
+ },
+ };
+
+ pub const IntCast = struct {
+ pub const base_tag = Tag.intcast;
+ base: Inst,
+
+ positionals: struct {
+ dest_type: *Inst,
+ value: *Inst,
+ },
+ kw_args: struct {},
+ };
+};
+
+pub const ErrorMsg = struct {
+ byte_offset: usize,
+ msg: []const u8,
+};
+
+pub const Module = struct {
+ decls: []*Inst,
+ errors: []ErrorMsg,
+ arena: std.heap.ArenaAllocator,
+
+ pub fn deinit(self: *Module, allocator: *Allocator) void {
+ allocator.free(self.decls);
+ allocator.free(self.errors);
+ self.arena.deinit();
+ self.* = undefined;
+ }
+
+ /// This is a debugging utility for rendering the tree to stderr.
+ pub fn dump(self: Module) void {
+ self.writeToStream(std.heap.page_allocator, std.io.getStdErr().outStream()) catch {};
+ }
+
+ const InstPtrTable = std.AutoHashMap(*Inst, struct { index: usize, fn_body: ?*Inst.Fn.Body });
+
+ /// The allocator is used for temporary storage, but this function always returns
+ /// with no resources allocated.
+ pub fn writeToStream(self: Module, allocator: *Allocator, stream: var) !void {
+ // First, build a map of *Inst to @ or % indexes
+ var inst_table = InstPtrTable.init(allocator);
+ defer inst_table.deinit();
+
+ try inst_table.ensureCapacity(self.decls.len);
+
+ for (self.decls) |decl, decl_i| {
+ try inst_table.putNoClobber(decl, .{ .index = decl_i, .fn_body = null });
+
+ if (decl.cast(Inst.Fn)) |fn_inst| {
+ for (fn_inst.positionals.body.instructions) |inst, inst_i| {
+ try inst_table.putNoClobber(inst, .{ .index = inst_i, .fn_body = &fn_inst.positionals.body });
+ }
+ }
+ }
+
+ for (self.decls) |decl, i| {
+ try stream.print("@{} ", .{i});
+ try self.writeInstToStream(stream, decl, &inst_table);
+ try stream.writeByte('\n');
+ }
+ }
+
+ fn writeInstToStream(
+ self: Module,
+ stream: var,
+ decl: *Inst,
+ inst_table: *const InstPtrTable,
+ ) @TypeOf(stream).Error!void {
+ // TODO I tried implementing this with an inline for loop and hit a compiler bug
+ switch (decl.tag) {
+ .str => return self.writeInstToStreamGeneric(stream, .str, decl, inst_table),
+ .int => return self.writeInstToStreamGeneric(stream, .int, decl, inst_table),
+ .ptrtoint => return self.writeInstToStreamGeneric(stream, .ptrtoint, decl, inst_table),
+ .fieldptr => return self.writeInstToStreamGeneric(stream, .fieldptr, decl, inst_table),
+ .deref => return self.writeInstToStreamGeneric(stream, .deref, decl, inst_table),
+ .as => return self.writeInstToStreamGeneric(stream, .as, decl, inst_table),
+ .@"asm" => return self.writeInstToStreamGeneric(stream, .@"asm", decl, inst_table),
+ .@"unreachable" => return self.writeInstToStreamGeneric(stream, .@"unreachable", decl, inst_table),
+ .@"fn" => return self.writeInstToStreamGeneric(stream, .@"fn", decl, inst_table),
+ .@"export" => return self.writeInstToStreamGeneric(stream, .@"export", decl, inst_table),
+ .primitive => return self.writeInstToStreamGeneric(stream, .primitive, decl, inst_table),
+ .fntype => return self.writeInstToStreamGeneric(stream, .fntype, decl, inst_table),
+ .intcast => return self.writeInstToStreamGeneric(stream, .intcast, decl, inst_table),
+ }
+ }
+
+ fn writeInstToStreamGeneric(
+ self: Module,
+ stream: var,
+ comptime inst_tag: Inst.Tag,
+ base: *Inst,
+ inst_table: *const InstPtrTable,
+ ) !void {
+ const SpecificInst = Inst.TagToType(inst_tag);
+ const inst = @fieldParentPtr(SpecificInst, "base", base);
+ const Positionals = @TypeOf(inst.positionals);
+ try stream.writeAll("= " ++ @tagName(inst_tag) ++ "(");
+ const pos_fields = @typeInfo(Positionals).Struct.fields;
+ inline for (pos_fields) |arg_field, i| {
+ if (i != 0) {
+ try stream.writeAll(", ");
+ }
+ try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table);
+ }
+
+ comptime var need_comma = pos_fields.len != 0;
+ const KW_Args = @TypeOf(inst.kw_args);
+ inline for (@typeInfo(KW_Args).Struct.fields) |arg_field, i| {
+ if (@typeInfo(arg_field.field_type) == .Optional) {
+ if (@field(inst.kw_args, arg_field.name)) |non_optional| {
+ if (need_comma) try stream.writeAll(", ");
+ try stream.print("{}=", .{arg_field.name});
+ try self.writeParamToStream(stream, non_optional, inst_table);
+ need_comma = true;
+ }
+ } else {
+ if (need_comma) try stream.writeAll(", ");
+ try stream.print("{}=", .{arg_field.name});
+ try self.writeParamToStream(stream, @field(inst.kw_args, arg_field.name), inst_table);
+ need_comma = true;
+ }
+ }
+
+ try stream.writeByte(')');
+ }
+
+ fn writeParamToStream(self: Module, stream: var, param: var, inst_table: *const InstPtrTable) !void {
+ if (@typeInfo(@TypeOf(param)) == .Enum) {
+ return stream.writeAll(@tagName(param));
+ }
+ switch (@TypeOf(param)) {
+ *Inst => return self.writeInstParamToStream(stream, param, inst_table),
+ []*Inst => {
+ try stream.writeByte('[');
+ for (param) |inst, i| {
+ if (i != 0) {
+ try stream.writeAll(", ");
+ }
+ try self.writeInstParamToStream(stream, inst, inst_table);
+ }
+ try stream.writeByte(']');
+ },
+ Inst.Fn.Body => {
+ try stream.writeAll("{\n");
+ for (param.instructions) |inst, i| {
+ try stream.print(" %{} ", .{i});
+ try self.writeInstToStream(stream, inst, inst_table);
+ try stream.writeByte('\n');
+ }
+ try stream.writeByte('}');
+ },
+ bool => return stream.writeByte("01"[@boolToInt(param)]),
+ []u8, []const u8 => return std.zig.renderStringLiteral(param, stream),
+ BigInt => return stream.print("{}", .{param}),
+ else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
+ }
+ }
+
+ fn writeInstParamToStream(self: Module, stream: var, inst: *Inst, inst_table: *const InstPtrTable) !void {
+ const info = inst_table.getValue(inst).?;
+ const prefix = if (info.fn_body == null) "@" else "%";
+ try stream.print("{}{}", .{ prefix, info.index });
+ }
+};
+
+pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module {
+ var global_name_map = std.StringHashMap(usize).init(allocator);
+ defer global_name_map.deinit();
+
+ var parser: Parser = .{
+ .allocator = allocator,
+ .arena = std.heap.ArenaAllocator.init(allocator),
+ .i = 0,
+ .source = source,
+ .decls = std.ArrayList(*Inst).init(allocator),
+ .errors = std.ArrayList(ErrorMsg).init(allocator),
+ .global_name_map = &global_name_map,
+ };
+ errdefer parser.arena.deinit();
+
+ parser.parseRoot() catch |err| switch (err) {
+ error.ParseFailure => {
+ assert(parser.errors.items.len != 0);
+ },
+ else => |e| return e,
+ };
+ return Module{
+ .decls = parser.decls.toOwnedSlice(),
+ .errors = parser.errors.toOwnedSlice(),
+ .arena = parser.arena,
+ };
+}
+
+const Parser = struct {
+ allocator: *Allocator,
+ arena: std.heap.ArenaAllocator,
+ i: usize,
+ source: [:0]const u8,
+ errors: std.ArrayList(ErrorMsg),
+ decls: std.ArrayList(*Inst),
+ global_name_map: *std.StringHashMap(usize),
+
+ const Body = struct {
+ instructions: std.ArrayList(*Inst),
+ name_map: std.StringHashMap(usize),
+ };
+
+ fn parseBody(self: *Parser) !Inst.Fn.Body {
+ var body_context = Body{
+ .instructions = std.ArrayList(*Inst).init(self.allocator),
+ .name_map = std.StringHashMap(usize).init(self.allocator),
+ };
+ defer body_context.instructions.deinit();
+ defer body_context.name_map.deinit();
+
+ try requireEatBytes(self, "{");
+ skipSpace(self);
+
+ while (true) : (self.i += 1) switch (self.source[self.i]) {
+ ';' => _ = try skipToAndOver(self, '\n'),
+ '%' => {
+ self.i += 1;
+ const ident = try skipToAndOver(self, ' ');
+ skipSpace(self);
+ try requireEatBytes(self, "=");
+ skipSpace(self);
+ const inst = try parseInstruction(self, &body_context);
+ const ident_index = body_context.instructions.items.len;
+ if (try body_context.name_map.put(ident, ident_index)) |_| {
+ return self.fail("redefinition of identifier '{}'", .{ident});
+ }
+ try body_context.instructions.append(inst);
+ continue;
+ },
+ ' ', '\n' => continue,
+ '}' => {
+ self.i += 1;
+ break;
+ },
+ else => |byte| return self.failByte(byte),
+ };
+
+ return Inst.Fn.Body{
+ .instructions = body_context.instructions.toOwnedSlice(),
+ };
+ }
+
+ fn parseStringLiteral(self: *Parser) ![]u8 {
+ const start = self.i;
+ try self.requireEatBytes("\"");
+
+ while (true) : (self.i += 1) switch (self.source[self.i]) {
+ '"' => {
+ self.i += 1;
+ const span = self.source[start..self.i];
+ var bad_index: usize = undefined;
+ const parsed = std.zig.parseStringLiteral(&self.arena.allocator, span, &bad_index) catch |err| switch (err) {
+ error.InvalidCharacter => {
+ self.i = start + bad_index;
+ const bad_byte = self.source[self.i];
+ return self.fail("invalid string literal character: '{c}'\n", .{bad_byte});
+ },
+ else => |e| return e,
+ };
+ return parsed;
+ },
+ '\\' => {
+ self.i += 1;
+ continue;
+ },
+ 0 => return self.failByte(0),
+ else => continue,
+ };
+ }
+
+ fn parseIntegerLiteral(self: *Parser) !BigInt {
+ const start = self.i;
+ if (self.source[self.i] == '-') self.i += 1;
+ while (true) : (self.i += 1) switch (self.source[self.i]) {
+ '0'...'9' => continue,
+ else => break,
+ };
+ const number_text = self.source[start..self.i];
+ var result = try BigInt.init(&self.arena.allocator);
+ result.setString(10, number_text) catch |err| {
+ self.i = start;
+ switch (err) {
+ error.InvalidBase => unreachable,
+ error.InvalidCharForDigit => return self.fail("invalid digit in integer literal", .{}),
+ error.DigitTooLargeForBase => return self.fail("digit too large in integer literal", .{}),
+ else => |e| return e,
+ }
+ };
+ return result;
+ }
+
+ fn parseRoot(self: *Parser) !void {
+ // The IR format is designed so that it can be tokenized and parsed at the same time.
+ while (true) : (self.i += 1) switch (self.source[self.i]) {
+ ';' => _ = try skipToAndOver(self, '\n'),
+ '@' => {
+ self.i += 1;
+ const ident = try skipToAndOver(self, ' ');
+ skipSpace(self);
+ try requireEatBytes(self, "=");
+ skipSpace(self);
+ const inst = try parseInstruction(self, null);
+ const ident_index = self.decls.items.len;
+ if (try self.global_name_map.put(ident, ident_index)) |_| {
+ return self.fail("redefinition of identifier '{}'", .{ident});
+ }
+ try self.decls.append(inst);
+ continue;
+ },
+ ' ', '\n' => continue,
+ 0 => break,
+ else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
+ };
+ }
+
+ fn eatByte(self: *Parser, byte: u8) bool {
+ if (self.source[self.i] != byte) return false;
+ self.i += 1;
+ return true;
+ }
+
+ fn skipSpace(self: *Parser) void {
+ while (self.source[self.i] == ' ' or self.source[self.i] == '\n') {
+ self.i += 1;
+ }
+ }
+
+ fn requireEatBytes(self: *Parser, bytes: []const u8) !void {
+ const start = self.i;
+ for (bytes) |byte| {
+ if (self.source[self.i] != byte) {
+ self.i = start;
+ return self.fail("expected '{}'", .{bytes});
+ }
+ self.i += 1;
+ }
+ }
+
+ fn skipToAndOver(self: *Parser, byte: u8) ![]const u8 {
+ const start_i = self.i;
+ while (self.source[self.i] != 0) : (self.i += 1) {
+ if (self.source[self.i] == byte) {
+ const result = self.source[start_i..self.i];
+ self.i += 1;
+ return result;
+ }
+ }
+ return self.fail("unexpected EOF", .{});
+ }
+
+ /// ParseFailure is an internal error code; handled in `parse`.
+ const InnerError = error{ ParseFailure, OutOfMemory };
+
+ fn failByte(self: *Parser, byte: u8) InnerError {
+ if (byte == 0) {
+ return self.fail("unexpected EOF", .{});
+ } else {
+ return self.fail("unexpected byte: '{c}'", .{byte});
+ }
+ }
+
+ fn fail(self: *Parser, comptime format: []const u8, args: var) InnerError {
+ @setCold(true);
+ const msg = try std.fmt.allocPrint(&self.arena.allocator, format, args);
+ (try self.errors.addOne()).* = .{
+ .byte_offset = self.i,
+ .msg = msg,
+ };
+ return error.ParseFailure;
+ }
+
+ fn parseInstruction(self: *Parser, body_ctx: ?*Body) InnerError!*Inst {
+ const fn_name = try skipToAndOver(self, '(');
+ inline for (@typeInfo(Inst.Tag).Enum.fields) |field| {
+ if (mem.eql(u8, field.name, fn_name)) {
+ const tag = @field(Inst.Tag, field.name);
+ return parseInstructionGeneric(self, field.name, Inst.TagToType(tag), body_ctx);
+ }
+ }
+ return self.fail("unknown instruction '{}'", .{fn_name});
+ }
+
+ fn parseInstructionGeneric(
+ self: *Parser,
+ comptime fn_name: []const u8,
+ comptime InstType: type,
+ body_ctx: ?*Body,
+ ) !*Inst {
+ const inst_specific = try self.arena.allocator.create(InstType);
+ inst_specific.base = .{
+ .src = self.i,
+ .tag = InstType.base_tag,
+ };
+
+ if (@hasField(InstType, "ty")) {
+ inst_specific.ty = opt_type orelse {
+ return self.fail("instruction '" ++ fn_name ++ "' requires type", .{});
+ };
+ }
+
+ const Positionals = @TypeOf(inst_specific.positionals);
+ inline for (@typeInfo(Positionals).Struct.fields) |arg_field| {
+ if (self.source[self.i] == ',') {
+ self.i += 1;
+ skipSpace(self);
+ } else if (self.source[self.i] == ')') {
+ return self.fail("expected positional parameter '{}'", .{arg_field.name});
+ }
+ @field(inst_specific.positionals, arg_field.name) = try parseParameterGeneric(
+ self,
+ arg_field.field_type,
+ body_ctx,
+ );
+ skipSpace(self);
+ }
+
+ const KW_Args = @TypeOf(inst_specific.kw_args);
+ inst_specific.kw_args = .{}; // assign defaults
+ skipSpace(self);
+ while (eatByte(self, ',')) {
+ skipSpace(self);
+ const name = try skipToAndOver(self, '=');
+ inline for (@typeInfo(KW_Args).Struct.fields) |arg_field| {
+ const field_name = arg_field.name;
+ if (mem.eql(u8, name, field_name)) {
+ const NonOptional = switch (@typeInfo(arg_field.field_type)) {
+ .Optional => |info| info.child,
+ else => arg_field.field_type,
+ };
+ @field(inst_specific.kw_args, field_name) = try parseParameterGeneric(self, NonOptional, body_ctx);
+ break;
+ }
+ } else {
+ return self.fail("unrecognized keyword parameter: '{}'", .{name});
+ }
+ skipSpace(self);
+ }
+ try requireEatBytes(self, ")");
+
+ return &inst_specific.base;
+ }
+
+ fn parseParameterGeneric(self: *Parser, comptime T: type, body_ctx: ?*Body) !T {
+ if (@typeInfo(T) == .Enum) {
+ const start = self.i;
+ while (true) : (self.i += 1) switch (self.source[self.i]) {
+ ' ', '\n', ',', ')' => {
+ const enum_name = self.source[start..self.i];
+ return std.meta.stringToEnum(T, enum_name) orelse {
+ return self.fail("tag '{}' not a member of enum '{}'", .{ enum_name, @typeName(T) });
+ };
+ },
+ 0 => return self.failByte(0),
+ else => continue,
+ };
+ }
+ switch (T) {
+ Inst.Fn.Body => return parseBody(self),
+ bool => {
+ const bool_value = switch (self.source[self.i]) {
+ '0' => false,
+ '1' => true,
+ else => |byte| return self.fail("expected '0' or '1' for boolean value, found {c}", .{byte}),
+ };
+ self.i += 1;
+ return bool_value;
+ },
+ []*Inst => {
+ try requireEatBytes(self, "[");
+ skipSpace(self);
+ if (eatByte(self, ']')) return &[0]*Inst{};
+
+ var instructions = std.ArrayList(*Inst).init(&self.arena.allocator);
+ while (true) {
+ skipSpace(self);
+ try instructions.append(try parseParameterInst(self, body_ctx));
+ skipSpace(self);
+ if (!eatByte(self, ',')) break;
+ }
+ try requireEatBytes(self, "]");
+ return instructions.toOwnedSlice();
+ },
+ *Inst => return parseParameterInst(self, body_ctx),
+ []u8, []const u8 => return self.parseStringLiteral(),
+ BigInt => return self.parseIntegerLiteral(),
+ else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)),
+ }
+ return self.fail("TODO parse parameter {}", .{@typeName(T)});
+ }
+
+ fn parseParameterInst(self: *Parser, body_ctx: ?*Body) !*Inst {
+ const local_ref = switch (self.source[self.i]) {
+ '@' => false,
+ '%' => true,
+ else => |byte| return self.fail("unexpected byte: '{c}'", .{byte}),
+ };
+ const map = if (local_ref)
+ if (body_ctx) |bc|
+ &bc.name_map
+ else
+ return self.fail("referencing a % instruction in global scope", .{})
+ else
+ self.global_name_map;
+
+ self.i += 1;
+ const name_start = self.i;
+ while (true) : (self.i += 1) switch (self.source[self.i]) {
+ 0, ' ', '\n', ',', ')', ']' => break,
+ else => continue,
+ };
+ const ident = self.source[name_start..self.i];
+ const kv = map.get(ident) orelse {
+ const bad_name = self.source[name_start - 1 .. self.i];
+ self.i = name_start - 1;
+ return self.fail("unrecognized identifier: {}", .{bad_name});
+ };
+ if (local_ref) {
+ return body_ctx.?.instructions.items[kv.value];
+ } else {
+ return self.decls.items[kv.value];
+ }
+ }
+};
+
+pub fn emit_zir(allocator: *Allocator, old_module: ir.Module) !Module {
+ var ctx: EmitZIR = .{
+ .allocator = allocator,
+ .decls = std.ArrayList(*Inst).init(allocator),
+ .decl_table = std.AutoHashMap(*ir.Inst, *Inst).init(allocator),
+ .arena = std.heap.ArenaAllocator.init(allocator),
+ .old_module = &old_module,
+ };
+ defer ctx.decls.deinit();
+ defer ctx.decl_table.deinit();
+ errdefer ctx.arena.deinit();
+
+ try ctx.emit();
+
+ return Module{
+ .decls = ctx.decls.toOwnedSlice(),
+ .arena = ctx.arena,
+ .errors = &[0]ErrorMsg{},
+ };
+}
+
+const EmitZIR = struct {
+ allocator: *Allocator,
+ arena: std.heap.ArenaAllocator,
+ old_module: *const ir.Module,
+ decls: std.ArrayList(*Inst),
+ decl_table: std.AutoHashMap(*ir.Inst, *Inst),
+
+ fn emit(self: *EmitZIR) !void {
+ for (self.old_module.exports) |module_export| {
+ const export_value = try self.emitTypedValue(module_export.src, module_export.typed_value);
+ const symbol_name = try self.emitStringLiteral(module_export.src, module_export.name);
+ const export_inst = try self.arena.allocator.create(Inst.Export);
+ export_inst.* = .{
+ .base = .{ .src = module_export.src, .tag = Inst.Export.base_tag },
+ .positionals = .{
+ .symbol_name = symbol_name,
+ .value = export_value,
+ },
+ .kw_args = .{},
+ };
+ try self.decls.append(&export_inst.base);
+ }
+ }
+
+ fn resolveInst(self: *EmitZIR, inst_table: *const std.AutoHashMap(*ir.Inst, *Inst), inst: *ir.Inst) !*Inst {
+ if (inst.cast(ir.Inst.Constant)) |const_inst| {
+ if (self.decl_table.getValue(inst)) |decl| {
+ return decl;
+ }
+ const new_decl = try self.emitTypedValue(inst.src, .{ .ty = inst.ty, .val = const_inst.val });
+ try self.decl_table.putNoClobber(inst, new_decl);
+ return new_decl;
+ } else {
+ return inst_table.getValue(inst).?;
+ }
+ }
+
+ fn emitComptimeIntVal(self: *EmitZIR, src: usize, val: Value) !*Inst {
+ const int_inst = try self.arena.allocator.create(Inst.Int);
+ int_inst.* = .{
+ .base = .{ .src = src, .tag = Inst.Int.base_tag },
+ .positionals = .{
+ .int = try val.toBigInt(&self.arena.allocator),
+ },
+ .kw_args = .{},
+ };
+ try self.decls.append(&int_inst.base);
+ return &int_inst.base;
+ }
+
+ fn emitTypedValue(self: *EmitZIR, src: usize, typed_value: ir.TypedValue) Allocator.Error!*Inst {
+ switch (typed_value.ty.zigTypeTag()) {
+ .Pointer => {
+ const ptr_elem_type = typed_value.ty.elemType();
+ switch (ptr_elem_type.zigTypeTag()) {
+ .Array => {
+ // TODO more checks to make sure this can be emitted as a string literal
+ //const array_elem_type = ptr_elem_type.elemType();
+ //if (array_elem_type.eql(Type.initTag(.u8)) and
+ // ptr_elem_type.hasSentinel(Value.initTag(.zero)))
+ //{
+ //}
+ const bytes = try typed_value.val.toAllocatedBytes(&self.arena.allocator);
+ return self.emitStringLiteral(src, bytes);
+ },
+ else => |t| std.debug.panic("TODO implement emitTypedValue for pointer to {}", .{@tagName(t)}),
+ }
+ },
+ .ComptimeInt => return self.emitComptimeIntVal(src, typed_value.val),
+ .Int => {
+ const as_inst = try self.arena.allocator.create(Inst.As);
+ as_inst.* = .{
+ .base = .{ .src = src, .tag = Inst.As.base_tag },
+ .positionals = .{
+ .dest_type = try self.emitType(src, typed_value.ty),
+ .value = try self.emitComptimeIntVal(src, typed_value.val),
+ },
+ .kw_args = .{},
+ };
+ try self.decls.append(&as_inst.base);
+
+ return &as_inst.base;
+ },
+ .Type => {
+ const ty = typed_value.val.toType();
+ return self.emitType(src, ty);
+ },
+ .Fn => {
+ const index = typed_value.val.cast(Value.Payload.Function).?.index;
+ const module_fn = self.old_module.fns[index];
+
+ var inst_table = std.AutoHashMap(*ir.Inst, *Inst).init(self.allocator);
+ defer inst_table.deinit();
+
+ var instructions = std.ArrayList(*Inst).init(self.allocator);
+ defer instructions.deinit();
+
+ for (module_fn.body) |inst| {
+ const new_inst = switch (inst.tag) {
+ .unreach => blk: {
+ const unreach_inst = try self.arena.allocator.create(Inst.Unreachable);
+ unreach_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.Unreachable.base_tag },
+ .positionals = .{},
+ .kw_args = .{},
+ };
+ break :blk &unreach_inst.base;
+ },
+ .constant => unreachable, // excluded from function bodies
+ .assembly => blk: {
+ const old_inst = inst.cast(ir.Inst.Assembly).?;
+ const new_inst = try self.arena.allocator.create(Inst.Asm);
+
+ const inputs = try self.arena.allocator.alloc(*Inst, old_inst.args.inputs.len);
+ for (inputs) |*elem, i| {
+ elem.* = try self.emitStringLiteral(inst.src, old_inst.args.inputs[i]);
+ }
+
+ const clobbers = try self.arena.allocator.alloc(*Inst, old_inst.args.clobbers.len);
+ for (clobbers) |*elem, i| {
+ elem.* = try self.emitStringLiteral(inst.src, old_inst.args.clobbers[i]);
+ }
+
+ const args = try self.arena.allocator.alloc(*Inst, old_inst.args.args.len);
+ for (args) |*elem, i| {
+ elem.* = try self.resolveInst(&inst_table, old_inst.args.args[i]);
+ }
+
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.Asm.base_tag },
+ .positionals = .{
+ .asm_source = try self.emitStringLiteral(inst.src, old_inst.args.asm_source),
+ .return_type = try self.emitType(inst.src, inst.ty),
+ },
+ .kw_args = .{
+ .@"volatile" = old_inst.args.is_volatile,
+ .output = if (old_inst.args.output) |o|
+ try self.emitStringLiteral(inst.src, o)
+ else
+ null,
+ .inputs = inputs,
+ .clobbers = clobbers,
+ .args = args,
+ },
+ };
+ break :blk &new_inst.base;
+ },
+ .ptrtoint => blk: {
+ const old_inst = inst.cast(ir.Inst.PtrToInt).?;
+ const new_inst = try self.arena.allocator.create(Inst.PtrToInt);
+ new_inst.* = .{
+ .base = .{ .src = inst.src, .tag = Inst.PtrToInt.base_tag },
+ .positionals = .{
+ .ptr = try self.resolveInst(&inst_table, old_inst.args.ptr),
+ },
+ .kw_args = .{},
+ };
+ break :blk &new_inst.base;
+ },
+ };
+ try instructions.append(new_inst);
+ try inst_table.putNoClobber(inst, new_inst);
+ }
+
+ const fn_type = try self.emitType(src, module_fn.fn_type);
+
+ const fn_inst = try self.arena.allocator.create(Inst.Fn);
+ fn_inst.* = .{
+ .base = .{ .src = src, .tag = Inst.Fn.base_tag },
+ .positionals = .{
+ .fn_type = fn_type,
+ .body = .{
+ .instructions = instructions.toOwnedSlice(),
+ },
+ },
+ .kw_args = .{},
+ };
+ try self.decls.append(&fn_inst.base);
+ return &fn_inst.base;
+ },
+ else => |t| std.debug.panic("TODO implement emitTypedValue for {}", .{@tagName(t)}),
+ }
+ }
+
+ fn emitType(self: *EmitZIR, src: usize, ty: Type) Allocator.Error!*Inst {
+ switch (ty.tag()) {
+ .isize => return self.emitPrimitiveType(src, .isize),
+ .usize => return self.emitPrimitiveType(src, .usize),
+ .c_short => return self.emitPrimitiveType(src, .c_short),
+ .c_ushort => return self.emitPrimitiveType(src, .c_ushort),
+ .c_int => return self.emitPrimitiveType(src, .c_int),
+ .c_uint => return self.emitPrimitiveType(src, .c_uint),
+ .c_long => return self.emitPrimitiveType(src, .c_long),
+ .c_ulong => return self.emitPrimitiveType(src, .c_ulong),
+ .c_longlong => return self.emitPrimitiveType(src, .c_longlong),
+ .c_ulonglong => return self.emitPrimitiveType(src, .c_ulonglong),
+ .c_longdouble => return self.emitPrimitiveType(src, .c_longdouble),
+ .c_void => return self.emitPrimitiveType(src, .c_void),
+ .f16 => return self.emitPrimitiveType(src, .f16),
+ .f32 => return self.emitPrimitiveType(src, .f32),
+ .f64 => return self.emitPrimitiveType(src, .f64),
+ .f128 => return self.emitPrimitiveType(src, .f128),
+ .anyerror => return self.emitPrimitiveType(src, .anyerror),
+ else => switch (ty.zigTypeTag()) {
+ .Bool => return self.emitPrimitiveType(src, .bool),
+ .Void => return self.emitPrimitiveType(src, .void),
+ .NoReturn => return self.emitPrimitiveType(src, .noreturn),
+ .Type => return self.emitPrimitiveType(src, .type),
+ .ComptimeInt => return self.emitPrimitiveType(src, .comptime_int),
+ .ComptimeFloat => return self.emitPrimitiveType(src, .comptime_float),
+ .Fn => {
+ const param_types = try self.allocator.alloc(Type, ty.fnParamLen());
+ defer self.allocator.free(param_types);
+
+ ty.fnParamTypes(param_types);
+ const emitted_params = try self.arena.allocator.alloc(*Inst, param_types.len);
+ for (param_types) |param_type, i| {
+ emitted_params[i] = try self.emitType(src, param_type);
+ }
+
+ const fntype_inst = try self.arena.allocator.create(Inst.FnType);
+ fntype_inst.* = .{
+ .base = .{ .src = src, .tag = Inst.FnType.base_tag },
+ .positionals = .{
+ .param_types = emitted_params,
+ .return_type = try self.emitType(src, ty.fnReturnType()),
+ },
+ .kw_args = .{
+ .cc = ty.fnCallingConvention(),
+ },
+ };
+ try self.decls.append(&fntype_inst.base);
+ return &fntype_inst.base;
+ },
+ else => std.debug.panic("TODO implement emitType for {}", .{ty}),
+ },
+ }
+ }
+
+ fn emitPrimitiveType(self: *EmitZIR, src: usize, tag: Inst.Primitive.BuiltinType) !*Inst {
+ const primitive_inst = try self.arena.allocator.create(Inst.Primitive);
+ primitive_inst.* = .{
+ .base = .{ .src = src, .tag = Inst.Primitive.base_tag },
+ .positionals = .{
+ .tag = tag,
+ },
+ .kw_args = .{},
+ };
+ try self.decls.append(&primitive_inst.base);
+ return &primitive_inst.base;
+ }
+
+ fn emitStringLiteral(self: *EmitZIR, src: usize, str: []const u8) !*Inst {
+ const str_inst = try self.arena.allocator.create(Inst.Str);
+ str_inst.* = .{
+ .base = .{ .src = src, .tag = Inst.Str.base_tag },
+ .positionals = .{
+ .bytes = str,
+ },
+ .kw_args = .{},
+ };
+ try self.decls.append(&str_inst.base);
+ return &str_inst.base;
+ }
+};
diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig
@@ -1,1075 +1,827 @@
const std = @import("std");
-const builtin = std.builtin;
-const Scope = @import("scope.zig").Scope;
-const Compilation = @import("compilation.zig").Compilation;
const Value = @import("value.zig").Value;
-const llvm = @import("llvm.zig");
-const event = std.event;
-const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
+const Allocator = std.mem.Allocator;
+const Target = std.Target;
+
+/// This is the raw data, with no bookkeeping, no memory awareness, no de-duplication.
+/// It's important for this struct to be small.
+/// It is not copyable since it may contain references to its inner data.
+/// Types are not de-duplicated, which helps with multi-threading since it obviates the requirement
+/// of obtaining a lock on a global type table, as well as making the
+/// garbage collection bookkeeping simpler.
+/// This union takes advantage of the fact that the first page of memory
+/// is unmapped, giving us 4096 possible enum tags that have no payload.
+pub const Type = extern union {
+ /// If the tag value is less than Tag.no_payload_count, then no pointer
+ /// dereference is needed.
+ tag_if_small_enough: usize,
+ ptr_otherwise: *Payload,
+
+ pub fn zigTypeTag(self: Type) std.builtin.TypeId {
+ switch (self.tag()) {
+ .@"u8",
+ .@"i8",
+ .@"isize",
+ .@"usize",
+ .@"c_short",
+ .@"c_ushort",
+ .@"c_int",
+ .@"c_uint",
+ .@"c_long",
+ .@"c_ulong",
+ .@"c_longlong",
+ .@"c_ulonglong",
+ .@"c_longdouble",
+ => return .Int,
+
+ .@"f16",
+ .@"f32",
+ .@"f64",
+ .@"f128",
+ => return .Float,
+
+ .@"c_void" => return .Opaque,
+ .@"bool" => return .Bool,
+ .@"void" => return .Void,
+ .@"type" => return .Type,
+ .@"anyerror" => return .ErrorSet,
+ .@"comptime_int" => return .ComptimeInt,
+ .@"comptime_float" => return .ComptimeFloat,
+ .@"noreturn" => return .NoReturn,
+
+ .fn_naked_noreturn_no_args => return .Fn,
+
+ .array, .array_u8_sentinel_0 => return .Array,
+ .single_const_pointer => return .Pointer,
+ .single_const_pointer_to_comptime_int => return .Pointer,
+ .const_slice_u8 => return .Pointer,
+ }
+ }
-pub const Type = struct {
- base: Value,
- id: Id,
- name: []const u8,
- abi_alignment: AbiAlignment,
-
- pub const AbiAlignment = event.Future(error{OutOfMemory}!u32);
-
- pub const Id = builtin.TypeId;
+ pub fn initTag(comptime small_tag: Tag) Type {
+ comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
+ return .{ .tag_if_small_enough = @enumToInt(small_tag) };
+ }
- pub fn destroy(base: *Type, comp: *Compilation) void {
- switch (base.id) {
- .Struct => @fieldParentPtr(Struct, "base", base).destroy(comp),
- .Fn => @fieldParentPtr(Fn, "base", base).destroy(comp),
- .Type => @fieldParentPtr(MetaType, "base", base).destroy(comp),
- .Void => @fieldParentPtr(Void, "base", base).destroy(comp),
- .Bool => @fieldParentPtr(Bool, "base", base).destroy(comp),
- .NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp),
- .Int => @fieldParentPtr(Int, "base", base).destroy(comp),
- .Float => @fieldParentPtr(Float, "base", base).destroy(comp),
- .Pointer => @fieldParentPtr(Pointer, "base", base).destroy(comp),
- .Array => @fieldParentPtr(Array, "base", base).destroy(comp),
- .ComptimeFloat => @fieldParentPtr(ComptimeFloat, "base", base).destroy(comp),
- .ComptimeInt => @fieldParentPtr(ComptimeInt, "base", base).destroy(comp),
- .EnumLiteral => @fieldParentPtr(EnumLiteral, "base", base).destroy(comp),
- .Undefined => @fieldParentPtr(Undefined, "base", base).destroy(comp),
- .Null => @fieldParentPtr(Null, "base", base).destroy(comp),
- .Optional => @fieldParentPtr(Optional, "base", base).destroy(comp),
- .ErrorUnion => @fieldParentPtr(ErrorUnion, "base", base).destroy(comp),
- .ErrorSet => @fieldParentPtr(ErrorSet, "base", base).destroy(comp),
- .Enum => @fieldParentPtr(Enum, "base", base).destroy(comp),
- .Union => @fieldParentPtr(Union, "base", base).destroy(comp),
- .BoundFn => @fieldParentPtr(BoundFn, "base", base).destroy(comp),
- .Opaque => @fieldParentPtr(Opaque, "base", base).destroy(comp),
- .Frame => @fieldParentPtr(Frame, "base", base).destroy(comp),
- .AnyFrame => @fieldParentPtr(AnyFrame, "base", base).destroy(comp),
- .Vector => @fieldParentPtr(Vector, "base", base).destroy(comp),
- }
+ pub fn initPayload(payload: *Payload) Type {
+ assert(@enumToInt(payload.tag) >= Tag.no_payload_count);
+ return .{ .ptr_otherwise = payload };
}
- pub fn getLlvmType(
- base: *Type,
- allocator: *Allocator,
- llvm_context: *llvm.Context,
- ) error{OutOfMemory}!*llvm.Type {
- switch (base.id) {
- .Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(allocator, llvm_context),
- .Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(allocator, llvm_context),
- .Type => unreachable,
- .Void => unreachable,
- .Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(allocator, llvm_context),
- .NoReturn => unreachable,
- .Int => return @fieldParentPtr(Int, "base", base).getLlvmType(allocator, llvm_context),
- .Float => return @fieldParentPtr(Float, "base", base).getLlvmType(allocator, llvm_context),
- .Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(allocator, llvm_context),
- .Array => return @fieldParentPtr(Array, "base", base).getLlvmType(allocator, llvm_context),
- .ComptimeFloat => unreachable,
- .ComptimeInt => unreachable,
- .EnumLiteral => unreachable,
- .Undefined => unreachable,
- .Null => unreachable,
- .Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(allocator, llvm_context),
- .ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(allocator, llvm_context),
- .ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(allocator, llvm_context),
- .Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(allocator, llvm_context),
- .Union => return @fieldParentPtr(Union, "base", base).getLlvmType(allocator, llvm_context),
- .BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(allocator, llvm_context),
- .Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(allocator, llvm_context),
- .Frame => return @fieldParentPtr(Frame, "base", base).getLlvmType(allocator, llvm_context),
- .AnyFrame => return @fieldParentPtr(AnyFrame, "base", base).getLlvmType(allocator, llvm_context),
- .Vector => return @fieldParentPtr(Vector, "base", base).getLlvmType(allocator, llvm_context),
+ pub fn tag(self: Type) Tag {
+ if (self.tag_if_small_enough < Tag.no_payload_count) {
+ return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough));
+ } else {
+ return self.ptr_otherwise.tag;
}
}
- pub fn handleIsPtr(base: *Type) bool {
- switch (base.id) {
- .Type,
- .ComptimeFloat,
- .ComptimeInt,
- .EnumLiteral,
- .Undefined,
- .Null,
- .BoundFn,
- .Opaque,
- => unreachable,
+ pub fn cast(self: Type, comptime T: type) ?*T {
+ if (self.tag_if_small_enough < Tag.no_payload_count)
+ return null;
+
+ const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag;
+ if (self.ptr_otherwise.tag != expected_tag)
+ return null;
- .NoReturn,
- .Void,
- .Bool,
- .Int,
+ return @fieldParentPtr(T, "base", self.ptr_otherwise);
+ }
+
+ pub fn eql(self: Type, other: Type) bool {
+ //std.debug.warn("test {} == {}\n", .{ self, other });
+ // As a shortcut, if the small tags / addresses match, we're done.
+ if (self.tag_if_small_enough == other.tag_if_small_enough)
+ return true;
+ const zig_tag_a = self.zigTypeTag();
+ const zig_tag_b = self.zigTypeTag();
+ if (zig_tag_a != zig_tag_b)
+ return false;
+ switch (zig_tag_a) {
+ .Type => return true,
+ .Void => return true,
+ .Bool => return true,
+ .NoReturn => return true,
+ .ComptimeFloat => return true,
+ .ComptimeInt => return true,
+ .Undefined => return true,
+ .Null => return true,
+ .Pointer => {
+ const is_slice_a = isSlice(self);
+ const is_slice_b = isSlice(other);
+ if (is_slice_a != is_slice_b)
+ return false;
+ @panic("TODO implement more pointer Type equality comparison");
+ },
+ .Int => {
+ if (self.tag() != other.tag()) {
+ // Detect that e.g. u64 != usize, even if the bits match on a particular target.
+ return false;
+ }
+ // The target will not be branched upon, because we handled target-dependent cases above.
+ const info_a = self.intInfo(@as(Target, undefined));
+ const info_b = self.intInfo(@as(Target, undefined));
+ return info_a.signed == info_b.signed and info_a.bits == info_b.bits;
+ },
.Float,
- .Pointer,
+ .Array,
+ .Struct,
+ .Optional,
+ .ErrorUnion,
.ErrorSet,
.Enum,
+ .Union,
.Fn,
- .Frame,
- .AnyFrame,
- .Vector,
- => return false,
-
- .Struct => @panic("TODO"),
- .Array => @panic("TODO"),
- .Optional => @panic("TODO"),
- .ErrorUnion => @panic("TODO"),
- .Union => @panic("TODO"),
- }
- }
-
- pub fn hasBits(base: *Type) bool {
- switch (base.id) {
- .Type,
- .ComptimeFloat,
- .ComptimeInt,
- .EnumLiteral,
- .Undefined,
- .Null,
.BoundFn,
.Opaque,
- => unreachable,
-
- .Void,
- .NoReturn,
- => return false,
-
- .Bool,
- .Int,
- .Float,
- .Fn,
.Frame,
.AnyFrame,
.Vector,
- => return true,
-
- .Pointer => {
- const ptr_type = @fieldParentPtr(Pointer, "base", base);
- return ptr_type.key.child_type.hasBits();
- },
-
- .ErrorSet => @panic("TODO"),
- .Enum => @panic("TODO"),
- .Struct => @panic("TODO"),
- .Array => @panic("TODO"),
- .Optional => @panic("TODO"),
- .ErrorUnion => @panic("TODO"),
- .Union => @panic("TODO"),
+ .EnumLiteral,
+ => @panic("TODO implement more Type equality comparison"),
}
}
- pub fn cast(base: *Type, comptime T: type) ?*T {
- if (base.id != @field(Id, @typeName(T))) return null;
- return @fieldParentPtr(T, "base", base);
- }
-
- pub fn dump(base: *const Type) void {
- std.debug.warn("{}", .{@tagName(base.id)});
+ pub fn format(
+ self: Type,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ out_stream: var,
+ ) !void {
+ comptime assert(fmt.len == 0);
+ var ty = self;
+ while (true) {
+ const t = ty.tag();
+ switch (t) {
+ .@"u8",
+ .@"i8",
+ .@"isize",
+ .@"usize",
+ .@"c_short",
+ .@"c_ushort",
+ .@"c_int",
+ .@"c_uint",
+ .@"c_long",
+ .@"c_ulong",
+ .@"c_longlong",
+ .@"c_ulonglong",
+ .@"c_longdouble",
+ .@"c_void",
+ .@"f16",
+ .@"f32",
+ .@"f64",
+ .@"f128",
+ .@"bool",
+ .@"void",
+ .@"type",
+ .@"anyerror",
+ .@"comptime_int",
+ .@"comptime_float",
+ .@"noreturn",
+ => return out_stream.writeAll(@tagName(t)),
+
+ .const_slice_u8 => return out_stream.writeAll("[]const u8"),
+ .fn_naked_noreturn_no_args => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
+ .single_const_pointer_to_comptime_int => return out_stream.writeAll("*const comptime_int"),
+
+ .array_u8_sentinel_0 => {
+ const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise);
+ return out_stream.print("[{}:0]u8", .{payload.len});
+ },
+ .array => {
+ const payload = @fieldParentPtr(Payload.Array, "base", ty.ptr_otherwise);
+ try out_stream.print("[{}]", .{payload.len});
+ ty = payload.elem_type;
+ continue;
+ },
+ .single_const_pointer => {
+ const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", ty.ptr_otherwise);
+ try out_stream.writeAll("*const ");
+ ty = payload.pointee_type;
+ continue;
+ },
+ }
+ unreachable;
+ }
}
- fn init(base: *Type, comp: *Compilation, id: Id, name: []const u8) void {
- base.* = Type{
- .base = Value{
- .id = .Type,
- .typ = &MetaType.get(comp).base,
- .ref_count = std.atomic.Int(usize).init(1),
+ pub fn toValue(self: Type, allocator: *Allocator) Allocator.Error!Value {
+ switch (self.tag()) {
+ .@"u8" => return Value.initTag(.u8_type),
+ .@"i8" => return Value.initTag(.i8_type),
+ .@"isize" => return Value.initTag(.isize_type),
+ .@"usize" => return Value.initTag(.usize_type),
+ .@"c_short" => return Value.initTag(.c_short_type),
+ .@"c_ushort" => return Value.initTag(.c_ushort_type),
+ .@"c_int" => return Value.initTag(.c_int_type),
+ .@"c_uint" => return Value.initTag(.c_uint_type),
+ .@"c_long" => return Value.initTag(.c_long_type),
+ .@"c_ulong" => return Value.initTag(.c_ulong_type),
+ .@"c_longlong" => return Value.initTag(.c_longlong_type),
+ .@"c_ulonglong" => return Value.initTag(.c_ulonglong_type),
+ .@"c_longdouble" => return Value.initTag(.c_longdouble_type),
+ .@"c_void" => return Value.initTag(.c_void_type),
+ .@"f16" => return Value.initTag(.f16_type),
+ .@"f32" => return Value.initTag(.f32_type),
+ .@"f64" => return Value.initTag(.f64_type),
+ .@"f128" => return Value.initTag(.f128_type),
+ .@"bool" => return Value.initTag(.bool_type),
+ .@"void" => return Value.initTag(.void_type),
+ .@"type" => return Value.initTag(.type_type),
+ .@"anyerror" => return Value.initTag(.anyerror_type),
+ .@"comptime_int" => return Value.initTag(.comptime_int_type),
+ .@"comptime_float" => return Value.initTag(.comptime_float_type),
+ .@"noreturn" => return Value.initTag(.noreturn_type),
+ .fn_naked_noreturn_no_args => return Value.initTag(.fn_naked_noreturn_no_args_type),
+ .single_const_pointer_to_comptime_int => return Value.initTag(.single_const_pointer_to_comptime_int_type),
+ .const_slice_u8 => return Value.initTag(.const_slice_u8_type),
+ else => {
+ const ty_payload = try allocator.create(Value.Payload.Ty);
+ ty_payload.* = .{ .ty = self };
+ return Value.initPayload(&ty_payload.base);
},
- .id = id,
- .name = name,
- .abi_alignment = AbiAlignment.init(),
- };
- }
-
- /// If you happen to have an llvm context handy, use getAbiAlignmentInContext instead.
- /// Otherwise, this one will grab one from the pool and then release it.
- pub fn getAbiAlignment(base: *Type, comp: *Compilation) !u32 {
- if (base.abi_alignment.start()) |ptr| return ptr.*;
-
- {
- const held = try comp.zig_compiler.getAnyLlvmContext();
- defer held.release(comp.zig_compiler);
-
- const llvm_context = held.node.data;
-
- base.abi_alignment.data = base.resolveAbiAlignment(comp, llvm_context);
}
- base.abi_alignment.resolve();
- return base.abi_alignment.data;
}
- /// If you have an llvm conext handy, you can use it here.
- pub fn getAbiAlignmentInContext(base: *Type, comp: *Compilation, llvm_context: *llvm.Context) !u32 {
- if (base.abi_alignment.start()) |ptr| return ptr.*;
-
- base.abi_alignment.data = base.resolveAbiAlignment(comp, llvm_context);
- base.abi_alignment.resolve();
- return base.abi_alignment.data;
+ pub fn isSinglePointer(self: Type) bool {
+ return switch (self.tag()) {
+ .@"u8",
+ .@"i8",
+ .@"isize",
+ .@"usize",
+ .@"c_short",
+ .@"c_ushort",
+ .@"c_int",
+ .@"c_uint",
+ .@"c_long",
+ .@"c_ulong",
+ .@"c_longlong",
+ .@"c_ulonglong",
+ .@"c_longdouble",
+ .@"f16",
+ .@"f32",
+ .@"f64",
+ .@"f128",
+ .@"c_void",
+ .@"bool",
+ .@"void",
+ .@"type",
+ .@"anyerror",
+ .@"comptime_int",
+ .@"comptime_float",
+ .@"noreturn",
+ .array,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .fn_naked_noreturn_no_args,
+ => false,
+
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ => true,
+ };
}
- /// Lower level function that does the work. See getAbiAlignment.
- fn resolveAbiAlignment(base: *Type, comp: *Compilation, llvm_context: *llvm.Context) !u32 {
- const llvm_type = try base.getLlvmType(comp.gpa(), llvm_context);
- return @intCast(u32, llvm.ABIAlignmentOfType(comp.target_data_ref, llvm_type));
+ pub fn isSlice(self: Type) bool {
+ return switch (self.tag()) {
+ .@"u8",
+ .@"i8",
+ .@"isize",
+ .@"usize",
+ .@"c_short",
+ .@"c_ushort",
+ .@"c_int",
+ .@"c_uint",
+ .@"c_long",
+ .@"c_ulong",
+ .@"c_longlong",
+ .@"c_ulonglong",
+ .@"c_longdouble",
+ .@"f16",
+ .@"f32",
+ .@"f64",
+ .@"f128",
+ .@"c_void",
+ .@"bool",
+ .@"void",
+ .@"type",
+ .@"anyerror",
+ .@"comptime_int",
+ .@"comptime_float",
+ .@"noreturn",
+ .array,
+ .array_u8_sentinel_0,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .fn_naked_noreturn_no_args,
+ => false,
+
+ .const_slice_u8 => true,
+ };
}
- pub const Struct = struct {
- base: Type,
- decls: *Scope.Decls,
-
- pub fn destroy(self: *Struct, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Struct, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const Fn = struct {
- base: Type,
- key: Key,
- non_key: NonKey,
- garbage_node: std.atomic.Stack(*Fn).Node,
+ /// Asserts the type is a pointer type.
+ pub fn pointerIsConst(self: Type) bool {
+ return switch (self.tag()) {
+ .@"u8",
+ .@"i8",
+ .@"isize",
+ .@"usize",
+ .@"c_short",
+ .@"c_ushort",
+ .@"c_int",
+ .@"c_uint",
+ .@"c_long",
+ .@"c_ulong",
+ .@"c_longlong",
+ .@"c_ulonglong",
+ .@"c_longdouble",
+ .@"f16",
+ .@"f32",
+ .@"f64",
+ .@"f128",
+ .@"c_void",
+ .@"bool",
+ .@"void",
+ .@"type",
+ .@"anyerror",
+ .@"comptime_int",
+ .@"comptime_float",
+ .@"noreturn",
+ .array,
+ .array_u8_sentinel_0,
+ .fn_naked_noreturn_no_args,
+ => unreachable,
- pub const Kind = enum {
- Normal,
- Generic,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .const_slice_u8,
+ => true,
};
+ }
- pub const NonKey = union {
- Normal: Normal,
- Generic: void,
+ /// Asserts the type is a pointer or array type.
+ pub fn elemType(self: Type) Type {
+ return switch (self.tag()) {
+ .@"u8",
+ .@"i8",
+ .@"isize",
+ .@"usize",
+ .@"c_short",
+ .@"c_ushort",
+ .@"c_int",
+ .@"c_uint",
+ .@"c_long",
+ .@"c_ulong",
+ .@"c_longlong",
+ .@"c_ulonglong",
+ .@"c_longdouble",
+ .@"f16",
+ .@"f32",
+ .@"f64",
+ .@"f128",
+ .@"c_void",
+ .@"bool",
+ .@"void",
+ .@"type",
+ .@"anyerror",
+ .@"comptime_int",
+ .@"comptime_float",
+ .@"noreturn",
+ .fn_naked_noreturn_no_args,
+ => unreachable,
- pub const Normal = struct {
- variable_list: std.ArrayList(*Scope.Var),
- };
+ .array => self.cast(Payload.Array).?.elem_type,
+ .single_const_pointer => self.cast(Payload.SingleConstPointer).?.pointee_type,
+ .array_u8_sentinel_0, .const_slice_u8 => Type.initTag(.u8),
+ .single_const_pointer_to_comptime_int => Type.initTag(.comptime_int),
};
+ }
- pub const Key = struct {
- data: Data,
- alignment: ?u32,
-
- pub const Data = union(Kind) {
- Generic: Generic,
- Normal: Normal,
- };
-
- pub const Normal = struct {
- params: []Param,
- return_type: *Type,
- is_var_args: bool,
- cc: CallingConvention,
- };
-
- pub const Generic = struct {
- param_count: usize,
- cc: CallingConvention,
- };
-
- pub fn hash(self: *const Key) u32 {
- var result: u32 = 0;
- result +%= hashAny(self.alignment, 0);
- switch (self.data) {
- .Generic => |generic| {
- result +%= hashAny(generic.param_count, 1);
- result +%= hashAny(generic.cc, 3);
- },
- .Normal => |normal| {
- result +%= hashAny(normal.return_type, 4);
- result +%= hashAny(normal.is_var_args, 5);
- result +%= hashAny(normal.cc, 6);
- for (normal.params) |param| {
- result +%= hashAny(param.is_noalias, 7);
- result +%= hashAny(param.typ, 8);
- }
- },
- }
- return result;
- }
-
- pub fn eql(self: *const Key, other: *const Key) bool {
- if ((self.alignment == null) != (other.alignment == null)) return false;
- if (self.alignment) |self_align| {
- if (self_align != other.alignment.?) return false;
- }
- if (@as(@TagType(Data), self.data) != @as(@TagType(Data), other.data)) return false;
- switch (self.data) {
- .Generic => |*self_generic| {
- const other_generic = &other.data.Generic;
- if (self_generic.param_count != other_generic.param_count) return false;
- if (self_generic.cc != other_generic.cc) return false;
- },
- .Normal => |*self_normal| {
- const other_normal = &other.data.Normal;
- if (self_normal.cc != other_normal.cc) return false;
- if (self_normal.is_var_args != other_normal.is_var_args) return false;
- if (self_normal.return_type != other_normal.return_type) return false;
- for (self_normal.params) |*self_param, i| {
- const other_param = &other_normal.params[i];
- if (self_param.is_noalias != other_param.is_noalias) return false;
- if (self_param.typ != other_param.typ) return false;
- }
- },
- }
- return true;
- }
-
- pub fn deref(key: Key, comp: *Compilation) void {
- switch (key.data) {
- .Generic => {},
- .Normal => |normal| {
- normal.return_type.base.deref(comp);
- for (normal.params) |param| {
- param.typ.base.deref(comp);
- }
- },
- }
- }
+ /// Asserts the type is an array.
+ pub fn arrayLen(self: Type) u64 {
+ return switch (self.tag()) {
+ .u8,
+ .i8,
+ .isize,
+ .usize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ .c_longdouble,
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .fn_naked_noreturn_no_args,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .const_slice_u8,
+ => unreachable,
- pub fn ref(key: Key) void {
- switch (key.data) {
- .Generic => {},
- .Normal => |normal| {
- normal.return_type.base.ref();
- for (normal.params) |param| {
- param.typ.base.ref();
- }
- },
- }
- }
+ .array => self.cast(Payload.Array).?.len,
+ .array_u8_sentinel_0 => self.cast(Payload.Array_u8_Sentinel0).?.len,
};
+ }
- const CallingConvention = builtin.CallingConvention;
+ /// Asserts the type is a fixed-width integer.
+ pub fn intInfo(self: Type, target: Target) struct { signed: bool, bits: u16 } {
+ return switch (self.tag()) {
+ .@"f16",
+ .@"f32",
+ .@"f64",
+ .@"f128",
+ .@"c_longdouble",
+ .@"c_void",
+ .@"bool",
+ .@"void",
+ .@"type",
+ .@"anyerror",
+ .@"comptime_int",
+ .@"comptime_float",
+ .@"noreturn",
+ .fn_naked_noreturn_no_args,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ => unreachable,
- pub const Param = struct {
- is_noalias: bool,
- typ: *Type,
+ .@"u8" => .{ .signed = false, .bits = 8 },
+ .@"i8" => .{ .signed = true, .bits = 8 },
+ .@"usize" => .{ .signed = false, .bits = target.cpu.arch.ptrBitWidth() },
+ .@"isize" => .{ .signed = true, .bits = target.cpu.arch.ptrBitWidth() },
+ .@"c_short" => .{ .signed = true, .bits = CInteger.short.sizeInBits(target) },
+ .@"c_ushort" => .{ .signed = false, .bits = CInteger.ushort.sizeInBits(target) },
+ .@"c_int" => .{ .signed = true, .bits = CInteger.int.sizeInBits(target) },
+ .@"c_uint" => .{ .signed = false, .bits = CInteger.uint.sizeInBits(target) },
+ .@"c_long" => .{ .signed = true, .bits = CInteger.long.sizeInBits(target) },
+ .@"c_ulong" => .{ .signed = false, .bits = CInteger.ulong.sizeInBits(target) },
+ .@"c_longlong" => .{ .signed = true, .bits = CInteger.longlong.sizeInBits(target) },
+ .@"c_ulonglong" => .{ .signed = false, .bits = CInteger.ulonglong.sizeInBits(target) },
};
+ }
- fn ccFnTypeStr(cc: CallingConvention) []const u8 {
- return switch (cc) {
- .Unspecified => "",
- .C => "extern ",
- .Cold => "coldcc ",
- .Naked => "nakedcc ",
- .Stdcall => "stdcallcc ",
- .Async => "async ",
- else => unreachable,
- };
- }
-
- pub fn paramCount(self: *Fn) usize {
- return switch (self.key.data) {
- .Generic => |generic| generic.param_count,
- .Normal => |normal| normal.params.len,
- };
- }
-
- /// takes ownership of key.Normal.params on success
- pub fn get(comp: *Compilation, key: Key) !*Fn {
- {
- const held = comp.fn_type_table.acquire();
- defer held.release();
-
- if (held.value.get(&key)) |entry| {
- entry.value.base.base.ref();
- return entry.value;
- }
- }
-
- key.ref();
- errdefer key.deref(comp);
-
- const self = try comp.gpa().create(Fn);
- self.* = Fn{
- .base = undefined,
- .key = key,
- .non_key = undefined,
- .garbage_node = undefined,
- };
- errdefer comp.gpa().destroy(self);
-
- var name_buf = std.ArrayList(u8).init(comp.gpa());
- defer name_buf.deinit();
-
- const name_stream = name_buf.outStream();
-
- switch (key.data) {
- .Generic => |generic| {
- self.non_key = NonKey{ .Generic = {} };
- const cc_str = ccFnTypeStr(generic.cc);
- try name_stream.print("{}fn(", .{cc_str});
- var param_i: usize = 0;
- while (param_i < generic.param_count) : (param_i += 1) {
- const arg = if (param_i == 0) "var" else ", var";
- try name_stream.write(arg);
- }
- try name_stream.write(")");
- if (key.alignment) |alignment| {
- try name_stream.print(" align({})", .{alignment});
- }
- try name_stream.write(" var");
- },
- .Normal => |normal| {
- self.non_key = NonKey{
- .Normal = NonKey.Normal{ .variable_list = std.ArrayList(*Scope.Var).init(comp.gpa()) },
- };
- const cc_str = ccFnTypeStr(normal.cc);
- try name_stream.print("{}fn(", .{cc_str});
- for (normal.params) |param, i| {
- if (i != 0) try name_stream.write(", ");
- if (param.is_noalias) try name_stream.write("noalias ");
- try name_stream.write(param.typ.name);
- }
- if (normal.is_var_args) {
- if (normal.params.len != 0) try name_stream.write(", ");
- try name_stream.write("...");
- }
- try name_stream.write(")");
- if (key.alignment) |alignment| {
- try name_stream.print(" align({})", .{alignment});
- }
- try name_stream.print(" {}", .{normal.return_type.name});
- },
- }
-
- self.base.init(comp, .Fn, name_buf.toOwnedSlice());
-
- {
- const held = comp.fn_type_table.acquire();
- defer held.release();
-
- _ = try held.value.put(&self.key, self);
- }
- return self;
- }
-
- pub fn destroy(self: *Fn, comp: *Compilation) void {
- self.key.deref(comp);
- switch (self.key.data) {
- .Generic => {},
- .Normal => {
- self.non_key.Normal.variable_list.deinit();
- },
- }
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: *llvm.Context) !*llvm.Type {
- const normal = &self.key.data.Normal;
- const llvm_return_type = switch (normal.return_type.id) {
- .Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory,
- else => try normal.return_type.getLlvmType(allocator, llvm_context),
- };
- const llvm_param_types = try allocator.alloc(*llvm.Type, normal.params.len);
- defer allocator.free(llvm_param_types);
- for (llvm_param_types) |*llvm_param_type, i| {
- llvm_param_type.* = try normal.params[i].typ.getLlvmType(allocator, llvm_context);
- }
-
- return llvm.FunctionType(
- llvm_return_type,
- llvm_param_types.ptr,
- @intCast(c_uint, llvm_param_types.len),
- @boolToInt(normal.is_var_args),
- ) orelse error.OutOfMemory;
- }
- };
-
- pub const MetaType = struct {
- base: Type,
- value: *Type,
-
- /// Adds 1 reference to the resulting type
- pub fn get(comp: *Compilation) *MetaType {
- comp.meta_type.base.base.ref();
- return comp.meta_type;
- }
-
- pub fn destroy(self: *MetaType, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Void = struct {
- base: Type,
-
- /// Adds 1 reference to the resulting type
- pub fn get(comp: *Compilation) *Void {
- comp.void_type.base.base.ref();
- return comp.void_type;
- }
-
- pub fn destroy(self: *Void, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Bool = struct {
- base: Type,
-
- /// Adds 1 reference to the resulting type
- pub fn get(comp: *Compilation) *Bool {
- comp.bool_type.base.base.ref();
- return comp.bool_type;
- }
-
- pub fn destroy(self: *Bool, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Bool, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const NoReturn = struct {
- base: Type,
-
- /// Adds 1 reference to the resulting type
- pub fn get(comp: *Compilation) *NoReturn {
- comp.noreturn_type.base.base.ref();
- return comp.noreturn_type;
- }
-
- pub fn destroy(self: *NoReturn, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Int = struct {
- base: Type,
- key: Key,
- garbage_node: std.atomic.Stack(*Int).Node,
-
- pub const Key = struct {
- bit_count: u32,
- is_signed: bool,
-
- pub fn hash(self: *const Key) u32 {
- var result: u32 = 0;
- result +%= hashAny(self.is_signed, 0);
- result +%= hashAny(self.bit_count, 1);
- return result;
- }
-
- pub fn eql(self: *const Key, other: *const Key) bool {
- return self.bit_count == other.bit_count and self.is_signed == other.is_signed;
- }
+ /// Asserts the type is a function.
+ pub fn fnParamLen(self: Type) usize {
+ return switch (self.tag()) {
+ .fn_naked_noreturn_no_args => 0,
+
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ => unreachable,
};
+ }
- pub fn get_u8(comp: *Compilation) *Int {
- comp.u8_type.base.base.ref();
- return comp.u8_type;
- }
-
- pub fn get(comp: *Compilation, key: Key) !*Int {
- {
- const held = comp.int_type_table.acquire();
- defer held.release();
-
- if (held.value.get(&key)) |entry| {
- entry.value.base.base.ref();
- return entry.value;
- }
- }
-
- const self = try comp.gpa().create(Int);
- self.* = Int{
- .base = undefined,
- .key = key,
- .garbage_node = undefined,
- };
- errdefer comp.gpa().destroy(self);
-
- const u_or_i = "ui"[@boolToInt(key.is_signed)];
- const name = try std.fmt.allocPrint(comp.gpa(), "{c}{}", .{ u_or_i, key.bit_count });
- errdefer comp.gpa().free(name);
-
- self.base.init(comp, .Int, name);
-
- {
- const held = comp.int_type_table.acquire();
- defer held.release();
-
- _ = try held.value.put(&self.key, self);
- }
- return self;
- }
-
- pub fn destroy(self: *Int, comp: *Compilation) void {
- self.garbage_node = std.atomic.Stack(*Int).Node{
- .data = self,
- .next = undefined,
- };
- comp.registerGarbage(Int, &self.garbage_node);
- }
-
- pub fn gcDestroy(self: *Int, comp: *Compilation) void {
- {
- const held = comp.int_type_table.acquire();
- defer held.release();
-
- _ = held.value.remove(&self.key).?;
- }
- // we allocated the name
- comp.gpa().free(self.base.name);
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Int, allocator: *Allocator, llvm_context: *llvm.Context) !*llvm.Type {
- return llvm.IntTypeInContext(llvm_context, self.key.bit_count) orelse return error.OutOfMemory;
- }
- };
-
- pub const Float = struct {
- base: Type,
-
- pub fn destroy(self: *Float, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Float, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
+ /// Asserts the type is a function. The length of the slice must be at least the length
+ /// given by `fnParamLen`.
+ pub fn fnParamTypes(self: Type, types: []Type) void {
+ switch (self.tag()) {
+ .fn_naked_noreturn_no_args => return,
+
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ => unreachable,
}
- };
- pub const Pointer = struct {
- base: Type,
- key: Key,
- garbage_node: std.atomic.Stack(*Pointer).Node,
-
- pub const Key = struct {
- child_type: *Type,
- mut: Mut,
- vol: Vol,
- size: Size,
- alignment: Align,
-
- pub fn hash(self: *const Key) u32 {
- var result: u32 = 0;
- result +%= switch (self.alignment) {
- .Abi => 0xf201c090,
- .Override => |x| hashAny(x, 0),
- };
- result +%= hashAny(self.child_type, 1);
- result +%= hashAny(self.mut, 2);
- result +%= hashAny(self.vol, 3);
- result +%= hashAny(self.size, 4);
- return result;
- }
-
- pub fn eql(self: *const Key, other: *const Key) bool {
- if (self.child_type != other.child_type or
- self.mut != other.mut or
- self.vol != other.vol or
- self.size != other.size or
- @as(@TagType(Align), self.alignment) != @as(@TagType(Align), other.alignment))
- {
- return false;
- }
- switch (self.alignment) {
- .Abi => return true,
- .Override => |x| return x == other.alignment.Override,
- }
- }
- };
-
- pub const Mut = enum {
- Mut,
- Const,
- };
+ }
- pub const Vol = enum {
- Non,
- Volatile,
+ /// Asserts the type is a function.
+ pub fn fnReturnType(self: Type) Type {
+ return switch (self.tag()) {
+ .fn_naked_noreturn_no_args => Type.initTag(.noreturn),
+
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ => unreachable,
};
+ }
- pub const Align = union(enum) {
- Abi,
- Override: u32,
+ /// Asserts the type is a function.
+ pub fn fnCallingConvention(self: Type) std.builtin.CallingConvention {
+ return switch (self.tag()) {
+ .fn_naked_noreturn_no_args => .Naked,
+
+ .f16,
+ .f32,
+ .f64,
+ .f128,
+ .c_longdouble,
+ .c_void,
+ .bool,
+ .void,
+ .type,
+ .anyerror,
+ .comptime_int,
+ .comptime_float,
+ .noreturn,
+ .array,
+ .single_const_pointer,
+ .single_const_pointer_to_comptime_int,
+ .array_u8_sentinel_0,
+ .const_slice_u8,
+ .u8,
+ .i8,
+ .usize,
+ .isize,
+ .c_short,
+ .c_ushort,
+ .c_int,
+ .c_uint,
+ .c_long,
+ .c_ulong,
+ .c_longlong,
+ .c_ulonglong,
+ => unreachable,
};
+ }
- pub const Size = builtin.TypeInfo.Pointer.Size;
-
- pub fn destroy(self: *Pointer, comp: *Compilation) void {
- self.garbage_node = std.atomic.Stack(*Pointer).Node{
- .data = self,
- .next = undefined,
- };
- comp.registerGarbage(Pointer, &self.garbage_node);
- }
-
- pub fn gcDestroy(self: *Pointer, comp: *Compilation) void {
- {
- const held = comp.ptr_type_table.acquire();
- defer held.release();
-
- _ = held.value.remove(&self.key).?;
- }
- self.key.child_type.base.deref(comp);
- comp.gpa().destroy(self);
- }
-
- pub fn getAlignAsInt(self: *Pointer, comp: *Compilation) u32 {
- switch (self.key.alignment) {
- .Abi => return self.key.child_type.getAbiAlignment(comp),
- .Override => |alignment| return alignment,
- }
- }
-
- pub fn get(
- comp: *Compilation,
- key: Key,
- ) !*Pointer {
- var normal_key = key;
- switch (key.alignment) {
- .Abi => {},
- .Override => |alignment| {
- // TODO https://github.com/ziglang/zig/issues/3190
- var align_spill = alignment;
- const abi_align = try key.child_type.getAbiAlignment(comp);
- if (abi_align == align_spill) {
- normal_key.alignment = .Abi;
- }
- },
- }
- {
- const held = comp.ptr_type_table.acquire();
- defer held.release();
-
- if (held.value.get(&normal_key)) |entry| {
- entry.value.base.base.ref();
- return entry.value;
- }
- }
-
- const self = try comp.gpa().create(Pointer);
- self.* = Pointer{
- .base = undefined,
- .key = normal_key,
- .garbage_node = undefined,
- };
- errdefer comp.gpa().destroy(self);
-
- const size_str = switch (self.key.size) {
- .One => "*",
- .Many => "[*]",
- .Slice => "[]",
- .C => "[*c]",
- };
- const mut_str = switch (self.key.mut) {
- .Const => "const ",
- .Mut => "",
- };
- const vol_str = switch (self.key.vol) {
- .Volatile => "volatile ",
- .Non => "",
- };
- const name = switch (self.key.alignment) {
- .Abi => try std.fmt.allocPrint(comp.gpa(), "{}{}{}{}", .{
- size_str,
- mut_str,
- vol_str,
- self.key.child_type.name,
- }),
- .Override => |alignment| try std.fmt.allocPrint(comp.gpa(), "{}align<{}> {}{}{}", .{
- size_str,
- alignment,
- mut_str,
- vol_str,
- self.key.child_type.name,
- }),
- };
- errdefer comp.gpa().free(name);
-
- self.base.init(comp, .Pointer, name);
-
- {
- const held = comp.ptr_type_table.acquire();
- defer held.release();
-
- _ = try held.value.put(&self.key, self);
- }
- return self;
- }
-
- pub fn getLlvmType(self: *Pointer, allocator: *Allocator, llvm_context: *llvm.Context) !*llvm.Type {
- const elem_llvm_type = try self.key.child_type.getLlvmType(allocator, llvm_context);
- return llvm.PointerType(elem_llvm_type, 0) orelse return error.OutOfMemory;
- }
+ /// This enum does not directly correspond to `std.builtin.TypeId` because
+ /// it has extra enum tags in it, as a way of using less memory. For example,
+ /// even though Zig recognizes `*align(10) i32` and `*i32` both as Pointer types
+ /// but with different alignment values, in this data structure they are represented
+ /// with different enum tags, because the the former requires more payload data than the latter.
+ /// See `zigTypeTag` for the function that corresponds to `std.builtin.TypeId`.
+ pub const Tag = enum {
+ // The first section of this enum are tags that require no payload.
+ u8,
+ i8,
+ isize,
+ usize,
+ c_short,
+ c_ushort,
+ c_int,
+ c_uint,
+ c_long,
+ c_ulong,
+ c_longlong,
+ c_ulonglong,
+ c_longdouble,
+ c_void,
+ f16,
+ f32,
+ f64,
+ f128,
+ bool,
+ void,
+ type,
+ anyerror,
+ comptime_int,
+ comptime_float,
+ noreturn,
+ fn_naked_noreturn_no_args,
+ single_const_pointer_to_comptime_int,
+ const_slice_u8, // See last_no_payload_tag below.
+ // After this, the tag requires a payload.
+
+ array_u8_sentinel_0,
+ array,
+ single_const_pointer,
+
+ pub const last_no_payload_tag = Tag.const_slice_u8;
+ pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
};
- pub const Array = struct {
- base: Type,
- key: Key,
- garbage_node: std.atomic.Stack(*Array).Node,
+ pub const Payload = struct {
+ tag: Tag,
- pub const Key = struct {
- elem_type: *Type,
- len: usize,
+ pub const Array_u8_Sentinel0 = struct {
+ base: Payload = Payload{ .tag = .array_u8_sentinel_0 },
- pub fn hash(self: *const Key) u32 {
- var result: u32 = 0;
- result +%= hashAny(self.elem_type, 0);
- result +%= hashAny(self.len, 1);
- return result;
- }
-
- pub fn eql(self: *const Key, other: *const Key) bool {
- return self.elem_type == other.elem_type and self.len == other.len;
- }
+ len: u64,
};
- pub fn destroy(self: *Array, comp: *Compilation) void {
- self.key.elem_type.base.deref(comp);
- comp.gpa().destroy(self);
- }
-
- pub fn get(comp: *Compilation, key: Key) !*Array {
- key.elem_type.base.ref();
- errdefer key.elem_type.base.deref(comp);
-
- {
- const held = comp.array_type_table.acquire();
- defer held.release();
-
- if (held.value.get(&key)) |entry| {
- entry.value.base.base.ref();
- return entry.value;
- }
- }
-
- const self = try comp.gpa().create(Array);
- self.* = Array{
- .base = undefined,
- .key = key,
- .garbage_node = undefined,
- };
- errdefer comp.gpa().destroy(self);
-
- const name = try std.fmt.allocPrint(comp.gpa(), "[{}]{}", .{ key.len, key.elem_type.name });
- errdefer comp.gpa().free(name);
-
- self.base.init(comp, .Array, name);
-
- {
- const held = comp.array_type_table.acquire();
- defer held.release();
-
- _ = try held.value.put(&self.key, self);
- }
- return self;
- }
-
- pub fn getLlvmType(self: *Array, allocator: *Allocator, llvm_context: *llvm.Context) !*llvm.Type {
- const elem_llvm_type = try self.key.elem_type.getLlvmType(allocator, llvm_context);
- return llvm.ArrayType(elem_llvm_type, @intCast(c_uint, self.key.len)) orelse return error.OutOfMemory;
- }
- };
-
- pub const Vector = struct {
- base: Type,
-
- pub fn destroy(self: *Vector, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Vector, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const ComptimeFloat = struct {
- base: Type,
+ pub const Array = struct {
+ base: Payload = Payload{ .tag = .array },
- pub fn destroy(self: *ComptimeFloat, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const ComptimeInt = struct {
- base: Type,
-
- /// Adds 1 reference to the resulting type
- pub fn get(comp: *Compilation) *ComptimeInt {
- comp.comptime_int_type.base.base.ref();
- return comp.comptime_int_type;
- }
-
- pub fn destroy(self: *ComptimeInt, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const EnumLiteral = struct {
- base: Type,
-
- /// Adds 1 reference to the resulting type
- pub fn get(comp: *Compilation) *EnumLiteral {
- comp.comptime_int_type.base.base.ref();
- return comp.comptime_int_type;
- }
-
- pub fn destroy(self: *EnumLiteral, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Undefined = struct {
- base: Type,
-
- pub fn destroy(self: *Undefined, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Null = struct {
- base: Type,
-
- pub fn destroy(self: *Null, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Optional = struct {
- base: Type,
-
- pub fn destroy(self: *Optional, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Optional, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const ErrorUnion = struct {
- base: Type,
-
- pub fn destroy(self: *ErrorUnion, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *ErrorUnion, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const ErrorSet = struct {
- base: Type,
-
- pub fn destroy(self: *ErrorSet, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *ErrorSet, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const Enum = struct {
- base: Type,
-
- pub fn destroy(self: *Enum, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Enum, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const Union = struct {
- base: Type,
-
- pub fn destroy(self: *Union, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Union, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
-
- pub const BoundFn = struct {
- base: Type,
-
- pub fn destroy(self: *BoundFn, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *BoundFn, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
+ elem_type: Type,
+ len: u64,
+ };
- pub const Opaque = struct {
- base: Type,
+ pub const SingleConstPointer = struct {
+ base: Payload = Payload{ .tag = .single_const_pointer },
- pub fn destroy(self: *Opaque, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmType(self: *Opaque, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
+ pointee_type: Type,
+ };
};
+};
- pub const Frame = struct {
- base: Type,
-
- pub fn destroy(self: *Frame, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
+pub const CInteger = enum {
+ short,
+ ushort,
+ int,
+ uint,
+ long,
+ ulong,
+ longlong,
+ ulonglong,
+
+ pub fn sizeInBits(self: CInteger, target: Target) u16 {
+ const arch = target.cpu.arch;
+ switch (target.os.tag) {
+ .freestanding, .other => switch (target.cpu.arch) {
+ .msp430 => switch (self) {
+ .short,
+ .ushort,
+ .int,
+ .uint,
+ => return 16,
+ .long,
+ .ulong,
+ => return 32,
+ .longlong,
+ .ulonglong,
+ => return 64,
+ },
+ else => switch (self) {
+ .short,
+ .ushort,
+ => return 16,
+ .int,
+ .uint,
+ => return 32,
+ .long,
+ .ulong,
+ => return target.cpu.arch.ptrBitWidth(),
+ .longlong,
+ .ulonglong,
+ => return 64,
+ },
+ },
- pub fn getLlvmType(self: *Frame, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
- }
- };
+ .linux,
+ .macosx,
+ .freebsd,
+ .netbsd,
+ .dragonfly,
+ .openbsd,
+ .wasi,
+ .emscripten,
+ => switch (self) {
+ .short,
+ .ushort,
+ => return 16,
+ .int,
+ .uint,
+ => return 32,
+ .long,
+ .ulong,
+ => return target.cpu.arch.ptrBitWidth(),
+ .longlong,
+ .ulonglong,
+ => return 64,
+ },
- pub const AnyFrame = struct {
- base: Type,
+ .windows, .uefi => switch (self) {
+ .short,
+ .ushort,
+ => return 16,
+ .int,
+ .uint,
+ .long,
+ .ulong,
+ => return 32,
+ .longlong,
+ .ulonglong,
+ => return 64,
+ },
- pub fn destroy(self: *AnyFrame, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
+ .ios => switch (self) {
+ .short,
+ .ushort,
+ => return 16,
+ .int,
+ .uint,
+ => return 32,
+ .long,
+ .ulong,
+ .longlong,
+ .ulonglong,
+ => return 64,
+ },
- pub fn getLlvmType(self: *AnyFrame, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type {
- @panic("TODO");
+ .ananas,
+ .cloudabi,
+ .fuchsia,
+ .kfreebsd,
+ .lv2,
+ .solaris,
+ .haiku,
+ .minix,
+ .rtems,
+ .nacl,
+ .cnk,
+ .aix,
+ .cuda,
+ .nvcl,
+ .amdhsa,
+ .ps4,
+ .elfiamcu,
+ .tvos,
+ .watchos,
+ .mesa3d,
+ .contiki,
+ .amdpal,
+ .hermit,
+ .hurd,
+ => @panic("TODO specify the C integer type sizes for this OS"),
}
- };
-};
-
-fn hashAny(x: var, comptime seed: u64) u32 {
- switch (@typeInfo(@TypeOf(x))) {
- .Int => |info| {
- comptime var rng = comptime std.rand.DefaultPrng.init(seed);
- const unsigned_x = @bitCast(std.meta.IntType(false, info.bits), x);
- if (info.bits <= 32) {
- return @as(u32, unsigned_x) *% comptime rng.random.scalar(u32);
- } else {
- return @truncate(u32, unsigned_x *% comptime rng.random.scalar(@TypeOf(unsigned_x)));
- }
- },
- .Pointer => |info| {
- switch (info.size) {
- .One => return hashAny(@ptrToInt(x), seed),
- .Many => @compileError("implement hash function"),
- .Slice => @compileError("implement hash function"),
- .C => unreachable,
- }
- },
- .Enum => return hashAny(@enumToInt(x), seed),
- .Bool => {
- comptime var rng = comptime std.rand.DefaultPrng.init(seed);
- const vals = comptime [2]u32{ rng.random.scalar(u32), rng.random.scalar(u32) };
- return vals[@boolToInt(x)];
- },
- .Optional => {
- if (x) |non_opt| {
- return hashAny(non_opt, seed);
- } else {
- return hashAny(@as(u32, 1), seed);
- }
- },
- else => @compileError("implement hash function for " ++ @typeName(@TypeOf(x))),
}
-}
+};
diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig
@@ -1,587 +1,475 @@
const std = @import("std");
-const Scope = @import("scope.zig").Scope;
-const Compilation = @import("compilation.zig").Compilation;
-const ObjectFile = @import("codegen.zig").ObjectFile;
-const llvm = @import("llvm.zig");
-const ArrayListSentineled = std.ArrayListSentineled;
+const Type = @import("type.zig").Type;
+const log2 = std.math.log2;
const assert = std.debug.assert;
+const BigInt = std.math.big.Int;
+const Target = std.Target;
+const Allocator = std.mem.Allocator;
+
+/// This is the raw data, with no bookkeeping, no memory awareness,
+/// no de-duplication, and no type system awareness.
+/// It's important for this struct to be small.
+/// This union takes advantage of the fact that the first page of memory
+/// is unmapped, giving us 4096 possible enum tags that have no payload.
+pub const Value = extern union {
+ /// If the tag value is less than Tag.no_payload_count, then no pointer
+ /// dereference is needed.
+ tag_if_small_enough: usize,
+ ptr_otherwise: *Payload,
+
+ pub const Tag = enum {
+ // The first section of this enum are tags that require no payload.
+ u8_type,
+ i8_type,
+ isize_type,
+ usize_type,
+ c_short_type,
+ c_ushort_type,
+ c_int_type,
+ c_uint_type,
+ c_long_type,
+ c_ulong_type,
+ c_longlong_type,
+ c_ulonglong_type,
+ c_longdouble_type,
+ f16_type,
+ f32_type,
+ f64_type,
+ f128_type,
+ c_void_type,
+ bool_type,
+ void_type,
+ type_type,
+ anyerror_type,
+ comptime_int_type,
+ comptime_float_type,
+ noreturn_type,
+ fn_naked_noreturn_no_args_type,
+ single_const_pointer_to_comptime_int_type,
+ const_slice_u8_type,
+
+ zero,
+ void_value,
+ noreturn_value,
+ bool_true,
+ bool_false, // See last_no_payload_tag below.
+ // After this, the tag requires a payload.
+
+ ty,
+ int_u64,
+ int_i64,
+ int_big,
+ function,
+ ref,
+ ref_val,
+ bytes,
+
+ pub const last_no_payload_tag = Tag.bool_false;
+ pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1;
+ };
-/// Values are ref-counted, heap-allocated, and copy-on-write
-/// If there is only 1 ref then write need not copy
-pub const Value = struct {
- id: Id,
- typ: *Type,
- ref_count: std.atomic.Int(usize),
+ pub fn initTag(comptime small_tag: Tag) Value {
+ comptime assert(@enumToInt(small_tag) < Tag.no_payload_count);
+ return .{ .tag_if_small_enough = @enumToInt(small_tag) };
+ }
- /// Thread-safe
- pub fn ref(base: *Value) void {
- _ = base.ref_count.incr();
+ pub fn initPayload(payload: *Payload) Value {
+ assert(@enumToInt(payload.tag) >= Tag.no_payload_count);
+ return .{ .ptr_otherwise = payload };
}
- /// Thread-safe
- pub fn deref(base: *Value, comp: *Compilation) void {
- if (base.ref_count.decr() == 1) {
- base.typ.base.deref(comp);
- switch (base.id) {
- .Type => @fieldParentPtr(Type, "base", base).destroy(comp),
- .Fn => @fieldParentPtr(Fn, "base", base).destroy(comp),
- .FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp),
- .Void => @fieldParentPtr(Void, "base", base).destroy(comp),
- .Bool => @fieldParentPtr(Bool, "base", base).destroy(comp),
- .NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp),
- .Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp),
- .Int => @fieldParentPtr(Int, "base", base).destroy(comp),
- .Array => @fieldParentPtr(Array, "base", base).destroy(comp),
- }
+ pub fn tag(self: Value) Tag {
+ if (self.tag_if_small_enough < Tag.no_payload_count) {
+ return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough));
+ } else {
+ return self.ptr_otherwise.tag;
}
}
- pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void {
- base.typ.base.deref(comp);
- new_type.base.ref();
- base.typ = new_type;
- }
+ pub fn cast(self: Value, comptime T: type) ?*T {
+ if (self.tag_if_small_enough < Tag.no_payload_count)
+ return null;
- pub fn getRef(base: *Value) *Value {
- base.ref();
- return base;
- }
+ const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag;
+ if (self.ptr_otherwise.tag != expected_tag)
+ return null;
- pub fn cast(base: *Value, comptime T: type) ?*T {
- if (base.id != @field(Id, @typeName(T))) return null;
- return @fieldParentPtr(T, "base", base);
+ return @fieldParentPtr(T, "base", self.ptr_otherwise);
}
- pub fn dump(base: *const Value) void {
- std.debug.warn("{}", .{@tagName(base.id)});
+ pub fn format(
+ self: Value,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ out_stream: var,
+ ) !void {
+ comptime assert(fmt.len == 0);
+ var val = self;
+ while (true) switch (val.tag()) {
+ .u8_type => return out_stream.writeAll("u8"),
+ .i8_type => return out_stream.writeAll("i8"),
+ .isize_type => return out_stream.writeAll("isize"),
+ .usize_type => return out_stream.writeAll("usize"),
+ .c_short_type => return out_stream.writeAll("c_short"),
+ .c_ushort_type => return out_stream.writeAll("c_ushort"),
+ .c_int_type => return out_stream.writeAll("c_int"),
+ .c_uint_type => return out_stream.writeAll("c_uint"),
+ .c_long_type => return out_stream.writeAll("c_long"),
+ .c_ulong_type => return out_stream.writeAll("c_ulong"),
+ .c_longlong_type => return out_stream.writeAll("c_longlong"),
+ .c_ulonglong_type => return out_stream.writeAll("c_ulonglong"),
+ .c_longdouble_type => return out_stream.writeAll("c_longdouble"),
+ .f16_type => return out_stream.writeAll("f16"),
+ .f32_type => return out_stream.writeAll("f32"),
+ .f64_type => return out_stream.writeAll("f64"),
+ .f128_type => return out_stream.writeAll("f128"),
+ .c_void_type => return out_stream.writeAll("c_void"),
+ .bool_type => return out_stream.writeAll("bool"),
+ .void_type => return out_stream.writeAll("void"),
+ .type_type => return out_stream.writeAll("type"),
+ .anyerror_type => return out_stream.writeAll("anyerror"),
+ .comptime_int_type => return out_stream.writeAll("comptime_int"),
+ .comptime_float_type => return out_stream.writeAll("comptime_float"),
+ .noreturn_type => return out_stream.writeAll("noreturn"),
+ .fn_naked_noreturn_no_args_type => return out_stream.writeAll("fn() callconv(.Naked) noreturn"),
+ .single_const_pointer_to_comptime_int_type => return out_stream.writeAll("*const comptime_int"),
+ .const_slice_u8_type => return out_stream.writeAll("[]const u8"),
+
+ .zero => return out_stream.writeAll("0"),
+ .void_value => return out_stream.writeAll("{}"),
+ .noreturn_value => return out_stream.writeAll("unreachable"),
+ .bool_true => return out_stream.writeAll("true"),
+ .bool_false => return out_stream.writeAll("false"),
+ .ty => return val.cast(Payload.Ty).?.ty.format("", options, out_stream),
+ .int_u64 => return std.fmt.formatIntValue(val.cast(Payload.Int_u64).?.int, "", options, out_stream),
+ .int_i64 => return std.fmt.formatIntValue(val.cast(Payload.Int_i64).?.int, "", options, out_stream),
+ .int_big => return out_stream.print("{}", .{val.cast(Payload.IntBig).?.big_int}),
+ .function => return out_stream.writeAll("(function)"),
+ .ref => return out_stream.writeAll("(ref)"),
+ .ref_val => {
+ try out_stream.writeAll("*const ");
+ val = val.cast(Payload.RefVal).?.val;
+ continue;
+ },
+ .bytes => return std.zig.renderStringLiteral(self.cast(Payload.Bytes).?.data, out_stream),
+ };
}
- pub fn getLlvmConst(base: *Value, ofile: *ObjectFile) (error{OutOfMemory}!?*llvm.Value) {
- switch (base.id) {
- .Type => unreachable,
- .Fn => return @fieldParentPtr(Fn, "base", base).getLlvmConst(ofile),
- .FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile),
- .Void => return null,
- .Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
- .NoReturn => unreachable,
- .Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile),
- .Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile),
- .Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile),
+ /// Asserts that the value is representable as an array of bytes.
+ /// Copies the value into a freshly allocated slice of memory, which is owned by the caller.
+ pub fn toAllocatedBytes(self: Value, allocator: *Allocator) Allocator.Error![]u8 {
+ if (self.cast(Payload.Bytes)) |bytes| {
+ return std.mem.dupe(allocator, u8, bytes.data);
}
+ unreachable;
}
- pub fn derefAndCopy(self: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
- if (self.ref_count.get() == 1) {
- // ( ͡° ͜ʖ ͡°)
- return self;
- }
-
- assert(self.ref_count.decr() != 1);
- return self.copy(comp);
+ /// Asserts that the value is representable as a type.
+ pub fn toType(self: Value) Type {
+ return switch (self.tag()) {
+ .ty => self.cast(Payload.Ty).?.ty,
+
+ .u8_type => Type.initTag(.@"u8"),
+ .i8_type => Type.initTag(.@"i8"),
+ .isize_type => Type.initTag(.@"isize"),
+ .usize_type => Type.initTag(.@"usize"),
+ .c_short_type => Type.initTag(.@"c_short"),
+ .c_ushort_type => Type.initTag(.@"c_ushort"),
+ .c_int_type => Type.initTag(.@"c_int"),
+ .c_uint_type => Type.initTag(.@"c_uint"),
+ .c_long_type => Type.initTag(.@"c_long"),
+ .c_ulong_type => Type.initTag(.@"c_ulong"),
+ .c_longlong_type => Type.initTag(.@"c_longlong"),
+ .c_ulonglong_type => Type.initTag(.@"c_ulonglong"),
+ .c_longdouble_type => Type.initTag(.@"c_longdouble"),
+ .f16_type => Type.initTag(.@"f16"),
+ .f32_type => Type.initTag(.@"f32"),
+ .f64_type => Type.initTag(.@"f64"),
+ .f128_type => Type.initTag(.@"f128"),
+ .c_void_type => Type.initTag(.@"c_void"),
+ .bool_type => Type.initTag(.@"bool"),
+ .void_type => Type.initTag(.@"void"),
+ .type_type => Type.initTag(.@"type"),
+ .anyerror_type => Type.initTag(.@"anyerror"),
+ .comptime_int_type => Type.initTag(.@"comptime_int"),
+ .comptime_float_type => Type.initTag(.@"comptime_float"),
+ .noreturn_type => Type.initTag(.@"noreturn"),
+ .fn_naked_noreturn_no_args_type => Type.initTag(.fn_naked_noreturn_no_args),
+ .single_const_pointer_to_comptime_int_type => Type.initTag(.single_const_pointer_to_comptime_int),
+ .const_slice_u8_type => Type.initTag(.const_slice_u8),
+
+ .zero,
+ .void_value,
+ .noreturn_value,
+ .bool_true,
+ .bool_false,
+ .int_u64,
+ .int_i64,
+ .int_big,
+ .function,
+ .ref,
+ .ref_val,
+ .bytes,
+ => unreachable,
+ };
}
- pub fn copy(base: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
- switch (base.id) {
- .Type => unreachable,
- .Fn => unreachable,
- .FnProto => unreachable,
- .Void => unreachable,
- .Bool => unreachable,
- .NoReturn => unreachable,
- .Ptr => unreachable,
- .Array => unreachable,
- .Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base,
+ /// Asserts the value is an integer.
+ pub fn toBigInt(self: Value, allocator: *Allocator) Allocator.Error!BigInt {
+ switch (self.tag()) {
+ .ty,
+ .u8_type,
+ .i8_type,
+ .isize_type,
+ .usize_type,
+ .c_short_type,
+ .c_ushort_type,
+ .c_int_type,
+ .c_uint_type,
+ .c_long_type,
+ .c_ulong_type,
+ .c_longlong_type,
+ .c_ulonglong_type,
+ .c_longdouble_type,
+ .f16_type,
+ .f32_type,
+ .f64_type,
+ .f128_type,
+ .c_void_type,
+ .bool_type,
+ .void_type,
+ .type_type,
+ .anyerror_type,
+ .comptime_int_type,
+ .comptime_float_type,
+ .noreturn_type,
+ .fn_naked_noreturn_no_args_type,
+ .single_const_pointer_to_comptime_int_type,
+ .const_slice_u8_type,
+ .void_value,
+ .noreturn_value,
+ .bool_true,
+ .bool_false,
+ .function,
+ .ref,
+ .ref_val,
+ .bytes,
+ => unreachable,
+
+ .zero => return BigInt.initSet(allocator, 0),
+
+ .int_u64 => return BigInt.initSet(allocator, self.cast(Payload.Int_u64).?.int),
+ .int_i64 => return BigInt.initSet(allocator, self.cast(Payload.Int_i64).?.int),
+ .int_big => return self.cast(Payload.IntBig).?.big_int,
}
}
- pub const Parent = union(enum) {
- None,
- BaseStruct: BaseStruct,
- BaseArray: BaseArray,
- BaseUnion: *Value,
- BaseScalar: *Value,
-
- pub const BaseStruct = struct {
- val: *Value,
- field_index: usize,
- };
-
- pub const BaseArray = struct {
- val: *Value,
- elem_index: usize,
- };
- };
-
- pub const Id = enum {
- Type,
- Fn,
- Void,
- Bool,
- NoReturn,
- Array,
- Ptr,
- Int,
- FnProto,
- };
-
- pub const Type = @import("type.zig").Type;
-
- pub const FnProto = struct {
- base: Value,
-
- /// The main external name that is used in the .o file.
- /// TODO https://github.com/ziglang/zig/issues/265
- symbol_name: ArrayListSentineled(u8, 0),
-
- pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: ArrayListSentineled(u8, 0)) !*FnProto {
- const self = try comp.gpa().create(FnProto);
- self.* = FnProto{
- .base = Value{
- .id = .FnProto,
- .typ = &fn_type.base,
- .ref_count = std.atomic.Int(usize).init(1),
+ /// Asserts the value is an integer, and the destination type is ComptimeInt or Int.
+ pub fn intFitsInType(self: Value, ty: Type, target: Target) bool {
+ switch (self.tag()) {
+ .ty,
+ .u8_type,
+ .i8_type,
+ .isize_type,
+ .usize_type,
+ .c_short_type,
+ .c_ushort_type,
+ .c_int_type,
+ .c_uint_type,
+ .c_long_type,
+ .c_ulong_type,
+ .c_longlong_type,
+ .c_ulonglong_type,
+ .c_longdouble_type,
+ .f16_type,
+ .f32_type,
+ .f64_type,
+ .f128_type,
+ .c_void_type,
+ .bool_type,
+ .void_type,
+ .type_type,
+ .anyerror_type,
+ .comptime_int_type,
+ .comptime_float_type,
+ .noreturn_type,
+ .fn_naked_noreturn_no_args_type,
+ .single_const_pointer_to_comptime_int_type,
+ .const_slice_u8_type,
+ .void_value,
+ .noreturn_value,
+ .bool_true,
+ .bool_false,
+ .function,
+ .ref,
+ .ref_val,
+ .bytes,
+ => unreachable,
+
+ .zero => return true,
+
+ .int_u64 => switch (ty.zigTypeTag()) {
+ .Int => {
+ const x = self.cast(Payload.Int_u64).?.int;
+ if (x == 0) return true;
+ const info = ty.intInfo(target);
+ const needed_bits = std.math.log2(x) + 1 + @boolToInt(info.signed);
+ return info.bits >= needed_bits;
},
- .symbol_name = symbol_name,
- };
- fn_type.base.base.ref();
- return self;
- }
-
- pub fn destroy(self: *FnProto, comp: *Compilation) void {
- self.symbol_name.deinit();
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?*llvm.Value {
- const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
- const llvm_fn = llvm.AddFunction(
- ofile.module,
- self.symbol_name.span(),
- llvm_fn_type,
- ) orelse return error.OutOfMemory;
-
- // TODO port more logic from codegen.cpp:fn_llvm_value
-
- return llvm_fn;
- }
- };
-
- pub const Fn = struct {
- base: Value,
-
- /// The main external name that is used in the .o file.
- /// TODO https://github.com/ziglang/zig/issues/265
- symbol_name: ArrayListSentineled(u8, 0),
-
- /// parent should be the top level decls or container decls
- fndef_scope: *Scope.FnDef,
-
- /// parent is scope for last parameter
- child_scope: *Scope,
-
- /// parent is child_scope
- block_scope: ?*Scope.Block,
-
- /// Path to the object file that contains this function
- containing_object: ArrayListSentineled(u8, 0),
-
- link_set_node: *std.TailQueue(?*Value.Fn).Node,
-
- /// Creates a Fn value with 1 ref
- /// Takes ownership of symbol_name
- pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: ArrayListSentineled(u8, 0)) !*Fn {
- const link_set_node = try comp.gpa().create(Compilation.FnLinkSet.Node);
- link_set_node.* = Compilation.FnLinkSet.Node{
- .data = null,
- .next = undefined,
- .prev = undefined,
- };
- errdefer comp.gpa().destroy(link_set_node);
-
- const self = try comp.gpa().create(Fn);
- self.* = Fn{
- .base = Value{
- .id = .Fn,
- .typ = &fn_type.base,
- .ref_count = std.atomic.Int(usize).init(1),
+ .ComptimeInt => return true,
+ else => unreachable,
+ },
+ .int_i64 => switch (ty.zigTypeTag()) {
+ .Int => {
+ const x = self.cast(Payload.Int_i64).?.int;
+ if (x == 0) return true;
+ const info = ty.intInfo(target);
+ if (!info.signed and x < 0)
+ return false;
+ @panic("TODO implement i64 intFitsInType");
},
- .fndef_scope = fndef_scope,
- .child_scope = &fndef_scope.base,
- .block_scope = null,
- .symbol_name = symbol_name,
- .containing_object = ArrayListSentineled(u8, 0).initNull(comp.gpa()),
- .link_set_node = link_set_node,
- };
- fn_type.base.base.ref();
- fndef_scope.fn_val = self;
- fndef_scope.base.ref();
- return self;
- }
-
- pub fn destroy(self: *Fn, comp: *Compilation) void {
- // remove with a tombstone so that we do not have to grab a lock
- if (self.link_set_node.data != null) {
- // it's now the job of the link step to find this tombstone and
- // deallocate it.
- self.link_set_node.data = null;
- } else {
- comp.gpa().destroy(self.link_set_node);
- }
-
- self.containing_object.deinit();
- self.fndef_scope.base.deref(comp);
- self.symbol_name.deinit();
- comp.gpa().destroy(self);
- }
-
- /// We know that the function definition will end up in an .o file somewhere.
- /// Here, all we have to do is generate a global prototype.
- /// TODO cache the prototype per ObjectFile
- pub fn getLlvmConst(self: *Fn, ofile: *ObjectFile) !?*llvm.Value {
- const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
- const llvm_fn = llvm.AddFunction(
- ofile.module,
- self.symbol_name.span(),
- llvm_fn_type,
- ) orelse return error.OutOfMemory;
-
- // TODO port more logic from codegen.cpp:fn_llvm_value
-
- return llvm_fn;
- }
- };
-
- pub const Void = struct {
- base: Value,
-
- pub fn get(comp: *Compilation) *Void {
- comp.void_value.base.ref();
- return comp.void_value;
- }
-
- pub fn destroy(self: *Void, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
- };
-
- pub const Bool = struct {
- base: Value,
- x: bool,
-
- pub fn get(comp: *Compilation, x: bool) *Bool {
- if (x) {
- comp.true_value.base.ref();
- return comp.true_value;
- } else {
- comp.false_value.base.ref();
- return comp.false_value;
- }
- }
-
- pub fn destroy(self: *Bool, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) !?*llvm.Value {
- const llvm_type = llvm.Int1TypeInContext(ofile.context) orelse return error.OutOfMemory;
- if (self.x) {
- return llvm.ConstAllOnes(llvm_type);
- } else {
- return llvm.ConstNull(llvm_type);
- }
- }
- };
-
- pub const NoReturn = struct {
- base: Value,
-
- pub fn get(comp: *Compilation) *NoReturn {
- comp.noreturn_value.base.ref();
- return comp.noreturn_value;
+ .ComptimeInt => return true,
+ else => unreachable,
+ },
+ .int_big => switch (ty.zigTypeTag()) {
+ .Int => {
+ const info = ty.intInfo(target);
+ return self.cast(Payload.IntBig).?.big_int.fitsInTwosComp(info.signed, info.bits);
+ },
+ .ComptimeInt => return true,
+ else => unreachable,
+ },
}
+ }
- pub fn destroy(self: *NoReturn, comp: *Compilation) void {
- comp.gpa().destroy(self);
+ /// Asserts the value is a pointer and dereferences it.
+ pub fn pointerDeref(self: Value) Value {
+ switch (self.tag()) {
+ .ty,
+ .u8_type,
+ .i8_type,
+ .isize_type,
+ .usize_type,
+ .c_short_type,
+ .c_ushort_type,
+ .c_int_type,
+ .c_uint_type,
+ .c_long_type,
+ .c_ulong_type,
+ .c_longlong_type,
+ .c_ulonglong_type,
+ .c_longdouble_type,
+ .f16_type,
+ .f32_type,
+ .f64_type,
+ .f128_type,
+ .c_void_type,
+ .bool_type,
+ .void_type,
+ .type_type,
+ .anyerror_type,
+ .comptime_int_type,
+ .comptime_float_type,
+ .noreturn_type,
+ .fn_naked_noreturn_no_args_type,
+ .single_const_pointer_to_comptime_int_type,
+ .const_slice_u8_type,
+ .zero,
+ .void_value,
+ .noreturn_value,
+ .bool_true,
+ .bool_false,
+ .function,
+ .int_u64,
+ .int_i64,
+ .int_big,
+ .bytes,
+ => unreachable,
+
+ .ref => return self.cast(Payload.Ref).?.cell.contents,
+ .ref_val => return self.cast(Payload.RefVal).?.val,
}
- };
+ }
- pub const Ptr = struct {
- base: Value,
- special: Special,
- mut: Mut,
+ /// This type is not copyable since it may contain pointers to its inner data.
+ pub const Payload = struct {
+ tag: Tag,
- pub const Mut = enum {
- CompTimeConst,
- CompTimeVar,
- RunTime,
+ pub const Int_u64 = struct {
+ base: Payload = Payload{ .tag = .int_u64 },
+ int: u64,
};
- pub const Special = union(enum) {
- Scalar: *Value,
- BaseArray: BaseArray,
- BaseStruct: BaseStruct,
- HardCodedAddr: u64,
- Discard,
+ pub const Int_i64 = struct {
+ base: Payload = Payload{ .tag = .int_i64 },
+ int: i64,
};
- pub const BaseArray = struct {
- val: *Value,
- elem_index: usize,
+ pub const IntBig = struct {
+ base: Payload = Payload{ .tag = .int_big },
+ big_int: BigInt,
};
- pub const BaseStruct = struct {
- val: *Value,
- field_index: usize,
+ pub const Function = struct {
+ base: Payload = Payload{ .tag = .function },
+ /// Index into the `fns` array of the `ir.Module`
+ index: usize,
};
- pub fn createArrayElemPtr(
- comp: *Compilation,
- array_val: *Array,
- mut: Type.Pointer.Mut,
- size: Type.Pointer.Size,
- elem_index: usize,
- ) !*Ptr {
- array_val.base.ref();
- errdefer array_val.base.deref(comp);
-
- const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type;
- const ptr_type = try Type.Pointer.get(comp, Type.Pointer.Key{
- .child_type = elem_type,
- .mut = mut,
- .vol = Type.Pointer.Vol.Non,
- .size = size,
- .alignment = .Abi,
- });
- var ptr_type_consumed = false;
- errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp);
-
- const self = try comp.gpa().create(Value.Ptr);
- self.* = Value.Ptr{
- .base = Value{
- .id = .Ptr,
- .typ = &ptr_type.base,
- .ref_count = std.atomic.Int(usize).init(1),
- },
- .special = Special{
- .BaseArray = BaseArray{
- .val = &array_val.base,
- .elem_index = 0,
- },
- },
- .mut = Mut.CompTimeConst,
- };
- ptr_type_consumed = true;
- errdefer comp.gpa().destroy(self);
-
- return self;
- }
-
- pub fn destroy(self: *Ptr, comp: *Compilation) void {
- comp.gpa().destroy(self);
- }
-
- pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?*llvm.Value {
- const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context);
- // TODO carefully port the logic from codegen.cpp:gen_const_val_ptr
- switch (self.special) {
- .Scalar => |scalar| @panic("TODO"),
- .BaseArray => |base_array| {
- // TODO put this in one .o file only, and after that, generate extern references to it
- const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?;
- const ptr_bit_count = ofile.comp.target_ptr_bits;
- const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory;
- var indices = [_]*llvm.Value{
- llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory,
- llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory,
- };
- return llvm.ConstInBoundsGEP(
- array_llvm_value,
- @ptrCast([*]*llvm.Value, &indices),
- @intCast(c_uint, indices.len),
- ) orelse return error.OutOfMemory;
- },
- .BaseStruct => |base_struct| @panic("TODO"),
- .HardCodedAddr => |addr| @panic("TODO"),
- .Discard => unreachable,
- }
- }
- };
-
- pub const Array = struct {
- base: Value,
- special: Special,
-
- pub const Special = union(enum) {
- Undefined,
- OwnedBuffer: []u8,
- Explicit: Data,
+ pub const ArraySentinel0_u8_Type = struct {
+ base: Payload = Payload{ .tag = .array_sentinel_0_u8_type },
+ len: u64,
};
- pub const Data = struct {
- parent: Parent,
- elements: []*Value,
+ pub const SingleConstPtrType = struct {
+ base: Payload = Payload{ .tag = .single_const_ptr_type },
+ elem_type: *Type,
};
- /// Takes ownership of buffer
- pub fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array {
- const u8_type = Type.Int.get_u8(comp);
- defer u8_type.base.base.deref(comp);
-
- const array_type = try Type.Array.get(comp, Type.Array.Key{
- .elem_type = &u8_type.base,
- .len = buffer.len,
- });
- errdefer array_type.base.base.deref(comp);
-
- const self = try comp.gpa().create(Value.Array);
- self.* = Value.Array{
- .base = Value{
- .id = .Array,
- .typ = &array_type.base,
- .ref_count = std.atomic.Int(usize).init(1),
- },
- .special = Special{ .OwnedBuffer = buffer },
- };
- errdefer comp.gpa().destroy(self);
+ pub const Ref = struct {
+ base: Payload = Payload{ .tag = .ref },
+ cell: *MemoryCell,
+ };
- return self;
- }
+ pub const RefVal = struct {
+ base: Payload = Payload{ .tag = .ref_val },
+ val: Value,
+ };
- pub fn destroy(self: *Array, comp: *Compilation) void {
- switch (self.special) {
- .Undefined => {},
- .OwnedBuffer => |buf| {
- comp.gpa().free(buf);
- },
- .Explicit => {},
- }
- comp.gpa().destroy(self);
- }
+ pub const Bytes = struct {
+ base: Payload = Payload{ .tag = .bytes },
+ data: []const u8,
+ };
- pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?*llvm.Value {
- switch (self.special) {
- .Undefined => {
- const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
- return llvm.GetUndef(llvm_type);
- },
- .OwnedBuffer => |buf| {
- const dont_null_terminate = 1;
- const llvm_str_init = llvm.ConstStringInContext(
- ofile.context,
- buf.ptr,
- @intCast(c_uint, buf.len),
- dont_null_terminate,
- ) orelse return error.OutOfMemory;
- const str_init_type = llvm.TypeOf(llvm_str_init);
- const global = llvm.AddGlobal(ofile.module, str_init_type, "") orelse return error.OutOfMemory;
- llvm.SetInitializer(global, llvm_str_init);
- llvm.SetLinkage(global, llvm.PrivateLinkage);
- llvm.SetGlobalConstant(global, 1);
- llvm.SetUnnamedAddr(global, 1);
- llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type));
- return global;
- },
- .Explicit => @panic("TODO"),
- }
-
- //{
- // uint64_t len = type_entry->data.array.len;
- // if (const_val->data.x_array.special == ConstArraySpecialUndef) {
- // return LLVMGetUndef(type_entry->type_ref);
- // }
-
- // LLVMValueRef *values = allocate<LLVMValueRef>(len);
- // LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref;
- // bool make_unnamed_struct = false;
- // for (uint64_t i = 0; i < len; i += 1) {
- // ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i];
- // LLVMValueRef val = gen_const_val(g, elem_value, "");
- // values[i] = val;
- // make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val);
- // }
- // if (make_unnamed_struct) {
- // return LLVMConstStruct(values, len, true);
- // } else {
- // return LLVMConstArray(element_type_ref, values, (unsigned)len);
- // }
- //}
- }
+ pub const Ty = struct {
+ base: Payload = Payload{ .tag = .ty },
+ ty: Type,
+ };
};
+};
- pub const Int = struct {
- base: Value,
- big_int: std.math.big.Int,
-
- pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int {
- const self = try comp.gpa().create(Value.Int);
- self.* = Value.Int{
- .base = Value{
- .id = .Int,
- .typ = typ,
- .ref_count = std.atomic.Int(usize).init(1),
- },
- .big_int = undefined,
- };
- typ.base.ref();
- errdefer comp.gpa().destroy(self);
-
- self.big_int = try std.math.big.Int.init(comp.gpa());
- errdefer self.big_int.deinit();
-
- try self.big_int.setString(base, value);
-
- return self;
- }
-
- pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?*llvm.Value {
- switch (self.base.typ.id) {
- .Int => {
- const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
- if (self.big_int.len() == 0) {
- return llvm.ConstNull(type_ref);
- }
- const unsigned_val = if (self.big_int.len() == 1) blk: {
- break :blk llvm.ConstInt(type_ref, self.big_int.limbs[0], @boolToInt(false));
- } else if (@sizeOf(std.math.big.Limb) == @sizeOf(u64)) blk: {
- break :blk llvm.ConstIntOfArbitraryPrecision(
- type_ref,
- @intCast(c_uint, self.big_int.len()),
- @ptrCast([*]u64, self.big_int.limbs.ptr),
- );
- } else {
- @compileError("std.math.Big.Int.Limb size does not match LLVM");
- };
- return if (self.big_int.isPositive()) unsigned_val else llvm.ConstNeg(unsigned_val);
- },
- .ComptimeInt => unreachable,
- else => unreachable,
- }
- }
-
- pub fn copy(old: *Int, comp: *Compilation) !*Int {
- old.base.typ.base.ref();
- errdefer old.base.typ.base.deref(comp);
-
- const new = try comp.gpa().create(Value.Int);
- new.* = Value.Int{
- .base = Value{
- .id = .Int,
- .typ = old.base.typ,
- .ref_count = std.atomic.Int(usize).init(1),
- },
- .big_int = undefined,
- };
- errdefer comp.gpa().destroy(new);
-
- new.big_int = try old.big_int.clone();
- errdefer new.big_int.deinit();
-
- return new;
- }
+/// This is the heart of resource management of the Zig compiler. The Zig compiler uses
+/// stop-the-world mark-and-sweep garbage collection during compilation to manage the resources
+/// associated with evaluating compile-time code and semantic analysis. Each `MemoryCell` represents
+/// a root.
+pub const MemoryCell = struct {
+ parent: Parent,
+ contents: Value,
- pub fn destroy(self: *Int, comp: *Compilation) void {
- self.big_int.deinit();
- comp.gpa().destroy(self);
- }
+ pub const Parent = union(enum) {
+ none,
+ struct_field: struct {
+ struct_base: *MemoryCell,
+ field_index: usize,
+ },
+ array_elem: struct {
+ array_base: *MemoryCell,
+ elem_index: usize,
+ },
+ union_field: *MemoryCell,
+ err_union_code: *MemoryCell,
+ err_union_payload: *MemoryCell,
+ optional_payload: *MemoryCell,
+ optional_flag: *MemoryCell,
};
};
diff --git a/src/ir.cpp b/src/ir.cpp
@@ -11283,9 +11283,9 @@ static bool ir_num_lit_fits_in_other_type(IrAnalyze *ira, IrInstGen *instruction
Buf *val_buf = buf_alloc();
bigint_append_buf(val_buf, &const_val->data.x_bigint, 10);
ir_add_error_node(ira, instruction->base.source_node,
- buf_sprintf("integer value %s has no representation in type '%s'",
- buf_ptr(val_buf),
- buf_ptr(&other_type->name)));
+ buf_sprintf("type %s cannot represent integer value %s",
+ buf_ptr(&other_type->name),
+ buf_ptr(val_buf)));
return false;
}
if (other_type->data.floating.bit_count >= const_val->type->data.floating.bit_count) {
diff --git a/test/stage2/ir.zig b/test/stage2/ir.zig
@@ -0,0 +1,54 @@
+test "hello world IR" {
+ exeCmp(
+ \\@0 = str("Hello, world!\n")
+ \\@1 = primitive(void)
+ \\@2 = primitive(usize)
+ \\@3 = fntype([], @1, cc=Naked)
+ \\@4 = int(0)
+ \\@5 = int(1)
+ \\@6 = int(231)
+ \\@7 = str("len")
+ \\
+ \\@8 = fn(@3, {
+ \\ %0 = as(@2, @5) ; SYS_write
+ \\ %1 = as(@2, @5) ; STDOUT_FILENO
+ \\ %2 = ptrtoint(@0) ; msg ptr
+ \\ %3 = fieldptr(@0, @7) ; msg len ptr
+ \\ %4 = deref(%3) ; msg len
+ \\ %sysoutreg = str("={rax}")
+ \\ %rax = str("{rax}")
+ \\ %rdi = str("{rdi}")
+ \\ %rsi = str("{rsi}")
+ \\ %rdx = str("{rdx}")
+ \\ %rcx = str("rcx")
+ \\ %r11 = str("r11")
+ \\ %memory = str("memory")
+ \\ %syscall = str("syscall")
+ \\ %5 = asm(%syscall, @2,
+ \\ volatile=1,
+ \\ output=%sysoutreg,
+ \\ inputs=[%rax, %rdi, %rsi, %rdx],
+ \\ clobbers=[%rcx, %r11, %memory],
+ \\ args=[%0, %1, %2, %4])
+ \\
+ \\ %6 = as(@2, @6) ;SYS_exit_group
+ \\ %7 = as(@2, @4) ;exit code
+ \\ %8 = asm(%syscall, @2,
+ \\ volatile=1,
+ \\ output=%sysoutreg,
+ \\ inputs=[%rax, %rdi],
+ \\ clobbers=[%rcx, %r11, %memory],
+ \\ args=[%6, %7])
+ \\
+ \\ %9 = unreachable()
+ \\})
+ \\
+ \\@9 = str("_start")
+ \\@10 = export(@9, @8)
+ ,
+ \\Hello, world!
+ \\
+ );
+}
+
+fn exeCmp(src: []const u8, expected_stdout: []const u8) void {}