refactor: boilerplate for semantic ir

This commit is contained in:
Brett Broadhurst 2026-03-08 14:54:28 -06:00
parent 98f5215629
commit f16162b5bb
Failed to generate hash of commit
2 changed files with 604 additions and 482 deletions

File diff suppressed because it is too large Load diff

264
src/Ir.zig Normal file
View file

@ -0,0 +1,264 @@
const std = @import("std");
const Ast = @import("Ast.zig");
const Ir = @This();
string_bytes: []u8,
instructions: []Inst,
extra: []u32,
errors: []Ast.Error,
pub const Inst = struct {
tag: Tag,
data: Data,
pub const Index = enum(u32) {
file_inst = 0,
_,
};
pub const Tag = enum {
file,
decl_knot,
block,
add,
sub,
mul,
div,
mod,
neg,
true_literal,
false_literal,
integer,
string,
content,
};
pub const Data = union {
payload: struct {
payload_index: u32,
},
un: struct {
lhs: Inst.Index,
},
bin: struct {
lhs: Inst.Index,
rhs: Inst.Index,
},
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 Knot = struct {
name_ref: NullTerminatedString,
body_len: u32,
};
pub const Block = struct {
body_len: u32,
};
};
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.extra);
gpa.free(ir.errors);
ir.* = undefined;
}
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);
}
};
const Render = struct {
gpa: std.mem.Allocator,
prefix: Prefix,
writer: *std.Io.Writer,
pub const Error = error{
OutOfMemory,
WriteFailed,
};
fn renderUnaryInst(r: *Render, inst: Inst) Error!void {
const io_w = r.writer;
return io_w.print("{s}(%{d})", .{ @tagName(inst.tag), inst.data.un.lhs });
}
fn renderBinaryInst(r: *Render, inst: Inst) Error!void {
const io_w = r.writer;
return io_w.print("{s}(%{d}, %{d})", .{ @tagName(inst.tag), inst.data.bin.lhs, inst.data.bin.rhs });
}
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 renderBlockInst(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 renderKnotInst(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);
const knot_ident_str = ir.nullTerminatedString(extra.data.name_ref);
try io_w.print("{s}(name=\"{s}\",body=", .{ @tagName(inst.tag), knot_ident_str });
try renderBodyInner(r, ir, body_slice);
try io_w.writeAll(")");
}
fn renderFileInst(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.renderFileInst(ir, inst),
.decl_knot => try r.renderKnotInst(ir, inst),
.block => try r.renderBlockInst(ir, inst),
.add => try r.renderBinaryInst(inst),
.sub => try r.renderBinaryInst(inst),
.mul => try r.renderBinaryInst(inst),
.div => try r.renderBinaryInst(inst),
.mod => try r.renderBinaryInst(inst),
.neg => try r.renderUnaryInst(inst),
.true_literal => {
try io_w.print("{s}", .{@tagName(inst.tag)});
},
.false_literal => {
try io_w.print("{s}", .{@tagName(inst.tag)});
},
.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 => try r.renderUnaryInst(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, .file_inst);
return writer.flush();
}
pub fn dumpStringsWithHex(cu: *const Ir) void {
const bytes = cu.string_bytes;
var start: usize = 0;
while (start < bytes.len) {
const end = std.mem.indexOfScalarPos(u8, bytes, start, 0) orelse break;
const s = bytes[start..end];
std.debug.print("[{d:04}] ", .{start});
for (s) |b| std.debug.print("{x:02} ", .{b});
std.debug.print("00: {s}\n", .{s});
start = end + 1;
}
}
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]),
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]);
}