add iacore's zig implementation

FossilOrigin-Name: 9e5619d102e37ddc606e41788e397f091eaf28c77414a05e5c0cae477ac6cc31
This commit is contained in:
crc 2024-02-21 15:46:34 +00:00
parent 1f6fbafe63
commit a34c08b74f
7 changed files with 1589 additions and 0 deletions

View file

@ -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
View 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)

View 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
View 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
View 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

File diff suppressed because it is too large Load diff

357
vm/libnga-zig/src/retro.zig Normal file
View 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);
}