484 lines
14 KiB
Zig
484 lines
14 KiB
Zig
const std = @import("std");
|
|
const Ast = @import("Ast.zig");
|
|
const assert = std.debug.assert;
|
|
const Ir = @This();
|
|
|
|
string_bytes: []u8,
|
|
instructions: []Inst,
|
|
globals: []Global,
|
|
extra: []u32,
|
|
errors: []Ast.Error,
|
|
|
|
pub const Inst = struct {
|
|
tag: Tag,
|
|
data: Data,
|
|
|
|
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));
|
|
}
|
|
};
|
|
|
|
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,
|
|
decl_ref,
|
|
alloc,
|
|
load,
|
|
store,
|
|
add,
|
|
sub,
|
|
mul,
|
|
div,
|
|
mod,
|
|
neg,
|
|
not,
|
|
cmp_eq,
|
|
cmp_neq,
|
|
cmp_gt,
|
|
cmp_gte,
|
|
cmp_lt,
|
|
cmp_lte,
|
|
integer,
|
|
string,
|
|
block,
|
|
condbr,
|
|
@"break",
|
|
switch_br,
|
|
content_push,
|
|
content_flush,
|
|
};
|
|
|
|
pub const Data = union {
|
|
payload: struct {
|
|
payload_index: u32,
|
|
},
|
|
un: struct {
|
|
lhs: Ref,
|
|
},
|
|
bin: struct {
|
|
lhs: Ref,
|
|
rhs: Ref,
|
|
},
|
|
integer: struct {
|
|
value: u64,
|
|
},
|
|
string: struct {
|
|
/// Offset into `string_bytes`.
|
|
start: NullTerminatedString,
|
|
|
|
pub fn get(self: @This(), ir: Ir) []const u8 {
|
|
return nullTerminatedString(ir, self.start);
|
|
}
|
|
},
|
|
};
|
|
|
|
pub const Declaration = struct {
|
|
name: NullTerminatedString,
|
|
value: Index,
|
|
};
|
|
|
|
pub const Knot = struct {
|
|
body_len: u32,
|
|
};
|
|
|
|
pub const Var = struct {
|
|
body_len: u32,
|
|
};
|
|
|
|
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 Global = struct {
|
|
tag: Tag,
|
|
name: Ir.NullTerminatedString,
|
|
is_constant: bool,
|
|
|
|
pub const Tag = enum {
|
|
knot,
|
|
variable,
|
|
};
|
|
};
|
|
|
|
pub const NullTerminatedString = enum(u32) {
|
|
empty,
|
|
_,
|
|
};
|
|
|
|
pub const IndexSlice = struct {
|
|
index: u32,
|
|
len: u32,
|
|
};
|
|
|
|
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);
|
|
gpa.free(ir.errors);
|
|
ir.* = undefined;
|
|
}
|
|
|
|
const Render = struct {
|
|
gpa: std.mem.Allocator,
|
|
prefix: Prefix,
|
|
writer: *std.Io.Writer,
|
|
|
|
pub const Error = error{
|
|
OutOfMemory,
|
|
WriteFailed,
|
|
};
|
|
|
|
const Prefix = struct {
|
|
buf: std.ArrayListUnmanaged(u8) = .empty,
|
|
|
|
pub fn deinit(self: *Prefix, gpa: std.mem.Allocator) void {
|
|
self.buf.deinit(gpa);
|
|
}
|
|
|
|
pub fn writeIndent(self: *const Prefix, writer: *std.Io.Writer) !void {
|
|
try writer.writeAll(self.buf.items);
|
|
}
|
|
|
|
pub fn pushChildPrefix(self: *Prefix, gpa: std.mem.Allocator) !usize {
|
|
const old_len = self.buf.items.len;
|
|
const seg: []const u8 = " ";
|
|
try self.buf.appendSlice(gpa, seg);
|
|
return old_len;
|
|
}
|
|
|
|
pub fn restore(self: *Prefix, new_len: usize) void {
|
|
self.buf.shrinkRetainingCapacity(new_len);
|
|
}
|
|
};
|
|
|
|
fn renderInstIndex(r: *Render, index: Inst.Index) !void {
|
|
const io_w = r.writer;
|
|
return io_w.print("%{d}", .{@intFromEnum(index)});
|
|
}
|
|
|
|
fn renderInstRef(r: *Render, ref: Inst.Ref) !void {
|
|
const io_w = r.writer;
|
|
if (ref == .none) {
|
|
return io_w.writeAll(".none");
|
|
} else if (ref.toIndex()) |i| {
|
|
return r.renderInstIndex(i);
|
|
} else {
|
|
return io_w.print("@{s}", .{@tagName(ref)});
|
|
}
|
|
}
|
|
|
|
fn renderSimple(r: *Render, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
return io_w.print("{s}(?)", .{@tagName(inst.tag)});
|
|
}
|
|
|
|
fn renderUnary(r: *Render, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const data = inst.data.un;
|
|
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
|
|
|
const lhs = data.lhs;
|
|
try renderInstRef(r, lhs);
|
|
return io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderBinary(r: *Render, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const data = inst.data.bin;
|
|
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
|
try renderInstRef(r, data.lhs);
|
|
try io_w.writeAll(", ");
|
|
try renderInstRef(r, data.rhs);
|
|
return io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderBodyInner(r: *Render, ir: Ir, body_list: []const Inst.Index) Error!void {
|
|
const io_w = r.writer;
|
|
try io_w.writeAll("{\n");
|
|
{
|
|
const old_len = try r.prefix.pushChildPrefix(r.gpa);
|
|
defer r.prefix.restore(old_len);
|
|
|
|
for (body_list) |inst| try r.renderInst(ir, inst);
|
|
}
|
|
try r.prefix.writeIndent(io_w);
|
|
try io_w.writeAll("}");
|
|
}
|
|
|
|
fn renderBlock(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const extra = ir.extraData(Inst.Block, inst.data.payload.payload_index);
|
|
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
|
|
|
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
|
try renderBodyInner(r, ir, body_slice);
|
|
try io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderBreak(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const extra = ir.extraData(Inst.Break, inst.data.payload.payload_index);
|
|
|
|
try io_w.print("{s}(%{d})", .{ @tagName(inst.tag), extra.data.block_inst });
|
|
}
|
|
|
|
fn renderCondbr(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const extra = ir.extraData(Inst.CondBr, inst.data.payload.payload_index);
|
|
const then_body = ir.bodySlice(extra.end, extra.data.then_body_len);
|
|
const else_body = ir.bodySlice(extra.end + then_body.len, extra.data.else_body_len);
|
|
|
|
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
|
try renderInstRef(r, extra.data.condition);
|
|
try io_w.writeAll(", ");
|
|
try renderBodyInner(r, ir, then_body);
|
|
try io_w.writeAll(", ");
|
|
try renderBodyInner(r, ir, else_body);
|
|
try io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderSwitchBr(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const switch_node = inst.data.payload;
|
|
const switch_extra = ir.extraData(Inst.SwitchBr, switch_node.payload_index);
|
|
const cases_slice = ir.bodySlice(switch_extra.end, switch_extra.data.cases_len);
|
|
|
|
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
|
try renderInstRef(r, switch_extra.data.operand);
|
|
try io_w.print(", \n", .{});
|
|
for (cases_slice) |case_index| {
|
|
const case_extra = ir.extraData(Inst.SwitchBr.Case, @intFromEnum(case_index));
|
|
const body_slice = ir.bodySlice(case_extra.end, case_extra.data.body_len);
|
|
const old_len = try r.prefix.pushChildPrefix(r.gpa);
|
|
defer r.prefix.restore(old_len);
|
|
|
|
try r.prefix.writeIndent(io_w);
|
|
try renderInstRef(r, case_extra.data.operand);
|
|
try io_w.print(" = ", .{});
|
|
try renderBodyInner(r, ir, body_slice);
|
|
try io_w.writeAll(",\n");
|
|
}
|
|
|
|
const else_body = ir.bodySlice(
|
|
switch_extra.end + switch_extra.data.cases_len,
|
|
switch_extra.data.else_body_len,
|
|
);
|
|
{
|
|
const old_len = try r.prefix.pushChildPrefix(r.gpa);
|
|
defer r.prefix.restore(old_len);
|
|
|
|
try r.prefix.writeIndent(io_w);
|
|
try io_w.print("else = ", .{});
|
|
try renderBodyInner(r, ir, else_body);
|
|
}
|
|
try io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderKnotDecl(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index);
|
|
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
|
|
|
try io_w.print("{s}(body=", .{@tagName(inst.tag)});
|
|
try renderBodyInner(r, ir, body_slice);
|
|
try io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderVarDecl(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const extra = ir.extraData(Inst.Knot, inst.data.payload.payload_index);
|
|
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
|
|
|
try io_w.print("{s}(body=", .{@tagName(inst.tag)});
|
|
try renderBodyInner(r, ir, body_slice);
|
|
try io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderDeclaration(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const extra = ir.extraData(Inst.Declaration, inst.data.payload.payload_index);
|
|
const ident_str = ir.nullTerminatedString(extra.data.name);
|
|
|
|
try io_w.print("{s}(name=\"{s}\", value={{\n", .{ @tagName(inst.tag), ident_str });
|
|
{
|
|
const old_len = try r.prefix.pushChildPrefix(r.gpa);
|
|
defer r.prefix.restore(old_len);
|
|
try renderInst(r, ir, extra.data.value);
|
|
}
|
|
try r.prefix.writeIndent(io_w);
|
|
return io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderFile(r: *Render, ir: Ir, inst: Inst) Error!void {
|
|
const io_w = r.writer;
|
|
const extra = ir.extraData(Inst.Block, inst.data.payload.payload_index);
|
|
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
|
|
|
try io_w.print("{s}(", .{@tagName(inst.tag)});
|
|
try renderBodyInner(r, ir, body_slice);
|
|
return io_w.writeAll(")");
|
|
}
|
|
|
|
fn renderInst(r: *Render, ir: Ir, index: Inst.Index) Error!void {
|
|
const io_w = r.writer;
|
|
const inst_index: u32 = @intFromEnum(index);
|
|
const inst = ir.instructions[inst_index];
|
|
|
|
try r.prefix.writeIndent(io_w);
|
|
try io_w.print("%{d} = ", .{inst_index});
|
|
switch (inst.tag) {
|
|
.file => try r.renderFile(ir, inst),
|
|
.declaration => try r.renderDeclaration(ir, inst),
|
|
.decl_knot => try r.renderKnotDecl(ir, inst),
|
|
.decl_var => try r.renderVarDecl(ir, inst),
|
|
.decl_ref => {
|
|
const str_bytes = inst.data.string.get(ir);
|
|
try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes });
|
|
},
|
|
.condbr => try r.renderCondbr(ir, inst),
|
|
.@"break" => try r.renderBreak(ir, inst),
|
|
.switch_br => try r.renderSwitchBr(ir, inst),
|
|
.alloc => try r.renderSimple(inst),
|
|
.load => try r.renderUnary(inst),
|
|
.store => try r.renderBinary(inst),
|
|
.block => try r.renderBlock(ir, inst),
|
|
.add => try r.renderBinary(inst),
|
|
.sub => try r.renderBinary(inst),
|
|
.mul => try r.renderBinary(inst),
|
|
.div => try r.renderBinary(inst),
|
|
.mod => try r.renderBinary(inst),
|
|
.neg => try r.renderUnary(inst),
|
|
.not => try r.renderUnary(inst),
|
|
.cmp_eq => try r.renderBinary(inst),
|
|
.cmp_neq => try r.renderBinary(inst),
|
|
.cmp_gt => try r.renderBinary(inst),
|
|
.cmp_gte => try r.renderBinary(inst),
|
|
.cmp_lt => try r.renderBinary(inst),
|
|
.cmp_lte => try r.renderBinary(inst),
|
|
.integer => {
|
|
const value = inst.data.integer.value;
|
|
try io_w.print("{s}({d})", .{ @tagName(inst.tag), value });
|
|
},
|
|
.string => {
|
|
const str_bytes = inst.data.string.get(ir);
|
|
try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes });
|
|
},
|
|
.content_push => try r.renderUnary(inst),
|
|
.content_flush => try r.renderUnary(inst),
|
|
}
|
|
try io_w.writeAll("\n");
|
|
}
|
|
};
|
|
|
|
pub fn render(ir: Ir, gpa: std.mem.Allocator, writer: *std.Io.Writer) !void {
|
|
std.debug.assert(ir.instructions.len > 0);
|
|
var r = Render{ .gpa = gpa, .writer = writer, .prefix = .{} };
|
|
defer r.prefix.deinit(gpa);
|
|
|
|
try r.renderInst(ir, @enumFromInt(0));
|
|
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();
|
|
}
|
|
|
|
fn ExtraData(comptime T: type) type {
|
|
return struct {
|
|
data: T,
|
|
end: usize,
|
|
};
|
|
}
|
|
|
|
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 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]);
|
|
}
|