diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 4a13d3e3e4..ef6f75601f 100644 --- 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 index 01f4b6dccd..693a360fa9 100644 --- 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 index 282ca61fc8..1900a36dfe 100644 --- 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 index 8388fda72a..eb4898e6ab 100644 --- 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 index 81f34b09c9..dcf7842a3c 100644 --- 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/string_literal.zig similarity index 75% rename from lib/std/zig/parse_string_literal.zig rename to lib/std/zig/string_literal.zig index 949940f550..cc6030ad15 100644 --- a/lib/std/zig/parse_string_literal.zig +++ b/lib/std/zig/string_literal.zig @@ -6,7 +6,7 @@ const State = enum { Backslash, }; -pub const ParseStringLiteralError = error{ +pub const ParseError = error{ OutOfMemory, /// When this is returned, index will be the position of the character. @@ -14,11 +14,11 @@ pub const ParseStringLiteralError = error{ }; /// caller owns returned memory -pub fn parseStringLiteral( +pub fn parse( allocator: *std.mem.Allocator, bytes: []const u8, bad_index: *usize, // populated if error.InvalidCharacter is returned -) ParseStringLiteralError![]u8 { +) ParseError![]u8 { assert(bytes.len >= 2 and bytes[0] == '"' and bytes[bytes.len - 1] == '"'); var list = std.ArrayList(u8).init(allocator); @@ -110,7 +110,7 @@ pub fn parseStringLiteral( unreachable; } -test "parseStringLiteral" { +test "parse" { const expect = std.testing.expect; const eql = std.mem.eql; @@ -119,7 +119,37 @@ test "parseStringLiteral" { 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))); + 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 deleted file mode 100644 index e61a5bf657..0000000000 --- a/src-self-hosted/c_int.zig +++ /dev/null @@ -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 index ce3d9080ba..e33d468a26 100644 --- 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, + tag: Tag, + ty: Type, + /// Byte offset into the source. + src: usize, - /// 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, + 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, - } - } - - pub fn setGenerated(base: *Inst) void { - base.is_generated = true; - } - - 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 isCompTime(base: *const Inst) bool { - return base.val == .KnownValue; - } - - pub fn linkToParent(self: *Inst, parent: *Inst) void { - assert(self.parent == null); - assert(parent.child == null); - self.parent = parent; - parent.child = self; - } - - pub const Id = enum { - Return, - Const, - Ref, - DeclVar, - CheckVoidStmt, - Phi, - Br, - AddImplicitReturnType, - Call, - DeclRef, - PtrType, - VarPtr, - LoadPtr, - }; - - pub const Call = struct { - 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; - } - }; - - pub const Const = struct { - 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); - } - }; - - pub const Return = struct { - 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; - } + if (base.tag != T.base_tag) return null; - } - }; - 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 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, - } - }, - } - } - }; - - 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 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 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 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 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 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); - - return ira.irb.buildConstValue(self.base.scope, self.base.span, &ptr_type.base.base); - } - }; - - 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 - } - }; - - 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); - } - }; - - 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 - } - }; - - 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 - } - }; - - 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 - } - }; - - pub const AddImplicitReturnType = struct { - base: Inst, - params: Params, - - pub const Params = struct { - target: *Inst, - }; - - const ir_val_init = IrVal.Init.Unknown; - - 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}); - } - - pub fn hasSideEffects(inst: *const TestErr) bool { - return false; - } - - 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); - }, - } - } - }; - - pub const TestCompTime = struct { - base: Inst, - params: Params, - - pub const Params = struct { - target: *Inst, - }; - - 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; + return @fieldParentPtr(T, "base", base); } - pub fn linkToParent(self: *BasicBlock, parent: *BasicBlock) void { - assert(self.parent == null); - assert(parent.child == null); - self.parent = parent; - parent.child = self; + pub fn Args(comptime T: type) type { + return std.meta.fieldInfo(T, "args").field_type; } + + /// 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, + + .assembly, + .ptrtoint, + => null, + }; + } + + pub const Unreach = struct { + pub const base_tag = Tag.unreach; + base: Inst, + args: void, + }; + + pub const Constant = struct { + pub const base_tag = Tag.constant; + base: Inst, + + val: Value, + }; + + pub const Assembly = struct { + pub const base_tag = Tag.assembly; + base: Inst, + + 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 PtrToInt = struct { + pub const base_tag = Tag.ptrtoint; + + base: Inst, + args: struct { + ptr: *Inst, + }, + }; }; -/// Stuff that survives longer than Builder -pub const Code = struct { - basic_block_list: std.ArrayList(*BasicBlock), +pub const TypedValue = struct { + ty: Type, + val: Value, +}; + +pub const Module = struct { + exports: []Export, + errors: []ErrorMsg, arena: std.heap.ArenaAllocator, - return_type: ?*Type, - tree_scope: *Scope.AstTree, + fns: []Fn, - /// allocator is comp.gpa() - pub fn destroy(self: *Code, allocator: *Allocator) void { + pub const Export = struct { + name: []const u8, + typed_value: TypedValue, + src: usize, + }; + + pub const Fn = struct { + analysis_status: enum { in_progress, failure, success }, + body: []*Inst, + fn_type: Type, + }; + + pub fn deinit(self: *Module, allocator: *Allocator) void { + allocator.free(self.exports); + allocator.free(self.errors); self.arena.deinit(); - allocator.destroy(self); - } - - 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", .{}); - } - } - } - - /// 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; + self.* = undefined; } }; -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, - }; - } - - pub fn abort(self: *Builder) void { - self.code.destroy(self.comp.gpa()); - } - - /// Call code.destroy() when done - pub fn finish(self: *Builder) *Code { - return self.code; - } - - /// 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; - } - - pub fn setCursorAtEndAndAppendBlock(self: *Builder, basic_block: *BasicBlock) !void { - try self.code.basic_block_list.append(basic_block); - self.setCursorAtEnd(basic_block); - } - - pub fn setCursorAtEnd(self: *Builder, basic_block: *BasicBlock) void { - self.current_basic_block = basic_block; - } - - 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; - } - - 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 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); - } - - //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, - }); - //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 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.?, - } - } - } - - 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; - }, - } - } else { - base = 10; - rest = int_token; - } - - 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 inst = try irb.build(Inst.Const, scope, Span.token(int_lit.token), Inst.Const.Params{}); - inst.val = IrVal{ .KnownValue = &int_val.base }; - return inst; - } - - 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; - }, - }; - 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); - } - } - - pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst { - const block_scope = try Scope.Block.create(irb.comp, parent_scope); - - 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); - } - - 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), - ); - } - - 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); - - 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; - } - - 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 }, - ); - } - } - - if (is_continuation_unreachable) { - assert(noreturn_return_value != null); - if (block.label == null or block_scope.incoming_blocks.len == 0) { - return noreturn_return_value.?; - } - - 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 (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); - - _ = 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, - }); - - 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(), - }); - } - - _ = try irb.genDefersForBlock(child_scope, outer_block_scope, .ScopeExit); - return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true); - } - - 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, - }); - - 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, - }); - - 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(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 }); - }, - } - }, - .NotFound => {}, - } - - //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, +pub const ErrorMsg = struct { + byte_offset: usize, + msg: []const u8, +}; + +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, + }; + 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, + }; + return Module{ + .exports = ctx.exports.toOwnedSlice(), + .errors = ctx.errors.toOwnedSlice(), + .fns = ctx.fns.toOwnedSlice(), + .arena = ctx.arena, + }; +} + +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, }; - fn countDefers(irb: *Builder, inner_scope: *Scope, outer_scope: *Scope) DeferCounts { - var result = DeferCounts{ .scope_exit = 0, .error_exit = 0 }; + const NewInst = struct { + /// null means a semantic analysis error happened + ptr: ?*Inst, + }; - 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; - }, - .FnDef => break, + const Fn = struct { + body: std.ArrayList(*Inst), + inst_table: std.AutoHashMap(*text.Inst, NewInst), + /// Index into Module fns array + fn_index: usize, + }; - .CompTime, - .Block, - .Decls, - .Root, - .Var, - => scope = scope.parent orelse break, + const InnerError = error{ OutOfMemory, AnalysisFail }; - .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, + 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 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, - }); - }, + 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; + } + } + + 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; } } - fn arena(self: *Builder) *Allocator { - return &self.code.arena.allocator; + fn requireFunctionBody(self: *Analyze, func: ?*Fn, src: usize) !*Fn { + return func orelse return self.fail(src, "instruction illegal outside function body", .{}); } - 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 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, }; + } - // 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)), - } + fn resolveConstValue(self: *Analyze, base: *Inst) !Value { + return base.value() orelse return self.fail(base.src, "unable to resolve comptime value", .{}); + } + + 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); + } + + 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(); + } + + 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, + }); + } - self.next_debug_id += 1; - try self.current_basic_block.instruction_list.append(&inst.base); + /// 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; } - 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; - } - - 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 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; - } - - /// 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 }, - ); - } - } - - 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, - ); - } - 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.?; - } - } -}; - -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, + 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: *Analyze) void { - self.irb.abort(); + 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; } - 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 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 }; - 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; - } + const ty_payload = try self.arena.allocator.create(Type.Payload.SingleConstPointer); + ty_payload.* = .{ .pointee_type = Type.initPayload(&array_payload.base) }; - 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; - } + const bytes_payload = try self.arena.allocator.create(Value.Payload.Bytes); + bytes_payload.* = .{ .data = str }; - 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; - } - 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; - } - if (!need_repeat) - return; - need_repeat = false; - ira.old_bb_index = 0; - continue; - } - } - - 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; - } - - 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 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); - } - - // 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, + return self.constInst(src, .{ + .ty = Type.initPayload(&ty_payload.base), + .val = Value.initPayload(&bytes_payload.base), }); - //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; } - fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Inst) ?*Value { - @panic("TODO"); + fn constType(self: *Analyze, src: usize, ty: Type) !*Inst { + return self.constInst(src, .{ + .ty = Type.initTag(.type), + .val = try ty.toValue(&self.arena.allocator), + }); } - 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; + fn constVoid(self: *Analyze, src: usize) !*Inst { + return self.constInst(src, .{ + .ty = Type.initTag(.void), + .val = Value.initTag(.void_value), + }); + } + + 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 }; + + return self.constInst(src, .{ + .ty = ty, + .val = Value.initPayload(&int_payload.base), + }); + } + + 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 }; + + return self.constInst(src, .{ + .ty = ty, + .val = Value.initPayload(&int_payload.base), + }); + } + + 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 { + if (big_int.to(i64)) |x| { + return self.constIntSigned(src, ty, x); + } else |err| switch (err) { + error.NegativeIntoUnsigned => unreachable, + error.TargetTooSmall => {}, // handled below + } + } + + const big_int_payload = try self.arena.allocator.create(Value.Payload.IntBig); + big_int_payload.* = .{ .big_int = big_int }; + + return self.constInst(src, .{ + .ty = ty, + .val = Value.initPayload(&big_int_payload.base), + }); + } + + 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); + }, + .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).?), + } + } + + 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); + + 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, + }; + + 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 }); + } + + const f = &self.fns.items[new_func.fn_index]; + f.analysis_status = .success; + f.body = new_func.body.toOwnedSlice(); + + const fn_payload = try self.arena.allocator.create(Value.Payload.Function); + fn_payload.* = .{ .index = new_func.fn_index }; + + return self.constInst(fn_inst.base.src, .{ + .ty = fn_type, + .val = Value.initPayload(&fn_payload.base), + }); + } + + fn analyzeInstFnType(self: *Analyze, func: ?*Fn, fntype: *text.Inst.FnType) InnerError!*Inst { + const return_type = try self.resolveType(func, fntype.positionals.return_type); + + 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)); + } + + return self.fail(fntype.base.src, "TODO implement fntype instruction more", .{}); + } + + fn analyzeInstPrimitive(self: *Analyze, func: ?*Fn, primitive: *text.Inst.Primitive) InnerError!*Inst { + return self.constType(primitive.base.src, primitive.positionals.tag.toType()); + } + + 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); + } + + 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}); + } + // 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 }); + } + + 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 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), + }); + } else { + return self.fail( + fieldptr.positionals.field_name.src, + "no member named '{}' in '{}'", + .{ field_name, elem_ty }, + ); + } + }, + else => return self.fail(fieldptr.base.src, "type '{}' does not support field access", .{elem_ty}), + } + } + + 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); + + 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, + }, + ), + }; + + switch (new_inst.ty.zigTypeTag()) { + .ComptimeInt, .Int => {}, + else => return self.fail( + intcast.positionals.value.src, + "expected integer type, found '{}'", + .{new_inst.ty}, + ), + } + + if (dest_is_comptime_int or new_inst.value() != null) { + return self.coerce(dest_type, new_inst); + } + + return self.fail(intcast.base.src, "TODO implement analyze widen or shorten int", .{}); + } + + 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}), + }; + if (ptr.value()) |val| { + return self.constInst(deref.base.src, .{ + .ty = elem_ty, + .val = val.pointerDeref(), + }); + } + + return self.fail(deref.base.src, "TODO implement runtime deref", .{}); + } + + 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; + + 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); + + for (inputs) |*elem, i| { + elem.* = try self.resolveConstString(func, assembly.kw_args.inputs[i]); + } + for (clobbers) |*elem, i| { + elem.* = try self.resolveConstString(func, assembly.kw_args.clobbers[i]); + } + for (args) |*elem, i| { + elem.* = try self.resolveInst(func, assembly.kw_args.args[i]); + } + + 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, + }); + } + + 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, {}); + } + + 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); + } + + // *[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); + } + } + + // 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 }); + } + return self.constInst(inst.src, .{ .ty = dest_type, .val = val }); + } + + return self.fail(inst.src, "TODO implement type coercion", .{}); + } + + 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 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 }); + } + return self.fail(inst.src, "TODO implement coerceArrayPtrToSlice runtime instruction", .{}); + } + + 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; + } + + const InMemoryCoercionResult = enum { + ok, + no_match, + }; + + fn coerceInMemoryAllowed(dest_type: Type, src_type: Type) InMemoryCoercionResult { + if (dest_type.eql(src_type)) + return .ok; + + // TODO: implement more of this function + + return .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(); +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 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 args = try std.process.argsAlloc(allocator); - 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); + const src_path = args[1]; + const debug_error_trace = true; + + const source = try std.fs.cwd().readFileAllocOptions(allocator, src_path, std.math.maxInt(u32), 1, 0); + + var zir_module = try text.parse(allocator, source); + defer zir_module.deinit(allocator); + + 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); } - return irb.finish(); + var analyzed_module = try analyze(allocator, zir_module); + defer analyzed_module.deinit(allocator); + + 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 }); + } + if (debug_error_trace) return error.ParseFailure; + std.process.exit(1); + } + + var new_zir_module = try text.emit_zir(allocator, analyzed_module); + defer new_zir_module.deinit(allocator); + + var bos = std.io.bufferedOutStream(std.io.getStdOut().outStream()); + try new_zir_module.writeToStream(allocator, bos.outStream()); + try bos.flush(); } -pub fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code { - const old_entry_bb = old_code.basic_block_list.at(0); - - var ira = try Analyze.init(comp, old_code.tree_scope, expected_type); - errdefer ira.abort(); - - const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null); - new_entry_bb.ref(&ira.irb); - - ira.irb.current_basic_block = new_entry_bb; - - ira.startBasicBlock(old_entry_bb, null); - - while (ira.old_bb_index < old_code.basic_block_list.len) { - const old_instruction = ira.parent_basic_block.instruction_list.at(ira.instruction_index); - - if (old_instruction.ref_count == 0 and !old_instruction.hasSideEffects()) { - ira.instruction_index += 1; - continue; +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; + }, } - - 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 - - if (return_inst.isNoReturn()) { - try ira.finishBasicBlock(old_code); - continue; - } - - ira.instruction_index += 1; } - - if (ira.src_implicit_return_type_list.len == 0) { - ira.irb.code.return_type = &Type.NoReturn.get(comp).base; - return ira.irb.finish(); - } - - ira.irb.code.return_type = try ira.resolvePeerTypes(expected_type, ira.src_implicit_return_type_list.span()); - return ira.irb.finish(); + 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 new file mode 100644 index 0000000000..d8606656b7 --- /dev/null +++ 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 index 6adad9fa67..51fe8cc685 100644 --- 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; -pub const Type = struct { - base: Value, - id: Id, - name: []const u8, - abi_alignment: AbiAlignment, +/// 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 const AbiAlignment = event.Future(error{OutOfMemory}!u32); + 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, - pub const Id = builtin.TypeId; + .@"f16", + .@"f32", + .@"f64", + .@"f128", + => return .Float, - 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), + .@"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 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 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 initPayload(payload: *Payload) Type { + assert(@enumToInt(payload.tag) >= Tag.no_payload_count); + return .{ .ptr_otherwise = payload }; + } + + 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; - .NoReturn, - .Void, - .Bool, - .Int, - .Float, - .Pointer, - .ErrorSet, - .Enum, - .Fn, - .Frame, - .AnyFrame, - .Vector, - => return false, + const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag; + if (self.ptr_otherwise.tag != expected_tag) + return null; - .Struct => @panic("TODO"), - .Array => @panic("TODO"), - .Optional => @panic("TODO"), - .ErrorUnion => @panic("TODO"), - .Union => @panic("TODO"), - } + return @fieldParentPtr(T, "base", self.ptr_otherwise); } - 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, - + 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 ptr_type = @fieldParentPtr(Pointer, "base", base); - return ptr_type.key.child_type.hasBits(); + 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"); }, - - .ErrorSet => @panic("TODO"), - .Enum => @panic("TODO"), - .Struct => @panic("TODO"), - .Array => @panic("TODO"), - .Optional => @panic("TODO"), - .ErrorUnion => @panic("TODO"), - .Union => @panic("TODO"), - } - } - - 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)}); - } - - 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), - }, - .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; - } - - /// 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 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, - - pub const Kind = enum { - Normal, - Generic, - }; - - pub const NonKey = union { - Normal: Normal, - Generic: void, - - pub const Normal = struct { - variable_list: std.ArrayList(*Scope.Var), - }; - }; - - 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); - } - }, - } - } - - 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(); - } - }, - } - } - }; - - const CallingConvention = builtin.CallingConvention; - - pub const Param = struct { - is_noalias: bool, - typ: *Type, - }; - - 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; - } - }; - - 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"); - } - }; - 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)) - { + .Int => { + if (self.tag() != other.tag()) { + // Detect that e.g. u64 != usize, even if the bits match on a particular target. 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, - }; - - pub const Align = union(enum) { - Abi, - Override: u32, - }; - - 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); + // 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, + .Array, + .Struct, + .Optional, + .ErrorUnion, + .ErrorSet, + .Enum, + .Union, + .Fn, + .BoundFn, + .Opaque, + .Frame, + .AnyFrame, + .Vector, + .EnumLiteral, + => @panic("TODO implement more Type equality comparison"), } + } - pub fn gcDestroy(self: *Pointer, comp: *Compilation) void { - { - const held = comp.ptr_type_table.acquire(); - defer held.release(); + 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)), - _ = held.value.remove(&self.key).?; - } - self.key.child_type.base.deref(comp); - comp.gpa().destroy(self); - } + .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"), - 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; - } + .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; }, } - { - 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; + unreachable; } + } - 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; + 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); + }, } + } + + 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, + }; + } + + 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, + }; + } + + /// 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, + + .single_const_pointer, + .single_const_pointer_to_comptime_int, + .const_slice_u8, + => true, + }; + } + + /// 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, + + .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), + }; + } + + /// 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, + + .array => self.cast(Payload.Array).?.len, + .array_u8_sentinel_0 => self.cast(Payload.Array_u8_Sentinel0).?.len, + }; + } + + /// 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, + + .@"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) }, + }; + } + + /// 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, + }; + } + + /// 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, + } + } + + /// 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, + }; + } + + /// 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, + }; + } + + /// 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 const Array = struct { + base: Payload = Payload{ .tag = .array }, - pub fn get(comp: *Compilation, key: Key) !*Array { - key.elem_type.base.ref(); - errdefer key.elem_type.base.deref(comp); + elem_type: Type, + len: u64, + }; - { - const held = comp.array_type_table.acquire(); - defer held.release(); + pub const SingleConstPointer = struct { + base: Payload = Payload{ .tag = .single_const_pointer }, - 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 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"); - } - }; - - pub const Opaque = struct { - base: Type, - - 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"); - } - }; - - pub const Frame = struct { - base: Type, - - pub fn destroy(self: *Frame, comp: *Compilation) void { - comp.gpa().destroy(self); - } - - pub fn getLlvmType(self: *Frame, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type { - @panic("TODO"); - } - }; - - pub const AnyFrame = struct { - base: Type, - - pub fn destroy(self: *AnyFrame, comp: *Compilation) void { - comp.gpa().destroy(self); - } - - pub fn getLlvmType(self: *AnyFrame, allocator: *Allocator, llvm_context: *llvm.Context) *llvm.Type { - @panic("TODO"); - } + pointee_type: Type, + }; }; }; -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))), +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, + }, + }, + + .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, + }, + + .windows, .uefi => switch (self) { + .short, + .ushort, + => return 16, + .int, + .uint, + .long, + .ulong, + => return 32, + .longlong, + .ulonglong, + => return 64, + }, + + .ios => switch (self) { + .short, + .ushort, + => return 16, + .int, + .uint, + => return 32, + .long, + .ulong, + .longlong, + .ulonglong, + => return 64, + }, + + .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"), + } } -} +}; diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index c35289e238..03cda71387 100644 --- 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; -/// 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), +/// 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, - /// Thread-safe - pub fn ref(base: *Value) void { - _ = base.ref_count.incr(); + 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; + }; + + 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 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 initPayload(payload: *Payload) Value { + assert(@enumToInt(payload.tag) >= Tag.no_payload_count); + return .{ .ptr_otherwise = payload }; + } + + 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; + + const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag; + if (self.ptr_otherwise.tag != expected_tag) + return null; + + return @fieldParentPtr(T, "base", self.ptr_otherwise); } - pub fn getRef(base: *Value) *Value { - base.ref(); - return base; - } + 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"), - pub fn cast(base: *Value, comptime T: type) ?*T { - if (base.id != @field(Id, @typeName(T))) return null; - return @fieldParentPtr(T, "base", base); - } - - pub fn dump(base: *const Value) void { - std.debug.warn("{}", .{@tagName(base.id)}); - } - - 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), - } - } - - 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); - } - - 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, - } - } - - pub const Parent = union(enum) { - None, - BaseStruct: BaseStruct, - BaseArray: BaseArray, - BaseUnion: *Value, - BaseScalar: *Value, - - pub const BaseStruct = struct { - val: *Value, - field_index: usize, + .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 const BaseArray = struct { - val: *Value, - elem_index: usize, + /// 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; + } + + /// 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 const Id = enum { - Type, - Fn, - Void, - Bool, - NoReturn, - Array, - Ptr, - Int, - FnProto, - }; + /// 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, - pub const Type = @import("type.zig").Type; + .zero => return BigInt.initSet(allocator, 0), - 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), - }, - .symbol_name = symbol_name, - }; - fn_type.base.base.ref(); - return self; + .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 fn destroy(self: *FnProto, comp: *Compilation) void { - self.symbol_name.deinit(); - comp.gpa().destroy(self); - } + /// 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, - 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; + .zero => return true, - // 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), - }, - .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; - } - - pub fn destroy(self: *NoReturn, comp: *Compilation) void { - comp.gpa().destroy(self); - } - }; - - pub const Ptr = struct { - base: Value, - special: Special, - mut: Mut, - - pub const Mut = enum { - CompTimeConst, - CompTimeVar, - RunTime, - }; - - pub const Special = union(enum) { - Scalar: *Value, - BaseArray: BaseArray, - BaseStruct: BaseStruct, - HardCodedAddr: u64, - Discard, - }; - - pub const BaseArray = struct { - val: *Value, - elem_index: usize, - }; - - pub const BaseStruct = struct { - val: *Value, - field_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 Data = struct { - parent: Parent, - elements: []*Value, - }; - - /// 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); - - return self; - } - - pub fn destroy(self: *Array, comp: *Compilation) void { - switch (self.special) { - .Undefined => {}, - .OwnedBuffer => |buf| { - comp.gpa().free(buf); - }, - .Explicit => {}, - } - comp.gpa().destroy(self); - } - - 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(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 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_u64 => switch (ty.zigTypeTag()) { .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); + 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; }, - .ComptimeInt => unreachable, + .ComptimeInt => return true, 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), + }, + .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"); }, - .big_int = undefined, - }; - errdefer comp.gpa().destroy(new); - - new.big_int = try old.big_int.clone(); - errdefer new.big_int.deinit(); - - return new; + .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: *Int, comp: *Compilation) void { - self.big_int.deinit(); - 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, } + } + + /// This type is not copyable since it may contain pointers to its inner data. + pub const Payload = struct { + tag: Tag, + + pub const Int_u64 = struct { + base: Payload = Payload{ .tag = .int_u64 }, + int: u64, + }; + + pub const Int_i64 = struct { + base: Payload = Payload{ .tag = .int_i64 }, + int: i64, + }; + + pub const IntBig = struct { + base: Payload = Payload{ .tag = .int_big }, + big_int: BigInt, + }; + + pub const Function = struct { + base: Payload = Payload{ .tag = .function }, + /// Index into the `fns` array of the `ir.Module` + index: usize, + }; + + pub const ArraySentinel0_u8_Type = struct { + base: Payload = Payload{ .tag = .array_sentinel_0_u8_type }, + len: u64, + }; + + pub const SingleConstPtrType = struct { + base: Payload = Payload{ .tag = .single_const_ptr_type }, + elem_type: *Type, + }; + + pub const Ref = struct { + base: Payload = Payload{ .tag = .ref }, + cell: *MemoryCell, + }; + + pub const RefVal = struct { + base: Payload = Payload{ .tag = .ref_val }, + val: Value, + }; + + pub const Bytes = struct { + base: Payload = Payload{ .tag = .bytes }, + data: []const u8, + }; + + pub const Ty = struct { + base: Payload = Payload{ .tag = .ty }, + ty: Type, + }; + }; +}; + +/// This is the heart of resource management of the Zig compiler. The Zig compiler uses +/// stop-the-world mark-and-sweep garbage collection during compilation to manage the resources +/// associated with evaluating compile-time code and semantic analysis. Each `MemoryCell` represents +/// a root. +pub const MemoryCell = struct { + parent: Parent, + contents: Value, + + pub const Parent = union(enum) { + none, + struct_field: struct { + struct_base: *MemoryCell, + field_index: usize, + }, + array_elem: struct { + array_base: *MemoryCell, + elem_index: usize, + }, + union_field: *MemoryCell, + err_union_code: *MemoryCell, + err_union_payload: *MemoryCell, + optional_payload: *MemoryCell, + optional_flag: *MemoryCell, }; }; diff --git a/src/ir.cpp b/src/ir.cpp index cdb86e5b15..0fcbc1be10 100644 --- 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 new file mode 100644 index 0000000000..450d8fa102 --- /dev/null +++ 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 {}