ink/src/Ir.zig
2026-03-16 14:19:49 -06:00

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]);
}