316 lines
7.7 KiB
Zig
316 lines
7.7 KiB
Zig
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,
|
|
};
|
|
};
|
|
};
|