improve debugging, fix a lot, add basic op test, heap allocation, dear god

This commit is contained in:
Jeeves 2025-03-05 02:56:41 -07:00
parent 24f5fd49fa
commit 6d2cb973d1
3 changed files with 193 additions and 115 deletions

View file

@ -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;
}

View file

@ -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 {}
const Memory = struct {
m: [0x10000]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],
else => @compileError("expected u8 or u16, got " + @typeName(T)),
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: []u8,
pub fn peek(self: *Memory, comptime T: type, idx: u16) T {
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,9 +716,12 @@ pub fn formatInstruction(
) !void {
_ = fmt;
_ = options;
if (!debug) return;
std.debug.assert(bytes.len == 1);
try writer.writeAll("\x1b[1;31m");
instr: {
switch (bytes[0] & 0x1F) {
0x00 => return switch (bytes[0]) {
0x00 => break :instr switch (bytes[0]) {
0x00 => try writer.writeAll("BRK"),
0x20 => try writer.writeAll("JCI"),
0x40 => try writer.writeAll("JMI"),
@ -717,6 +769,8 @@ pub fn formatInstruction(
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) {
return .{ .data = bytes };

View file

@ -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 => {},
}
}