feat: code generation for diverts, wip

This commit is contained in:
Brett Broadhurst 2026-03-17 23:19:54 -06:00
parent ee26be6254
commit 20292bcc6a
Failed to generate hash of commit
5 changed files with 699 additions and 149 deletions

View file

@ -19,9 +19,9 @@ const InnerError = error{
};
const Ref = union(enum) {
none,
bool_true,
bool_false,
none,
index: u32,
constant: u32,
global: u32,
@ -64,11 +64,11 @@ fn addGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref {
fn getGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref {
const interned = try sema.getConstant(.{ .string = name });
for (sema.ir.globals) |global| {
if (name == global.name) {
if (global.name == name) {
return .{ .global = interned.constant };
}
}
return sema.fail("unknown global variable");
return fail(sema, "unknown global variable");
}
fn irInteger(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref {
@ -352,9 +352,7 @@ fn irDeclKnot(
.sema = sema,
.knot = &knot,
};
defer chunk.fixups.deinit(gpa);
defer chunk.labels.deinit(gpa);
defer chunk.inst_map.deinit(gpa);
defer chunk.deinit(gpa);
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
try blockBodyInner(sema, &chunk, body);
@ -374,6 +372,63 @@ fn irDeclaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst.Index) !void
}
}
fn irCall(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref {
return .none;
}
fn irDivert(
sema: *Sema,
chunk: *Chunk,
inst: Ir.Inst.Index,
comptime kind: enum { direct, field },
) !void {
const ExtraType = switch (kind) {
.direct => Ir.Inst.Call,
.field => Ir.Inst.FieldCall,
};
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
const extra = sema.ir.extraData(ExtraType, data.payload_index);
const args_len = extra.data.args_len;
const body = sema.ir.extra[extra.end..];
const callee = switch (kind) {
.direct => chunk.resolveInst(extra.data.callee),
.field => chunk.resolveInst(extra.data.obj_ptr),
};
_ = try chunk.doLoad(callee);
var arg_start: u32 = args_len;
var i: u32 = 0;
while (i < args_len) : (i += 1) {
const arg_end = sema.ir.extra[extra.end + i];
defer arg_start = arg_end;
const arg_body = body[arg_start..arg_end];
try blockBodyInner(sema, chunk, @ptrCast(arg_body));
// FIXME: hack
{
const last_inst: Ir.Inst.Index = @enumFromInt(arg_body[arg_body.len - 1]);
const val = chunk.resolveInst(last_inst.toRef());
_ = try chunk.doLoad(val);
}
}
_ = try chunk.addConstOp(.divert, @intCast(args_len));
}
fn irFieldPtr(_: *Sema, _: *Chunk, _: Ir.Inst.Index) !Ref {
return .none;
}
// TODO: Check for duplicate parameters.
fn irParam(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) !Ref {
//const data = sema.ir.instructions[@intFromEnum(inst)].data.string;
const local_index = chunk.knot.stack_size;
// TODO: Add constraints on how many temporaries we can have.
// max(u8) or max(u16) are most likey appropriate.
chunk.knot.arity += 1;
chunk.knot.stack_size += 1;
return .{ .local = local_index };
}
fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) InnerError!void {
const gpa = sema.gpa;
@ -433,6 +488,18 @@ fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) Inner
continue;
},
.implicit_ret => try irImplicitRet(sema, chunk, inst),
.call => try irCall(sema, chunk, inst),
.divert => {
try irDivert(sema, chunk, inst, .direct);
continue;
},
.field_call => try irCall(sema, chunk, inst),
.field_divert => {
try irDivert(sema, chunk, inst, .field);
continue;
},
.field_ptr => try irFieldPtr(sema, chunk, inst),
.param => try irParam(sema, chunk, inst),
};
try chunk.inst_map.put(gpa, inst, ref);
}
@ -473,6 +540,12 @@ const Chunk = struct {
code_offset: u32,
};
fn deinit(chunk: *Chunk, gpa: std.mem.Allocator) void {
chunk.fixups.deinit(gpa);
chunk.labels.deinit(gpa);
chunk.inst_map.deinit(gpa);
}
fn addByteOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref {
const gpa = chunk.sema.gpa;
const bytecode = &chunk.knot.bytecode;
@ -627,13 +700,13 @@ pub const CompiledStory = struct {
pub fn buildRuntime(
self: *CompiledStory,
gpa: std.mem.Allocator,
ir: Ir,
ir: *Ir,
story: *Story,
) !void {
const globals_len = self.globals.len + self.knots.len;
const constants_pool = &story.constants_pool;
try constants_pool.ensureUnusedCapacity(gpa, self.constants.len);
try story.paths.ensureUnusedCapacity(gpa, self.knots.len);
try story.globals.ensureUnusedCapacity(gpa, @intCast(self.globals.len));
try story.globals.ensureUnusedCapacity(gpa, @intCast(globals_len));
for (self.constants) |constant| {
switch (constant) {
@ -656,15 +729,18 @@ pub const CompiledStory = struct {
story.globals.putAssumeCapacity(name_bytes, null);
}
for (self.knots) |*knot| {
const knot_name = ir.nullTerminatedString(knot.name);
const runtime_chunk: *Object.ContentPath = try .create(story, .{
.name = try .create(story, ir.nullTerminatedString(knot.name)),
.name = try .create(story, knot_name),
.arity = @intCast(knot.arity),
.locals_count = @intCast(knot.stack_size - knot.arity),
.const_pool = try knot.constants.toOwnedSlice(gpa),
.bytes = try knot.bytecode.toOwnedSlice(gpa),
});
story.paths.appendAssumeCapacity(&runtime_chunk.base);
story.globals.putAssumeCapacity(knot_name, &runtime_chunk.base);
}
story.string_bytes = ir.string_bytes;
ir.string_bytes = &.{};
}
};