{"id":16200210,"url":"https://github.com/mobarski/vimes2","last_synced_at":"2025-09-16T04:04:05.124Z","repository":{"id":218135764,"uuid":"745697491","full_name":"mobarski/vimes2","owner":"mobarski","description":"Virtual Machines Experimentation Sandbox 2","archived":false,"fork":false,"pushed_at":"2024-02-25T21:42:39.000Z","size":192,"stargazers_count":16,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-13T19:51:19.686Z","etag":null,"topics":["nim","nim-lang","pcode","virtual-machine","wirth"],"latest_commit_sha":null,"homepage":"","language":"Nim","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mobarski.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-01-19T22:34:09.000Z","updated_at":"2025-01-16T06:54:13.000Z","dependencies_parsed_at":"2024-01-20T17:55:49.856Z","dependency_job_id":"96bb8a0d-b0b3-461a-a513-f058f0dbbed6","html_url":"https://github.com/mobarski/vimes2","commit_stats":null,"previous_names":["mobarski/pcode-vimes","mobarski/vimes2"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobarski%2Fvimes2","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobarski%2Fvimes2/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobarski%2Fvimes2/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mobarski%2Fvimes2/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mobarski","download_url":"https://codeload.github.com/mobarski/vimes2/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247704514,"owners_count":20982292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nim","nim-lang","pcode","virtual-machine","wirth"],"created_at":"2024-10-10T09:29:33.050Z","updated_at":"2025-09-16T04:04:00.082Z","avatar_url":"https://github.com/mobarski.png","language":"Nim","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Vimes2 - Virtual Machines Experimentation Sandbox 2\n\nAnother take on my [Vimes project](https://github.com/mobarski/vimes).\n\n**Vimes** is a collection of virtual machines (VMs) and related resources for studying their performance, ease of  implementation, and ease of use. This sandbox includes a variety of VMs  with different architectures, dispatch techniques and implementations as well as benchmarks and utilities to help measure and  compare their characteristics.\n\n**Warning**: this is experimental / pre-alpha code.\n\n\n\n## Key Takeaways\n\n**Development:**\n\n- VM instruction sets are easy to implement, as each instruction is relatively simple.\n- The biggest investments (so far) when creating a new VM are:\n\n  - Transforming assembly into machine code.\n  - Loading machine code into the VM.\n  - Creating a buffered reader and an int parser for stdin.\n  - Creating CLI.\n  - Creating tests and benchmarks.\n- The ability to define aliases for values (like `n=1`) and mnemonics (like `peek=lpa`) is a huge step forward in assembly UX with very little changes in the assembler (1 token is still 1 token).\n- The ability to define multi-token aliases facilitates keeping the instruction set orthogonal (ie you can quickly add stacks with `peek`, `poke`, `inc`, `dec` and they can either grow up or down).\n- Having all 3 conditional jumps related to comparison (\u003c0, \u003e0, ==0) makes the code much easier to write and read.\n- Having array like access (ptr+offset) vs just pointers is a huge step forward in the assembly UX.\n\n**Performance:**\n\n- 🔥 [benchmarking results](benchmark.md) 🔥\n\n- Wirth's machine is 2-3 times slower than register machines without stack frames.\n\n- C enables more performant dispatch techniques, such as indirect and direct threading.\n\n- Indirect and direct threading are twice as fast as switch-based dispatch.\n\n- Indirect threading seems to be the best approach when writing a VM in C (10% slower but no code remapping).\n\n- The performance of C programs compiled with Zig is similar to that of programs compiled with gcc (±20%).\n\n- Nim's {.computedGoto.} pragma resulted in 10% slower code.\n\n- Translation of machine code into C results in **extremely** fast execution but the return from the procedure call is a bit tricky.\n\n- Zig acting as a C compiler outshines gcc in optimization of the translated machine code.\n\n  \n\n## VM versions\n\n- **mk1** - machine from [Wirth](https://en.wikipedia.org/wiki/Niklaus_Wirth)'s 1976 book [Algorithms + Data Structures = Programs](https://en.wikipedia.org/wiki/Algorithms_%2B_Data_Structures_%3D_Programs)\n\n  - **mk2** - `mk1` with variable number of arguments, swapped a and l\n    - **mk3** - internal bytecode version of `mk2`\n      - **abandoned** as bytecode requires more work than fixed-width words - instructions variants and assembler changes (🛑)\n\n    - **mk4** - switch call threading version of `mk2`\n    - **mk5** - indirect call threading version of `mk2`\n\n- **mk6** - register based vm inspired by [smol](https://github.com/mobarski/smol)\n\n  - **mk11** - similar to `mk6` but conditional jumps are based on `acc` register\n    - **mk13** - similar to `mk11` but closer to the [smol](https://github.com/mobarski/smol) language\n  - **mk12** - one operand version ok `mk6`\n  - **mkXX**- `mk6` with stack frame similar to `mk1` (🌱)\n\n- **mk7** - register based vm inspired by [Human Resource Machine](https://store.steampowered.com/app/375820/Human_Resource_Machine/)\n\n  - **mk7c** - `mk7` implemented in C\n  - **mk7ci** - `mk7` implementation in C, indirect threading\n  - **mk7cd** - `mk7` implementation in C, direct threading\n  - **mk7cc** - `mk7` asm compiled to C code (🚧)\n  - **mk8** - `mk7` extended with pointer operations, subroutine call/return and one more conditional jump (🏆)\n    - **mk8c** - `mk8` implemented in C\n    - **mk8ci** - `mk8` implemented in C, indirect threading\n    - **mk8cd** - `mk8` implemented in C, indirect threading\n    - **mk8cc** - `mk8` asm compiled to C code (🚧)\n  - **mk9** - two operands version of `mk7`\n    - **mk10** - `mk9` extended with pointer operations and subroutine call/return\n      - **mkXX** - `mk10` with better UX (🌱)\n  - **mkXX** - `mk7` extended with cooperative multitasking instructions (🌱)\n\n  \n\n## VM registers / variable names\n\n### mk1 - mk5\n\nThese machines use the same variable names as Wirth's example p-code machine from 1976 (available [here](https://en.wikipedia.org/wiki/P-code_machine#Example_machine)).\n\n- **p** - program register\n\n- **b** - base register\n\n- **t** - topstack register\n\n- **s** - stack\n\n- **i** - instruction register\n\n- **a** - argument register\n\n- **l** - level register\n\n- **code** - program memory\n\n  \n\n### mk6+\n\n- **pc** - program counter\n- **sp** - stack pointer\n- **acc** - accumulator / main register of the VM\n- **code** - program storage memory\n- **mem** - main memory\n- **stack** - return stack\n\n\n\n## VM instructions\n\n### mk1 - mk5\n\n**instructions**\n\n```\n- LIT a   ; load constant (a)\n- INT a   ; increment t-register by (a)\n- LOD a b ; load variable (a) from level (b)\n- STO a b ; store variable (a) at level (b)\n- CAL a b ; call procedute (a) at level (b)\n- JMP a   ; jump to (a)\n- JPC a   ; jump conditional to (a)\n- OPR a   ; execute operation (a) ie OPR ADD\n- EX1 a   ; execute operation (a) from VM extension 1\n- EX2 a   ; execute operation (a) from VM extension 2\n- EX3 a   ; execute operation (a) from VM extension 3\n  ...\n- HLT     ; halt the program\n```\n\n**operations**\n\n```\n- ADD ; (ab--c)  c = a + b\n- SUB ; (ab--c)  c = a - b\n- MUL ; (ab--c)  c = a * b\n- DIV ; (ab--c)  c = \n- RET ; (ab--c)  c = \n- NEG ; (a--b)   b = -a\n- ODD ; (a--b)   b = a % 2\n- MOD ; (ab--c)  c = a % b\n- EQ  ; (ab--c)  c = 1 if a==b else 0\n- NE  ; (ab--c)  c = 1 if a!=b else 0\n- LT  ; (ab--c)  c = 1 if a\u003cb  else 0\n- LE  ; (ab--c)  c = 1 if a\u003c=b else 0\n- GT  ; (ab--c)  c = 1 if a\u003eb  else 0\n- GE  ; (ab--c)  c = 1 if a\u003e=b else 0\n```\n\nThe notation `(ab--c)` describes the stack effect of an operation. It indicates that the  operation expects two items to be on the stack before execution,  referred to as `a` and `b`, and after the operation is executed, these items are replaced by a single item `c` on the stack. The items before `--` are consumed (popped) from the stack, and the items after `--` are produced (pushed) onto the stack.\n\n\n\n**extensions**\n\n```\nExtension 1 - stdtio:\n- PUTI ; (a--) \n- GETI ; (--a) \n- EOF  ; (--a) \n- PUTC ; (a--) \n- GETC ; (--a) \n\nExtension 2 - ALU - bit ops\n- AND ; (ab--c) \n- OR  ; (ab--c) \n- XOR ; (ab--c) \n- NOT ; (a--b)  \n- SHL ; (ab--c) \n- SHR ; (ab--c) \n- SAR ; (ab--c) \n\nExtension 3 - ALU - common ops\n- INC ; (a--)   \n- DEC ; (a--)   \n- EQZ ; (ab--c) \n- NEZ ; (ab--c) \n- LTZ ; (ab--c) \n- LEZ ; (ab--c) \n- GTZ ; (ab--c) \n- GEZ ; (ab--c) \n\n```\n\n### mk6 instruction set\n\n```\n- LIT  a b  ; set memory location (a) to literal value (b)\n- MOV  a b  ; set memory location (a) to value from memory location (b)\n- PEEK a b  ; set memory location (a) with value from memory location indicated by (b)\n- POKE a b  ; set memory location indicated by (a) to value from memory location (b)\n\n- JMP  a 0  ; jump to program location (a)\n- JZ   a b  ; if memory location (a) is zero then jump to program location (b)\n- JNZ  a b  ; if memory location (a) is not zero then jump to program location (b)\n- CAL  a 0  ; call subroutine at program location (a)\n- RET  0 0  ; return from subroutine call\n- HLT  0 0  ; halt the program\n\n- ADD  a b  ; mem[a] = mem[a] + mem[b]\n- SUB  a b  ; mem[a] = mem[a] - mem[b]\n- MUL  a b  ; mem[a] = mem[a] * mem[b]\n- DIV  a b  ; mem[a] = mem[a] / mem[b]\n- MOD  a b  ; mem[a] = mem[a] % mem[b]\n- NEG  a 0  ; mem[a] = -mem[a]\n\n- EQ   a b  ; mem[a] = 1 if mem[a] == mem[b] else 0\n- NE   a b  ; mem[a] = 1 if mem[a] != mem[b] else 0\n- LT   a b  ; mem[a] = 1 if mem[a] \u003c  mem[b] else 0\n- LE   a b  ; mem[a] = 1 if mem[a] \u003c= mem[b] else 0\n- GT   a b  ; mem[a] = 1 if mem[a] \u003e  mem[b] else 0\n- GE   a b  ; mem[a] = 1 if mem[a] \u003e= mem[b] else 0\n\n- PUTC a 0  ; write the value from memory location (a) to stdout (as character)\n- PUTI a 0  ; write the value from memory location (a) to stdout (as integer)\n- GETC a 0  ; read a character from stdin and store it in memory location (a)\n- GETI a 0  ; read an integer from stdin and store it in memory location (a), skip initial whitespaces\n- EOF  a 0  ; set memory location (a) to 1 if stdin indicates end-of-file or to 0 otherwise\n\n```\n\n### mk7 instruction set\n\n```\n- IN  0  ; read input to ACC\n- OUT 0  ; write ACC to output\n- LDA a  ; load memory location (a) to ACC\n- STA a  ; store ACC in memory location (a)\n- ADD a  ; add memory location (a) to ACC\n- SUB a  ; subtract memory location (a) from ACC\n- INC a  ; increase memory location (a) by 1\n- DEC a  ; decrease memory location (a) by 1\n- JMP a  ; jump to address (a)\n- JZ  a  ; jump to address (a) if ACC is zero\n- JN  a  ; jump to address (a) if ACC is negative\n- LIT a  ; load (a) to ACC\n- HLT 0  ; halt the program\n```\n\n\n\n### mk8 instruction set\n\nmk7 instructions extended with:\n\n```\n- CAL a ; call procedure at address (a)\n- RET 0 ; return from procedure\n- LPA a ; load memory location pointed by (a) to ACC\n- SPA a ; store ACC in memory location pointed by (a)\n```\n\nmisc instructions:\n\n```\n- ASR a ; arithmetic shift right ACC by (a)\n- NOP a ; do nothing, (a) can be used to mark labels\n\n```\n\n### mk9 instruction set\n\n```\n- IN  a 0 ; read input to mem[a]\n- OUT a 0 ; write mem[a] to output\n- LIT a b ; mem[a] = b\n- MOV a b ; mem[a] = mem[b]\n- ADD a b ; mem[a] = mem[a] + mem[b]\n- SUB a b ; mem[a] = mem[a] - mem[b]\n- JMP a 0 ; jump to address (a)\n- JZ  a b ; jump to address (a) if mem[b] is zero\n- JN  a b ; jump to address (a) if mem[b] is negative\n- HLT 0 0 ; halt the program\n```\n\n### mk10 instruction set\n\nmk9 instructions extended with\n\n```\n- CAL a 0 ; call procedure at address (a)\n- RET 0 0 ; return from procedure\n- PTM a b ; transfer from pointer (b) to memory location (a)\n- MTP a b ; transfer from memory location (b) to pointer (a)\n- ASR a b ; arithmetic shift right mem[a] by (b)\n- NOP a b ; do nothing, (a) can be used to mark labels\n- JP  a b ; jump to address (a) if mem[b] is positve (\u003e0)\n```\n\n\n\n### mk11 instruction set\n\n```\n- LIT  a b  ; mem[a] = b\n- MOV  a b  ; mem[a] = mem[b]\n- LDA  a 0  ; acc = mem[a] + b\n- LDAP a b  ; acc = mem[mem[a]+mem[b]]\n- STA  a 0  ; mem[a] = acc + b\n- STAP a b  ; mem[mem[a]+mem[b]] = acc\n\n- JMP  a 0  ; jump to program location (a)\n- CAL  a 0  ; call subroutine at (a)\n- RET  0 0  ; return from subroutine call\n- HLT  0 0  ; halt the program\n\n- ADD  a b  ; mem[a] = mem[a] + mem[b]\n- SUB  a b  ; mem[a] = mem[a] - mem[b]\n- MUL  a b  ; mem[a] = mem[a] * mem[b]\n- DIV  a b  ; mem[a] = mem[a] / mem[b]\n- MOD  a b  ; mem[a] = mem[a] % mem[b]\n- NEG  a 0  ; mem[a] = -mem[a]\n\n- CMP  a b  ; acc = mem[a] - mem[b] (compare)\n- JEQ  a 0  ; jump to (a) if acc == 0\n- JLT  a 0  ; jump to (a) if acc \u003c 0\n- JGT  a 0  ; jump to (a) if acc \u003e 0\n- JNE  a 0  ; jump to (a) if acc != 0\n- JLE  a 0  ; jump to (a) if acc \u003c= 0\n- JGE  a 0  ; jump to (a) if acc \u003e= 0\n\n- GET  a 0  ; mem[a] = read integer from stdin (skip whitespace, block)\n- PUT  a 0  ; write mem[a] as integer to stdout\n- EOF  a 0  ; mem[a] = 1 if stdid.eof else 0\n- GETC a 0  ; mem[a] = read character from stdin (block)\n- PUTC a 0  ; write mem[a] as character to stdout\n```\n\n### mk12 instruction set\n\n```\n- LIT  a  ; acc = a\n- LDA  a  ; acc = mem[a]\n- STA  a  ; mem[a] = acc\n- PEEK a  ; acc = mem[mem[a]]\n- POKE a  ; mem[mem[a]] = acc\n\n- JMP  a  ; jump to program location (a)\n- CAL  a  ; call subroutine at (a)\n- RET  0  ; return from subroutine call\n- HLT  0  ; halt the program\n\n- EQ   a  ; acc = 1 if acc == mem[a] else 0\n- NE   a  ; acc = 1 if acc != mem[a] else 0\n- LT   a  ; acc = 1 if acc \u003c  mem[a] else 0\n- LE   a  ; acc = 1 if acc \u003c= mem[a] else 0\n- GT   a  ; acc = 1 if acc \u003e  mem[a] else 0\n- GE   a  ; acc = 1 if acc \u003e= mem[a] else 0\n- JZ   a  ; jump to (a) if acc == 0 \n- JNZ  a  ; jump to (a) if acc != 0\n\n- ADD  a  ; acc += mem[a]\n- SUB  a  ; acc -= mem[a]\n- MUL  a  ; acc *= mem[a]\n- DIV  a  ; acc /= mem[a]\n- MOD  a  ; acc %= mem[a]\n- NEG  0  ; acc = -acc\n- INC  a  ; mem[a] += 1\n- DEC  a  ; mem[b] -= 1\n\n- PUT  0  ; write acc as integer to stdout\n- GET  0  ; acc = read integer from stdin (skip whitespace, block)\n- EOF  0  ; acc = 1 if stdid.eof else 0\n- PUTC 0  ; write acc as character to stdout\n- GETC 0  ; acc = read character from stdin (block)\n```\n\n### mk13 instruction set\n\n```\n- LIT  a b  ; mem[a] = b\n- MOV  a b  ; mem[a] = mem[b]\n- LDA  a b  ; acc = mem[a] + b\n- LDAP a b  ; acc = mem[mem[a]+mem[b]]\n- STA  a b  ; mem[a] = acc + b\n- STAP a b  ; mem[mem[a]+mem[b]] = acc\n\n- JMP  a 0  ; jump to program location (a)\n- CAL  a 0  ; call subroutine at (a)\n- RET  0 0  ; return from subroutine call\n- HLT  0 0  ; halt the program\n\n- ADD  a b  ; mem[a] = mem[a] + mem[b]\n- SUB  a b  ; mem[a] = mem[a] - mem[b]\n- MUL  a b  ; mem[a] = mem[a] * mem[b]\n- DIV  a b  ; mem[a] = mem[a] / mem[b]\n- MOD  a b  ; mem[a] = mem[a] % mem[b]\n- NEG  a 0  ; mem[a] = -mem[a]\n\n- EQ   a b  ; acc = 1 if mem[a] == mem[b] else 0\n- NE   a b  ; acc = 1 if mem[a] != mem[b] else 0\n- LT   a b  ; acc = 1 if mem[a] \u003c  mem[b] else 0\n- LE   a b  ; acc = 1 if mem[a] \u003c= mem[b] else 0\n- GT   a b  ; acc = 1 if mem[a] \u003e  mem[b] else 0\n- GE   a b  ; acc = 1 if mem[a] \u003e= mem[b] else 0\n- JZ   a 0  ; jump to (a) if acc == 0 \n- JNZ  a 0  ; jump to (a) if acc != 0\n\n- GET  a 0  ; mem[a] = read integer from stdin (skip whitespace, block)\n- PUT  a 0  ; write mem[a] as integer to stdout\n- EOF  a 0  ; mem[a] = 1 if stdid.eof else 0\n- GETC a 0  ; mem[a] = read character from stdin (block)\n- PUTC a 0  ; write mem[a] as character to stdout\n```\n\n\n\n## Reference materials\n\n- https://github.com/mobarski/vimes/blob/main/references.md\n- https://github.com/mobarski/vimes\n- https://github.com/mobarski/smol\n- PL/0 and p-code VM:\n  - https://rosettacode.org/wiki/Category:PL/0\n  - https://rosettacode.org/wiki/Category:XPL0\n  - http://pascal.hansotten.com/niklaus-wirth/pl0/\n  - https://en.wikipedia.org/wiki/P-code_machine\n\n- Another World VM:\n  - https://github.com/fabiensanglard/Another-World-Bytecode-Interpreter/blob/master/src/vm.cpp\n  - https://fabiensanglard.net/anotherWorld_code_review/index.php\n  - http://www.anotherworld.fr/anotherworld_uk/another_world.htm\n\n\n\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmobarski%2Fvimes2","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmobarski%2Fvimes2","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmobarski%2Fvimes2/lists"}