feat: added Ir.Inst.Ref, global constant pool, lazy lowering in Sema
This commit is contained in:
parent
ce5385ebac
commit
e5e2b7c559
6 changed files with 615 additions and 534 deletions
706
src/Sema.zig
706
src/Sema.zig
|
|
@ -7,8 +7,9 @@ const Sema = @This();
|
|||
|
||||
gpa: std.mem.Allocator,
|
||||
ir: *const Ir,
|
||||
bytecode: std.ArrayListUnmanaged(u8) = .empty,
|
||||
constants: std.ArrayListUnmanaged(CompiledStory.Constant) = .empty,
|
||||
constant_map: std.AutoHashMapUnmanaged(CompiledStory.Constant, u32) = .empty,
|
||||
globals: std.ArrayListUnmanaged(u32) = .empty,
|
||||
knots: std.ArrayListUnmanaged(CompiledStory.Knot) = .empty,
|
||||
|
||||
const InnerError = error{
|
||||
|
|
@ -17,10 +18,21 @@ const InnerError = error{
|
|||
InvalidJump,
|
||||
};
|
||||
|
||||
const Ref = union(enum) {
|
||||
bool_true,
|
||||
bool_false,
|
||||
none,
|
||||
index: u32,
|
||||
constant: u32,
|
||||
global: u32,
|
||||
local: u32,
|
||||
};
|
||||
|
||||
fn deinit(sema: *Sema) void {
|
||||
const gpa = sema.gpa;
|
||||
sema.bytecode.deinit(gpa);
|
||||
sema.constants.deinit(gpa);
|
||||
sema.constant_map.deinit(gpa);
|
||||
sema.globals.deinit(gpa);
|
||||
sema.knots.deinit(gpa);
|
||||
sema.* = undefined;
|
||||
}
|
||||
|
|
@ -29,51 +41,290 @@ fn fail(_: *Sema, message: []const u8) InnerError {
|
|||
@panic(message);
|
||||
}
|
||||
|
||||
fn resolveIndex(sema: *Sema, index: Ir.Inst.Index) Ir.Inst {
|
||||
return sema.ir.instructions[@intFromEnum(index)];
|
||||
}
|
||||
|
||||
fn emitByte(sema: *Sema, byte: u8) !void {
|
||||
fn getConstant(sema: *Sema, data: CompiledStory.Constant) !Ref {
|
||||
const gpa = sema.gpa;
|
||||
return sema.bytecode.append(gpa, byte);
|
||||
|
||||
if (sema.constant_map.get(data)) |index| {
|
||||
return .{ .constant = index };
|
||||
} else {
|
||||
const index = sema.constants.items.len;
|
||||
try sema.constants.append(gpa, data);
|
||||
try sema.constant_map.put(gpa, data, @intCast(index));
|
||||
return .{ .constant = @intCast(index) };
|
||||
}
|
||||
}
|
||||
|
||||
fn emitByteOp(sema: *Sema, op: Story.Opcode) !void {
|
||||
return emitByte(sema, @intFromEnum(op));
|
||||
}
|
||||
|
||||
fn emitConstOp(sema: *Sema, op: Story.Opcode, arg: u8) !void {
|
||||
fn addGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref {
|
||||
const gpa = sema.gpa;
|
||||
if (arg >= std.math.maxInt(u8)) return error.TooManyConstants;
|
||||
try sema.bytecode.ensureUnusedCapacity(gpa, 2);
|
||||
sema.bytecode.appendAssumeCapacity(@intFromEnum(op));
|
||||
sema.bytecode.appendAssumeCapacity(arg);
|
||||
const interned = try sema.getConstant(.{ .string = name });
|
||||
try sema.globals.append(gpa, interned.constant);
|
||||
return .{ .global = interned.constant };
|
||||
}
|
||||
|
||||
fn emitJumpOp(sema: *Sema, op: Story.Opcode) error{OutOfMemory}!usize {
|
||||
const gpa = sema.gpa;
|
||||
try sema.bytecode.ensureUnusedCapacity(gpa, 3);
|
||||
sema.bytecode.appendAssumeCapacity(@intFromEnum(op));
|
||||
sema.bytecode.appendAssumeCapacity(0xff);
|
||||
sema.bytecode.appendAssumeCapacity(0xff);
|
||||
return sema.bytecode.items.len - 2;
|
||||
fn getGlobal(sema: *Sema, name: Ir.NullTerminatedString) !Ref {
|
||||
const interned = try sema.getConstant(.{ .string = name });
|
||||
for (sema.ir.globals) |global| {
|
||||
if (name == global.name) {
|
||||
return .{ .global = interned.constant };
|
||||
}
|
||||
}
|
||||
return sema.fail("unknown global variable");
|
||||
}
|
||||
|
||||
fn makeConstant(sema: *Sema, data: CompiledStory.Constant) !usize {
|
||||
fn irInteger(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.integer;
|
||||
return sema.getConstant(.{ .integer = data.value });
|
||||
}
|
||||
|
||||
fn irString(sema: *Sema, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.string;
|
||||
return sema.getConstant(.{ .string = data.start });
|
||||
}
|
||||
|
||||
fn irUnary(
|
||||
sema: *Sema,
|
||||
chunk: *Chunk,
|
||||
inst: Ir.Inst.Index,
|
||||
op: Story.Opcode,
|
||||
) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
||||
const lhs = chunk.resolveInst(data.lhs);
|
||||
_ = try chunk.doLoad(lhs);
|
||||
return chunk.addByteOp(op);
|
||||
}
|
||||
|
||||
fn irBinary(
|
||||
sema: *Sema,
|
||||
chunk: *Chunk,
|
||||
inst: Ir.Inst.Index,
|
||||
op: Story.Opcode,
|
||||
) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
|
||||
const lhs = chunk.resolveInst(data.lhs);
|
||||
const rhs = chunk.resolveInst(data.rhs);
|
||||
|
||||
_ = try chunk.doLoad(lhs);
|
||||
_ = try chunk.doLoad(rhs);
|
||||
return chunk.addByteOp(op);
|
||||
}
|
||||
|
||||
fn irAlloc(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref {
|
||||
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.stack_size += 1;
|
||||
return .{ .local = local_index };
|
||||
}
|
||||
|
||||
fn irStore(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.bin;
|
||||
const lhs = chunk.resolveInst(data.lhs);
|
||||
const rhs = chunk.resolveInst(data.rhs);
|
||||
_ = try chunk.doLoad(rhs);
|
||||
switch (lhs) {
|
||||
.bool_true, .bool_false => unreachable, // TODO: "Cannot assign to boolean"
|
||||
.none => unreachable,
|
||||
.constant => |_| unreachable, // TODO: "Cannot assign to constant"
|
||||
.global => |id| _ = try chunk.addConstOp(.store_global, @intCast(id)),
|
||||
.local => |id| _ = try chunk.addConstOp(.store, @intCast(id)),
|
||||
.index => unreachable,
|
||||
}
|
||||
_ = try chunk.addByteOp(.pop);
|
||||
}
|
||||
|
||||
// TODO: Check what the target is!
|
||||
fn irLoad(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
||||
const lhs = chunk.resolveInst(data.lhs);
|
||||
return chunk.doLoad(lhs);
|
||||
}
|
||||
|
||||
fn irCondBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.CondBr, data.payload_index);
|
||||
const then_body = sema.ir.bodySlice(extra.end, extra.data.then_body_len);
|
||||
const else_body = sema.ir.bodySlice(extra.end + then_body.len, extra.data.else_body_len);
|
||||
const else_label = try chunk.addLabel();
|
||||
const end_label = try chunk.addLabel();
|
||||
const condition = chunk.resolveInst(extra.data.condition);
|
||||
|
||||
_ = try chunk.doLoad(condition);
|
||||
try chunk.addFixup(.jmp_f, else_label);
|
||||
_ = try chunk.addByteOp(.pop);
|
||||
try blockBodyInner(sema, chunk, then_body);
|
||||
|
||||
try chunk.addFixup(.jmp, end_label);
|
||||
chunk.setLabel(else_label);
|
||||
_ = try chunk.addByteOp(.pop);
|
||||
|
||||
try blockBodyInner(sema, chunk, else_body);
|
||||
chunk.setLabel(end_label);
|
||||
return .none;
|
||||
}
|
||||
|
||||
fn irBreak(sema: *Sema, inst: Ir.Inst.Index) InnerError!void {
|
||||
_ = sema;
|
||||
_ = inst;
|
||||
}
|
||||
|
||||
fn irBlock(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Block, data.payload_index);
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
return blockBodyInner(sema, chunk, body);
|
||||
}
|
||||
|
||||
fn irContentPush(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.un;
|
||||
const lhs = chunk.resolveInst(data.lhs);
|
||||
_ = try chunk.doLoad(lhs);
|
||||
return chunk.addByteOp(.stream_push);
|
||||
}
|
||||
|
||||
fn irContentFlush(_: *Sema, chunk: *Chunk, _: Ir.Inst.Index) InnerError!Ref {
|
||||
return chunk.addByteOp(.stream_flush);
|
||||
}
|
||||
|
||||
fn irDeclRef(sema: *Sema, _: *Chunk, inst: Ir.Inst.Index) InnerError!Ref {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.string;
|
||||
return sema.getGlobal(data.start);
|
||||
}
|
||||
|
||||
fn irDeclVar(
|
||||
sema: *Sema,
|
||||
chunk: *Chunk,
|
||||
name: Ir.NullTerminatedString,
|
||||
inst: Ir.Inst.Index,
|
||||
) InnerError!void {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Block, data.payload_index);
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
try blockBodyInner(sema, chunk, body);
|
||||
// FIXME: hack
|
||||
{
|
||||
const last_inst = body[body.len - 1].toRef();
|
||||
const val = chunk.resolveInst(last_inst);
|
||||
_ = try chunk.doLoad(val);
|
||||
}
|
||||
const global = try sema.addGlobal(name);
|
||||
_ = try chunk.addConstOp(.store_global, @intCast(global.global));
|
||||
_ = try chunk.addByteOp(.pop);
|
||||
}
|
||||
|
||||
fn irDeclKnot(
|
||||
sema: *Sema,
|
||||
name_ref: Ir.NullTerminatedString,
|
||||
inst: Ir.Inst.Index,
|
||||
) InnerError!void {
|
||||
const gpa = sema.gpa;
|
||||
const const_index = sema.constants.items.len;
|
||||
try sema.constants.append(gpa, data);
|
||||
return const_index;
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Knot, data.payload_index);
|
||||
|
||||
var knot: CompiledStory.Knot = .{
|
||||
.name = name_ref,
|
||||
.arity = 0,
|
||||
.stack_size = 0,
|
||||
};
|
||||
var chunk: Chunk = .{
|
||||
.sema = sema,
|
||||
.knot = &knot,
|
||||
};
|
||||
defer chunk.fixups.deinit(gpa);
|
||||
defer chunk.labels.deinit(gpa);
|
||||
defer chunk.inst_map.deinit(gpa);
|
||||
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
try blockBodyInner(sema, &chunk, body);
|
||||
_ = try chunk.addByteOp(.exit);
|
||||
try chunk.resolveLabels();
|
||||
try sema.knots.append(gpa, knot);
|
||||
}
|
||||
|
||||
fn irDeclaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst.Index) !void {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Declaration, data.payload_index).data;
|
||||
const value_data = sema.ir.instructions[@intFromEnum(extra.value)];
|
||||
switch (value_data.tag) {
|
||||
.decl_var => try irDeclVar(sema, parent_chunk.?, extra.name, extra.value),
|
||||
.decl_knot => try irDeclKnot(sema, extra.name, extra.value),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn blockBodyInner(sema: *Sema, chunk: *Chunk, body: []const Ir.Inst.Index) InnerError!void {
|
||||
const gpa = sema.gpa;
|
||||
|
||||
for (body) |inst| {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)];
|
||||
const ref: Ref = switch (data.tag) {
|
||||
.file => unreachable,
|
||||
.declaration => {
|
||||
try irDeclaration(sema, chunk, inst);
|
||||
continue;
|
||||
},
|
||||
.decl_var => unreachable, // handled in declaration()
|
||||
.decl_knot => unreachable, // handled in declaration()
|
||||
.alloc => try irAlloc(sema, chunk, inst),
|
||||
.store => {
|
||||
try irStore(sema, chunk, inst);
|
||||
continue;
|
||||
},
|
||||
.load => try irLoad(sema, chunk, inst),
|
||||
.add => try irBinary(sema, chunk, inst, .add),
|
||||
.sub => try irBinary(sema, chunk, inst, .sub),
|
||||
.mul => try irBinary(sema, chunk, inst, .mul),
|
||||
.div => try irBinary(sema, chunk, inst, .div),
|
||||
.mod => try irBinary(sema, chunk, inst, .mod),
|
||||
.neg => try irUnary(sema, chunk, inst, .neg),
|
||||
.not => try irUnary(sema, chunk, inst, .not),
|
||||
.cmp_eq => try irBinary(sema, chunk, inst, .cmp_eq),
|
||||
.cmp_neq => blk: {
|
||||
_ = try irBinary(sema, chunk, inst, .cmp_eq);
|
||||
const tmp = try chunk.addByteOp(.not);
|
||||
break :blk tmp;
|
||||
},
|
||||
.cmp_lt => try irBinary(sema, chunk, inst, .cmp_lt),
|
||||
.cmp_lte => try irBinary(sema, chunk, inst, .cmp_lte),
|
||||
.cmp_gt => try irBinary(sema, chunk, inst, .cmp_gt),
|
||||
.cmp_gte => try irBinary(sema, chunk, inst, .cmp_gte),
|
||||
.decl_ref => try irDeclRef(sema, chunk, inst),
|
||||
.integer => try irInteger(sema, inst),
|
||||
.string => try irString(sema, inst),
|
||||
.condbr => try irCondBr(sema, chunk, inst),
|
||||
.@"break" => {
|
||||
try irBreak(sema, inst);
|
||||
continue;
|
||||
},
|
||||
.block => {
|
||||
try irBlock(sema, chunk, inst);
|
||||
continue;
|
||||
},
|
||||
.content_push => try irContentPush(sema, chunk, inst),
|
||||
.content_flush => try irContentFlush(sema, chunk, inst),
|
||||
};
|
||||
try chunk.inst_map.put(gpa, inst, ref);
|
||||
}
|
||||
}
|
||||
|
||||
fn file(sema: *Sema, inst: Ir.Inst.Index) !void {
|
||||
const data = sema.ir.instructions[@intFromEnum(inst)].data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Block, data.payload_index);
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
|
||||
// FIXME: We are going to get burned by this if we don't formalize it.
|
||||
// Adding common constants to the constant pool.
|
||||
_ = try sema.getConstant(.{ .integer = 0 });
|
||||
_ = try sema.getConstant(.{ .integer = 1 });
|
||||
|
||||
for (body) |body_index| try irDeclaration(sema, null, body_index);
|
||||
}
|
||||
|
||||
const Chunk = struct {
|
||||
sema: *Sema,
|
||||
name: Ir.NullTerminatedString,
|
||||
arity: u32,
|
||||
stack_size: u32,
|
||||
stack_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, u32),
|
||||
labels: std.ArrayListUnmanaged(Label),
|
||||
fixups: std.ArrayListUnmanaged(Fixup),
|
||||
knot: *CompiledStory.Knot,
|
||||
labels: std.ArrayListUnmanaged(Label) = .empty,
|
||||
fixups: std.ArrayListUnmanaged(Fixup) = .empty,
|
||||
inst_map: std.AutoHashMapUnmanaged(Ir.Inst.Index, Ref) = .empty,
|
||||
|
||||
const dummy_address = 0xffffffff;
|
||||
|
||||
|
|
@ -86,15 +337,55 @@ const Chunk = struct {
|
|||
relative,
|
||||
absolute,
|
||||
},
|
||||
label_index: usize,
|
||||
code_offset: usize,
|
||||
label_index: u32,
|
||||
code_offset: u32,
|
||||
};
|
||||
|
||||
fn addByteOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref {
|
||||
const gpa = chunk.sema.gpa;
|
||||
const bytecode = &chunk.knot.bytecode;
|
||||
const byte_index = bytecode.items.len;
|
||||
try bytecode.append(gpa, @intFromEnum(op));
|
||||
return .{ .index = @intCast(byte_index) };
|
||||
}
|
||||
|
||||
fn addConstOp(chunk: *Chunk, op: Story.Opcode, arg: u8) error{OutOfMemory}!Ref {
|
||||
const gpa = chunk.sema.gpa;
|
||||
const bytecode = &chunk.knot.bytecode;
|
||||
const byte_index = bytecode.items.len;
|
||||
try bytecode.ensureUnusedCapacity(gpa, 2);
|
||||
bytecode.appendAssumeCapacity(@intFromEnum(op));
|
||||
bytecode.appendAssumeCapacity(arg);
|
||||
return .{ .index = @intCast(byte_index) };
|
||||
}
|
||||
|
||||
fn addJumpOp(chunk: *Chunk, op: Story.Opcode) error{OutOfMemory}!Ref {
|
||||
const gpa = chunk.sema.gpa;
|
||||
const bytecode = &chunk.knot.bytecode;
|
||||
try bytecode.ensureUnusedCapacity(gpa, 3);
|
||||
bytecode.appendAssumeCapacity(@intFromEnum(op));
|
||||
bytecode.appendAssumeCapacity(0xff);
|
||||
bytecode.appendAssumeCapacity(0xff);
|
||||
return .{ .index = @intCast(bytecode.items.len - 2) };
|
||||
}
|
||||
|
||||
fn resolveInst(chunk: *Chunk, ref: Ir.Inst.Ref) Ref {
|
||||
if (ref.toIndex()) |index| {
|
||||
return chunk.inst_map.get(index).?;
|
||||
}
|
||||
switch (ref) {
|
||||
.bool_true => return .bool_true,
|
||||
.bool_false => return .bool_false,
|
||||
else => return .{ .constant = @intFromEnum(ref) },
|
||||
}
|
||||
}
|
||||
|
||||
fn addFixup(chunk: *Chunk, op: Story.Opcode, label: usize) !void {
|
||||
const code_ref = try chunk.addJumpOp(op);
|
||||
return chunk.fixups.append(chunk.sema.gpa, .{
|
||||
.mode = .relative,
|
||||
.label_index = label,
|
||||
.code_offset = try chunk.sema.emitJumpOp(op),
|
||||
.label_index = @intCast(label),
|
||||
.code_offset = code_ref.index,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +398,7 @@ const Chunk = struct {
|
|||
}
|
||||
|
||||
fn setLabel(chunk: *Chunk, label_index: usize) void {
|
||||
const code_offset = chunk.sema.bytecode.items.len;
|
||||
const code_offset = chunk.knot.bytecode.items.len;
|
||||
assert(label_index <= chunk.labels.items.len);
|
||||
|
||||
const label_data = &chunk.labels.items[label_index];
|
||||
|
|
@ -117,7 +408,7 @@ const Chunk = struct {
|
|||
fn resolveLabels(chunk: *Chunk) !void {
|
||||
const start_index = 0;
|
||||
const end_index = chunk.fixups.items.len;
|
||||
const bytecode = &chunk.sema.bytecode;
|
||||
const bytecode = &chunk.knot.bytecode;
|
||||
|
||||
for (chunk.fixups.items[start_index..end_index]) |fixup| {
|
||||
const label = chunk.labels.items[fixup.label_index];
|
||||
|
|
@ -135,244 +426,44 @@ const Chunk = struct {
|
|||
bytecode.items[fixup.code_offset + 1] = @intCast(target_offset & 0xff);
|
||||
}
|
||||
}
|
||||
|
||||
fn doLoad(chunk: *Chunk, ref: Ref) InnerError!Ref {
|
||||
const gpa = chunk.sema.gpa;
|
||||
switch (ref) {
|
||||
.bool_true => return chunk.addByteOp(.true),
|
||||
.bool_false => return chunk.addByteOp(.false),
|
||||
.none => return ref,
|
||||
.constant => |id| {
|
||||
// TODO: This isn't great. New constant indexes are
|
||||
// created each time.
|
||||
const ref_const = chunk.knot.constants.items.len;
|
||||
try chunk.knot.constants.append(gpa, id);
|
||||
return chunk.addConstOp(.load_const, @intCast(ref_const));
|
||||
},
|
||||
.global => |id| {
|
||||
// TODO: This isn't great. New constant indexes are
|
||||
// created each time.
|
||||
const ref_const = chunk.knot.constants.items.len;
|
||||
try chunk.knot.constants.append(gpa, id);
|
||||
return chunk.addConstOp(.load_global, @intCast(ref_const));
|
||||
},
|
||||
.local => |id| return chunk.addConstOp(.load, @intCast(id)),
|
||||
.index => return ref,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fn integerInst(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||
const int_const = try sema.makeConstant(.{
|
||||
.integer = inst.data.integer.value,
|
||||
});
|
||||
return emitConstOp(sema, .load_const, @intCast(int_const));
|
||||
}
|
||||
|
||||
fn stringInst(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||
const str_const = try sema.makeConstant(.{
|
||||
.string = inst.data.string.start,
|
||||
});
|
||||
return emitConstOp(sema, .load_const, @intCast(str_const));
|
||||
}
|
||||
|
||||
fn unaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void {
|
||||
return emitByteOp(sema, op);
|
||||
}
|
||||
|
||||
fn binaryInst(sema: *Sema, _: Ir.Inst, op: Story.Opcode) InnerError!void {
|
||||
return emitByteOp(sema, op);
|
||||
}
|
||||
|
||||
fn irContentPush(sema: *Sema, _: Ir.Inst) InnerError!void {
|
||||
return emitByteOp(sema, .stream_push);
|
||||
}
|
||||
|
||||
fn irContentFlush(sema: *Sema, _: Ir.Inst) InnerError!void {
|
||||
return emitByteOp(sema, .stream_flush);
|
||||
}
|
||||
|
||||
fn allocLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst.Index) InnerError!void {
|
||||
const gpa = sema.gpa;
|
||||
// TODO: Add constraints on how many temporaries we can have.
|
||||
// max(u8) or max(u16) are most likey appropriate.
|
||||
try chunk.stack_map.put(gpa, inst, chunk.stack_size);
|
||||
chunk.stack_size += 1;
|
||||
}
|
||||
|
||||
fn storeLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void {
|
||||
const temp_inst = inst.data.bin.lhs;
|
||||
const stack_offset = chunk.stack_map.get(temp_inst).?;
|
||||
try emitConstOp(sema, .store, @intCast(stack_offset));
|
||||
try emitByteOp(sema, .pop);
|
||||
}
|
||||
|
||||
fn loadLocal(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void {
|
||||
const temp_inst = inst.data.un.lhs;
|
||||
const stack_offset = chunk.stack_map.get(temp_inst).?;
|
||||
try emitConstOp(sema, .load, @intCast(stack_offset));
|
||||
}
|
||||
|
||||
fn irCondBr(sema: *Sema, chunk: *Chunk, inst: Ir.Inst) InnerError!void {
|
||||
const payload_node = inst.data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.CondBr, payload_node.payload_index);
|
||||
const then_body = sema.ir.bodySlice(extra.end, extra.data.then_body_len);
|
||||
const else_body = sema.ir.bodySlice(extra.end + then_body.len, extra.data.else_body_len);
|
||||
const else_label = try chunk.addLabel();
|
||||
const end_label = try chunk.addLabel();
|
||||
|
||||
try chunk.addFixup(.jmp_f, else_label);
|
||||
try emitByteOp(sema, .pop);
|
||||
for (then_body) |body_index| try compileInst(sema, chunk, body_index);
|
||||
|
||||
try chunk.addFixup(.jmp, end_label);
|
||||
chunk.setLabel(else_label);
|
||||
try emitByteOp(sema, .pop);
|
||||
|
||||
for (else_body) |body_index| try compileInst(sema, chunk, body_index);
|
||||
chunk.setLabel(end_label);
|
||||
}
|
||||
|
||||
fn irBreak(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||
_ = sema;
|
||||
_ = inst;
|
||||
}
|
||||
|
||||
fn irBlock(sema: *Sema, chunk: *Chunk, block_inst: Ir.Inst) InnerError!void {
|
||||
const payload_node = block_inst.data.payload;
|
||||
const extra = sema.ir.extraData(Ir.Inst.Block, payload_node.payload_index);
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
for (body) |body_index| try compileInst(sema, chunk, body_index);
|
||||
}
|
||||
|
||||
fn declRef(sema: *Sema, inst: Ir.Inst) InnerError!void {
|
||||
const ir = sema.ir;
|
||||
const str = inst.data.string.start;
|
||||
for (ir.globals) |global| {
|
||||
if (str == global.name) {
|
||||
const constant_index = try sema.makeConstant(.{ .string = str });
|
||||
return emitConstOp(sema, .load_global, @intCast(constant_index));
|
||||
}
|
||||
}
|
||||
return sema.fail("unknown global variable");
|
||||
}
|
||||
|
||||
fn declVar(
|
||||
sema: *Sema,
|
||||
chunk: *Chunk,
|
||||
name: Ir.NullTerminatedString,
|
||||
inst: Ir.Inst,
|
||||
) InnerError!void {
|
||||
const ir = sema.ir;
|
||||
const payload_index = inst.data.payload.payload_index;
|
||||
const extra = ir.extraData(Ir.Inst.Block, payload_index);
|
||||
const body = ir.bodySlice(extra.end, extra.data.body_len);
|
||||
for (body) |body_index| try compileInst(sema, chunk, body_index);
|
||||
for (ir.globals) |global| {
|
||||
if (name == global.name) {
|
||||
const constant_index = try sema.makeConstant(.{ .string = name });
|
||||
return emitConstOp(sema, .store_global, @intCast(constant_index));
|
||||
}
|
||||
}
|
||||
|
||||
return sema.fail("unknown global variable");
|
||||
}
|
||||
|
||||
fn declKnot(sema: *Sema, name_ref: Ir.NullTerminatedString, inst: Ir.Inst) InnerError!void {
|
||||
const gpa = sema.gpa;
|
||||
const ir = sema.ir;
|
||||
const extra = ir.extraData(Ir.Inst.Knot, inst.data.payload.payload_index);
|
||||
const bytecode_start = sema.bytecode.items.len;
|
||||
const const_start = sema.constants.items.len;
|
||||
|
||||
var chunk: Chunk = .{
|
||||
.sema = sema,
|
||||
.name = name_ref,
|
||||
.arity = 0,
|
||||
.stack_size = 0,
|
||||
.stack_map = .empty,
|
||||
.fixups = .empty,
|
||||
.labels = .empty,
|
||||
};
|
||||
defer chunk.stack_map.deinit(gpa);
|
||||
defer chunk.fixups.deinit(gpa);
|
||||
defer chunk.labels.deinit(gpa);
|
||||
|
||||
const body_slice = ir.bodySlice(extra.end, extra.data.body_len);
|
||||
for (body_slice) |body_inst| try compileInst(sema, &chunk, body_inst);
|
||||
|
||||
try emitByteOp(sema, .exit);
|
||||
try chunk.resolveLabels();
|
||||
|
||||
try sema.knots.append(gpa, .{
|
||||
.name_ref = name_ref,
|
||||
.arity = chunk.arity,
|
||||
.stack_size = chunk.stack_size,
|
||||
.bytecode = .{
|
||||
.start = @intCast(bytecode_start),
|
||||
.len = @intCast(sema.bytecode.items.len - bytecode_start),
|
||||
},
|
||||
.constants = .{
|
||||
.start = @intCast(const_start),
|
||||
.len = @intCast(sema.constants.items.len - const_start),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn declaration(sema: *Sema, parent_chunk: ?*Chunk, inst: Ir.Inst) !void {
|
||||
const ir = sema.ir;
|
||||
const extra = ir.extraData(Ir.Inst.Declaration, inst.data.payload.payload_index);
|
||||
const value_inst = sema.resolveIndex(extra.data.value);
|
||||
const str = extra.data.name;
|
||||
|
||||
switch (value_inst.tag) {
|
||||
.decl_var => try declVar(sema, parent_chunk.?, str, value_inst),
|
||||
.decl_knot => try declKnot(sema, str, value_inst),
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn compileInst(sema: *Sema, chunk: *Chunk, index: Ir.Inst.Index) InnerError!void {
|
||||
const inst = sema.resolveIndex(index);
|
||||
switch (inst.tag) {
|
||||
.file => unreachable,
|
||||
.declaration => try declaration(sema, chunk, inst),
|
||||
.decl_var => unreachable, // handled in declaration()
|
||||
.decl_knot => unreachable, // handled in declaration()
|
||||
.decl_ref => try declRef(sema, inst),
|
||||
.condbr => try irCondBr(sema, chunk, inst),
|
||||
.@"break" => try irBreak(sema, inst),
|
||||
.block => try irBlock(sema, chunk, inst),
|
||||
.alloc_local => try allocLocal(sema, chunk, index),
|
||||
.store_local => try storeLocal(sema, chunk, inst),
|
||||
.load_local => try loadLocal(sema, chunk, inst),
|
||||
.true_literal => unreachable, // TODO
|
||||
.false_literal => unreachable, // TODO
|
||||
.add => try binaryInst(sema, inst, .add),
|
||||
.sub => try binaryInst(sema, inst, .sub),
|
||||
.mul => try binaryInst(sema, inst, .mul),
|
||||
.div => try binaryInst(sema, inst, .div),
|
||||
.mod => try binaryInst(sema, inst, .mod),
|
||||
.neg => try unaryInst(sema, inst, .neg),
|
||||
.not => try unaryInst(sema, inst, .not),
|
||||
.cmp_eq => try unaryInst(sema, inst, .cmp_eq),
|
||||
.cmp_neq => {
|
||||
try unaryInst(sema, inst, .cmp_eq);
|
||||
try emitByteOp(sema, .not);
|
||||
},
|
||||
.cmp_lt => try unaryInst(sema, inst, .cmp_lt),
|
||||
.cmp_lte => try unaryInst(sema, inst, .cmp_lte),
|
||||
.cmp_gt => try unaryInst(sema, inst, .cmp_gt),
|
||||
.cmp_gte => try unaryInst(sema, inst, .cmp_gte),
|
||||
.content_push => try irContentPush(sema, inst),
|
||||
.content_flush => try irContentFlush(sema, inst),
|
||||
.string => try stringInst(sema, inst),
|
||||
.integer => try integerInst(sema, inst),
|
||||
}
|
||||
}
|
||||
|
||||
fn file(sema: *Sema, inst: Ir.Inst) !void {
|
||||
const extra = sema.ir.extraData(Ir.Inst.Block, inst.data.payload.payload_index);
|
||||
const body = sema.ir.bodySlice(extra.end, extra.data.body_len);
|
||||
for (body) |body_index| {
|
||||
const body_inst = sema.resolveIndex(body_index);
|
||||
assert(body_inst.tag == .declaration);
|
||||
try declaration(sema, null, body_inst);
|
||||
}
|
||||
}
|
||||
|
||||
pub const CompiledStory = struct {
|
||||
knots: []Knot,
|
||||
constants: []Constant,
|
||||
bytecode: []u8,
|
||||
globals: []u32,
|
||||
|
||||
pub const Knot = struct {
|
||||
name_ref: Ir.NullTerminatedString,
|
||||
name: Ir.NullTerminatedString,
|
||||
arity: u32,
|
||||
stack_size: u32,
|
||||
constants: struct {
|
||||
start: u32,
|
||||
len: u32,
|
||||
},
|
||||
bytecode: struct {
|
||||
start: u32,
|
||||
len: u32,
|
||||
},
|
||||
constants: std.ArrayListUnmanaged(u32) = .empty,
|
||||
bytecode: std.ArrayListUnmanaged(u8) = .empty,
|
||||
};
|
||||
|
||||
pub const Constant = union(enum) {
|
||||
|
|
@ -381,58 +472,57 @@ pub const CompiledStory = struct {
|
|||
};
|
||||
|
||||
pub fn deinit(self: *CompiledStory, gpa: std.mem.Allocator) void {
|
||||
for (self.knots) |*knot| {
|
||||
knot.constants.deinit(gpa);
|
||||
knot.bytecode.deinit(gpa);
|
||||
}
|
||||
|
||||
gpa.free(self.knots);
|
||||
gpa.free(self.bytecode);
|
||||
gpa.free(self.globals);
|
||||
gpa.free(self.constants);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn bytecodeSlice(self: CompiledStory, knot: *const Knot) []const u8 {
|
||||
return self.bytecode[knot.bytecode.start..][0..knot.bytecode.len];
|
||||
}
|
||||
|
||||
fn constantsSlice(self: CompiledStory, knot: *const Knot) []const Constant {
|
||||
return self.constants[knot.constants.start..][0..knot.constants.len];
|
||||
}
|
||||
|
||||
pub fn buildRuntime(
|
||||
self: *CompiledStory,
|
||||
gpa: std.mem.Allocator,
|
||||
ir: Ir,
|
||||
story: *Story,
|
||||
) !void {
|
||||
for (self.knots) |compiled_knot| {
|
||||
var constant_pool: std.ArrayListUnmanaged(*Object) = .empty;
|
||||
try constant_pool.ensureUnusedCapacity(gpa, compiled_knot.constants.len);
|
||||
defer constant_pool.deinit(gpa);
|
||||
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));
|
||||
|
||||
const constants_slice = self.constantsSlice(&compiled_knot);
|
||||
for (constants_slice) |constant| {
|
||||
switch (constant) {
|
||||
.integer => |value| {
|
||||
const object: *Object.Number = try .create(story, .{
|
||||
.integer = @intCast(value),
|
||||
});
|
||||
constant_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
.string => |ref| {
|
||||
const bytes = ir.nullTerminatedString(ref);
|
||||
const object: *Object.String = try .create(story, bytes);
|
||||
constant_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
}
|
||||
for (self.constants) |constant| {
|
||||
switch (constant) {
|
||||
.integer => |value| {
|
||||
const object: *Object.Number = try .create(story, .{
|
||||
.integer = @intCast(value),
|
||||
});
|
||||
constants_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
.string => |ref| {
|
||||
const bytes = ir.nullTerminatedString(ref);
|
||||
const object: *Object.String = try .create(story, bytes);
|
||||
constants_pool.appendAssumeCapacity(&object.base);
|
||||
},
|
||||
}
|
||||
|
||||
const bytecode_slice = self.bytecodeSlice(&compiled_knot);
|
||||
const chunk_name = ir.nullTerminatedString(compiled_knot.name_ref);
|
||||
}
|
||||
for (self.globals) |global_index| {
|
||||
const str = self.constants[global_index];
|
||||
const name_bytes = ir.nullTerminatedString(str.string);
|
||||
story.globals.putAssumeCapacity(name_bytes, null);
|
||||
}
|
||||
for (self.knots) |*knot| {
|
||||
const runtime_chunk: *Object.ContentPath = try .create(story, .{
|
||||
.name = try .create(story, chunk_name),
|
||||
.arity = @intCast(compiled_knot.arity),
|
||||
.locals_count = @intCast(compiled_knot.stack_size - compiled_knot.arity),
|
||||
.const_pool = try constant_pool.toOwnedSlice(gpa),
|
||||
.bytes = try gpa.dupe(u8, bytecode_slice),
|
||||
.name = try .create(story, ir.nullTerminatedString(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),
|
||||
});
|
||||
try story.paths.append(gpa, &runtime_chunk.base);
|
||||
story.paths.appendAssumeCapacity(&runtime_chunk.base);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -444,10 +534,10 @@ pub fn compile(gpa: std.mem.Allocator, ir: *const Ir) !CompiledStory {
|
|||
};
|
||||
defer sema.deinit();
|
||||
|
||||
try file(&sema, ir.instructions[0]);
|
||||
try file(&sema, @enumFromInt(0));
|
||||
return .{
|
||||
.bytecode = try sema.bytecode.toOwnedSlice(gpa),
|
||||
.constants = try sema.constants.toOwnedSlice(gpa),
|
||||
.globals = try sema.globals.toOwnedSlice(gpa),
|
||||
.knots = try sema.knots.toOwnedSlice(gpa),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue