383 lines
13 KiB
Zig
383 lines
13 KiB
Zig
const std = @import("std");
|
|
const Story = @import("../Story.zig");
|
|
const Value = Story.Value;
|
|
const Object = Story.Object;
|
|
const Opcode = Story.Opcode;
|
|
const assert = std.debug.assert;
|
|
const Dumper = @This();
|
|
|
|
story: *const Story,
|
|
indent_level: usize = 0,
|
|
|
|
fn dumpSimpleInst(_: *Dumper, w: *std.Io.Writer, offset: usize, op: Opcode) !usize {
|
|
try w.print("{s}\n", .{@tagName(op)});
|
|
return offset + 1;
|
|
}
|
|
|
|
fn dumpByteInst(
|
|
self: *Dumper,
|
|
w: *std.Io.Writer,
|
|
knot: *const Object.Knot,
|
|
offset: usize,
|
|
op: Opcode,
|
|
) !usize {
|
|
const code = knot.code;
|
|
const constants_pool = &self.story.constants_pool;
|
|
|
|
assert(code.bytecode.len > offset + 1);
|
|
const arg = code.bytecode[offset + 1];
|
|
if (op == .load_const) {
|
|
try w.print("{s} {d}", .{ @tagName(op), arg });
|
|
try w.writeAll(" (");
|
|
if (code.constants.len > arg) {
|
|
const constant_index = code.constants[arg];
|
|
if (constants_pool.items.len > constant_index) {
|
|
const global_constant = &constants_pool.items[constant_index];
|
|
try self.dumpValue(w, global_constant);
|
|
} else {
|
|
try w.writeAll("invalid!");
|
|
}
|
|
} else {
|
|
try w.writeAll("invalid!");
|
|
}
|
|
try w.writeAll(")\n");
|
|
} else {
|
|
try w.print("{s} {x}\n", .{ @tagName(op), arg });
|
|
}
|
|
return offset + 2;
|
|
}
|
|
|
|
fn dumpGlobalInst(
|
|
self: *Dumper,
|
|
w: *std.Io.Writer,
|
|
knot: *const Object.Knot,
|
|
offset: usize,
|
|
op: Opcode,
|
|
) !usize {
|
|
const code = knot.code;
|
|
const constants_pool = &self.story.constants_pool;
|
|
|
|
assert(code.bytecode.len > offset + 1);
|
|
const arg = code.bytecode[offset + 1];
|
|
assert(code.constants.len > arg);
|
|
const constant_index = code.constants[arg];
|
|
const global_constant = constants_pool.items[constant_index];
|
|
switch (global_constant) {
|
|
.object => |object| switch (object.tag) {
|
|
.string => {
|
|
const global_name: *Object.String = @ptrCast(object);
|
|
const name_bytes = global_name.bytes[0..global_name.length];
|
|
try w.print("{s} {x} '{s}'\n", .{ @tagName(op), arg, name_bytes });
|
|
},
|
|
else => unreachable,
|
|
},
|
|
else => unreachable,
|
|
}
|
|
return offset + 2;
|
|
}
|
|
|
|
const Jump = enum {
|
|
relative,
|
|
absolute,
|
|
};
|
|
|
|
fn dumpJumpInst(
|
|
_: *Dumper,
|
|
w: *std.Io.Writer,
|
|
knot: *const Object.Knot,
|
|
offset: usize,
|
|
op: Opcode,
|
|
mode: Jump,
|
|
) !usize {
|
|
const code = knot.code;
|
|
assert(code.bytecode.len > offset + 2);
|
|
var jump: u16 = @as(u16, code.bytecode[offset + 1]) << 8;
|
|
jump |= code.bytecode[offset + 2];
|
|
|
|
switch (mode) {
|
|
.relative => try w.print("{s} 0x{x:0>4} (0x{x:0>4} -> 0x{x:0>4})\n", .{
|
|
@tagName(op),
|
|
jump,
|
|
offset,
|
|
offset + 3 + jump,
|
|
}),
|
|
.absolute => try w.print("{s} 0x{x:0>4}\n", .{
|
|
@tagName(op),
|
|
jump,
|
|
}),
|
|
}
|
|
return offset + 3;
|
|
}
|
|
|
|
pub fn dumpInst(
|
|
self: *Dumper,
|
|
w: *std.Io.Writer,
|
|
knot: *const Object.Knot,
|
|
offset: usize,
|
|
should_prefix: bool,
|
|
) !usize {
|
|
const name_object = knot.name;
|
|
const name_bytes = name_object.bytes[0..name_object.length];
|
|
const op: Opcode = @enumFromInt(knot.code.bytecode[offset]);
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
|
|
if (should_prefix) {
|
|
try w.print("<{s}>:0x{x:0>4} | ", .{ name_bytes, offset });
|
|
} else {
|
|
try w.print("0x{x:0>4} | ", .{offset});
|
|
}
|
|
switch (op) {
|
|
.exit => return self.dumpSimpleInst(w, offset, op),
|
|
.done => return self.dumpSimpleInst(w, offset, op),
|
|
.ret => return self.dumpSimpleInst(w, offset, op),
|
|
.pop => return self.dumpSimpleInst(w, offset, op),
|
|
.true => return self.dumpSimpleInst(w, offset, op),
|
|
.false => return self.dumpSimpleInst(w, offset, op),
|
|
.add => return self.dumpSimpleInst(w, offset, op),
|
|
.sub => return self.dumpSimpleInst(w, offset, op),
|
|
.mul => return self.dumpSimpleInst(w, offset, op),
|
|
.div => return self.dumpSimpleInst(w, offset, op),
|
|
.mod => return self.dumpSimpleInst(w, offset, op),
|
|
.neg => return self.dumpSimpleInst(w, offset, op),
|
|
.not => return self.dumpSimpleInst(w, offset, op),
|
|
.cmp_eq => return self.dumpSimpleInst(w, offset, op),
|
|
.cmp_lt => return self.dumpSimpleInst(w, offset, op),
|
|
.cmp_lte => return self.dumpSimpleInst(w, offset, op),
|
|
.cmp_gt => return self.dumpSimpleInst(w, offset, op),
|
|
.cmp_gte => return self.dumpSimpleInst(w, offset, op),
|
|
.load_const => return self.dumpByteInst(w, knot, offset, op),
|
|
.load => return self.dumpByteInst(w, knot, offset, op),
|
|
.store => return self.dumpByteInst(w, knot, offset, op),
|
|
.load_global => return self.dumpGlobalInst(w, knot, offset, op),
|
|
.store_global => return self.dumpGlobalInst(w, knot, offset, op),
|
|
.call => return self.dumpByteInst(w, knot, offset, op),
|
|
.divert => return self.dumpByteInst(w, knot, offset, op),
|
|
.jmp => return self.dumpJumpInst(w, knot, offset, op, .relative),
|
|
.jmp_t => return self.dumpJumpInst(w, knot, offset, op, .relative),
|
|
.jmp_f => return self.dumpJumpInst(w, knot, offset, op, .relative),
|
|
.load_attr => return self.dumpGlobalInst(w, knot, offset, op),
|
|
.store_attr => return self.dumpGlobalInst(w, knot, offset, op),
|
|
.stream_push => return self.dumpSimpleInst(w, offset, op),
|
|
.stream_flush => return self.dumpSimpleInst(w, offset, op),
|
|
.stream_line => return self.dumpSimpleInst(w, offset, op),
|
|
.stream_glue => return self.dumpSimpleInst(w, offset, op),
|
|
.br_push => return self.dumpJumpInst(w, knot, offset, op, .absolute),
|
|
.br_table => return self.dumpSimpleInst(w, offset, op),
|
|
.br_dispatch => return self.dumpSimpleInst(w, offset, op),
|
|
.br_select_index => return self.dumpSimpleInst(w, offset, op),
|
|
else => |code| {
|
|
try w.print("Unknown opcode 0x{x:0>4}\n", .{@intFromEnum(code)});
|
|
return offset + 1;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn dumpKnotConstants(w: *std.Io.Writer, knot: *const Object.Knot) !void {
|
|
try w.writeAll("[");
|
|
for (knot.code.constants) |index| {
|
|
try w.print("{d},", .{index});
|
|
}
|
|
try w.writeAll("]");
|
|
try w.flush();
|
|
}
|
|
|
|
fn dumpKnotChildren(w: *std.Io.Writer, knot: *const Object.Knot) !void {
|
|
try w.writeAll("[");
|
|
var stitch_iter = knot.members.iterator();
|
|
while (stitch_iter.next()) |entry| {
|
|
try w.print("Stitch: \"{s}\",", .{entry.key_ptr.*});
|
|
}
|
|
try w.writeAll("]");
|
|
try w.flush();
|
|
}
|
|
|
|
fn dumpKnotBytecode(self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot) !void {
|
|
var index: usize = 0;
|
|
while (index < knot.code.bytecode.len) {
|
|
index = try self.dumpInst(w, knot, index, false);
|
|
try w.flush();
|
|
}
|
|
return w.flush();
|
|
}
|
|
|
|
pub fn dumpKnot(self: *Dumper, w: *std.Io.Writer, knot: *const Object.Knot) !void {
|
|
const name_object = knot.name;
|
|
const name_bytes = name_object.bytes[0..name_object.length];
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Name: \"{s}\"\n", .{name_bytes});
|
|
|
|
self.indent_level += 1;
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Arguments: {d}\n", .{knot.code.args_count});
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Locals: {d}\n", .{knot.code.locals_count});
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Stack Size: {d}\n", .{knot.code.stack_size});
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.writeAll("Constants: ");
|
|
try dumpKnotConstants(w, knot);
|
|
try w.writeAll("\n");
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.writeAll("Children: ");
|
|
try dumpKnotChildren(w, knot);
|
|
try w.writeAll("\n");
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.writeAll("Bytecode: \n");
|
|
self.indent_level += 2;
|
|
try self.dumpKnotBytecode(w, knot);
|
|
try w.writeAll("\n");
|
|
self.indent_level -= 2;
|
|
|
|
var stitch_iter = knot.members.iterator();
|
|
var count: usize = 0;
|
|
while (stitch_iter.next()) |entry| : (count += 1) {
|
|
const key = entry.key_ptr.*;
|
|
const stitch: *Object.Knot = @ptrCast(entry.value_ptr.*);
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Stitch #{d}: \"{s}\"\n", .{ count, key });
|
|
|
|
self.indent_level += 2;
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Arguments: {d}\n", .{stitch.code.args_count});
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Locals: {d}\n", .{stitch.code.locals_count});
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.print("Stack Size: {d}\n", .{stitch.code.stack_size});
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.writeAll("Constants: ");
|
|
try dumpKnotConstants(w, stitch);
|
|
try w.writeAll("\n");
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.writeAll("Children: ");
|
|
try dumpKnotChildren(w, stitch);
|
|
try w.writeAll("\n");
|
|
|
|
try w.splatByteAll(' ', self.indent_level);
|
|
try w.writeAll("Bytecode: \n");
|
|
self.indent_level += 2;
|
|
try self.dumpKnotBytecode(w, stitch);
|
|
try w.writeAll("\n");
|
|
self.indent_level -= 4;
|
|
|
|
try w.flush();
|
|
}
|
|
self.indent_level = 2;
|
|
return w.flush();
|
|
}
|
|
|
|
pub fn dumpValue(_: Dumper, w: *std.Io.Writer, value: *const Value) !void {
|
|
switch (value.*) {
|
|
.bool => |_| try w.print(
|
|
"<{s} value={f}>",
|
|
.{ value.tagBytes(), value },
|
|
),
|
|
.int => |_| try w.print(
|
|
"<{s} value={f}, address={*}>",
|
|
.{ value.tagBytes(), value, value },
|
|
),
|
|
.float => |_| try w.print(
|
|
"<{s} value={f}, address={*}>",
|
|
.{ value.tagBytes(), value, value },
|
|
),
|
|
.object => |object| switch (object.tag) {
|
|
.string => {
|
|
const typed_object: *const Object.String = @ptrCast(object);
|
|
const string_bytes = typed_object.bytes[0..typed_object.length];
|
|
try w.print("<{s} value=\"{s}\", address={*}>", .{
|
|
object.tag.tagBytes(),
|
|
string_bytes,
|
|
object,
|
|
});
|
|
},
|
|
else => try w.print("<{s} address={*}>", .{ object.tag.tagBytes(), object }),
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn dump(story: *Story, writer: *std.Io.Writer) !void {
|
|
var story_dumper: Dumper = .{ .story = story };
|
|
|
|
try writer.writeAll("=== Constants ===\n");
|
|
|
|
var i: usize = 0;
|
|
while (i < story.constants_pool.items.len) : (i += 1) {
|
|
const global_constant = &story.constants_pool.items[i];
|
|
try story_dumper.dumpValue(writer, 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");
|
|
try writer.flush();
|
|
|
|
var knots_iter = story.globals.iterator();
|
|
while (knots_iter.next()) |entry| {
|
|
if (entry.value_ptr.*) |global| {
|
|
switch (global) {
|
|
.object => |object| switch (object.tag) {
|
|
.knot => {
|
|
try writer.writeAll("*");
|
|
story_dumper.indent_level += 2;
|
|
try story_dumper.dumpKnot(writer, @ptrCast(object));
|
|
story_dumper.indent_level -= 2;
|
|
},
|
|
else => {},
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn trace(story: *Story, writer: *std.Io.Writer, frame: *Story.CallFrame) !void {
|
|
var dumper: Dumper = .{ .story = story };
|
|
const stack = &story.stack;
|
|
const stack_top = story.stack.items.len;
|
|
|
|
try writer.print("\tStack => stack_pointer={d}, objects=[", .{frame.sp});
|
|
|
|
if (stack_top > 0) {
|
|
// FIXME: There has to be a better way to do this.
|
|
if (stack_top > 1) {
|
|
var i: usize = frame.sp;
|
|
while (i < stack.items.len - 1) : (i += 1) {
|
|
if (stack.items[i]) |*value| {
|
|
try dumper.dumpValue(writer, value);
|
|
} else {
|
|
try writer.writeAll("null");
|
|
}
|
|
try writer.writeAll(", ");
|
|
}
|
|
}
|
|
if (stack.items[stack.items.len - 1]) |*object| {
|
|
try dumper.dumpValue(writer, object);
|
|
} else {
|
|
try writer.writeAll("null");
|
|
}
|
|
}
|
|
|
|
try writer.writeAll("]\n");
|
|
_ = try dumper.dumpInst(writer, frame.callee, frame.ip, true);
|
|
return writer.flush();
|
|
}
|