Browse Source

Initial commit

master
commit
3a0d6eec62
No known key found for this signature in database GPG Key ID: DA34C790D267C164
5 changed files with 212 additions and 0 deletions
  1. +1
    -0
      .gitignore
  2. +12
    -0
      README.md
  3. +14
    -0
      build.zig
  4. +89
    -0
      src/main.zig
  5. +96
    -0
      src/oidavm.zig

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
zig-cache/

+ 12
- 0
README.md View File

@@ -0,0 +1,12 @@
# OidaVM

A minimal bytecode VM implemented in Zig.

## Specs

- 256 16-bit words of memory
- Operations: Add, Substract, Read, Write, numeric and ASCII Output, Unconditional and Conditional Jump, Noop, Halt

## Why?

Yes.

+ 14
- 0
build.zig View File

@@ -0,0 +1,14 @@
const Builder = @import("std").build.Builder;

pub fn build(b: *Builder) void {
const mode = b.standardReleaseOptions();
const exe = b.addExecutable("ovm", "src/main.zig");
exe.setBuildMode(mode);
exe.install();

const run_cmd = exe.run();
run_cmd.step.dependOn(b.getInstallStep());

const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

+ 89
- 0
src/main.zig View File

@@ -0,0 +1,89 @@
const std = @import("std");
usingnamespace @import("oidavm.zig");

pub fn main() void {
var vm = OidaVm{};
std.debug.warn("Hello World:\n");
hello_world(&vm);
vm.dump();
std.debug.warn("Count to 100:\n");
count_to_100(&vm);
vm.dump();
std.debug.warn("Fourier Sequence:\n");
fourier_sequence(&vm);
vm.dump();
}

fn instruction(comptime op: Opcode, comptime address: u8) u16 {
return (@intCast(u16, @enumToInt(op)) << 8) + address;
}

fn hello_world(vm: *OidaVm) void {
vm.flush();
const hello = "Hello, World!\n";
inline for (hello) |c, i| {
vm.load(@intCast(u8, i), c); // Write character into i
vm.load(@intCast(u8, (i * 2) + 100), instruction(.Read, @intCast(u8, i))); // Write READ i into 100 plus i * 2
vm.load(@intCast(u8, (i * 2 + 1) + 100), instruction(.OutputChar, 0)); // Write OUTPUTCHAR into 100 plus i * 2, plus 1
// i.e. i = 0 => 100, 101; i = 1 => 102, 103; i = 2 => 104, 105; …
}
vm.load(100 + hello.len * 2, instruction(.Halt, 0)); // Write HALT after last instruction
vm.eval(100);
}

fn count_to_100(vm: *OidaVm) void {
vm.flush();
vm.load(0, 1);
vm.load(2, 99);
vm.load(10, "\n"[0]);
vm.load(100, instruction(.Add, 0));
vm.load(101, instruction(.Write, 1));
vm.load(102, instruction(.Sub, 2));
vm.load(103, instruction(.JumpEqualsZero, 110));
vm.load(104, instruction(.Read, 1));
vm.load(105, instruction(.Output, 0));
vm.load(106, instruction(.Read, 10));
vm.load(107, instruction(.OutputChar, 0));
vm.load(108, instruction(.Halt, 0));
vm.load(110, instruction(.Read, 1));
vm.load(111, instruction(.Jump, 100));
vm.eval(100);
}

fn fourier_sequence(vm: *OidaVm) void {
vm.flush();

const _newline = 20;
vm.load(_newline, "\n"[0]);

const _old = 25;
vm.load(_old, 1);

const _new = 30;
vm.load(_new, 1);

const _oldtemp = 35;

const _overflow_full = 40;
vm.load(_overflow_full, 65534);
const _overflow_acc = 45;

vm.load(100, instruction(.Read, _old));
vm.load(101, instruction(.Write, _oldtemp));
vm.load(102, instruction(.Add, _new));
vm.load(103, instruction(.Write, _overflow_acc));
vm.load(104, instruction(.Sub, _overflow_full));
vm.load(105, instruction(.JumpEqualsZero, 110));
vm.load(106, instruction(.Halt, 0));

vm.load(110, instruction(.Read, _overflow_acc));
vm.load(111, instruction(.Write, _old));
vm.load(112, instruction(.Read, _oldtemp));
vm.load(113, instruction(.Write, _new));
vm.load(114, instruction(.Output, 0));
vm.load(115, instruction(.Read, _newline));
vm.load(116, instruction(.OutputChar, 0));
vm.load(117, instruction(.Jump, 100));
vm.eval(100);
}

+ 96
- 0
src/oidavm.zig View File

@@ -0,0 +1,96 @@
const std = @import("std");

pub const Opcode = enum(u8) {
/// Skips instruction.
Noop = 0x0,
/// Reads address into accumulator.
Read = 0x1,
/// Dereferences address, adds to accumulator. Overflow gets silently truncated to 65535.
Add = 0x2,
/// Dereferences address, substracts from accumulator. Overflow gets silently truncated to 0.
Sub = 0x3,
/// Prints numeric value of accumulator to stderr.
Output = 0x4,
/// Stops execution.
Halt = 0x5,
/// Dereferences address, prints ASCII representation of lower 8 bits to stderr.
OutputChar = 0x6,
/// Unconditionally continues execution at address.
Jump = 0x7,
/// Continues execution at address if accumulator is 0, otherwise skips instruction.
JumpEqualsZero = 0x8,
/// Writes content of accumulator to address.
Write = 0x9,
};

pub const OidaVm = struct {
memory: [256]u16 = [_]u16{0} ** 256,
accumulator: u16 = 0,
instruction_ptr: u8 = 0,

/// Executes a single instruction
fn exec(this: *OidaVm, op: Opcode, addr: u8) void {
switch (op) {
.Noop => return,
.Read => this.accumulator = this.memory[addr],
.Add => if (@intCast(u32, this.accumulator) + this.memory[addr] <= 65535) {
this.accumulator += this.memory[addr];
} else {
this.accumulator = 65535;
},
.Sub => if (this.accumulator >= this.memory[addr]) {
this.accumulator -= this.memory[addr];
} else {
this.accumulator = 0;
},
.Output => std.debug.warn("{}", this.accumulator),
.Halt => return, // Handled by eval()
.OutputChar => std.debug.warn("{c}", @truncate(u8, this.accumulator)),
.Jump => this.instruction_ptr = addr - 1, // The continuation of eval()'s loop will increase iptr by one
.JumpEqualsZero => if (this.accumulator == 0) {
this.instruction_ptr = addr - 1;
} else return,
.Write => this.memory[addr] = this.accumulator,
}
}

/// Starts to evaluate memory as instructions starting at `addr`.
/// Invalid opcodes invoke safety-checked UB, so try to avoid them.
fn eval(this: *OidaVm, addr: u8) void {
this.instruction_ptr = addr;
while (this.instruction_ptr < 255) : (this.instruction_ptr += 1) {
const address: u8 = @truncate(u8, this.memory[this.instruction_ptr]);
const operation: u8 = @intCast(u8, this.memory[this.instruction_ptr] >> 8);
if (operation == @enumToInt(Opcode.Halt)) return;
this.exec(@intToEnum(Opcode, operation), address);
}
}

/// Writes `value` to `addr` in the VM's memory.
fn load(this: *OidaVm, addr: u8, value: u16) void {
this.memory[addr] = value;
}

/// Dumps the VM's state to stderr.
fn dump(this: OidaVm) void {
std.debug.warn("== OidaVM dump ==\n");
std.debug.warn("Instruction Pointer: 0x{X:0^2}\n", this.instruction_ptr);
std.debug.warn("Accumulator: 0x{X:0^4}\n", this.accumulator);
std.debug.warn("Memory: \n");
for (this.memory) |val, addr| {
std.debug.warn("{}{}0x{X:0^2}: 0x{X:0^4}{} {}", if (addr == this.instruction_ptr) "\x1b[7m" else "", // Inverted formatting for instruction ptr
if (val != 0) "\x1b[1m" else "", // Bold formatting for non-null values
addr, val, if (addr == this.instruction_ptr or val != 0) "\x1b[0m" else "", // Reset all formatting
if ((addr + 1) % 8 == 0) "\n" else "| " // If next entry is 8, 16, … print newline
);
}
std.debug.warn("== end dump ==\n");
}

/// Resets the VM to starting conditions.
fn flush(this: *OidaVm) void {
this.instruction_ptr = 0;
this.accumulator = 0;
this.memory = [_]u16{0} ** 256;
}
};

Loading…
Cancel
Save