add iacore's zig implementation
FossilOrigin-Name: 9e5619d102e37ddc606e41788e397f091eaf28c77414a05e5c0cae477ac6cc31
This commit is contained in:
parent
1f6fbafe63
commit
a34c08b74f
7 changed files with 1589 additions and 0 deletions
|
@ -34,6 +34,11 @@
|
|||
|
||||
- new nga implementations
|
||||
|
||||
- nga-zig
|
||||
|
||||
- added a zig implementation from iacore under vm/libnga-zig
|
||||
- build with: zig build -Doptimize=ReleaseFast -p ~/.local
|
||||
|
||||
- nga-c
|
||||
|
||||
- use UTF32 internally (translating to/from UTF8 externally)
|
||||
|
|
18
vm/libnga-zig/LICENSE
Normal file
18
vm/libnga-zig/LICENSE
Normal file
|
@ -0,0 +1,18 @@
|
|||
awawawawawa
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software
|
||||
for any purpose with or without fee is hereby granted, provided
|
||||
that the copyright notice and this permission notice appear in
|
||||
all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
|
||||
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
|
||||
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
||||
OF THIS SOFTWARE.
|
||||
|
||||
src/image.c is copied from vm/nga-c/image.c from the official retro repository (https://git.sr.ht/~crc_/retroforth)
|
51
vm/libnga-zig/LICENSE-RETRO
Normal file
51
vm/libnga-zig/LICENSE-RETRO
Normal file
|
@ -0,0 +1,51 @@
|
|||
RETRO is a personal, minimalistic Forth
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software
|
||||
for any purpose with or without fee is hereby granted, provided
|
||||
that the copyright notice and this permission notice appear in
|
||||
all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS
|
||||
ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO
|
||||
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
|
||||
RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
|
||||
OF THIS SOFTWARE.
|
||||
|
||||
RETRO 12 is:
|
||||
|
||||
Copyright (c) 2008 - 2023, Charles Childers
|
||||
|
||||
Portions of the code derive from RETRO 11, which was:
|
||||
|
||||
Copyright (c) 2008 - 2016, Charles Childers
|
||||
Copyright (c) 2012 - 2013, Michal J Wallace
|
||||
Copyright (c) 2009 - 2011, Luke Parrish
|
||||
Copyright (c) 2009 - 2010, JGL
|
||||
Copyright (c) 2010 - 2011, Marc Simpson
|
||||
Copyright (c) 2011 - 2012, Oleksandr Kozachuk
|
||||
Copyright (c) 2010, Jay Skeer
|
||||
Copyright (c) 2010, Greg Copeland
|
||||
Copyright (c) 2011, Aleksej Saushev
|
||||
Copyright (c) 2011, Foucist
|
||||
Copyright (c) 2011, Erturk Kocalar
|
||||
Copyright (c) 2011, Kenneth Keating
|
||||
Copyright (c) 2011, Ashley Feniello
|
||||
Copyright (c) 2011, Peter Salvi
|
||||
Copyright (c) 2011, Christian Kellermann
|
||||
Copyright (c) 2011, Jorge Acereda
|
||||
Copyright (c) 2011, Remy Moueza
|
||||
Copyright (c) 2012, John M Harrison
|
||||
Copyright (c) 2012, Todd Thomas
|
||||
Copyright (c) 2022, Rick Carlino
|
||||
|
||||
The Free Pascal implementation in vm/nga-pascal is:
|
||||
|
||||
Copyright (c) 2016, Rob Judd
|
||||
|
||||
The Nim implementation in vm/nga-nim is:
|
||||
|
||||
Copyright (c) 2021, Jorge Acereda
|
79
vm/libnga-zig/build.zig
Normal file
79
vm/libnga-zig/build.zig
Normal file
|
@ -0,0 +1,79 @@
|
|||
const std = @import("std");
|
||||
|
||||
// Although this function looks imperative, note that its job is to
|
||||
// declaratively construct a build graph that will be executed by an external
|
||||
// runner.
|
||||
pub fn build(b: *std.Build) void {
|
||||
// Standard target options allows the person running `zig build` to choose
|
||||
// what target to build for. Here we do not override the defaults, which
|
||||
// means any target is allowed, and the default is native. Other options
|
||||
// for restricting supported target set are available.
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
// Standard optimization options allow the person running `zig build` to select
|
||||
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const entry = .{ .path = "src/retro.zig" };
|
||||
|
||||
_ = b.addModule("nga", .{
|
||||
.source_file = entry,
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "retro-zig",
|
||||
// In this case the main source file is merely a path, however, in more
|
||||
// complicated build scripts, this could be a generated file.
|
||||
.root_source_file = entry,
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
exe.disable_sanitize_c = true;
|
||||
exe.addCSourceFile(.{ .file = .{ .path = "src/image.c" }, .flags = &.{} });
|
||||
exe.addIncludePath(.{ .path = "src" });
|
||||
|
||||
// This declares intent for the executable to be installed into the
|
||||
// standard location when the user invokes the "install" step (the default
|
||||
// step when running `zig build`).
|
||||
b.installArtifact(exe);
|
||||
|
||||
// This *creates* a Run step in the build graph, to be executed when another
|
||||
// step is evaluated that depends on it. The next line below will establish
|
||||
// such a dependency.
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
|
||||
// By making the run step depend on the install step, it will be run from the
|
||||
// installation directory rather than directly from within the cache directory.
|
||||
// This is not necessary, however, if the application depends on other installed
|
||||
// files, this ensures they will be present and in the expected location.
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
// This allows the user to pass arguments to the application in the build
|
||||
// command itself, like this: `zig build run -- arg1 arg2 etc`
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build run`
|
||||
// This will evaluate the `run` step rather than the default, which is "install".
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const unit_tests = b.addTest(.{
|
||||
.root_source_file = entry,
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const run_unit_tests = b.addRunArtifact(unit_tests);
|
||||
|
||||
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||
// the `zig build --help` menu, providing a way for the user to request
|
||||
// running the unit tests.
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_unit_tests.step);
|
||||
}
|
15
vm/libnga-zig/readme.md
Normal file
15
vm/libnga-zig/readme.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
nga VM for retroforth, in Zig.
|
||||
|
||||
It's faster than the official C VM somehow.
|
||||
|
||||
## Building
|
||||
|
||||
This project is developed with Zig 0.11.0.
|
||||
|
||||
```
|
||||
# Build and run
|
||||
zig build run
|
||||
|
||||
# Install to ~/.local/bin/retro-zig
|
||||
zig build -Doptimize=ReleaseFast -p ~/.local
|
||||
```
|
1064
vm/libnga-zig/src/image.c
Normal file
1064
vm/libnga-zig/src/image.c
Normal file
File diff suppressed because it is too large
Load diff
357
vm/libnga-zig/src/retro.zig
Normal file
357
vm/libnga-zig/src/retro.zig
Normal file
|
@ -0,0 +1,357 @@
|
|||
const std = @import("std");
|
||||
|
||||
pub const CELL = i32;
|
||||
pub const CELL_MIN = std.math.minInt(CELL) + 1;
|
||||
pub const CELL_MAX = std.math.maxInt(CELL) - 1;
|
||||
const CELL_1: CELL = -1;
|
||||
const CELL_0: CELL = 0;
|
||||
|
||||
fn cell_lsb(data: CELL) u8 {
|
||||
return @truncate(@as(u32, @bitCast(data)));
|
||||
}
|
||||
|
||||
/// Amount of RAM.
|
||||
pub const IMAGE_SIZE = 524288;
|
||||
/// Max address stack depth
|
||||
pub const ADDRESSES = 128;
|
||||
/// Max data stack depth
|
||||
pub const STACK_DEPTH = 32;
|
||||
|
||||
// zig fmt: off
|
||||
pub const Inst = enum(u8) {
|
||||
no_, li_, du_, dr_, sw_, pu_, po_, ju_,
|
||||
ca_, cc_, re_, eq_, ne_, lt_, gt_, fe_,
|
||||
st_, ad_, su_, mu_, di_, an_, or_, xo_,
|
||||
sh_, zr_, ha_, ie_, iq_, ii_,
|
||||
};
|
||||
// zig fmt: on
|
||||
|
||||
pub const VM = struct {
|
||||
/// Data Stack pointer
|
||||
sp: CELL = 0,
|
||||
/// Return Stack pointer
|
||||
rp: CELL = 0,
|
||||
/// Instruction pointer
|
||||
ip: CELL = 0,
|
||||
/// The data stack
|
||||
data: [STACK_DEPTH]CELL = .{0} ** STACK_DEPTH,
|
||||
/// The address stack
|
||||
address: [ADDRESSES]CELL = .{0} ** ADDRESSES,
|
||||
/// Image Memory
|
||||
memory: [IMAGE_SIZE]CELL = .{0} ** IMAGE_SIZE,
|
||||
|
||||
fin: std.fs.File,
|
||||
fout: std.fs.File,
|
||||
|
||||
pub fn init() VM {
|
||||
return .{
|
||||
.fin = std.io.getStdIn(),
|
||||
.fout = std.io.getStdOut(),
|
||||
};
|
||||
}
|
||||
|
||||
// helper functions
|
||||
|
||||
/// Top item on stack
|
||||
pub fn TOS(__: *VM) *CELL {
|
||||
return &__.data[@intCast(__.sp)];
|
||||
}
|
||||
/// Next Top item on stack
|
||||
pub fn NOS(__: *VM) *CELL {
|
||||
return &__.data[@intCast(__.sp - 1)];
|
||||
}
|
||||
/// Top item on address stack
|
||||
pub fn TORS(__: *VM) *CELL {
|
||||
return &__.address[@intCast(__.rp)];
|
||||
}
|
||||
/// direct memory access
|
||||
pub fn MEM(__: *VM, addr: CELL) *CELL {
|
||||
return &__.memory[@intCast(addr)];
|
||||
}
|
||||
pub fn stack_push(__: *VM, value: CELL) void {
|
||||
__.sp += 1;
|
||||
__.TOS().* = value;
|
||||
}
|
||||
pub fn stack_pop(__: *VM) CELL {
|
||||
const r = __.TOS().*;
|
||||
__.dr_();
|
||||
return r;
|
||||
}
|
||||
|
||||
pub fn halt(__: *VM) void {
|
||||
@setCold(true);
|
||||
__.ip = IMAGE_SIZE;
|
||||
}
|
||||
fn not_halted(__: VM) bool {
|
||||
return __.ip < IMAGE_SIZE;
|
||||
}
|
||||
|
||||
pub fn execute_from(__: *VM, initial_ip: CELL) void {
|
||||
__.rp = 1;
|
||||
__.ip = initial_ip;
|
||||
while (__.not_halted()) {
|
||||
const opcode = __.MEM(__.ip).*;
|
||||
__.process_opcode_bundle(opcode);
|
||||
if (__.rp == 0)
|
||||
// __.halt();
|
||||
break;
|
||||
__.ip += 1;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_opcode_bundle(__: *@This(), opcodes: CELL) void {
|
||||
const opcode: u32 = @bitCast(opcodes);
|
||||
__.handle_instruction(@enumFromInt(opcode & 0xFF));
|
||||
__.handle_instruction(@enumFromInt((opcode >> 8) & 0xFF));
|
||||
__.handle_instruction(@enumFromInt((opcode >> 16) & 0xFF));
|
||||
__.handle_instruction(@enumFromInt((opcode >> 24) & 0xFF));
|
||||
}
|
||||
|
||||
/// instruction/io handler type
|
||||
pub fn handle_instruction(__: *VM, inst: Inst) void {
|
||||
switch (inst) {
|
||||
inline else => |tag| @field(VM, @tagName(tag))(__),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_(__: *VM) void {
|
||||
_ = __;
|
||||
}
|
||||
pub fn li_(__: *VM) void {
|
||||
__.ip += 1;
|
||||
__.stack_push(__.MEM(__.ip).*);
|
||||
}
|
||||
pub fn du_(__: *VM) void {
|
||||
__.stack_push(__.TOS().*);
|
||||
}
|
||||
pub fn dr_(__: *VM) void {
|
||||
__.TOS().* = 0; // sus: cound set to undefined
|
||||
if (__.sp == 0) { // stack exhausted
|
||||
__.halt();
|
||||
} else {
|
||||
__.sp -= 1;
|
||||
}
|
||||
}
|
||||
pub fn sw_(__: *VM) void {
|
||||
std.mem.swap(CELL, __.TOS(), __.NOS());
|
||||
}
|
||||
pub fn pu_(__: *VM) void {
|
||||
__.rp += 1;
|
||||
__.TORS().* = __.stack_pop();
|
||||
}
|
||||
pub fn po_(__: *VM) void {
|
||||
__.stack_push(__.TORS().*);
|
||||
__.rp -= 1;
|
||||
}
|
||||
pub fn ju_(__: *VM) void {
|
||||
__.ip = __.stack_pop() - 1;
|
||||
}
|
||||
pub fn ca_(__: *VM) void {
|
||||
__.rp += 1;
|
||||
__.TORS().* = __.ip;
|
||||
__.ip = __.stack_pop() - 1;
|
||||
}
|
||||
pub fn cc_(__: *VM) void {
|
||||
const a = __.stack_pop();
|
||||
const b = __.stack_pop();
|
||||
if (b != 0) {
|
||||
__.rp += 1;
|
||||
__.TORS().* = __.ip;
|
||||
__.ip = a - 1;
|
||||
}
|
||||
}
|
||||
pub fn re_(__: *VM) void {
|
||||
__.ip = __.TORS().*;
|
||||
__.rp -= 1;
|
||||
}
|
||||
pub fn eq_(__: *VM) void {
|
||||
const cond = (__.NOS().* == __.TOS().*);
|
||||
__.NOS().* = if (cond) CELL_1 else CELL_0;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn ne_(__: *VM) void {
|
||||
const cond = (__.NOS().* != __.TOS().*);
|
||||
__.NOS().* = if (cond) CELL_1 else CELL_0;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn lt_(__: *VM) void {
|
||||
const cond = (__.NOS().* < __.TOS().*);
|
||||
__.NOS().* = if (cond) CELL_1 else CELL_0;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn gt_(__: *VM) void {
|
||||
const cond = (__.NOS().* > __.TOS().*);
|
||||
__.NOS().* = if (cond) CELL_1 else CELL_0;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn fe_(__: *VM) void {
|
||||
const tos = __.TOS().*;
|
||||
__.TOS().* = switch (tos) {
|
||||
-1 => __.sp - 1,
|
||||
-2 => __.rp,
|
||||
-3 => IMAGE_SIZE,
|
||||
-4 => CELL_MIN,
|
||||
-5 => CELL_MAX,
|
||||
else => __.MEM(tos).*,
|
||||
};
|
||||
}
|
||||
pub fn st_(__: *VM) void {
|
||||
const tos = __.stack_pop();
|
||||
const nos = __.stack_pop();
|
||||
if (tos >= 0 and tos <= IMAGE_SIZE) {
|
||||
__.MEM(tos).* = nos;
|
||||
} else {
|
||||
__.halt();
|
||||
}
|
||||
}
|
||||
pub fn ad_(__: *VM) void {
|
||||
__.NOS().* +%= __.TOS().*;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn su_(__: *VM) void {
|
||||
__.NOS().* -%= __.TOS().*;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn mu_(__: *VM) void {
|
||||
__.NOS().* *%= __.TOS().*;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn di_(__: *VM) void {
|
||||
// sus: division by zero is UB from the view of VM | handle this error
|
||||
const tos = __.TOS().*;
|
||||
const nos = __.NOS().*;
|
||||
__.TOS().* = @divTrunc(nos, tos);
|
||||
__.NOS().* = @rem(nos, tos);
|
||||
}
|
||||
pub fn an_(__: *VM) void {
|
||||
__.NOS().* &= __.TOS().*;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn or_(__: *VM) void {
|
||||
__.NOS().* |= __.TOS().*;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn xo_(__: *VM) void {
|
||||
__.NOS().* ^= __.TOS().*;
|
||||
__.dr_();
|
||||
}
|
||||
pub fn sh_(__: *VM) void {
|
||||
const tos = __.TOS().*;
|
||||
const nos = __.NOS().*;
|
||||
__.NOS().* = if (tos < 0)
|
||||
nos << @intCast(-tos)
|
||||
else
|
||||
// signed shift
|
||||
if (nos < 0 and tos > 0)
|
||||
nos >> @intCast(tos) | ~(~@as(CELL, 0) >> @intCast(tos))
|
||||
else
|
||||
nos >> @intCast(tos);
|
||||
__.dr_();
|
||||
}
|
||||
pub fn zr_(__: *VM) void {
|
||||
if (__.TOS().* == 0) {
|
||||
__.dr_();
|
||||
__.ip = __.TORS().*;
|
||||
__.rp -= 1;
|
||||
}
|
||||
}
|
||||
pub fn ha_(__: *VM) void {
|
||||
__.halt();
|
||||
}
|
||||
pub fn ie_(__: *VM) void {
|
||||
__.stack_push(io.devices.len);
|
||||
}
|
||||
pub fn iq_(__: *VM) void {
|
||||
const tos = __.stack_pop();
|
||||
switch (tos) {
|
||||
inline 0...io.devices.len - 1 => |i| {
|
||||
const device = io.devices[i];
|
||||
__.stack_push(device.type);
|
||||
__.stack_push(device.version);
|
||||
},
|
||||
else => @panic("iq | accessing invalid device"),
|
||||
}
|
||||
}
|
||||
pub fn ii_(__: *VM) void {
|
||||
const tos = __.stack_pop();
|
||||
switch (tos) {
|
||||
inline 0...io.devices.len - 1 => |i| {
|
||||
const device = io.devices[i];
|
||||
device.handler(__);
|
||||
},
|
||||
else => @panic("ii | accessing invalid device"),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
pub const Handler = @TypeOf(VM.no_);
|
||||
|
||||
pub const io = struct {
|
||||
pub const DeviceDesc = struct {
|
||||
type: CELL,
|
||||
version: CELL,
|
||||
handler: Handler,
|
||||
};
|
||||
pub const devices = [_]DeviceDesc{
|
||||
.{
|
||||
.type = 0,
|
||||
.version = 0,
|
||||
.handler = putchar,
|
||||
},
|
||||
.{
|
||||
.type = 0,
|
||||
.version = 1,
|
||||
.handler = getchar,
|
||||
},
|
||||
};
|
||||
pub fn putchar(__: *VM) void {
|
||||
const char = cell_lsb(__.TOS().*);
|
||||
__.fout.writeAll(&.{char}) catch @panic("sus: handle this error");
|
||||
__.dr_();
|
||||
}
|
||||
pub fn getchar(__: *VM) void {
|
||||
const maybe_char = _: {
|
||||
var buf: [1]u8 = undefined;
|
||||
const nread = __.fin.readAll(&buf) catch @panic("sus: handle this error");
|
||||
if (nread != buf.len) // eof
|
||||
break :_ null;
|
||||
break :_ buf[0];
|
||||
};
|
||||
if (maybe_char) |char|
|
||||
__.stack_push(char)
|
||||
else // on eof
|
||||
__.halt(); // sus: could do better here
|
||||
}
|
||||
};
|
||||
|
||||
test "VM dry test" {
|
||||
var vm = VM.init();
|
||||
const memory: [*]u8 = @ptrCast(@alignCast(&vm.memory));
|
||||
memory[0] = @intFromEnum(VM.Inst.li_);
|
||||
memory[1] = 0;
|
||||
vm.execute_from(0);
|
||||
try std.testing.expectEqual(vm.ip, 524288);
|
||||
}
|
||||
|
||||
pub extern const ngaImageCells: c_int;
|
||||
pub const ngaImage: [*c]c_int = @extern([*c]c_int, .{
|
||||
.name = "ngaImage",
|
||||
});
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
const a = gpa.allocator();
|
||||
var vm = VM.init();
|
||||
if (std.os.argv.len > 1) {
|
||||
var p = std.ChildProcess.init(&.{ "retro-unu", std.mem.span(std.os.argv[1]) }, a);
|
||||
p.stdin_behavior = .Ignore;
|
||||
p.stdout_behavior = .Pipe;
|
||||
try p.spawn();
|
||||
// _ = try p.wait();
|
||||
vm.fin = p.stdout.?;
|
||||
}
|
||||
@memcpy(vm.memory[0..@intCast(ngaImageCells)], ngaImage[0..@intCast(ngaImageCells)]);
|
||||
vm.execute_from(0);
|
||||
}
|
Loading…
Reference in a new issue