start of x86-64 implementation; stack pushing, sinking, add with overflow, procedure return ops
This commit is contained in:
commit
d26f11aec4
70
build.zig
Normal file
70
build.zig
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
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 exe = b.addExecutable(.{
|
||||||
|
.name = "nmvm",
|
||||||
|
// 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 = .{ .path = "src/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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 = .{ .path = "src/main.zig" },
|
||||||
|
.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);
|
||||||
|
}
|
2
idea.md
Normal file
2
idea.md
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# .nmvm Near Metal Virtual Machine
|
||||||
|
Exercise in building low overhead VM via architecture specific means.
|
91
src/arch/x86-64.zig
Normal file
91
src/arch/x86-64.zig
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// Execution thread convention:
|
||||||
|
// rdi <- binary thread
|
||||||
|
|
||||||
|
// Resources used:
|
||||||
|
// https://mort.coffee/home/fast-interpreters/
|
||||||
|
// https://blog.reverberate.org/2021/04/21/musttail-efficient-interpreters.html
|
||||||
|
// https://en.wikibooks.org/wiki/X86_Assembly/GNU_assembly_syntax
|
||||||
|
// https://www.cs.princeton.edu/courses/archive/spr18/cos217/lectures/15_AssemblyFunctions.pdf
|
||||||
|
// https://ziglang.org/documentation/master/#toc-Assembly
|
||||||
|
// https://csiflabs.cs.ucdavis.edu/~ssdavis/50/att-syntax.htm
|
||||||
|
|
||||||
|
pub const Word = u64;
|
||||||
|
|
||||||
|
// todo: Variant that pushes array of words.
|
||||||
|
/// (iw | -- iw)
|
||||||
|
pub fn opPushWord() callconv(.Naked) noreturn {
|
||||||
|
asm volatile (
|
||||||
|
\\ add $0x10, %%rdi
|
||||||
|
\\ pushq -8(%%rdi)
|
||||||
|
\\ jmpq *(%%rdi)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: Variant that discards array of words.
|
||||||
|
/// (w --)
|
||||||
|
pub fn opSinkWord() callconv(.Naked) noreturn {
|
||||||
|
asm volatile (
|
||||||
|
\\ add $0x08, %%rdi
|
||||||
|
\\ addq $0x08, %%rsp
|
||||||
|
\\ jmpq *(%%rdi)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (iw | -- (iw'nth word from stack) )
|
||||||
|
// fn opTakeWord(binary: [*]const Word, cond: bool) noreturn {
|
||||||
|
// @setRuntimeSafety(false);
|
||||||
|
// takeWord(binary[1].word);
|
||||||
|
// @call(.always_tail, binary[2].function, .{ &binary[2], cond });
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// (iw | w)
|
||||||
|
// fn opSetWord(binary: [*]const Word, cond: bool) noreturn {
|
||||||
|
// @setRuntimeSafety(false);
|
||||||
|
// setWord(binary[1].word, popWord());
|
||||||
|
// @call(.always_tail, binary[2].function, .{ &binary[2], cond });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// todo: Generate operation permutations procedurally.
|
||||||
|
// todo: Jump on overflow instead of cond setting?
|
||||||
|
/// (w1 w2 -- sum overflow)
|
||||||
|
pub fn opSumWordsWithOverflow() callconv(.Naked) noreturn {
|
||||||
|
// https://www.felixcloutier.com/x86/adc
|
||||||
|
// https://www.felixcloutier.com/x86/setcc
|
||||||
|
// idea: Could https://www.felixcloutier.com/x86/cmovcc be better for overflow push?
|
||||||
|
asm volatile (
|
||||||
|
\\ movq (%%rsp), %%rax
|
||||||
|
\\ adcq 8(%%rsp), %%rax
|
||||||
|
\\ movq %%rax, 8(%%rsp)
|
||||||
|
\\ setc %%al
|
||||||
|
\\ movb %%al, 7(%%rsp)
|
||||||
|
\\ addq $0x08, %%rdi
|
||||||
|
\\ jmpq *(%%rdi)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: Generate operation permutations procedurally.
|
||||||
|
// todo: We might not need cond register if conditions and jumps are combined?
|
||||||
|
/// (w1 w2)
|
||||||
|
// fn opRelativeJumpIfGreaterThan(binary: [*]const Word, cond: bool) noreturn {
|
||||||
|
// @setRuntimeSafety(false);
|
||||||
|
// const offset = if (popWord() > popWord()) binary[1].word else 2;
|
||||||
|
// @call(.always_tail, binary[offset].function, .{ &binary[offset], cond });
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// (addr)
|
||||||
|
pub fn opReturn() callconv(.Naked) noreturn {
|
||||||
|
// https://www.felixcloutier.com/x86/ret
|
||||||
|
asm volatile ("ret");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(binary: []const Word, entry_addr: usize) void {
|
||||||
|
// todo: Ensure correctness.
|
||||||
|
// https://wiki.osdev.org/System_V_ABI
|
||||||
|
// https://www.felixcloutier.com/x86/call
|
||||||
|
asm volatile (
|
||||||
|
\\ call *(%%rdi)
|
||||||
|
:
|
||||||
|
: [thread] "rdi" (&binary[entry_addr]),
|
||||||
|
: "rflags", "rax", "rbx", "rsp", "rbp", "r12", "r13", "r14", "r15", "rsi", "rdx", "rcx", "r8", "r9", "r10", "r11", "memory"
|
||||||
|
);
|
||||||
|
}
|
17
src/interpreter.zig
Normal file
17
src/interpreter.zig
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// todo: Interpreter context as binary local variable.
|
||||||
|
// It would hold memory mappings, as well as error stack.
|
||||||
|
// todo: Define procedure call for user code.
|
||||||
|
// todo: Instruction set extensions, such as memory management schemes, non-exhaustive logging,
|
||||||
|
// exception mechanism, coroutines via yield/resume and etc.
|
||||||
|
// todo: Threading scheme.
|
||||||
|
// todo: Extension for native floating point stack ops.
|
||||||
|
// todo: Try using small code model with nopie/nopic binary.
|
||||||
|
|
||||||
|
// idea: Specialized opcodes that have side effects on read and write, such as
|
||||||
|
// zero-check on push/pop, or jump if condition bit met. This would create a lot
|
||||||
|
// of permutations tho, we might try to discover which code devices are most used.
|
||||||
|
|
||||||
|
// idea: 'JIT' could be done by simple op* compiled binary copying up until `jmpq *(%%rdi)`,
|
||||||
|
// with immediate operand prelude modified, which could be done procedurally.
|
||||||
|
|
||||||
|
usingnamespace @import("arch/x86-64.zig");
|
16
src/main.zig
Normal file
16
src/main.zig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
const int = @import("interpreter.zig");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
const binary = [_]int.Word{
|
||||||
|
@as(int.Word, @intFromPtr(&int.opPushWord)),
|
||||||
|
~@as(int.Word, 1),
|
||||||
|
@as(int.Word, @intFromPtr(&int.opPushWord)),
|
||||||
|
~@as(int.Word, 1),
|
||||||
|
@as(int.Word, @intFromPtr(&int.opSumWordsWithOverflow)),
|
||||||
|
@as(int.Word, @intFromPtr(&int.opSinkWord)),
|
||||||
|
@as(int.Word, @intFromPtr(&int.opSinkWord)),
|
||||||
|
@as(int.Word, @intFromPtr(&int.opReturn)),
|
||||||
|
};
|
||||||
|
|
||||||
|
int.execute(&binary, 0);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user