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
281
src/AstGen.zig
281
src/AstGen.zig
|
|
@ -105,7 +105,8 @@ const GenIr = struct {
|
|||
) []Ir.Inst.Index {
|
||||
return if (self.instructions_top == unstacked_top)
|
||||
&[0]Ir.Inst.Index{}
|
||||
else if (self.instructions == stacked_block.instructions and stacked_block.instructions_top != unstacked_top)
|
||||
else if (self.instructions == stacked_block.instructions and
|
||||
stacked_block.instructions_top != unstacked_top)
|
||||
self.instructions.items[self.instructions_top..stacked_block.instructions_top]
|
||||
else
|
||||
self.instructions.items[self.instructions_top..];
|
||||
|
|
@ -127,24 +128,28 @@ const GenIr = struct {
|
|||
};
|
||||
}
|
||||
|
||||
fn add(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Index {
|
||||
fn add(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Ref {
|
||||
return (try gi.addAsIndex(inst)).toRef();
|
||||
}
|
||||
|
||||
fn addAsIndex(gi: *GenIr, inst: Ir.Inst) !Ir.Inst.Index {
|
||||
const gpa = gi.astgen.gpa;
|
||||
try gi.instructions.ensureUnusedCapacity(gpa, 1);
|
||||
try gi.astgen.instructions.ensureUnusedCapacity(gpa, 1);
|
||||
|
||||
const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
|
||||
const new_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
|
||||
gi.astgen.instructions.appendAssumeCapacity(inst);
|
||||
gi.instructions.appendAssumeCapacity(inst_index);
|
||||
return inst_index;
|
||||
gi.instructions.appendAssumeCapacity(new_index);
|
||||
return new_index;
|
||||
}
|
||||
|
||||
fn addInt(gi: *GenIr, value: u64) !Ir.Inst.Index {
|
||||
fn addInt(gi: *GenIr, value: u64) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = .integer, .data = .{
|
||||
.integer = .{ .value = value },
|
||||
} });
|
||||
}
|
||||
|
||||
fn addUnaryNode(gi: *GenIr, tag: Ir.Inst.Tag, arg: Ir.Inst.Index) !Ir.Inst.Index {
|
||||
fn addUnaryNode(gi: *GenIr, tag: Ir.Inst.Tag, arg: Ir.Inst.Ref) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = tag, .data = .{
|
||||
.un = .{ .lhs = arg },
|
||||
} });
|
||||
|
|
@ -153,15 +158,15 @@ const GenIr = struct {
|
|||
fn addBinaryNode(
|
||||
gi: *GenIr,
|
||||
tag: Ir.Inst.Tag,
|
||||
lhs: Ir.Inst.Index,
|
||||
rhs: Ir.Inst.Index,
|
||||
) !Ir.Inst.Index {
|
||||
lhs: Ir.Inst.Ref,
|
||||
rhs: Ir.Inst.Ref,
|
||||
) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = tag, .data = .{
|
||||
.bin = .{ .lhs = lhs, .rhs = rhs },
|
||||
} });
|
||||
}
|
||||
|
||||
fn addDeclRef(gi: *GenIr, decl_ref: Ir.NullTerminatedString) !Ir.Inst.Index {
|
||||
fn addDeclRef(gi: *GenIr, decl_ref: Ir.NullTerminatedString) !Ir.Inst.Ref {
|
||||
return add(gi, .{ .tag = .decl_ref, .data = .{
|
||||
.string = .{
|
||||
.start = decl_ref,
|
||||
|
|
@ -185,8 +190,16 @@ const GenIr = struct {
|
|||
return makePayloadNode(gi, .declaration);
|
||||
}
|
||||
|
||||
fn makeBlockInst(gi: *GenIr) !Ir.Inst.Index {
|
||||
return makePayloadNode(gi, .block);
|
||||
fn makeBlockInst(gi: *GenIr, tag: Ir.Inst.Tag) !Ir.Inst.Index {
|
||||
const inst_index: Ir.Inst.Index = @enumFromInt(gi.astgen.instructions.items.len);
|
||||
const gpa = gi.astgen.gpa;
|
||||
try gi.astgen.instructions.append(gpa, .{
|
||||
.tag = tag,
|
||||
.data = .{
|
||||
.payload = .{ .payload_index = undefined },
|
||||
},
|
||||
});
|
||||
return inst_index;
|
||||
}
|
||||
|
||||
fn addKnot(self: *GenIr) !Ir.Inst.Index {
|
||||
|
|
@ -331,7 +344,7 @@ fn setDeclaration(
|
|||
|
||||
fn setCondBrPayload(
|
||||
condbr: Ir.Inst.Index,
|
||||
cond: Ir.Inst.Index,
|
||||
cond: Ir.Inst.Ref,
|
||||
then_block: *GenIr,
|
||||
else_block: *GenIr,
|
||||
) !void {
|
||||
|
|
@ -374,6 +387,7 @@ fn setExtra(astgen: *AstGen, index: usize, extra: anytype) void {
|
|||
astgen.extra.items[i] = switch (field.type) {
|
||||
u32 => @field(extra, field.name),
|
||||
Ir.Inst.Index => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.Inst.Ref => @intFromEnum(@field(extra, field.name)),
|
||||
Ir.NullTerminatedString => @intFromEnum(@field(extra, field.name)),
|
||||
else => @compileError("bad field type"),
|
||||
};
|
||||
|
|
@ -444,9 +458,9 @@ fn unaryOp(
|
|||
scope: *Scope,
|
||||
expr_node: *const Ast.Node,
|
||||
op: Ir.Inst.Tag,
|
||||
) InnerError!Ir.Inst.Index {
|
||||
assert(expr_node.data.bin.lhs != null);
|
||||
const lhs = try expr(gi, scope, expr_node.data.bin.lhs orelse unreachable);
|
||||
) InnerError!Ir.Inst.Ref {
|
||||
const data = expr_node.data.bin;
|
||||
const lhs = try expr(gi, scope, data.lhs.?);
|
||||
return gi.addUnaryNode(op, lhs);
|
||||
}
|
||||
|
||||
|
|
@ -455,12 +469,11 @@ fn binaryOp(
|
|||
scope: *Scope,
|
||||
expr_node: *const Ast.Node,
|
||||
op: Ir.Inst.Tag,
|
||||
) InnerError!Ir.Inst.Index {
|
||||
) InnerError!Ir.Inst.Ref {
|
||||
const data = expr_node.data.bin;
|
||||
assert(data.lhs != null and data.rhs != null);
|
||||
|
||||
const lhs = try expr(gi, scope, data.lhs orelse unreachable);
|
||||
const rhs = try expr(gi, scope, data.rhs orelse unreachable);
|
||||
const lhs = try expr(gi, scope, data.lhs.?);
|
||||
const rhs = try expr(gi, scope, data.rhs.?);
|
||||
return gi.addBinaryNode(op, lhs, rhs);
|
||||
}
|
||||
|
||||
|
|
@ -490,23 +503,14 @@ fn logicalOp(
|
|||
gen.setLabel(else_label);
|
||||
}
|
||||
|
||||
fn trueLiteral(gi: *GenIr) InnerError!Ir.Inst.Index {
|
||||
return gi.add(.{ .tag = .true_literal, .data = undefined });
|
||||
}
|
||||
|
||||
fn falseLiteral(gi: *GenIr) InnerError!Ir.Inst.Index {
|
||||
return gi.add(.{ .tag = .false_literal, .data = undefined });
|
||||
}
|
||||
|
||||
fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
fn numberLiteral(gen: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const lexeme = sliceFromNode(gen.astgen, node);
|
||||
const int_value = try std.fmt.parseUnsigned(u64, lexeme, 10);
|
||||
return gen.addInt(int_value);
|
||||
}
|
||||
|
||||
fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
const astgen = gi.astgen;
|
||||
const str = try astgen.stringFromNode(node);
|
||||
fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const str = try gi.astgen.stringFromNode(node);
|
||||
return gi.add(.{
|
||||
.tag = .string,
|
||||
.data = .{ .string = .{
|
||||
|
|
@ -515,47 +519,46 @@ fn stringLiteral(gi: *GenIr, node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
|||
});
|
||||
}
|
||||
|
||||
fn stringExpr(gen: *GenIr, expr_node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
assert(expr_node.data.bin.lhs != null);
|
||||
const first_node = expr_node.data.bin.lhs orelse unreachable;
|
||||
fn stringExpr(gen: *GenIr, expr_node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const first_node = expr_node.data.bin.lhs.?;
|
||||
return stringLiteral(gen, first_node);
|
||||
}
|
||||
|
||||
fn identifier(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
fn identifier(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const astgen = gi.astgen;
|
||||
const str = try astgen.stringFromNode(node);
|
||||
if (scope.lookup(str)) |decl| {
|
||||
return gi.addUnaryNode(.load_local, decl.inst_index);
|
||||
return gi.addUnaryNode(.load, decl.inst_index.toRef());
|
||||
}
|
||||
return gi.addDeclRef(str);
|
||||
}
|
||||
|
||||
fn expr(block: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
const expr_node = optional_expr orelse unreachable;
|
||||
fn expr(gi: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const expr_node = optional_expr.?;
|
||||
switch (expr_node.tag) {
|
||||
.file => unreachable,
|
||||
.true_literal => return trueLiteral(block),
|
||||
.false_literal => return falseLiteral(block),
|
||||
.number_literal => return numberLiteral(block, expr_node),
|
||||
.string_literal => return stringLiteral(block, expr_node),
|
||||
.string_expr => return stringExpr(block, expr_node),
|
||||
.empty_string => return stringLiteral(block, expr_node),
|
||||
.identifier => return identifier(block, scope, expr_node),
|
||||
.add_expr => return binaryOp(block, scope, expr_node, .add),
|
||||
.subtract_expr => return binaryOp(block, scope, expr_node, .sub),
|
||||
.multiply_expr => return binaryOp(block, scope, expr_node, .mul),
|
||||
.divide_expr => return binaryOp(block, scope, expr_node, .div),
|
||||
.mod_expr => return binaryOp(block, scope, expr_node, .mod),
|
||||
.negate_expr => return unaryOp(block, scope, expr_node, .neg),
|
||||
.true_literal => return .bool_true,
|
||||
.false_literal => return .bool_false,
|
||||
.number_literal => return numberLiteral(gi, expr_node),
|
||||
.string_literal => return stringLiteral(gi, expr_node),
|
||||
.string_expr => return stringExpr(gi, expr_node),
|
||||
.empty_string => return stringLiteral(gi, expr_node),
|
||||
.identifier => return identifier(gi, scope, expr_node),
|
||||
.add_expr => return binaryOp(gi, scope, expr_node, .add),
|
||||
.subtract_expr => return binaryOp(gi, scope, expr_node, .sub),
|
||||
.multiply_expr => return binaryOp(gi, scope, expr_node, .mul),
|
||||
.divide_expr => return binaryOp(gi, scope, expr_node, .div),
|
||||
.mod_expr => return binaryOp(gi, scope, expr_node, .mod),
|
||||
.negate_expr => return unaryOp(gi, scope, expr_node, .neg),
|
||||
.logical_and_expr => unreachable,
|
||||
.logical_or_expr => unreachable,
|
||||
.logical_not_expr => return unaryOp(block, scope, expr_node, .not),
|
||||
.logical_equality_expr => return binaryOp(block, scope, expr_node, .cmp_eq),
|
||||
.logical_inequality_expr => return binaryOp(block, scope, expr_node, .cmp_neq),
|
||||
.logical_greater_expr => return binaryOp(block, scope, expr_node, .cmp_gt),
|
||||
.logical_greater_or_equal_expr => return binaryOp(block, scope, expr_node, .cmp_gte),
|
||||
.logical_lesser_expr => return binaryOp(block, scope, expr_node, .cmp_lt),
|
||||
.logical_lesser_or_equal_expr => return binaryOp(block, scope, expr_node, .cmp_lte),
|
||||
.logical_not_expr => return unaryOp(gi, scope, expr_node, .not),
|
||||
.logical_equality_expr => return binaryOp(gi, scope, expr_node, .cmp_eq),
|
||||
.logical_inequality_expr => return binaryOp(gi, scope, expr_node, .cmp_neq),
|
||||
.logical_greater_expr => return binaryOp(gi, scope, expr_node, .cmp_gt),
|
||||
.logical_greater_or_equal_expr => return binaryOp(gi, scope, expr_node, .cmp_gte),
|
||||
.logical_lesser_expr => return binaryOp(gi, scope, expr_node, .cmp_lt),
|
||||
.logical_lesser_or_equal_expr => return binaryOp(gi, scope, expr_node, .cmp_lte),
|
||||
.call_expr => unreachable,
|
||||
.choice_expr => unreachable,
|
||||
.choice_start_expr => unreachable,
|
||||
|
|
@ -588,22 +591,28 @@ fn expr(block: *GenIr, scope: *Scope, optional_expr: ?*const Ast.Node) InnerErro
|
|||
.ref_parameter_decl => unreachable,
|
||||
.argument_list => unreachable,
|
||||
.parameter_list => unreachable,
|
||||
.switch_stmt => unreachable,
|
||||
.switch_case => unreachable,
|
||||
.if_stmt => unreachable,
|
||||
.multi_if_stmt => unreachable,
|
||||
.if_branch => unreachable,
|
||||
.else_branch => unreachable,
|
||||
.switch_stmt => unreachable, // Handled in switchStmt
|
||||
.switch_case => unreachable, // Handled in switchStmt
|
||||
.if_stmt => unreachable, // Handled in ifStmt
|
||||
.multi_if_stmt => unreachable, // Handled in multiIfStmt
|
||||
.if_branch => unreachable, // Handled in ifStmt and multiIfStmt
|
||||
.else_branch => unreachable, // Handled in switchStmt, multiIfStmt, and ifStmt
|
||||
.content => unreachable,
|
||||
.inline_logic_expr => unreachable,
|
||||
.invalid => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
fn exprStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
fn exprStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
// TODO: Maybe we should introduce a unary node type to avoid optional checks?
|
||||
const expr_node = stmt_node.data.bin.lhs.?;
|
||||
return expr(gen, scope, expr_node);
|
||||
const expr_node = node.data.bin.lhs.?;
|
||||
return expr(gi, scope, expr_node);
|
||||
}
|
||||
|
||||
fn inlineLogicExpr(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
// TODO: Maybe we should introduce a unary node type to avoid optional checks?
|
||||
const main_node = node.data.bin.lhs.?;
|
||||
return expr(gi, scope, main_node);
|
||||
}
|
||||
|
||||
fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void {
|
||||
|
|
@ -633,96 +642,11 @@ fn validateSwitchProngs(gen: *GenIr, stmt_node: *const Ast.Node) InnerError!void
|
|||
}
|
||||
}
|
||||
|
||||
fn switchStmt(
|
||||
gen: *GenIr,
|
||||
parent_scope: *Scope,
|
||||
stmt_node: *const Ast.Node,
|
||||
) InnerError!void {
|
||||
const gpa = gen.astgen.gpa;
|
||||
var child_scope = try gen.astgen.createScope(parent_scope);
|
||||
defer child_scope.deinit();
|
||||
|
||||
const label_index = try gen.makeLabel();
|
||||
gen.setExit(label_index);
|
||||
|
||||
const eval_expr = stmt_node.data.switch_stmt.condition_expr;
|
||||
const case_list = stmt_node.data.switch_stmt.cases;
|
||||
|
||||
// NOTE: We're going to create an array of label indexes here, since we
|
||||
// may create additional labels while traversing nested expressions.
|
||||
var label_list: std.ArrayList(usize) = .empty;
|
||||
defer label_list.deinit(gpa);
|
||||
try label_list.ensureUnusedCapacity(gpa, case_list.len);
|
||||
|
||||
const stack_slot = try gen.makeStackSlot();
|
||||
try expr(gen, child_scope, eval_expr);
|
||||
try gen.emitConstInst(.store, stack_slot);
|
||||
try gen.emitSimpleInst(.pop);
|
||||
|
||||
for (case_list) |case_stmt| {
|
||||
const case_label_index = try gen.makeLabel();
|
||||
label_list.appendAssumeCapacity(case_label_index);
|
||||
|
||||
switch (case_stmt.tag) {
|
||||
.switch_case => {
|
||||
const case_eval_expr = case_stmt.data.bin.lhs orelse unreachable;
|
||||
switch (case_eval_expr.tag) {
|
||||
.number_literal, .true_literal, .false_literal => {},
|
||||
else => return gen.fail(.invalid_switch_case, case_stmt),
|
||||
}
|
||||
|
||||
try gen.emitConstInst(.load, stack_slot);
|
||||
try expr(gen, child_scope, case_eval_expr);
|
||||
try gen.emitSimpleInst(.cmp_eq);
|
||||
|
||||
const fixup_offset = try gen.emitJumpInst(.jmp_t);
|
||||
_ = try gen.makeFixup(.{
|
||||
.mode = .relative,
|
||||
.label_index = case_label_index,
|
||||
.code_offset = fixup_offset,
|
||||
});
|
||||
try gen.emitSimpleInst(.pop);
|
||||
},
|
||||
.else_branch => {
|
||||
const fixup_offset = try gen.emitJumpInst(.jmp);
|
||||
_ = try gen.makeFixup(.{
|
||||
.mode = .relative,
|
||||
.label_index = case_label_index,
|
||||
.code_offset = fixup_offset,
|
||||
});
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
for (case_list, label_list.items) |case_stmt, case_label_index| {
|
||||
gen.setLabel(case_label_index);
|
||||
|
||||
switch (case_stmt.tag) {
|
||||
.switch_case => {
|
||||
try gen.emitSimpleInst(.pop);
|
||||
},
|
||||
.else_branch => {},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
const block_stmt = case_stmt.data.bin.rhs;
|
||||
try blockStmt(gen, child_scope, block_stmt);
|
||||
const fixup_offset = try gen.emitJumpInst(.jmp);
|
||||
_ = try gen.makeFixup(.{
|
||||
.mode = .relative,
|
||||
.label_index = gen.exit_label,
|
||||
.code_offset = fixup_offset,
|
||||
});
|
||||
}
|
||||
|
||||
gen.setLabel(gen.exit_label);
|
||||
}
|
||||
|
||||
fn ifStmt(
|
||||
parent_block: *GenIr,
|
||||
scope: *Scope,
|
||||
stmt_node: *const Ast.Node,
|
||||
) InnerError!Ir.Inst.Index {
|
||||
) InnerError!Ir.Inst.Ref {
|
||||
const astgen = parent_block.astgen;
|
||||
const cond_expr = stmt_node.data.switch_stmt.condition_expr.?;
|
||||
try validateSwitchProngs(parent_block, stmt_node);
|
||||
|
|
@ -736,7 +660,7 @@ fn ifStmt(
|
|||
|
||||
const cond_inst = try expr(&block_scope, scope, cond_expr);
|
||||
const condbr = try block_scope.addCondBr(.condbr);
|
||||
const block = try parent_block.makeBlockInst();
|
||||
const block = try parent_block.makeBlockInst(.block);
|
||||
try block_scope.setBlockBody(block); // unstacks block
|
||||
try parent_block.instructions.append(astgen.gpa, block);
|
||||
|
||||
|
|
@ -757,14 +681,14 @@ fn ifStmt(
|
|||
}
|
||||
|
||||
try setCondBrPayload(condbr, cond_inst, &then_block, &else_block);
|
||||
return @enumFromInt(0);
|
||||
return condbr.toRef();
|
||||
}
|
||||
|
||||
fn ifChain(
|
||||
parent_block: *GenIr,
|
||||
scope: *Scope,
|
||||
branch_list: []const *Ast.Node,
|
||||
) InnerError!Ir.Inst.Index {
|
||||
) InnerError!Ir.Inst.Ref {
|
||||
const gpa = parent_block.astgen.gpa;
|
||||
if (branch_list.len == 0) return @enumFromInt(0);
|
||||
if (branch_list[0].data.bin.lhs == null) {
|
||||
|
|
@ -781,7 +705,7 @@ fn ifChain(
|
|||
const body_node = branch.data.bin.rhs.?;
|
||||
const cond_inst = try expr(&block_scope, scope, cond_expr);
|
||||
const condbr = try block_scope.addCondBr(.condbr);
|
||||
const block_inst = try parent_block.makeBlockInst();
|
||||
const block_inst = try parent_block.makeBlockInst(.block);
|
||||
try block_scope.setBlockBody(block_inst);
|
||||
try parent_block.instructions.append(gpa, block_inst);
|
||||
|
||||
|
|
@ -803,7 +727,7 @@ fn multiIfStmt(
|
|||
parent_block: *GenIr,
|
||||
scope: *Scope,
|
||||
stmt_node: *const Ast.Node,
|
||||
) InnerError!Ir.Inst.Index {
|
||||
) InnerError!Ir.Inst.Ref {
|
||||
try validateSwitchProngs(parent_block, stmt_node);
|
||||
|
||||
const branch_list = stmt_node.data.switch_stmt.cases;
|
||||
|
|
@ -817,19 +741,9 @@ fn multiIfStmt(
|
|||
return @enumFromInt(0);
|
||||
}
|
||||
|
||||
fn inlineLogicExpr(
|
||||
gen: *GenIr,
|
||||
scope: *Scope,
|
||||
expr_node: *const Ast.Node,
|
||||
) InnerError!Ir.Inst.Index {
|
||||
const main_node = expr_node.data.bin.lhs;
|
||||
assert(main_node != null);
|
||||
return expr(gen, scope, main_node);
|
||||
}
|
||||
|
||||
fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
// FIXME: This is a placeholder until we figure out what this function should be returning.
|
||||
var last_inst: Ir.Inst.Index = undefined;
|
||||
var last_inst: Ir.Inst.Ref = undefined;
|
||||
// TODO: Make sure that this is not nullable.
|
||||
const node_list = expr_node.data.list.items.?;
|
||||
for (node_list) |child_node| {
|
||||
|
|
@ -838,29 +752,30 @@ fn contentExpr(block: *GenIr, scope: *Scope, expr_node: *const Ast.Node) InnerEr
|
|||
.inline_logic_expr => try inlineLogicExpr(block, scope, child_node),
|
||||
.if_stmt => try ifStmt(block, scope, child_node),
|
||||
.multi_if_stmt => try multiIfStmt(block, scope, child_node),
|
||||
//.switch_stmt => try switchStmt(gen, scope, child_node),
|
||||
//.switch_stmt => try switchStmt(block, scope, child_node),
|
||||
else => unreachable,
|
||||
};
|
||||
last_inst = try block.add(.{ .tag = .content_push, .data = undefined });
|
||||
last_inst = try block.addUnaryNode(.content_push, last_inst);
|
||||
}
|
||||
return last_inst;
|
||||
}
|
||||
|
||||
fn contentStmt(gen: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!Ir.Inst.Index {
|
||||
const expr_node = stmt_node.data.bin.lhs orelse unreachable;
|
||||
fn contentStmt(gen: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!Ir.Inst.Ref {
|
||||
const expr_node = node.data.bin.lhs.?;
|
||||
const expr_ref = try contentExpr(gen, scope, expr_node);
|
||||
return gen.addUnaryNode(.content_flush, expr_ref);
|
||||
}
|
||||
|
||||
fn assignStmt(gi: *GenIr, scope: *Scope, stmt_node: *const Ast.Node) InnerError!void {
|
||||
fn assignStmt(gi: *GenIr, scope: *Scope, node: *const Ast.Node) InnerError!void {
|
||||
const astgen = gi.astgen;
|
||||
const identifier_node = stmt_node.data.bin.lhs orelse unreachable;
|
||||
const expr_node = stmt_node.data.bin.rhs orelse unreachable;
|
||||
const identifier_node = node.data.bin.lhs.?;
|
||||
const expr_node = node.data.bin.rhs.?;
|
||||
const name_ref = try astgen.stringFromNode(identifier_node);
|
||||
|
||||
// TODO: Support globals as well
|
||||
if (scope.lookup(name_ref)) |decl| {
|
||||
const expr_result = try expr(gi, scope, expr_node);
|
||||
_ = try gi.addBinaryNode(.store_local, decl.inst_index, expr_result);
|
||||
_ = try gi.addBinaryNode(.store, decl.inst_index.toRef(), expr_result);
|
||||
return;
|
||||
}
|
||||
return gi.fail(.unknown_identifier, identifier_node);
|
||||
|
|
@ -949,13 +864,13 @@ fn tempDecl(gi: *GenIr, scope: *Scope, decl_node: *const Ast.Node) !void {
|
|||
return gi.fail(.redefined_identifier, decl_node);
|
||||
}
|
||||
|
||||
const alloc_inst = try gi.add(.{ .tag = .alloc_local, .data = undefined });
|
||||
const alloc_inst = try gi.add(.{ .tag = .alloc, .data = undefined });
|
||||
const expr_result = try expr(gi, scope, expr_node);
|
||||
_ = try gi.addBinaryNode(.store_local, alloc_inst, expr_result);
|
||||
_ = try gi.addBinaryNode(.store, alloc_inst, expr_result);
|
||||
|
||||
return scope.insert(name_ref, .{
|
||||
.decl_node = decl_node,
|
||||
.inst_index = alloc_inst,
|
||||
.inst_index = alloc_inst.toIndex().?,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
105
src/Ir.zig
105
src/Ir.zig
|
|
@ -1,5 +1,6 @@
|
|||
const std = @import("std");
|
||||
const Ast = @import("Ast.zig");
|
||||
const assert = std.debug.assert;
|
||||
const Ir = @This();
|
||||
|
||||
string_bytes: []u8,
|
||||
|
|
@ -13,8 +14,35 @@ pub const Inst = struct {
|
|||
data: Data,
|
||||
|
||||
pub const Index = enum(u32) {
|
||||
file_inst = 0,
|
||||
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 {
|
||||
|
|
@ -23,12 +51,9 @@ pub const Inst = struct {
|
|||
decl_knot,
|
||||
decl_var,
|
||||
decl_ref,
|
||||
block,
|
||||
condbr,
|
||||
@"break",
|
||||
alloc_local,
|
||||
load_local,
|
||||
store_local,
|
||||
alloc,
|
||||
load,
|
||||
store,
|
||||
add,
|
||||
sub,
|
||||
mul,
|
||||
|
|
@ -42,10 +67,11 @@ pub const Inst = struct {
|
|||
cmp_gte,
|
||||
cmp_lt,
|
||||
cmp_lte,
|
||||
true_literal,
|
||||
false_literal,
|
||||
integer,
|
||||
string,
|
||||
block,
|
||||
condbr,
|
||||
@"break",
|
||||
content_push,
|
||||
content_flush,
|
||||
};
|
||||
|
|
@ -55,11 +81,11 @@ pub const Inst = struct {
|
|||
payload_index: u32,
|
||||
},
|
||||
un: struct {
|
||||
lhs: Index,
|
||||
lhs: Ref,
|
||||
},
|
||||
bin: struct {
|
||||
lhs: Index,
|
||||
rhs: Index,
|
||||
lhs: Ref,
|
||||
rhs: Ref,
|
||||
},
|
||||
integer: struct {
|
||||
value: u64,
|
||||
|
|
@ -96,7 +122,7 @@ pub const Inst = struct {
|
|||
};
|
||||
|
||||
pub const CondBr = struct {
|
||||
condition: Index,
|
||||
condition: Ref,
|
||||
then_body_len: u32,
|
||||
else_body_len: u32,
|
||||
};
|
||||
|
|
@ -165,6 +191,22 @@ const Render = struct {
|
|||
}
|
||||
};
|
||||
|
||||
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)});
|
||||
|
|
@ -172,12 +214,22 @@ const Render = struct {
|
|||
|
||||
fn renderUnary(r: *Render, inst: Inst) Error!void {
|
||||
const io_w = r.writer;
|
||||
return io_w.print("{s}(%{d})", .{ @tagName(inst.tag), inst.data.un.lhs });
|
||||
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;
|
||||
return io_w.print("{s}(%{d}, %{d})", .{ @tagName(inst.tag), inst.data.bin.lhs, inst.data.bin.rhs });
|
||||
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 {
|
||||
|
|
@ -216,7 +268,9 @@ const Render = struct {
|
|||
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}(%{d}, ", .{ @tagName(inst.tag), extra.data.condition });
|
||||
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);
|
||||
|
|
@ -286,9 +340,9 @@ const Render = struct {
|
|||
},
|
||||
.condbr => try r.renderCondbr(ir, inst),
|
||||
.@"break" => try r.renderBreak(ir, inst),
|
||||
.alloc_local => try r.renderSimple(inst),
|
||||
.load_local => try r.renderUnary(inst),
|
||||
.store_local => try r.renderBinary(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),
|
||||
|
|
@ -303,12 +357,6 @@ const Render = struct {
|
|||
.cmp_gte => try r.renderBinary(inst),
|
||||
.cmp_lt => try r.renderBinary(inst),
|
||||
.cmp_lte => try r.renderBinary(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 });
|
||||
|
|
@ -317,8 +365,8 @@ const Render = struct {
|
|||
const str_bytes = inst.data.string.get(ir);
|
||||
try io_w.print("{s}(\"{s}\")", .{ @tagName(inst.tag), str_bytes });
|
||||
},
|
||||
.content_push => try r.renderSimple(inst),
|
||||
.content_flush => try r.renderUnary(inst),
|
||||
.content_push => try r.renderUnary(inst),
|
||||
.content_flush => try r.renderSimple(inst),
|
||||
}
|
||||
try io_w.writeAll("\n");
|
||||
}
|
||||
|
|
@ -329,7 +377,7 @@ pub fn render(ir: Ir, gpa: std.mem.Allocator, writer: *std.Io.Writer) !void {
|
|||
var r = Render{ .gpa = gpa, .writer = writer, .prefix = .{} };
|
||||
defer r.prefix.deinit(gpa);
|
||||
|
||||
try r.renderInst(ir, .file_inst);
|
||||
try r.renderInst(ir, @enumFromInt(0));
|
||||
return writer.flush();
|
||||
}
|
||||
|
||||
|
|
@ -367,6 +415,7 @@ pub fn extraData(ir: Ir, comptime T: type, index: usize) ExtraData(T) {
|
|||
@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"),
|
||||
};
|
||||
|
|
|
|||
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),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ is_exited: bool = false,
|
|||
can_advance: bool = false,
|
||||
choice_index: usize = 0,
|
||||
current_choices: std.ArrayListUnmanaged(Choice) = .empty,
|
||||
constants_pool: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
globals: std.StringHashMapUnmanaged(?*Object) = .empty,
|
||||
paths: std.ArrayListUnmanaged(*Object) = .empty,
|
||||
stack: std.ArrayListUnmanaged(?*Object) = .empty,
|
||||
|
|
@ -101,6 +102,7 @@ pub fn deinit(story: *Story) void {
|
|||
}
|
||||
|
||||
story.current_choices.deinit(gpa);
|
||||
story.constants_pool.deinit(gpa);
|
||||
story.globals.deinit(gpa);
|
||||
story.paths.deinit(gpa);
|
||||
story.stack.deinit(gpa);
|
||||
|
|
@ -109,6 +111,24 @@ pub fn deinit(story: *Story) void {
|
|||
|
||||
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
||||
const story_dumper: Dumper = .{ .story = story, .writer = writer };
|
||||
|
||||
try writer.writeAll("=== Constants ===\n");
|
||||
for (story.constants_pool.items) |global_constant| {
|
||||
try story_dumper.dumpObject(global_constant);
|
||||
try writer.writeAll("\n");
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Globals ===\n");
|
||||
|
||||
var global_iter = story.globals.iterator();
|
||||
while (global_iter.next()) |entry| {
|
||||
try writer.print("{s} => ...\n", .{entry.key_ptr.*});
|
||||
}
|
||||
|
||||
try writer.writeAll("\n");
|
||||
try writer.writeAll("=== Knots ===\n");
|
||||
|
||||
for (story.paths.items) |path_object| {
|
||||
try story_dumper.dump(@ptrCast(path_object));
|
||||
}
|
||||
|
|
@ -152,8 +172,7 @@ fn currentFrame(vm: *Story) *CallFrame {
|
|||
|
||||
fn peekStack(vm: *Story, offset: usize) ?*Object {
|
||||
const stack_top = vm.stack.items.len;
|
||||
assert(stack_top > offset);
|
||||
assert(stack_top != 0);
|
||||
if (stack_top <= offset) return null;
|
||||
|
||||
return vm.stack.items[stack_top - offset - 1];
|
||||
}
|
||||
|
|
@ -171,10 +190,11 @@ fn popStack(vm: *Story) ?*Object {
|
|||
return vm.stack.pop() orelse unreachable;
|
||||
}
|
||||
|
||||
fn getConstant(_: *Story, frame: *CallFrame, offset: u8) !*Object {
|
||||
const constant_pool = frame.callee.const_pool;
|
||||
if (offset >= constant_pool.len) return error.InvalidArgument;
|
||||
return constant_pool[offset];
|
||||
fn getConstant(story: *Story, frame: *CallFrame, offset: u8) !*Object {
|
||||
if (offset >= frame.callee.const_pool.len) return error.InvalidArgument;
|
||||
|
||||
const constant_index = frame.callee.const_pool[offset];
|
||||
return story.constants_pool.items[constant_index];
|
||||
}
|
||||
|
||||
fn getLocal(vm: *Story, frame: *CallFrame, offset: u8) ?*Object {
|
||||
|
|
@ -380,14 +400,17 @@ fn execute(vm: *Story) !std.ArrayListUnmanaged(u8) {
|
|||
frame.ip += 2;
|
||||
},
|
||||
.stream_push => {
|
||||
const arg_object = vm.popStack();
|
||||
if (arg_object) |object| {
|
||||
// FIXME: This should be more strict.
|
||||
// Its not because theres a bug in when these instructions are
|
||||
// emitted.
|
||||
if (vm.peekStack(0)) |object| {
|
||||
const string_object = try Object.String.fromObject(vm, object);
|
||||
const string_bytes = string_object.bytes[0..string_object.length];
|
||||
try stream_writer.writer.writeAll(string_bytes);
|
||||
} else {
|
||||
return error.InvalidArgument;
|
||||
}
|
||||
_ = vm.popStack();
|
||||
} //else {
|
||||
//return error.InvalidArgument;
|
||||
//}
|
||||
|
||||
frame.ip += 1;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@ fn dumpSimpleInst(d: Dumper, offset: usize, op: Opcode) !usize {
|
|||
fn dumpByteInst(d: Dumper, context: *const Object.ContentPath, offset: usize, op: Opcode) !usize {
|
||||
const arg = context.bytes[offset + 1];
|
||||
if (op == .load_const) {
|
||||
const constant_index = context.const_pool[arg];
|
||||
const global_constant = d.story.constants_pool.items[constant_index];
|
||||
try d.writer.print("{s} {d} (", .{ @tagName(op), arg });
|
||||
try d.dumpObject(context.const_pool[arg]);
|
||||
try d.dumpObject(global_constant);
|
||||
try d.writer.print(")\n", .{});
|
||||
} else {
|
||||
try d.writer.print("{s} {x}\n", .{ @tagName(op), arg });
|
||||
|
|
@ -31,7 +33,9 @@ fn dumpGlobalInst(
|
|||
op: Opcode,
|
||||
) !usize {
|
||||
const arg = context.bytes[offset + 1];
|
||||
const global_name: *Object.String = @ptrCast(context.const_pool[arg]);
|
||||
const constant_index = context.const_pool[arg];
|
||||
const global_constant = d.story.constants_pool.items[constant_index];
|
||||
const global_name: *Object.String = @ptrCast(global_constant);
|
||||
const name_bytes = global_name.bytes[0..global_name.length];
|
||||
|
||||
try d.writer.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes });
|
||||
|
|
|
|||
|
|
@ -264,14 +264,14 @@ pub const Object = struct {
|
|||
// TODO: Rename this to stack size.
|
||||
locals_count: usize,
|
||||
// TODO: Rename this to constant_pool.
|
||||
const_pool: []*Object,
|
||||
const_pool: []u32,
|
||||
bytes: []const u8,
|
||||
|
||||
pub const CreateOptions = struct {
|
||||
name: *Object.String,
|
||||
arity: usize,
|
||||
locals_count: usize,
|
||||
const_pool: []*Object,
|
||||
const_pool: []u32,
|
||||
bytes: []const u8,
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue