const std = @import("std"); const Ast = @import("Ast.zig"); const Writer = @import("print_ir.zig").Writer; const assert = std.debug.assert; const Ir = @This(); /// List of IR instructions for the translation unit. instructions: []Inst, /// Interned string bytes. All strings are null-terminated. /// Index 0 is reserved for the empty string. string_bytes: []u8, /// Ancillary data for instructions. The meaning of this data is determined by /// the value of `Inst.Tag`. See `ExtraIndex` for the values of reserved indexes. extra: []u32, globals: []Global, pub const ExtraIndex = enum(u32) { /// If this is 0, no compile errors. Otherwise there is a `CompileErrors` /// payload at this index. compile_errors, _, }; fn ExtraData(comptime T: type) type { return struct { data: T, end: usize, }; } /// Extract a slice of `extra` into an `Inst` payload structure. pub fn extraData(ir: Ir, comptime T: type, index: usize) ExtraData(T) { const fields = @typeInfo(T).@"struct".fields; var i: usize = index; var result: T = undefined; inline for (fields) |field| { @field(result, field.name) = switch (field.type) { u32 => ir.extra[i], Inst.Index => @enumFromInt(ir.extra[i]), Inst.Ref => @enumFromInt(ir.extra[i]), NullTerminatedString => @enumFromInt(ir.extra[i]), else => @compileError("bad field type"), }; i += 1; } return .{ .data = result, .end = i }; } pub const NullTerminatedString = enum(u32) { empty, _, }; pub fn nullTerminatedString(ir: Ir, index: NullTerminatedString) [:0]const u8 { const slice = ir.string_bytes[@intFromEnum(index)..]; return slice[0..std.mem.indexOfScalar(u8, slice, 0).? :0]; } pub fn bodySlice(ir: Ir, start: usize, len: usize) []Inst.Index { return @ptrCast(ir.extra[start..][0..len]); } pub fn hasCompileErrors(ir: Ir) bool { if (ir.extra[@intFromEnum(ExtraIndex.compile_errors)] != 0) { return true; } else { assert(ir.instructions.len != 0); return false; } } pub fn render(ir: Ir, writer: *std.Io.Writer) !void { if (ir.instructions.len > 0) { var w: Writer = .{ .code = ir }; try w.writeInst(writer, .file_inst); return writer.flush(); } } pub fn dumpInfo(ir: Ir, writer: *std.Io.Writer) !void { const bytes = ir.string_bytes; var start: usize = 0; while (start < bytes.len) { const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break; const str = bytes[start..end]; try writer.print("[{d:04}] ", .{start}); for (str) |b| try writer.print("{x:02} ", .{b}); try writer.print("00: {s}\n", .{str}); start = end + 1; } for (ir.globals) |global| { try writer.print("{any}\n", .{global}); } return writer.flush(); } pub fn deinit(ir: *Ir, gpa: std.mem.Allocator) void { gpa.free(ir.string_bytes); gpa.free(ir.instructions); gpa.free(ir.globals); gpa.free(ir.extra); ir.* = undefined; } pub const Global = struct { tag: Tag, name: Ir.NullTerminatedString, is_constant: bool, pub const Tag = enum { knot, variable, }; }; pub const Inst = struct { tag: Tag, data: Data, /// An index to an IR instruction. Some values are reserved. pub const Index = enum(u32) { file_inst, ref_start_index = 32, _, pub fn toRef(i: Index) Inst.Ref { return @enumFromInt(@intFromEnum(Index.ref_start_index) + @intFromEnum(i)); } }; /// A reference to an IR instruction, or to an statically interned value, /// or neither. /// /// If the integer tag value is < `Index.ref_start_index`, then it /// corresponds to an interned value. Otherwise, this refers to a IR /// instruction. /// /// The tag type is specified so that it is safe to bitcast between `[]u32` /// and `[]Ref`. pub const Ref = enum(u32) { bool_true, bool_false, none = std.math.maxInt(u32), _, pub fn toIndex(inst: Ref) ?Index { assert(inst != .none); const ref_int = @intFromEnum(inst); if (ref_int >= @intFromEnum(Index.ref_start_index)) { return @enumFromInt(ref_int - @intFromEnum(Index.ref_start_index)); } else { return null; } } pub fn toIndexAllowNone(inst: Ref) ?Index { if (inst == .none) return null; return toIndex(inst); } }; pub const Tag = enum { file, declaration, decl_knot, decl_var, /// Uses the `str_tok` union field. decl_ref, alloc, load, store, /// Uses the `bin` union field. add, /// Uses the `bin` union field. sub, /// Uses the `bin` union field. mul, /// Uses the `bin` union field. div, /// Uses the `bin` union field. mod, /// Uses the `un` union field. neg, /// Uses the `un` union field. not, cmp_eq, cmp_neq, cmp_gt, cmp_gte, cmp_lt, cmp_lte, /// Uses the `int` union field. int, /// Uses the `str` union field. str, block, condbr, @"break", switch_br, content_push, content_flush, choice_br, implicit_ret, call, divert, field_ptr, field_call, field_divert, param, }; pub const Data = union { payload: struct { payload_index: u32, }, un: struct { lhs: Ref, }, bin: struct { lhs: Ref, rhs: Ref, }, int: u64, str: struct { /// Offset into `string_bytes`. start: NullTerminatedString, /// Number of bytes in the string. len: u32, pub fn get(self: @This(), code: Ir) []const u8 { return code.string_bytes[@intFromEnum(self.start)..][0..self.len]; } }, str_tok: struct { /// Offset into `string_bytes`. start: NullTerminatedString, src_offset: u32, }, }; pub const Declaration = struct { name: NullTerminatedString, value: Index, }; pub const Knot = struct { body_len: u32, }; pub const Var = struct { body_len: u32, }; pub const Field = struct { lhs: Ref, field_name_start: NullTerminatedString, }; pub const Block = struct { body_len: u32, }; pub const Break = struct { block_inst: Index, }; pub const CondBr = struct { condition: Ref, then_body_len: u32, else_body_len: u32, }; pub const SwitchBr = struct { operand: Ref, cases_len: u32, else_body_len: u32, pub const Case = struct { operand: Ref, body_len: u32, }; }; pub const ChoiceBr = struct { cases_len: u32, pub const Case = struct { operand_1: Ref, operand_2: Ref, operand_3: Ref, body_len: u32, }; }; pub const Call = struct { args_len: u32, callee: Ref, }; pub const FieldCall = struct { args_len: u32, obj_ptr: Ref, field_name_start: NullTerminatedString, }; pub const CompileErrors = struct { items_len: u32, pub const Item = struct { msg: NullTerminatedString, byte_offset: u32, }; }; };