From eac8ad4f421afb98cd3876da816180009acc3034 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sun, 30 Jun 2024 09:24:22 -0400 Subject: [PATCH] start on the debugger --- build.zig | 2 + build.zig.zon | 4 ++ graph/plot.py | 2 +- src/compiler/CodeObject.zig | 2 +- src/compiler/Instruction.zig | 2 + src/crash_report.zig | 18 +++++- src/frontend/Python.zig | 9 ++- src/main.zig | 12 +++- src/modules/builtins.zig | 8 ++- src/print_co.zig | 23 ------- src/vm/Object.zig | 42 +++++++++++-- src/vm/Vm.zig | 26 +++++--- src/vm/debug.zig | 115 +++++++++++++++++++++++++++++++++++ 13 files changed, 217 insertions(+), 48 deletions(-) delete mode 100644 src/print_co.zig create mode 100644 src/vm/debug.zig diff --git a/build.zig b/build.zig index bc68fdb..cd69176 100644 --- a/build.zig +++ b/build.zig @@ -61,10 +61,12 @@ pub fn build(b: *std.Build) !void { const tracer_dep = b.dependency("tracer", .{ .optimize = optimize, .target = target }); const libgc_dep = b.dependency("libgc", .{ .optimize = optimize, .target = target }); const cpython_dep = b.dependency("cpython", .{ .optimize = optimize, .target = target }); + const libvaxis = b.dependency("libvaxis", .{ .optimize = optimize, .target = target }); exe.root_module.addImport("tracer", tracer_dep.module("tracer")); exe.root_module.addImport("gc", libgc_dep.module("gc")); exe.root_module.addImport("cpython", cpython_dep.module("cpython")); + exe.root_module.addImport("vaxis", libvaxis.module("vaxis")); exe_options.addOption([]const u8, "lib_path", "../python/Lib"); diff --git a/build.zig.zon b/build.zig.zon index 47aef96..3edd201 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -20,5 +20,9 @@ .url = "https://github.com/Rexicon226/zig-libgc/archive/1ea12e140ec1471af5d15dae71b225764c9747ba.tar.gz", .hash = "12209ad7282d9422973afe3e468e2f867bb102d04fa446a1d1c94f900bda94b7c68e", }, + .libvaxis = .{ + .url = "https://github.com/rockorager/libvaxis/archive/c213919849114ce03def64806af546b9fc391859.tar.gz", + .hash = "12206f2265ca2262f919ec125ff7fe15fa6f3be622806e211d7a9bd3055ba51a0908", + }, }, } diff --git a/graph/plot.py b/graph/plot.py index f2ef169..215e413 100644 --- a/graph/plot.py +++ b/graph/plot.py @@ -82,7 +82,7 @@ def read_graph_binary(filename): node_colors = ['green' if node == first_node else 'red' if node in nodes_with_no_successors else 'skyblue' for node in G_filtered.nodes] -pos = nx.nx_agraph.graphviz_layout(G_filtered, prog='neato') +pos = nx.nx_agraph.graphviz_layout(G_filtered, prog='dot') plt.figure(figsize=(15, 10)) nx.draw(G_filtered, pos, with_labels=True, labels=nx.get_node_attributes(G_filtered, 'label'), node_color=node_colors, node_size=3000, font_size=10, font_color='black', font_weight='bold', edge_color='gray') diff --git a/src/compiler/CodeObject.zig b/src/compiler/CodeObject.zig index 54ff83f..2aa7496 100644 --- a/src/compiler/CodeObject.zig +++ b/src/compiler/CodeObject.zig @@ -237,7 +237,7 @@ pub fn hash( hasher.update(std.mem.asBytes(&co.stacksize)); hasher.update(std.mem.asBytes(&co.consts)); hasher.update(std.mem.asBytes(&co.names)); - hasher.update(std.mem.asBytes(&co.varnames)); + hasher.update(std.mem.sliceAsBytes(co.varnames)); hasher.update(std.mem.asBytes(co.instructions.?)); // CodeObject should be processed before hashing // we don't hash the index on purpose as it has nothing to do with the unique contents of the object diff --git a/src/compiler/Instruction.zig b/src/compiler/Instruction.zig index e0cfde2..3d90269 100644 --- a/src/compiler/Instruction.zig +++ b/src/compiler/Instruction.zig @@ -61,8 +61,10 @@ fn format2( .LOAD_NAME, .STORE_NAME, .IMPORT_NAME, + .LOAD_METHOD, => try writer.print("{d} ({s})", .{ extra, co.getName(extra) }), .CALL_FUNCTION, + .CALL_METHOD, => try writer.print("{d}", .{extra}), .MAKE_FUNCTION, => { diff --git a/src/crash_report.zig b/src/crash_report.zig index aa0ec2d..48e3ebb 100644 --- a/src/crash_report.zig +++ b/src/crash_report.zig @@ -7,7 +7,6 @@ const std = @import("std"); const CodeObject = @import("compiler/CodeObject.zig"); -const print_co = @import("print_co.zig"); const builtin = @import("builtin"); const build_options = @import("options"); @@ -123,6 +122,21 @@ pub fn prepVmContext(current_co: CodeObject) VmContext { } else .{}; } +pub fn print_co(writer: anytype, data: struct { co: CodeObject, index: ?usize }) !void { + const co = data.co; + const maybe_index = data.index; + + try writer.print("{}", .{co}); + + const instructions = co.instructions.?; // should have already been processed + try writer.writeAll("Instructions:\n"); + for (instructions, 0..) |inst, i| { + if (maybe_index) |index| if (index == i) try writer.print("(#{d}) -> ", .{index}); + if (maybe_index == null or maybe_index.? != i) try writer.writeAll("\t"); + try writer.print("{d}\t{}\n", .{ i * 2, inst.fmt(co) }); + } +} + var buffer: [10 * 1024]u8 = undefined; fn dumpObjectTrace() !void { @@ -134,7 +148,7 @@ fn dumpObjectTrace() !void { var current_co = state.current_co; try current_co.process(allocator); - try print_co.print_co(stderr, .{ + try print_co(stderr, .{ .co = current_co, .index = state.index, }); diff --git a/src/frontend/Python.zig b/src/frontend/Python.zig index 5b98d5a..8d1954f 100644 --- a/src/frontend/Python.zig +++ b/src/frontend/Python.zig @@ -52,8 +52,8 @@ pub fn parse( try writer.writeInt(u32, @intCast(source.len), .little); try writer.writeAll(pyc_bytes); - helpers.DecRef(bytecode); - helpers.Finalize(); + externs.Py_DecRef(bytecode); + externs.Py_Finalize(); return bytes; } @@ -86,7 +86,10 @@ pub fn Initialize( ); _ = externs.PyConfig_Read(&config); - const utf32_path = try utf8ToUtf32Z("../python/Lib", allocator); + const utf32_path = try utf8ToUtf32Z( + "/home/dr/Zython/osmium/zig-out/python/Lib", + allocator, + ); config.module_search_paths_set = 1; _ = externs.PyWideStringList_Append( diff --git a/src/main.zig b/src/main.zig index 43239ea..a4573bd 100644 --- a/src/main.zig +++ b/src/main.zig @@ -8,9 +8,10 @@ const builtin = @import("builtin"); const Graph = @import("graph/Graph.zig"); const Python = @import("frontend/Python.zig"); const Marshal = @import("compiler/Marshal.zig"); -const Vm = @import("vm/Vm.zig"); const crash_report = @import("crash_report.zig"); +const Vm = @import("vm/Vm.zig"); const Object = @import("vm/Object.zig"); +const debug = @import("vm/debug.zig"); const build_options = @import("options"); @@ -70,6 +71,7 @@ pub fn log( const Args = struct { make_graph: bool, + run_debug: bool, run: bool, }; @@ -105,6 +107,7 @@ pub fn main() !u8 { var file_path: ?[:0]const u8 = null; var options: Args = .{ .make_graph = false, + .run_debug = false, .run = true, }; @@ -131,6 +134,9 @@ pub fn main() !u8 { options.make_graph = true; } else if (std.mem.eql(u8, arg, "--no-run")) { options.run = false; + } else if (std.mem.eql(u8, arg, "--debug")) { + options.run_debug = true; + options.run = false; } } @@ -156,6 +162,7 @@ fn usage() void { \\ Debug Options: \\ --no-run Doesn't run the VM, useful for debugging Osmium \\ --graph, Creates a "graph.bin" which contains CFG information + \\ --debug, Runs a interactable debug mode to debug the VM ; const stdout = std.io.getStdOut().writer(); @@ -217,13 +224,14 @@ pub fn run_file( try graph.dump(); } - var vm = try Vm.init(gc_allocator, file_name, seed); + var vm = try Vm.init(gc_allocator, seed); defer vm.deinit(); { var dir_path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; const source_file_path = try std.os.getFdPath(source_file.handle, &dir_path_buf); try vm.initBuiltinMods(source_file_path); } + if (options.run_debug) try debug.run(&vm, gc_allocator); if (options.run) try vm.run(); main_log.debug("Run stats:", .{}); diff --git a/src/modules/builtins.zig b/src/modules/builtins.zig index 19064f3..1e5a728 100644 --- a/src/modules/builtins.zig +++ b/src/modules/builtins.zig @@ -317,7 +317,7 @@ fn __import__(vm: *Vm, args: []const Object, maybe_kw: ?KW_Type) BuiltinError!vo const object = try marshal.parse(); // create a new vm to evaluate the global scope of the module - var mod_vm = try Vm.init(vm.allocator, absolute_path, object); + var mod_vm = try Vm.init(vm.allocator, object); mod_vm.initBuiltinMods(absolute_path) catch |err| { return vm.fail("failed init evaulte module {s} with error {s}", .{ absolute_path, @errorName(err) }); }; @@ -390,5 +390,9 @@ fn __build_class__(vm: *Vm, args: []const Object, maybe_kw: ?KW_Type) BuiltinErr assert(func_obj.tag == .function); assert(name_obj.tag == .string); - std.debug.panic("TODO: implement __build_class__", .{}); + const class = try vm.createObject(.class, .{ + .name = name_obj.get(.string), + .under_func = func_obj, + }); + try vm.stack.append(vm.allocator, class); } diff --git a/src/print_co.zig b/src/print_co.zig deleted file mode 100644 index aa1e939..0000000 --- a/src/print_co.zig +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2024, David Rubin -// -// SPDX-License-Identifier: GPL-3.0-only - -//! Logic to print a CodeObject in a tasteful manner. - -const std = @import("std"); -const CodeObject = @import("compiler/CodeObject.zig"); - -pub fn print_co(writer: anytype, data: struct { co: CodeObject, index: ?usize }) !void { - const co = data.co; - const maybe_index = data.index; - - try writer.print("{}", .{co}); - - const instructions = co.instructions.?; // should have already been processed - try writer.writeAll("Instructions:\n"); - for (instructions, 0..) |inst, i| { - if (maybe_index) |index| if (index == i) try writer.print("(#{d}) -> ", .{index}); - if (maybe_index == null or maybe_index.? != i) try writer.writeAll("\t"); - try writer.print("{d}\t{}\n", .{ i * 2, inst.fmt(co) }); - } -} diff --git a/src/vm/Object.zig b/src/vm/Object.zig index 54d12d4..c789bb8 100644 --- a/src/vm/Object.zig +++ b/src/vm/Object.zig @@ -46,7 +46,6 @@ pub const Tag = enum(usize) { list, set, - /// A builtin Zig defined function. zig_function, codeobject, @@ -54,6 +53,8 @@ pub const Tag = enum(usize) { module, + class, + pub fn PayloadType(comptime t: Tag) type { assert(@intFromEnum(t) >= Tag.first_payload); @@ -72,8 +73,10 @@ pub const Tag = enum(usize) { .function => Payload.PythonFunction, .module => Payload.Module, + .class => Payload.Class, .none => unreachable, + else => @compileError("TODO: PayloadType " ++ @tagName(t)), }; } @@ -306,6 +309,18 @@ pub fn getMemberFunction(object: *const Object, name: []const u8, allocator: All } break :blk try list.toOwnedSlice(); }, + .class => { + var list = std.ArrayList(std.meta.Child(Payload.MemberFuncTy)).init(allocator); + const class = object.get(.class); + _ = &list; + + const under_func = class.under_func; + const under_co = under_func.get(.codeobject); + + std.debug.print("Name: {s}\n", .{under_co.name}); + + unreachable; + }, else => std.debug.panic("{s} has no member functions", .{@tagName(object.tag)}), }; for (member_list) |func| { @@ -356,6 +371,7 @@ pub const Payload = union(enum) { list: List, codeobject: CodeObject, function: PythonFunction, + class: Class, pub const Int = BigIntManaged; pub const String = []u8; @@ -525,6 +541,16 @@ pub const Payload = union(enum) { }; } }; + + pub const Class = struct { + name: []const u8, + under_func: Object, + + pub fn deinit(class: *Class, allocator: std.mem.Allocator) void { + allocator.free(class.name); + class.under_func.deinit(allocator); + } + }; }; pub fn format( @@ -606,10 +632,10 @@ pub fn format( @intFromPtr(&function.co), }); }, - // .codeobject => { - // const co = object.get(.codeobject); - // try writer.print("{}", .{co.*}); - // }, + .codeobject => { + const co = object.get(.codeobject); + try writer.print("co({s})", .{co.name}); + }, else => try writer.print("TODO: Object.format '{s}'", .{@tagName(object.tag)}), } @@ -670,6 +696,12 @@ pub const Context = struct { const payload = obj.get(.zig_function); std.hash.autoHash(&hasher, payload); }, + .class => { + const payload = obj.get(.class); + + hasher.update(payload.name); + hasher.update(std.mem.asBytes(&ctx.hash(payload.under_func))); + }, inline else => |t| { const payload = obj.get(t); std.hash.autoHashStrat(&hasher, payload, .Deep); diff --git a/src/vm/Vm.zig b/src/vm/Vm.zig index fdd7209..38782c5 100644 --- a/src/vm/Vm.zig +++ b/src/vm/Vm.zig @@ -48,12 +48,6 @@ depth: u32 = 0, /// VM State is_running: bool, -/// Name of the python file being executed. -/// -/// TODO: this should be taken from the current codeobject, -/// however that doesn't seem to be working right now. -name: [:0]const u8, - stack: std.ArrayListUnmanaged(Object), scopes: std.ArrayListUnmanaged(std.StringHashMapUnmanaged(Object)) = .{}, @@ -62,7 +56,7 @@ crash_info: crash_report.VmContext, builtin_mods: std.StringHashMapUnmanaged(Object.Payload.Module) = .{}, /// Takes ownership of `co`. -pub fn init(allocator: Allocator, name: [:0]const u8, co: CodeObject) !Vm { +pub fn init(allocator: Allocator, co: CodeObject) !Vm { const t = tracer.trace(@src(), "", .{}); defer t.end(); @@ -70,7 +64,6 @@ pub fn init(allocator: Allocator, name: [:0]const u8, co: CodeObject) !Vm { .allocator = allocator, .is_running = false, .co = co, - .name = name, .crash_info = crash_report.prepVmContext(co), .stack = try std.ArrayListUnmanaged(Object).initCapacity(allocator, co.stacksize), }; @@ -142,7 +135,7 @@ pub fn run( log.debug( "{s} - {s}: {s} (stack_size={}, pc={}/{}, depth={}, heap={})", .{ - vm.name, + vm.co.filename, vm.co.name, @tagName(instruction.op), vm.stack.items.len, @@ -314,6 +307,7 @@ fn execStoreName(vm: *Vm, inst: Instruction) !void { const name = vm.co.getName(inst.extra); // NOTE: STORE_NAME does NOT pop the stack, it only stores the TOS. const tos = vm.stack.items[vm.stack.items.len - 1]; + std.debug.print("STORE_NAME: {}\n", .{tos}); try vm.scopes.items[vm.depth].put(vm.allocator, name, tos); } @@ -409,6 +403,20 @@ fn execCallFunction(vm: *Vm, inst: Instruction) !void { } vm.depth += 1; }, + .class => { + const class = func_object.get(.class); + const under_func = class.under_func; + + const func = under_func.get(.function); + + try vm.scopes.append(vm.allocator, .{}); + try vm.co_stack.append(vm.allocator, vm.co); + vm.setNewCo(func.co); + for (args, 0..) |arg, i| { + vm.co.varnames[i] = arg; + } + vm.depth += 1; + }, else => unreachable, } } diff --git a/src/vm/debug.zig b/src/vm/debug.zig new file mode 100644 index 0000000..d19968f --- /dev/null +++ b/src/vm/debug.zig @@ -0,0 +1,115 @@ +//! Provides a debugging interface for the python virtual machine + +const std = @import("std"); +const mem = std.mem; + +const Vm = @import("Vm.zig"); +const vaxis = @import("vaxis"); + +const Cell = vaxis.Cell; +const TextInput = vaxis.widgets.TextInput; +const TextView = vaxis.widgets.TextView; +const Buffer = TextView.Buffer; +const border = vaxis.widgets.border; + +const Event = union(enum) { + key_press: vaxis.Key, + winsize: vaxis.Winsize, + quit, + process_command: []const u8, +}; + +/// Takes over the stdout to provide an interactable interface. +pub fn run( + vm: *const Vm, + allocator: std.mem.Allocator, +) !void { + _ = vm; + + var tty = try vaxis.Tty.init(); + defer tty.deinit(); + + var vx = try vaxis.init(allocator, .{}); + defer vx.deinit(allocator, tty.anyWriter()); + + var loop: vaxis.Loop(Event) = .{ + .tty = &tty, + .vaxis = &vx, + }; + try loop.init(); + + try loop.start(); + defer loop.stop(); + + try vx.enterAltScreen(tty.anyWriter()); + try vx.setTitle(tty.anyWriter(), "Osmium Debug Session"); + + var cli_text = std.ArrayList(u8).init(allocator); + + var cli_term = TextInput.init(allocator, &vx.unicode); + defer cli_term.deinit(); + + while (true) { + const event = loop.nextEvent(); + switch (event) { + .key_press => |key| { + if (key.matches('c', .{ .ctrl = true })) { + loop.postEvent(.quit); + } else if (key.matches(0xd, .{})) { // new line + loop.postEvent(.{ .process_command = cli_term.buf.items }); + } else { + try cli_term.update(.{ .key_press = key }); + } + }, + .winsize => |ws| try vx.resize(allocator, tty.anyWriter(), ws), + .process_command => |command| { + if (command.len > 0) { + try processCommand(&loop, &cli_text, command); + cli_term.clearAndFree(); + } + }, + .quit => break, + } + + const win = vx.window(); + win.clear(); + + const text = win.child(.{ + .x_off = 0, + .y_off = 0, + .width = .{ .limit = win.width / 2 }, + .height = .{ .limit = win.height }, + .border = .{ + .where = .all, + .style = .{ .fg = .default }, + }, + }); + + const cli_border = text.child(.{ + .x_off = 0, + .y_off = text.height - 3, + .width = .{ .limit = text.width }, + .height = .{ .limit = 3 }, + .border = .{ + .where = .all, + .style = .{ .fg = .default }, + }, + }); + cli_term.draw(cli_border); + var seg = [_]vaxis.Segment{.{ + .text = cli_text.items, + .style = .{}, + }}; + _ = try text.print(&seg, .{ .row_offset = 0 }); + + try vx.render(tty.anyWriter()); + } +} + +fn processCommand(loop: *vaxis.Loop(Event), text: *std.ArrayList(u8), command: []const u8) !void { + if (mem.eql(u8, command, "help")) { + try text.appendSlice("help command\n"); + } else if (mem.eql(u8, command, "quit")) { + loop.postEvent(.quit); + } +}