From 6d2cb973d1bf8f0bd217155b5df4c889b9a4cdce Mon Sep 17 00:00:00 2001 From: Jeeves Date: Wed, 5 Mar 2025 02:56:41 -0700 Subject: [PATCH] improve debugging, fix a lot, add basic op test, heap allocation, dear god --- src/main.zig | 15 +-- src/uxn.zig | 240 +++++++++++++++++++++++++++++------------------- src/varvara.zig | 53 +++++++---- 3 files changed, 193 insertions(+), 115 deletions(-) diff --git a/src/main.zig b/src/main.zig index 59a1b95..c05f030 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,6 +3,8 @@ const std = @import("std"); const Uxn = @import("uxn.zig"); const Varvara = @import("varvara.zig"); +const DEBUG = true; + pub fn main() !void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; defer _ = gpa.deinit(); @@ -19,16 +21,17 @@ pub fn main() !void { const rom = try file.readToEndAlloc(allocator, 0xFF00); defer allocator.free(rom); - var varvara = Varvara.init(rom); + Uxn.setDebug(DEBUG); - var running = true; - while (running) { - std.debug.print("pc={X} code={X} op={s}\n", .{ + var varvara = try Varvara.init(allocator, rom); + defer varvara.deinit(); + + while (varvara.uxn.eval()) { + if (DEBUG) std.debug.print("{s}\t\tpc {X}, code {X}\n", .{ + Uxn.fmtInstrs(varvara.uxn.mem.m[varvara.uxn.pc .. varvara.uxn.pc +% 1]), varvara.uxn.pc, varvara.uxn.mem.m[varvara.uxn.pc], - Uxn.fmtInstrs(varvara.uxn.mem.m[varvara.uxn.pc .. varvara.uxn.pc +% 1]), }); - if (varvara.uxn.eval()) running = false; } } else return error.NoRom; } diff --git a/src/uxn.zig b/src/uxn.zig index 1a032e2..0e230c1 100644 --- a/src/uxn.zig +++ b/src/uxn.zig @@ -1,11 +1,17 @@ const std = @import("std"); const Uxn = @This(); +var debug = true; + +pub fn setDebug(v: bool) void { + debug = v; +} + mem: Memory = .{ .m = undefined }, ws: Stack = .{ .s = undefined }, rs: Stack = .{ .s = undefined }, dev: [0x100]u8 = undefined, -pc: u16, +pc: u16 = 0x100, deo8: *const fn (*Uxn, *Stack, u8, u8) void = deo8Stub, deo16: *const fn (*Uxn, *Stack, u8, u16) void = deo16Stub, @@ -17,18 +23,43 @@ fn deo16Stub(_: *Uxn, _: *Stack, _: u8, _: u16) void {} fn dei8Stub(_: *Uxn, _: *Stack, _: u8) void {} fn dei16Stub(_: *Uxn, _: *Stack, _: u8) void {} +pub fn init(allocator: std.mem.Allocator) !Uxn { + const mem = try allocator.alloc(u8, 0x10000); + errdefer allocator.free(mem); + for (mem) |*b| b.* = 0; + return .{ + .mem = .{ .m = mem }, + .ws = .{ .s = [_]u8{0} ** 0x100 }, + .rs = .{ .s = [_]u8{0} ** 0x100 }, + .dev = [_]u8{0} ** 0x100, + }; +} + +pub fn deinit(self: *Uxn, allocator: std.mem.Allocator) void { + allocator.free(self.mem.m); +} + const Memory = struct { - m: [0x10000]u8, + m: []u8, pub fn peek(self: *Memory, comptime T: type, idx: u16) T { - return switch (T) { - u8 => self.m[idx], - u16 => @as(u16, @intCast(self.m[idx])) << 8 | self.m[idx +% 1], + switch (T) { + u8 => { + const val = self.m[idx]; + if (debug) std.debug.print("\x1b[33mmem peek:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val }); + return val; + }, + u16 => { + const val = @as(u16, @intCast(self.m[idx])) << 8 | self.m[idx +% 1]; + if (debug) std.debug.print("\x1b[33mmem peek:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val }); + return val; + }, else => @compileError("expected u8 or u16, got " + @typeName(T)), - }; + } } pub fn poke(self: *Memory, comptime T: type, idx: u16, val: T) void { + if (debug) std.debug.print("\x1b[33mmem poke:\x1b[0m\tidx {d}, type {s}, val {d}\n", .{ idx, @typeName(T), val }); switch (T) { u8 => self.m[idx] = val, u16 => { @@ -41,28 +72,37 @@ const Memory = struct { }; test "memory peek/poke" { - var mem = Memory{ .m = undefined }; + const m = try std.testing.allocator.alloc(u8, 0x10000); + defer std.testing.allocator.free(m); + var mem = Memory{ .m = m }; mem.poke(u16, 0, 0xABCD); mem.poke(u8, 2, 0x69); try std.testing.expectEqual(0xABCD, mem.peek(u16, 0)); try std.testing.expectEqual(0x69, mem.peek(u8, 2)); } -const Stack = struct { +pub const Stack = struct { s: [0x100]u8, sp: u8 = 0, pub fn peek(self: *Stack, comptime T: type) T { - std.debug.print("\x1b[1;32mstack peek:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) }); - return switch (T) { - u8 => self.s[self.sp -% 1], - u16 => @as(u16, @intCast(self.s[self.sp -% 2])) << 8 | self.s[self.sp -% 1], + switch (T) { + u8 => { + const val = self.s[self.sp -% 1]; + if (debug) std.debug.print("\x1b[32mstack peek:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val }); + return val; + }, + u16 => { + const val = @as(u16, @intCast(self.s[self.sp -% 2])) << 8 | self.s[self.sp -% 1]; + if (debug) std.debug.print("\x1b[32mstack peek:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val }); + return val; + }, else => @compileError("expected u8 or u16, got " + @typeName(T)), - }; + } } pub fn poke(self: *Stack, comptime T: type, v: T) void { - std.debug.print("\x1b[1;32mstack poke:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) }); + if (debug) std.debug.print("\x1b[32mstack poke:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), v }); switch (T) { u8 => self.s[self.sp -% 1] = v, u16 => { @@ -75,16 +115,23 @@ const Stack = struct { pub fn pop(self: *Stack, comptime T: type) T { self.sp -%= @intCast(@divExact(@typeInfo(T).int.bits, 8)); - std.debug.print("\x1b[1;32mstack pop:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) }); return switch (T) { - u8 => self.s[self.sp], - u16 => @as(u16, @intCast(self.s[self.sp])) << 8 | self.s[self.sp +% 1], + u8 => { + const val = self.s[self.sp]; + if (debug) std.debug.print("\x1b[32mstack pop:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val }); + return val; + }, + u16 => { + const val = @as(u16, @intCast(self.s[self.sp])) << 8 | self.s[self.sp +% 1]; + if (debug) std.debug.print("\x1b[32mstack pop:\x1b[0m\tsp {d}, type {s}, val {d}\n", .{ self.sp, @typeName(T), val }); + return val; + }, else => @compileError("expected u8 or u16, got " + @typeName(T)), }; } pub fn push(self: *Stack, comptime T: type, v: T) void { - std.debug.print("\x1b[1;32mstack push:\x1b[0m sp {d}, type {s}\n", .{ self.sp, @typeName(T) }); + if (debug) std.debug.print("\x1b[32mstack push:\x1b[0m\tsp {d}, type {s}, value {d}\n", .{ self.sp, @typeName(T), v }); switch (T) { u8 => self.s[self.sp] = v, u16 => { @@ -113,7 +160,7 @@ test "stack poke/peek/push/pop" { pub fn eval(self: *Uxn) bool { switch (self.mem.m[self.pc]) { - 0x00 => return true, // BRK + 0x00 => return false, // BRK 0x01 => inc(&self.ws, u8, false), // INC 0x02 => pop(&self.ws, u8, false), // POP 0x03 => nip(&self.ws, u8, false), // NIP @@ -256,8 +303,7 @@ pub fn eval(self: *Uxn) bool { 0x7F => sft(&self.rs, u16, false), // SFT2r 0x80 => { - self.ws.push(u8, self.mem.m[self.pc +% 1]); - std.debug.print("LIT: {d}\n", .{self.ws.peek(u8)}); + self.ws.push(u8, self.mem.peek(u8, self.pc +% 1)); self.pc +%= 1; }, // LIT 0x81 => inc(&self.ws, u8, true), // INCk @@ -293,8 +339,7 @@ pub fn eval(self: *Uxn) bool { 0x9F => sft(&self.ws, u8, true), // SFTk 0xA0 => { - self.ws.push(u16, self.mem.m[self.pc +% 1]); - std.debug.print("LIT2: {d}\n", .{self.ws.peek(u16)}); + self.ws.push(u16, self.mem.peek(u16, self.pc +% 1)); self.pc +%= 2; }, // LIT2 0xA1 => inc(&self.ws, u16, true), // INC2k @@ -330,8 +375,7 @@ pub fn eval(self: *Uxn) bool { 0xBF => sft(&self.ws, u16, true), // SFT2k 0xC0 => { - self.rs.push(u8, self.mem.m[self.pc +% 1]); - std.debug.print("LITr: {d}\n", .{self.rs.peek(u8)}); + self.rs.push(u8, self.mem.peek(u8, self.pc +% 1)); self.pc +%= 1; }, // LITr 0xC1 => inc(&self.rs, u8, true), // INCkr @@ -367,8 +411,7 @@ pub fn eval(self: *Uxn) bool { 0xDF => sft(&self.rs, u8, true), // SFTkr 0xE0 => { - self.rs.push(u16, self.mem.m[self.pc +% 1]); - std.debug.print("LIT2r: {d}\n", .{self.rs.peek(u16)}); + self.rs.push(u16, self.mem.peek(u16, self.pc +% 1)); self.pc +%= 2; }, // LIT2r 0xE1 => inc(&self.rs, u16, true), // INC2kr @@ -405,7 +448,7 @@ pub fn eval(self: *Uxn) bool { } self.pc +%= 1; - return false; + return true; } fn brk() void {} @@ -565,7 +608,7 @@ fn dei(self: *Uxn, stack: *Stack, comptime T: type, comptime keep: bool) void { fn deo(self: *Uxn, stack: *Stack, comptime T: type, comptime keep: bool) void { const d = if (keep) stack.peek(u8) else stack.pop(u8); const v = if (keep) stack.peek(T) else stack.pop(T); - std.debug.print("DEO: dev {X}, val {X}\n", .{ d, v }); + if (debug) std.debug.print("DEO: dev {X}, val {X}\n", .{ d, v }); switch (T) { u8 => { self.dev[d] = v; @@ -640,24 +683,30 @@ fn sft(stack: *Stack, comptime T: type, comptime keep: bool) void { } } -// test "arithmetic instructions" { -// var uxn = Uxn{ -// .mem = .{ .m = undefined }, -// .ws = .{ .s = undefined }, -// .rs = .{ .s = undefined }, -// .pc = 0, -// }; -// uxn.mem.m[0] = 0x18; -// uxn.mem.m[1] = 0x19; -// uxn.mem.m[2] = 0x1A; -// uxn.mem.m[3] = 0x1B; -// uxn.ws.push(u8, 4); -// uxn.ws.push(u8, 20); -// uxn.ws.push(u8, 6); -// uxn.loop(); -// uxn.loop(); -// try std.testing.expectEqual(18, uxn.ws.pop(u8)); -// } +test "op" { + var uxn = try Uxn.init(std.testing.allocator); + defer uxn.deinit(std.testing.allocator); + + const rom = [_]u8{ + 0x01, 0x01, 0x01, 0x01, + 0x00, + }; + @memcpy(uxn.mem.m[0x100 .. rom.len + 0x100], &rom); + + std.debug.print("op.inc\n", .{}); + uxn.ws.push(u8, 0x01); + uxn.ws.push(u8, 0xff); + uxn.ws.push(u8, 0xfe); + uxn.ws.push(u8, 0x00); + _ = uxn.eval(); + try std.testing.expectEqual(0x01, uxn.ws.pop(u8)); + _ = uxn.eval(); + try std.testing.expectEqual(0xff, uxn.ws.pop(u8)); + _ = uxn.eval(); + try std.testing.expectEqual(0x00, uxn.ws.pop(u8)); + _ = uxn.eval(); + try std.testing.expectEqual(0x02, uxn.ws.pop(u8)); +} pub fn formatInstruction( bytes: []const u8, @@ -667,55 +716,60 @@ pub fn formatInstruction( ) !void { _ = fmt; _ = options; + if (!debug) return; std.debug.assert(bytes.len == 1); - switch (bytes[0] & 0x1F) { - 0x00 => return switch (bytes[0]) { - 0x00 => try writer.writeAll("BRK"), - 0x20 => try writer.writeAll("JCI"), - 0x40 => try writer.writeAll("JMI"), - 0x60 => try writer.writeAll("JSI"), - 0x80 => try writer.writeAll("LIT"), - 0xA0 => try writer.writeAll("LIT2"), - 0xC0 => try writer.writeAll("LITr"), - 0xE0 => try writer.writeAll("LIT2r"), + try writer.writeAll("\x1b[1;31m"); + instr: { + switch (bytes[0] & 0x1F) { + 0x00 => break :instr switch (bytes[0]) { + 0x00 => try writer.writeAll("BRK"), + 0x20 => try writer.writeAll("JCI"), + 0x40 => try writer.writeAll("JMI"), + 0x60 => try writer.writeAll("JSI"), + 0x80 => try writer.writeAll("LIT"), + 0xA0 => try writer.writeAll("LIT2"), + 0xC0 => try writer.writeAll("LITr"), + 0xE0 => try writer.writeAll("LIT2r"), + else => unreachable, + }, + 0x01 => try writer.writeAll("INC"), + 0x02 => try writer.writeAll("POP"), + 0x03 => try writer.writeAll("NIP"), + 0x04 => try writer.writeAll("SWP"), + 0x05 => try writer.writeAll("ROT"), + 0x06 => try writer.writeAll("DUP"), + 0x07 => try writer.writeAll("OVR"), + 0x08 => try writer.writeAll("EQU"), + 0x09 => try writer.writeAll("NEQ"), + 0x0A => try writer.writeAll("GTH"), + 0x0B => try writer.writeAll("LTH"), + 0x0C => try writer.writeAll("JMP"), + 0x0D => try writer.writeAll("JCN"), + 0x0E => try writer.writeAll("JSR"), + 0x0F => try writer.writeAll("STH"), + 0x10 => try writer.writeAll("LDZ"), + 0x11 => try writer.writeAll("STZ"), + 0x12 => try writer.writeAll("LDR"), + 0x13 => try writer.writeAll("STR"), + 0x14 => try writer.writeAll("LDA"), + 0x15 => try writer.writeAll("STA"), + 0x16 => try writer.writeAll("DEI"), + 0x17 => try writer.writeAll("DEO"), + 0x18 => try writer.writeAll("ADD"), + 0x19 => try writer.writeAll("SUB"), + 0x1A => try writer.writeAll("MUL"), + 0x1B => try writer.writeAll("DIV"), + 0x1C => try writer.writeAll("AND"), + 0x1D => try writer.writeAll("ORA"), + 0x1E => try writer.writeAll("EOR"), + 0x1F => try writer.writeAll("SFT"), else => unreachable, - }, - 0x01 => try writer.writeAll("INC"), - 0x02 => try writer.writeAll("POP"), - 0x03 => try writer.writeAll("NIP"), - 0x04 => try writer.writeAll("SWP"), - 0x05 => try writer.writeAll("ROT"), - 0x06 => try writer.writeAll("DUP"), - 0x07 => try writer.writeAll("OVR"), - 0x08 => try writer.writeAll("EQU"), - 0x09 => try writer.writeAll("NEQ"), - 0x0A => try writer.writeAll("GTH"), - 0x0B => try writer.writeAll("LTH"), - 0x0C => try writer.writeAll("JMP"), - 0x0D => try writer.writeAll("JCN"), - 0x0E => try writer.writeAll("JSR"), - 0x0F => try writer.writeAll("STH"), - 0x10 => try writer.writeAll("LDZ"), - 0x11 => try writer.writeAll("STZ"), - 0x12 => try writer.writeAll("LDR"), - 0x13 => try writer.writeAll("STR"), - 0x14 => try writer.writeAll("LDA"), - 0x15 => try writer.writeAll("STA"), - 0x16 => try writer.writeAll("DEI"), - 0x17 => try writer.writeAll("DEO"), - 0x18 => try writer.writeAll("ADD"), - 0x19 => try writer.writeAll("SUB"), - 0x1A => try writer.writeAll("MUL"), - 0x1B => try writer.writeAll("DIV"), - 0x1C => try writer.writeAll("AND"), - 0x1D => try writer.writeAll("ORA"), - 0x1E => try writer.writeAll("EOR"), - 0x1F => try writer.writeAll("SFT"), - else => unreachable, + } + if (bytes[0] & 0x20 == 0x20) try writer.writeByte('2'); + if (bytes[0] & 0x40 == 0x40) try writer.writeByte('k'); + if (bytes[0] & 0x80 == 0x80) try writer.writeByte('r'); } - if (bytes[0] & 0x20 == 0x20) try writer.writeByte('2'); - if (bytes[0] & 0x40 == 0x40) try writer.writeByte('k'); - if (bytes[0] & 0x80 == 0x80) try writer.writeByte('r'); + try writer.writeAll("\x1b[0m"); } pub fn fmtInstrs(bytes: []const u8) std.fmt.Formatter(formatInstruction) { diff --git a/src/varvara.zig b/src/varvara.zig index 60110b9..5134afc 100644 --- a/src/varvara.zig +++ b/src/varvara.zig @@ -5,36 +5,57 @@ const Varvara = @This(); uxn: Uxn, -pub fn init(rom: []const u8) Varvara { - var uxn = Uxn{ .pc = 0x100 }; +allocator: std.mem.Allocator, +metadata: ?[]const u8 = null, + +pub fn init(allocator: std.mem.Allocator, rom: []const u8) !Varvara { + var uxn = try Uxn.init(allocator); + uxn.deo8 = deo8; + uxn.deo16 = deo16; + uxn.dei8 = dei8; + uxn.dei16 = dei16; @memcpy(uxn.mem.m[0x100 .. rom.len + 0x100], rom); - return .{ .uxn = uxn }; + return .{ .uxn = uxn, .allocator = allocator }; +} + +pub fn deinit(self: *Varvara) void { + self.uxn.deinit(self.allocator); } pub fn deo8(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u8) void { - const self: Varvara = @fieldParentPtr("uxn", uxn); - // _ = .{ self, stack, device, value }; - _ = self; - _ = stack; - switch (device) { + const self: *Varvara = @fieldParentPtr("uxn", uxn); + _ = .{ self, stack, device, value }; + switch (device) { // Console/write 0x18 => { - std.io.getStdOut().writer().writeByte(value) catch |e| @panic(e); + std.io.getStdOut().writer().writeByte(value) catch |e| @panic(@errorName(e)); }, else => {}, } } pub fn deo16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void { - const self: Varvara = @fieldParentPtr("uxn", uxn); + const self: *Varvara = @fieldParentPtr("uxn", uxn); _ = .{ self, stack, device, value }; + switch (device) { + 0x06, 0x07 => { // System/metadata + self.metadata = std.mem.span(@as([*:0]const u8, @ptrCast(&uxn.mem.m[value +% 1]))); + }, + else => {}, + } } -pub fn dei8(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u8) void { - const self: Varvara = @fieldParentPtr("uxn", uxn); - _ = .{ self, stack, device, value }; +pub fn dei8(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void { + const self: *Varvara = @fieldParentPtr("uxn", uxn); + _ = .{ self, stack, device }; + switch (device) { + else => {}, + } } -pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8, value: u16) void { - const self: Varvara = @fieldParentPtr("uxn", uxn); - _ = .{ self, stack, device, value }; +pub fn dei16(uxn: *Uxn, stack: *Uxn.Stack, device: u8) void { + const self: *Varvara = @fieldParentPtr("uxn", uxn); + _ = .{ self, stack, device }; + switch (device) { + else => {}, + } }