diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index b8b3cb2dcc..d3bbf1324f 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -33,6 +33,14 @@ pub const Inst = struct { }; } + pub fn cast(base: *Inst, comptime T: type) ?*T { + const expected_tag = std.meta.fieldInfo(T, "base").default_value.?.tag; + if (base.tag != expected_tag) + return null; + + return @fieldParentPtr(T, "base", base); + } + /// This struct owns the `Value` memory. When the struct is deallocated, /// so is the `Value`. The value of a constant must be copied into /// a memory location for the value to survive after a const instruction. @@ -130,6 +138,92 @@ pub const ErrorMsg = struct { pub const Tree = struct { decls: []*Inst, errors: []ErrorMsg, + + pub fn deinit(self: *Tree) void { + // TODO resource deallocation + self.* = undefined; + } + + /// This is a debugging utility for rendering the tree to stderr. + pub fn dump(self: Tree) 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 }); + + pub fn writeToStream(self: Tree, 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); + } + } + + fn writeInstToStream(self: Tree, stream: var, decl: *Inst, inst_table: *const InstPtrTable) !void { + // TODO I tried implementing this with an inline for loop and hit a compiler bug + switch (decl.tag) { + .constant => return self.writeInstToStreamGeneric(stream, .constant, 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), + .@"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), + } + } + + fn writeInstToStreamGeneric( + self: Tree, + 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) ++ "("); + inline for (@typeInfo(Positionals).Struct.fields) |arg_field, i| { + if (i != 0) { + try stream.writeAll(", "); + } + try self.writeParamToStream(stream, @field(inst.positionals, arg_field.name), inst_table); + } + try stream.writeAll(")\n"); + } + + pub fn writeParamToStream(self: Tree, stream: var, param: var, inst_table: *const InstPtrTable) !void { + switch (@TypeOf(param)) { + Value => { + try stream.print("{}", .{param}); + }, + *Inst => { + const info = inst_table.getValue(param).?; + const prefix = if (info.fn_body == null) "@" else "%"; + try stream.print("{}{}", .{ prefix, info.index }); + }, + Inst.Fn.Body => { + try stream.print("(fn body)", .{}); + }, + else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)), + } + } }; const ParseContext = struct { @@ -278,6 +372,7 @@ fn parseInstructionGeneric( body_ctx: ?*BodyContext, ) !*Inst { const inst_specific = try ctx.allocator.create(InstType); + inst_specific.base = std.meta.fieldInfo(InstType, "base").default_value.?; if (@hasField(InstType, "ty")) { inst_specific.ty = opt_type orelse { @@ -286,7 +381,7 @@ fn parseInstructionGeneric( } const Positionals = @TypeOf(inst_specific.positionals); - inline for (@typeInfo(Positionals).Struct.fields) |arg_field, i| { + inline for (@typeInfo(Positionals).Struct.fields) |arg_field| { if (ctx.source[ctx.i] == ',') { ctx.i += 1; skipSpace(ctx); @@ -379,7 +474,11 @@ fn parseParameterInst(ctx: *ParseContext, body_ctx: ?*BodyContext) !*Inst { const local_ref = switch (ctx.source[ctx.i]) { '@' => false, '%' => true, - '"' => return parseStringLiteralConst(ctx, null), + '"' => { + const str_lit_inst = try parseStringLiteralConst(ctx, null); + try ctx.decls.append(str_lit_inst); + return str_lit_inst; + }, else => |byte| return parseError(ctx, "unexpected byte: '{c}'", .{byte}), }; const map = if (local_ref) @@ -538,7 +637,9 @@ pub fn main() anyerror!void { const source = try std.fs.cwd().readFileAlloc(allocator, src_path, std.math.maxInt(u32)); - const tree = try parse(allocator, source); + var tree = try parse(allocator, source); + defer tree.deinit(); + if (tree.errors.len != 0) { for (tree.errors) |err_msg| { const loc = findLineColumn(source, err_msg.byte_offset); @@ -547,6 +648,11 @@ pub fn main() anyerror!void { if (debug_error_trace) return error.ParseFailure; std.process.exit(1); } + + tree.dump(); + + //const new_tree = try semanticallyAnalyze(tree); + //defer new_tree.deinit(); } fn findLineColumn(source: []const u8, byte_offset: usize) struct { line: usize, column: usize } { diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig index d7093a02f6..380c82ff68 100644 --- a/src-self-hosted/type.zig +++ b/src-self-hosted/type.zig @@ -36,7 +36,7 @@ pub const Type = extern union { pub fn tag(self: Type) Tag { if (self.tag_if_small_enough < Tag.no_payload_count) { - return @intToEnum(self.tag_if_small_enough); + return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough)); } else { return self.ptr_otherwise.tag; } @@ -49,21 +49,31 @@ pub const Type = extern union { out_stream: var, ) !void { comptime assert(fmt.len == 0); - switch (self.tag()) { - .int_u8 => return out_stream.writeAll("u8"), - .int_usize => return out_stream.writeAll("usize"), - .array_u8_sentinel_0 => { - const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", self.ptr_otherwise); - return out_stream.print("[{}:0]u8", .{payload.len}); - }, - .array => { - const payload = @fieldParentPtr(Payload.Array, "base", self.ptr_otherwise); - return out_stream.print("[{}]{}", .{ payload.len, payload.elem_type }); - }, - .single_const_pointer => { - const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", self.ptr_otherwise); - return out_stream.print("*const {}", .{payload.pointee_type}); - }, + var ty = self; + while (true) { + switch (ty.tag()) { + .no_return => return out_stream.writeAll("noreturn"), + .int_comptime => return out_stream.writeAll("comptime_int"), + .int_u8 => return out_stream.writeAll("u8"), + .int_usize => return out_stream.writeAll("usize"), + .array_u8_sentinel_0 => { + const payload = @fieldParentPtr(Payload.Array_u8_Sentinel0, "base", ty.ptr_otherwise); + return out_stream.print("[{}:0]u8", .{payload.len}); + }, + .array => { + const payload = @fieldParentPtr(Payload.Array, "base", ty.ptr_otherwise); + try out_stream.print("[{}]", .{payload.len}); + ty = payload.elem_type; + continue; + }, + .single_const_pointer => { + const payload = @fieldParentPtr(Payload.SingleConstPointer, "base", ty.ptr_otherwise); + try out_stream.writeAll("*const "); + ty = payload.pointee_type; + continue; + }, + } + unreachable; } } @@ -78,15 +88,15 @@ pub const Type = extern union { no_return, int_comptime, int_u8, - int_usize, - // Bump this when adding items above. - pub const last_no_payload_tag = Tag.int_usize; - pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; + int_usize, // 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.int_usize; + pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; }; pub const Payload = struct { diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig index 3035cfbc06..b2c9b0dad6 100644 --- a/src-self-hosted/value.zig +++ b/src-self-hosted/value.zig @@ -23,10 +23,7 @@ pub const Value = extern union { void_value, noreturn_value, bool_true, - bool_false, - // Bump this when adding items above. - pub const last_no_payload_tag = Tag.bool_false; - pub const no_payload_count = @enumToInt(last_no_payload_tag) + 1; + bool_false, // See last_no_payload_tag below. // After this, the tag requires a payload. ty, @@ -35,6 +32,9 @@ pub const Value = extern union { function, ref, 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 { @@ -49,12 +49,48 @@ pub const Value = extern union { pub fn tag(self: Value) Tag { if (self.tag_if_small_enough < Tag.no_payload_count) { - return @intToEnum(self.tag_if_small_enough); + return @intToEnum(Tag, @intCast(@TagType(Tag), self.tag_if_small_enough)); } else { return self.ptr_otherwise.tag; } } + 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 format( + self: Value, + comptime fmt: []const u8, + options: std.fmt.FormatOptions, + out_stream: var, + ) !void { + comptime assert(fmt.len == 0); + switch (self.tag()) { + .void_type => return out_stream.writeAll("void"), + .noreturn_type => return out_stream.writeAll("noreturn"), + .bool_type => return out_stream.writeAll("bool"), + .usize_type => return out_stream.writeAll("usize"), + .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 self.cast(Payload.Ty).?.ty.format("", options, out_stream), + .int_u64 => return std.fmt.formatIntValue(self.cast(Payload.Int_u64).?.int, "", options, out_stream), + .int_i64 => return std.fmt.formatIntValue(self.cast(Payload.Int_i64).?.int, "", options, out_stream), + .function => return out_stream.writeAll("(function)"), + .ref => return out_stream.writeAll("(ref)"), + .bytes => return out_stream.writeAll("(bytes)"), + } + } + /// This type is not copyable since it may contain pointers to its inner data. pub const Payload = struct { tag: Tag, @@ -94,8 +130,8 @@ pub const Value = extern union { }; pub const Ty = struct { - base: Payload = Payload{ .tag = .fully_qualified_type }, - ptr: *Type, + base: Payload = Payload{ .tag = .ty }, + ty: Type, }; }; };