From 53f8ba27fb69f9383370729164ceecf0f184ee35 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 25 Mar 2024 04:33:09 +0000 Subject: [PATCH] HMN Learning Jam 2024 (Initial Commit) Not all instructions are implemented in VM. Only Copy, Add and Halt. --- .gitignore | 2 + README.md | 23 +++++ buildall.sh | 6 ++ doc/asm.md | 47 ++++++++++ doc/exe.md | 3 + doc/inst.md | 13 +++ doc/io.md | 2 + doc/mem.md | 45 +++++++++ gepvm/build.sh | 8 ++ gepvm/gepvm.odin | 166 +++++++++++++++++++++++++++++++++ gepvm/registers/registers.odin | 38 ++++++++ 11 files changed, 353 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 buildall.sh create mode 100644 doc/asm.md create mode 100644 doc/exe.md create mode 100644 doc/inst.md create mode 100644 doc/io.md create mode 100644 doc/mem.md create mode 100644 gepvm/build.sh create mode 100644 gepvm/gepvm.odin create mode 100644 gepvm/registers/registers.odin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f79e96a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +debug/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..3defcfd --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# The Gepetto Virtual Machine +...is a generic virtual machine designed to be a simple abstraction layer of +minimal operating systems. See [here](https://midnadimple.bearblog.dev/ideal-comp-env) +for the big picture. + +While it is intended to run on minimal operating systems that are specialised +to hardware, a reference implmentation for Windows, MacOS and Linux is included. +This reference implementation includes a virtual machine and an assembler is +fast, easily extensible by the end user and allows you to bundle the VM with +your program's bytecode for distribution. + +There's also plans to add a dedicated Odin-like programming language for ease +of development. + +To build all the tools, run `./buildall.sh` in a Bash shell. + +See: +- [Memory and Registers](doc/mem.md) +- [A tutorial for the assembly language](doc/asm.md) +- [All instructions on the machine](doc/inst.md) +- [I/O in the machine](doc/io.md) +- [The Pinnochio Executable Format](doc/exe.md) +- [Livestreams of development](https://youtube.com/@midnadimple) \ No newline at end of file diff --git a/buildall.sh b/buildall.sh new file mode 100644 index 0000000..1760429 --- /dev/null +++ b/buildall.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +test -d build/ || mkdir -p build + +# GepVM +gepvm/build.sh \ No newline at end of file diff --git a/doc/asm.md b/doc/asm.md new file mode 100644 index 0000000..5bfbc5a --- /dev/null +++ b/doc/asm.md @@ -0,0 +1,47 @@ +# A tutorial on the assembly language +**Required Reading: *None*** + +Gepetto executables are assembled from `.gepe` source files, which are +sequential lists of mnemonics and their arguments. + +Here's an example of assembly: +``` +{ main + move l0 9 + add l0 29 + store l0, 0xf1 +} +``` + +Whitespace before the first character on a line is ignored, whitespace after a +character seperates tokens. (i.e. `{` and `main` are seperate tokens). Note that +any character can be used fo mnemonics, though most are alphanumeric. Mnemonics +can also have any length >= 1. + +You can include other `.gepe` files: +``` +%include "../test.gepe" +``` + +Gepetto executables are intended to be compiled as single translation units. + +The reference implementation allows user-defined macros, which are simple string +substitutions. A number of arguments can be specified and indexed within +the macro: +``` +%macro coolio 2 + move %1 %2 + add %1 %2 +%endmacro +``` + +There is also a set of standard macros provided with the reference assembler to +improve your development experience. They can be accessed using includes: +``` +%include "std.gepe" +``` + +The search path for source files is, in order: +- Path where assembler was run (source directory) +- Path where assembler was compiled (runtime directory) +- Path specified in assembler args \ No newline at end of file diff --git a/doc/exe.md b/doc/exe.md new file mode 100644 index 0000000..1c2fef5 --- /dev/null +++ b/doc/exe.md @@ -0,0 +1,3 @@ +# The Pinnochio Executable Format +**Required Reading: *[Memory Layout in Gepetto](mem.md)*** + diff --git a/doc/inst.md b/doc/inst.md new file mode 100644 index 0000000..f0483c3 --- /dev/null +++ b/doc/inst.md @@ -0,0 +1,13 @@ +# The Gepetto Instructions Reference +**Required reading: *[Memory Layout in Gepetto](mem.md)*, *[A tutorial on the assembly language](asm.md)*** + +Maximum of 256 instructions. Opcode = 8bits + +- Arithmetic +- Control Flow +- Data Flow + - Copy + - Copy Register to Register + - Copy Literal to Register + - Copy Memory to Register + - Copy Register to Memory \ No newline at end of file diff --git a/doc/io.md b/doc/io.md new file mode 100644 index 0000000..11689a0 --- /dev/null +++ b/doc/io.md @@ -0,0 +1,2 @@ +# Gepetto and IO +**Required Reading: *[Memory Layout in Gepetto](mem.md)*** \ No newline at end of file diff --git a/doc/mem.md b/doc/mem.md new file mode 100644 index 0000000..4058e31 --- /dev/null +++ b/doc/mem.md @@ -0,0 +1,45 @@ +# Memory Layout in Gepetto +**Required Readed: *None*** + +Gepetto uses a 64-bit address space, which allows for a theoretical maximum of +2^64 bytes of memory. The practical maximum is dependent on the implementation. +The reference implementation uses Odin's `[dynamic]u64`, which can allocate +more memory when needed. By default, 2 times the size of the executable is +reserved. + +There are 2 classes of registers in Gepetto, all of which are 64-bit. These are: + +## Global Registers +...are visible throughout the duration of the program. These include: + +- `gi` = Instruction Pointer, holds the address to the next instruction in +memory. Initialized to 0. Encoded as `0000` +- `gs` = Stack Pointer, holds the address at the top of the stack. Encoded as `0001` +Initialized to 0, so must be set before using stack operations. +- `gb` = Base Pointer, holds the address for displacement and bottom stack. Encoded as `0010` +Initialized to 0, so must be set before using stack operations. +- `gr` = Return Register, intended to store the returned values of +subroutines. Initialized to 0. Encoded as `0011` +- `gl` = Local Pointer, explained in the next section. Initialized to 0. Encoded as `0100` + +## Local Registers +In Gepetto, there is technically an *infinite* amount of registers (the +reference implementation uses Odin's `[dynamic]u64`). However, for the +sake of simplicity, only 16 are visible at a time. Those are: + +- `l[0-9]` = 10 General-Purpose Registers, all initialized to 0. l0 is encoded +as `0101` with each register after incrementing. +- `lf` = Flag Register, updated by conditional branch branch instructions. The +following table explains the bit layout of the register. Initialized to 0. Encoded as `1111` +**TODO** + +Which registers are used is dependent of the `gl` register. It acts as an +offset into this array. Each register's index is calculate by adding the value +in `gl` to the number of the local register. So, `l9` = +`register[9 + register[GL]]`, for example. + +This means, if you need more registers, you can simply increment `gl` by 10, and +you have a fresh new set of registers. You can also increment `gl` by a smaller +value, if you only need a few more. The reference implementation provides the +macro `newreg`, which calls `add gl, 10`, and `{`, which calls `newreg` and +defines a label (giving you a new scope/subroutine). \ No newline at end of file diff --git a/gepvm/build.sh b/gepvm/build.sh new file mode 100644 index 0000000..48d6e34 --- /dev/null +++ b/gepvm/build.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +test -d build/gepvm || mkdir -p build/gepvm +pushd build/gepvm + +odin build ../../gepvm -debug + +popd diff --git a/gepvm/gepvm.odin b/gepvm/gepvm.odin new file mode 100644 index 0000000..b5bdbaf --- /dev/null +++ b/gepvm/gepvm.odin @@ -0,0 +1,166 @@ +package gepvm + +import "./registers" +import "core:fmt" + +memory: [dynamic]u64 + +Opcode_Type :: enum { + Copy, + Add, + Halt, +} + +sign_extend :: proc(x, bit_count: u64) -> u64 { + result := x + if ((result >> (bit_count - 1)) & 1) != 0 { + result |= 0xFFFFFFFFFFFFFFFF << bit_count + } + return result +} + +main :: proc() { + // TODO: Load file into memory + resize(&memory, 1024) + defer delete(memory) + resize(®isters.registers, 1024) + defer delete(registers.registers) + + memory[0xFF] = 255 + memory[0x0] = 0x0080000000000FF5 // copy l0 [0xFF] + memory[0x1] = 0x0000000000000065 // copy l1 l0 + memory[0x2] = 0x0100000000000065 // add l1 l0 + memory[0x3] = 0x01800000000000F6 // add l1 15 + memory[0x4] = 0x00C0000000000FF6 // copy [0xFF] l1 + memory[0x5] = 0x0200000000000000 // halt + + // Loop over instructions + running := true + for running { + // Get The opcode + inst := memory[registers.get(registers.GI)] + opcode := Opcode_Type(inst >> 56) + + // Execute 'em + switch opcode { + case .Copy: { + type_of_copy := (inst >> 54) & 0x3 + switch type_of_copy { + // Register To Register + case 0x0: { + dest_index := (inst >> 4) & 0xF + src_index := inst & 0xF + + src_val := registers.get(src_index) + + when ODIN_DEBUG { + fmt.printf("Copying register %d with value %d to register %d\n", src_index, src_val, dest_index) + } + + registers.set(dest_index, src_val) + } + // Literal to Register + case 0x1: { + lit_50 := (inst >> 4) & 0x3FFFFFFFFFF + dest_index := inst & 0xF + + lit_50 = sign_extend(lit_50, 50) + + when ODIN_DEBUG { + fmt.printf("Copying literal %d to register %d\n", lit_50, dest_index) + } + + registers.set(dest_index, lit_50) + } + // Memory to Register + case 0x2: { + disp_50 := (inst >> 4) & 0x3FFFFFFFFFF + dest_index := inst & 0xF + + mem_index := registers.get(registers.GB) + disp_50 + value := memory[mem_index] + + when ODIN_DEBUG { + fmt.printf("Copying value %d at memory address %d to register %d\n", value, mem_index, dest_index) + } + + registers.set(dest_index, value) + } + // Register to Memory + case 0x3: { + disp_50 := (inst >> 4) & 0x3FFFFFFFFFF + src_index := inst & 0xF + + index := registers.get(registers.GB) + disp_50 + value := registers.get(src_index) + + when ODIN_DEBUG { + fmt.printf("Copying register %d with value %d to memory address %d\n", src_index, value, index) + } + + memory[index] = value + } + case: { + when ODIN_DEBUG { + fmt.println("Invalid Copy, skipping") + } + } + } + } + case .Add: { + type_of_add := (inst >> 55) & 0x1 + switch type_of_add { + // Register and Register + case 0x0: { + dest_index := (inst >> 4) & 0xF + src_index := inst & 0xF + + dest_val := registers.get(dest_index) + src_val := registers.get(src_index) + result := dest_val + src_val + + when ODIN_DEBUG { + fmt.printf("Adding register %d with value %d to register %d with value %d, result is %d\n", + src_index, src_val, dest_index, dest_val, result) + } + + registers.set(dest_index, result) + } + // Literal and Register + case 0x1: { + lit_51 := (inst >> 4) & 0x7FFFFFFFFFF + dest_index := inst & 0xF + + lit_51 = sign_extend(lit_51, 51) + dest_val := registers.get(dest_index) + result := dest_val + lit_51 + + when ODIN_DEBUG { + fmt.printf("Adding %d to register %d with value %d, result is %d\n", lit_51, dest_index, dest_val, result) + } + + registers.set(dest_index, value) + } + case: { + when ODIN_DEBUG { + fmt.println("Invalid Add, skipping") + } + } + } + } + case .Halt: { + when ODIN_DEBUG { + fmt.println("Halting") + } + running = false + } + case: { + when ODIN_DEBUG { + fmt.println("Invalid Instrction, skipping") + } + } + } + + registers.set(registers.GI, registers.get(registers.GI) + 1) + } +} \ No newline at end of file diff --git a/gepvm/registers/registers.odin b/gepvm/registers/registers.odin new file mode 100644 index 0000000..0d1570f --- /dev/null +++ b/gepvm/registers/registers.odin @@ -0,0 +1,38 @@ +package registers + +registers: [dynamic]u64 + +GI :: 0 +GS :: 1 +GB :: 2 +GR :: 3 +GL :: 4 +L0 :: 5 +L1 :: 6 +L2 :: 7 +L3 :: 8 +L4 :: 9 +L5 :: 10 +L6 :: 11 +L7 :: 12 +L8 :: 13 +L9 :: 14 +LF :: 15 + +get :: proc(index: u64) -> u64 { + if index <= GL { + return registers[index] + } + else { + return registers[index + GL] + } +} + +set :: proc(index, value: u64) { + if index <= GL { + registers[index] = value + } + else { + registers[index + GL] = value + } +} \ No newline at end of file