├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── assemble.py ├── disassemble.py ├── main.c ├── nanac.h ├── nanac_builtins.c ├── nanac_vm.c ├── test ├── example.asm └── example.tst ├── test_main.c └── tocfile.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.bin 2 | *.o 3 | *.exe 4 | *.pyc 5 | *.a 6 | test/*.out 7 | test/*.exe.tst -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Harry Roberts 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -Wall -Wextra -DNANAC_TRACE -std=c89 -pedantic 2 | 3 | ifdef RELEASE 4 | OPTFLAGS = -fuse-linker-plugin -flto -O2 -fdata-sections -ffunction-sections -Wl,--gc-sections 5 | endif 6 | 7 | all: nanac.exe test 8 | 9 | .PHONY: test 10 | test: nanac.exe $(patsubst %.asm,%.test,$(wildcard test/*.asm)) 11 | 12 | libnanac.a: nanac_vm.o nanac_builtins.o 13 | $(AR) r $@ $+ 14 | 15 | %.o: %.c 16 | $(CC) $(CFLAGS) $(OPTFLAGS) -o $@ -c $< 17 | 18 | nanac.exe: main.o libnanac.a 19 | $(CC) $(CFLAGS) $(OPTFLAGS) -o $@ $< -L. -lnanac 20 | 21 | clean: 22 | rm -f *.o *.bin *.exe *.pyc *.a test/*.bin test/*.c test/*.out test/*.exe test/*.exe.tst 23 | 24 | %.bin: %.asm 25 | ./assemble.py $< 26 | @echo "" 27 | 28 | test/%.out: test/%.bin 29 | ./nanac.exe $< > $@ 30 | 31 | test/%.c: test/%.bin tocfile.py 32 | ./tocfile.py $< > $@ 33 | 34 | test/%.exe.out: test/%.exe 35 | ./$< | cut -f 1 -d ' ' > test/$*.exe.out 36 | 37 | test/%.exe.tst: test/%.tst 38 | cat test/$*.tst | cut -f 1 -d ' ' > test/$*.exe.tst 39 | 40 | test/%.exe: test/%.c test/%.out 41 | $(CC) $(CFLAGS) $(OPTFLAGS) -I. -o $@ $< nanac_vm.c test_main.c 42 | 43 | test/%.exe.diff: test/%.exe.tst test/%.exe.out 44 | diff test/$*.exe.tst test/$*.exe.out 45 | 46 | test/%.test: test/%.diff test/%.exe.diff 47 | @echo "Done" 48 | 49 | test/%.diff: test/%.out 50 | diff test/$*.tst $< 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nanac 2 | 3 | Nanac is a tiny C virtual machine, with a small Python two-pass assembler. The portable microcode VM is under 200 lines of C and is fully modular, which allows modules to be added at fruntime or compile-time without changing the assembler or core VM code. 4 | 5 | ### Features 6 | 7 | * 256 registers, no stack, 16bit address space 8 | * Register windows for subroutines 9 | * Easy and quick to modify and experiment 10 | * Typeless registers, `void*`, for extensibility 11 | * No heap allocation in VM core 12 | * Microcontroller friendly 13 | * Clean portable C89 / ANSI-C code 14 | * Auto-updating (dis)assembler 15 | * Bytecode to C translator 16 | * Highly modular (can even add opcodes at runtime) 17 | 18 | 19 | ## Example.asm 20 | 21 | ```asm 22 | $ZERO 0 # Friendly names for registers 23 | $ONE 1 24 | 25 | :start jmp to :main 26 | :exit jmp die 27 | :main 28 | reg mov $ZERO $ONE 29 | reg swp $ONE $ZERO 30 | cnd neq $ZERO $ONE 31 | jmp to :exit 32 | 33 | :end 34 | cnd eq $ZERO $ONE 35 | jmp to :exit 36 | ``` 37 | 38 | When executing it will generate a trace: 39 | 40 | ```asm 41 | @0 jmp to 2 0 42 | @2 reg mov 0 1 43 | @3 reg swp 1 0 44 | @4 cnd neq 0 1 45 | @6 cnd eq 0 1 46 | @7 jmp to 1 0 47 | @1 jmp die 0 0 48 | ``` 49 | 50 | The assembler will generate a listing: 51 | 52 | ```asm 53 | 00000200 @0 jmp to :main # test/example.asm:5 54 | 00010000 @1 jmp die # test/example.asm:6 55 | 02000001 @2 reg mov $ZERO $ONE # test/example.asm:8 56 | 02020100 @3 reg swp $ONE $ZERO # test/example.asm:9 57 | 01010001 @4 cnd neq $ZERO $ONE # test/example.asm:10 58 | 00000100 @5 jmp to :exit # test/example.asm:11 59 | 01000001 @6 cnd eq $ZERO $ONE # test/example.asm:14 60 | 00000100 @7 jmp to :exit # test/example.asm:15 61 | ``` 62 | 63 | 64 | ## Using and Extending 65 | 66 | The builtins and vm are contained in `libnanac.a` and `nanac.h` which can be 67 | included in projects as a git submodule. Build with: 68 | 69 | ``` 70 | git submodule add https://github.com/HarryR/nanac 71 | cc -o myproject.exe -Inanac myproject.c -Lnanac -lnanac 72 | ``` 73 | 74 | ### Implementing a native command 75 | 76 | All commands have the same interface, they are passed the CPU context pointer and 77 | two 8 bit values from the data half of the opcode. Each opcode is 4 bytes, two 78 | indicating the module and command, then two arbitrary arguments. 79 | 80 | Access registers through `nanac_reg_get` and `nanac_reg_set`, all registers are 81 | a union type called `nanac_reg_t`. 82 | 83 | ```c 84 | int reg_mov( struct nanac_s *cpu, unsigned char arga, unsigned char argb ) 85 | { 86 | nanac_reg_set(cpu, arga, nanac_reg_get(cpu, argb)); 87 | return NANAC_OK; 88 | } 89 | ``` 90 | 91 | ### Registering a native module 92 | 93 | ```c 94 | int jmp_to( struct nanac_s *cpu, unsigned char arga, unsigned char argb ) 95 | { 96 | // ... 97 | return NANAC_OK 98 | } 99 | 100 | static const nanac_cmd_t _cmds_jmp[4] = { 101 | {"to", &jmp_to}, 102 | {"die", &jmp_die}, 103 | {"sub", &jmp_sub}, 104 | {"ret", &jmp_ret}, 105 | }; 106 | nanac_mods_add(mods, "jmp", 4, _cmds_jmp); 107 | ``` 108 | 109 | ### Example host program 110 | 111 | ```c 112 | int main( int argc, char **argv ) 113 | { 114 | struct nanac_mods_s mods; 115 | struct nanac_s ctx; 116 | int ret; 117 | 118 | nanac_mods_init(&mods); /* setup standard built-in modules + commands */ 119 | nanac_mods_builtins(&mods); 120 | 121 | nanac_init(&ctx, &mods); /* initialise context/CPU with the modules */ 122 | 123 | if( load_file(&ctx, argv[1]) ) /* load bytecode file into ctx.ops */ 124 | { 125 | ret = nanac_run(&ctx); 126 | if( ctx.ops ) 127 | free(ctx.ops); /* free loaded file */ 128 | } 129 | 130 | return ret; 131 | } 132 | ``` 133 | 134 | 135 | ## Opcodes and Instructions 136 | 137 | The machine uses 32bit operations encoded as 4 independent bytes: 138 | 139 | * module ID `uint8` 140 | * cmd ID `uint8` 141 | * arg A `uint8` 142 | * arg B `uint` 143 | 144 | New operations are registered as modules containing commands in your programs source code, 145 | the module and cmd IDs, when new modules are added the assembler & disassembler automatically update their instructions. 146 | 147 | There are `2^8` registers, each is a native sized `void*` pointer. 148 | 149 | There can be up to `2^16` operations (256 kilobytes of code), the instruction pointer `eip` is 16bit. 150 | 151 | Basic Modules: 152 | 153 | * `jmp` - Jumps and subroutines 154 | * `cnd` - Conditionally execute the next opcode 155 | * `reg` - Register manipulation 156 | 157 | Other modules must be implemented by the user. 158 | 159 | ### `jmp` module 160 | 161 | * `to :label` - Immediate jump to an absolute address 162 | * `die` - Terminate program 163 | * `sub :label` - Enter sub-routine, allowing for `ret` to return 164 | * `ret $A $B` - Load opcode from register A, reduce register window by B, run temporary opcode 165 | 166 | 167 | ### `cnd` module 168 | 169 | * `eq $A $B` - If $A and $B are equal, execute next instruction 170 | * `neq $A $B` - If $A and $B aren't the same, execute next instruction 171 | * `nil $A $B` - f either $A or $B are uninitialised (`NULL`), execute next instruction 172 | * `nz $A $B` - If either $A or $B are *not* uninitialised (`NULL`), execute next instruction 173 | 174 | ### `reg` module 175 | 176 | * `swp $A $B` - Swap register B with register A 177 | * `mov $A $B` - Copy register B to register A 178 | * `crl $A $B` - Reset registers A and B to an uninitialised state (e.g. `NULL`) 179 | * `win $A $B` - Increment register window by $A, decrement by $B -------------------------------------------------------------------------------- /assemble.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from __future__ import print_function 3 | import sys 4 | import re 5 | import struct 6 | import os 7 | 8 | 9 | RX_LINE = re.compile(r""" 10 | ^ 11 | ( 12 | ( 13 | ((?P