├── .gitattributes ├── .gitignore ├── Makefile ├── README.md ├── examples ├── ack.bin ├── ack.c ├── fib.bin ├── fib.c ├── sieve.bin ├── sieve.c ├── tests.bin └── tests.c ├── lib ├── link.ld ├── start.s ├── syscalls.h └── tinyc.c ├── machine.nelua ├── riscvm.c └── riscvm.nelua /.gitattributes: -------------------------------------------------------------------------------- 1 | *.nelua text linguist-language=lua 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.S 2 | *.o 3 | *.out 4 | riscvm -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXAMPLE=fib 2 | FILE=examples/$(EXAMPLE) 3 | NELUA=nelua 4 | TOOLCHAIN=riscv64-elf 5 | CC=$(TOOLCHAIN)-gcc 6 | OBJDUMP=$(TOOLCHAIN)-objdump 7 | OBJCOPY=$(TOOLCHAIN)-objcopy 8 | CFLAGS=-march=rv64im -mabi=lp64 \ 9 | -nostdlib \ 10 | -Wl,-T,lib/link.ld \ 11 | -I lib \ 12 | -O2 13 | CGENPRAGMAS=\ 14 | -Pnogc \ 15 | -Pnochecks \ 16 | -Pnocwarnpragmas \ 17 | -Pnocstaticassert \ 18 | -Pnocfeaturessetup 19 | 20 | example: 21 | $(CC) $(CFLAGS) -o $(FILE).out $(FILE).c lib/tinyc.c lib/start.s 22 | $(OBJDUMP) -M no-aliases -x -d $(FILE).out 23 | $(OBJCOPY) -O binary $(FILE).out $(FILE).bin 24 | $(NELUA) -qr riscvm.nelua $(FILE).bin 25 | 26 | csrc: 27 | $(NELUA) $(CGENPRAGMAS) -o riscvm.c riscvm.nelua 28 | 29 | clean: 30 | rm -f tests/*.out tests/*.bin 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Tiny RISC-V virtual machine written in Nelua (and C as consequence). 2 | 3 | This is a minimal and simple RISC-V ISA emulator 4 | implementing a subset of RV64I instructions. 5 | 6 | Only the instructions required to run a minimal 7 | userspace virtual machine are implemented. 8 | 9 | The emulator allows to run C files compiled with GCC to RISC-V 10 | inside a minimal sandboxed virtual machine. 11 | The virtual machine is able to call host functions through system calls. 12 | 13 | This project has inspired by [libriscv](https://github.com/fwsGonzo/libriscv). 14 | 15 | ## What is interesting about this? 16 | 17 | This is a minimal example on how to interpret C programs 18 | inside a sandboxed virtual machine. One may use this 19 | concept to run compiled programs with GCC on any system 20 | that have the emulator, or to sandbox a C application 21 | inside isolated environment, or to do any kind 22 | of hot reloading in application on the fly 23 | (usually done with scripting languages). 24 | 25 | ## Run examples 26 | 27 | Run examples with Nelua: 28 | ``` 29 | nelua -r riscvm.nelua examples/fib.bin 30 | nelua -r riscvm.nelua examples/ack.bin 31 | nelua -r riscvm.nelua examples/sieve.bin 32 | ``` 33 | 34 | The examples were compiled from the respective C files into RV64I binary. 35 | 36 | Alternatively if you don't have Nelua: 37 | ``` 38 | gcc -O2 -o riscvm riscvm.c 39 | ./riscvm examples/fib.bin 40 | ``` 41 | 42 | ## Compiling an example 43 | 44 | All the examples are already compiled to RV64I, but in case you 45 | want to edit or run a new example, 46 | then use for example `make EXAMPLE=fib.c` to compile `fib.c` into `fib.bin`, 47 | this requires RISC-V elf toolchain. 48 | 49 | ## How it works? 50 | 51 | 0. An example is coded in freestanding C code, 52 | functionality outside the sandboxed environment such as printing to terminal 53 | is implemented through system calls in `lib/syscalls.h` and minimal 54 | libc is implemented in `lib/tinyc.c`. 55 | 1. The C example is compiled to RISC-V elf binary, 56 | using `lib/start.s` to initialize the properly the virtual machine state, 57 | and using special link rules through `lib/link.ld` to adjust the instruction 58 | addresses. 59 | 2. RISC-V bytecode is stripped from the compiled elf binary into a bytecode binary. 60 | 3. The bytecode binary is loaded and run through `riscvm`, 61 | interpreting RV64I instructions. 62 | 4. While interpreting the virtual machine may 63 | call the host through system calls. 64 | 5. The application stops once the `exit` system call is called or if any error occur. 65 | 66 | ## How this was implemented? 67 | 68 | This was implemented by reading the [RISC-V specification](https://riscv.org/technical/specifications/) 69 | 70 | ## Benchmarks 71 | 72 | Equivalent code was run with the Lua 5.4, riscvm and natively 73 | in the same system for some examples, 74 | from the experiments the interpreted code can be 10~20x slower 75 | than native code: 76 | 77 | | example | lua 5.4 | riscvm | x86_64 | 78 | |---------|----------|--------|--------| 79 | | ack | 1070ms | 1022ms | 47ms | 80 | | sieve | 1077ms | 716ms | 64ms | 81 | 82 | NOTE: This VM does not do any kind of JIT and has a very simple implementation, 83 | there are space for optimizations. 84 | 85 | ## Future improvements 86 | 87 | These extensions are not implemented yet and would be useful: 88 | 89 | * M extension - Multiply and division 90 | * F extension - Floating point with single precision 91 | * D extension - Floating point with double precision 92 | 93 | Also more C functions such as malloc/free/memcpy could 94 | be implemented yet as system calls. -------------------------------------------------------------------------------- /examples/ack.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edubart/riscvm/bd9853ea774be625321c63bf51406040786025e0/examples/ack.bin -------------------------------------------------------------------------------- /examples/ack.c: -------------------------------------------------------------------------------- 1 | #include "syscalls.h" 2 | 3 | static long ack(long m, long n) { 4 | if(m == 0) { 5 | return n + 1; 6 | } 7 | if(n == 0) { 8 | return ack(m - 1, 1); 9 | } 10 | return ack(m - 1, ack(m, n - 1)); 11 | } 12 | 13 | int main() { 14 | long res = ack(3, 10); 15 | sys_prints("ack result:"); 16 | sys_printi(res); 17 | sys_assert(res == 8189); 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /examples/fib.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edubart/riscvm/bd9853ea774be625321c63bf51406040786025e0/examples/fib.bin -------------------------------------------------------------------------------- /examples/fib.c: -------------------------------------------------------------------------------- 1 | #include "syscalls.h" 2 | 3 | static unsigned long fib(unsigned long n) { 4 | unsigned long a = 0; 5 | unsigned long b = 1; 6 | for(unsigned long i = 1; i <= n; i += 1) { 7 | unsigned long nb = a + b; 8 | a = b; 9 | b = nb; 10 | } 11 | return a; 12 | } 13 | 14 | int main() { 15 | unsigned long res = fib(20); 16 | sys_prints("fib result:"); 17 | sys_printi(res); 18 | sys_assert(res == 6765); 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /examples/sieve.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edubart/riscvm/bd9853ea774be625321c63bf51406040786025e0/examples/sieve.bin -------------------------------------------------------------------------------- /examples/sieve.c: -------------------------------------------------------------------------------- 1 | #include "syscalls.h" 2 | 3 | bool is_prime[10000000+1]; 4 | 5 | static int64_t sieve(int64_t N) { 6 | is_prime[1] = false; 7 | for(int64_t n = 2; n <= N; n += 1) { 8 | is_prime[n] = true; 9 | } 10 | int64_t nprimes = 0; 11 | for(int64_t n = 2; n <= N; n += 1) { 12 | if(is_prime[n]) { 13 | nprimes = nprimes + 1; 14 | for(int64_t m = n + n; m <= N; m += n) { 15 | is_prime[m] = false; 16 | } 17 | } 18 | } 19 | return nprimes; 20 | } 21 | 22 | int main() { 23 | int64_t res = sieve(10000000); 24 | sys_prints("sieve result:"); 25 | sys_printi(res); 26 | sys_assert(res == 664579); 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /examples/tests.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/edubart/riscvm/bd9853ea774be625321c63bf51406040786025e0/examples/tests.bin -------------------------------------------------------------------------------- /examples/tests.c: -------------------------------------------------------------------------------- 1 | #include "syscalls.h" 2 | #include 3 | typedef __int128 int128_t; 4 | typedef unsigned __int128 uint128_t; 5 | 6 | void test_mul() { 7 | volatile int64_t a = 5, b = 7; 8 | sys_assert((a * b) == 35); 9 | } 10 | 11 | void test_mulhu() { 12 | volatile int128_t a = -5; 13 | volatile int128_t b = -7; 14 | sys_assert((a * b) == 35); 15 | } 16 | 17 | void test_div() { 18 | volatile int64_t a = 37, b = 7; 19 | sys_assert((a / b) == 5); 20 | b = 0; sys_assert((a / b) == -1); // divide by zero 21 | a = 0x8000000000000000; b = -1; sys_assert((a / b) == a); // divide overflow 22 | } 23 | 24 | void test_divu() { 25 | volatile uint64_t a = 37, b = 7; 26 | sys_assert((a / b) == 5); // divide 27 | b = 0; sys_assert((a / b) == 0xffffffffffffffff); // divide by zero 28 | } 29 | 30 | void test_rem() { 31 | volatile int64_t a = 37, b = 7; 32 | sys_assert((a % b) == 2); 33 | b = 0; sys_assert((a % b) == a); // divide by zero 34 | a = 0x8000000000000000; b = -1; sys_assert((a % b) == 0); // divide overflow 35 | } 36 | 37 | void test_remu() { 38 | volatile uint64_t a = 37, b = 7; 39 | sys_assert((a % b) == 2); 40 | b = 0; sys_assert((a % b) == a); // divide by zero 41 | } 42 | 43 | void test_mulw() { 44 | volatile int32_t a = 5, b = 7; 45 | sys_assert((a * b) == 35); 46 | } 47 | 48 | void test_divw() { 49 | volatile int32_t a = 37, b = 7; 50 | sys_assert((a / b) == 5); // divide 51 | b = 0; sys_assert((a / b) == -1); // divide by zero 52 | a = 0x80000000; b = -1; sys_assert((a / b) == a); // divide overflow 53 | } 54 | 55 | void test_divuw() { 56 | volatile uint32_t a = 37, b = 7; 57 | sys_assert((a / b) == 5); // divide 58 | b = 0; sys_assert((a / b) == 0xffffffff); // divide by zero 59 | } 60 | 61 | void test_remw() { 62 | volatile int32_t a = 37, b = 7; 63 | sys_assert((a % b) == 2); 64 | b = 0; sys_assert((a % b) == a); // divide by zero 65 | a = 0x80000000; b = -1; sys_assert((a % b) == 0); // divide overflow 66 | } 67 | 68 | void test_remuw() { 69 | volatile uint32_t a = 37, b = 7; 70 | sys_assert((a % b) == 2); 71 | b = 0; sys_assert((a % b) == a); // divide by zero 72 | } 73 | 74 | int main() { 75 | test_mul(); 76 | test_mulhu(); 77 | test_div(); 78 | test_divu(); 79 | test_rem(); 80 | test_remu(); 81 | 82 | test_mulw(); 83 | test_divw(); 84 | test_divuw(); 85 | test_remw(); 86 | test_remuw(); 87 | return 0; 88 | } 89 | -------------------------------------------------------------------------------- /lib/link.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", 2 | "elf64-littleriscv") 3 | OUTPUT_ARCH(riscv) 4 | ENTRY(_start) 5 | SEARCH_DIR("=/usr/riscv64-elf/lib"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib"); 6 | SECTIONS 7 | { 8 | /* Read-only sections, merged into text segment: */ 9 | PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x1000 - SIZEOF_HEADERS)); 10 | . = SEGMENT_START("text-segment", 0x1000); 11 | .interp : { *(.interp) } 12 | .note.gnu.build-id : { *(.note.gnu.build-id) } 13 | .hash : { *(.hash) } 14 | .gnu.hash : { *(.gnu.hash) } 15 | .dynsym : { *(.dynsym) } 16 | .dynstr : { *(.dynstr) } 17 | .gnu.version : { *(.gnu.version) } 18 | .gnu.version_d : { *(.gnu.version_d) } 19 | .gnu.version_r : { *(.gnu.version_r) } 20 | .rela.dyn : 21 | { 22 | *(.rela.init) 23 | *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) 24 | *(.rela.fini) 25 | *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) 26 | *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) 27 | *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) 28 | *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) 29 | *(.rela.ctors) 30 | *(.rela.dtors) 31 | *(.rela.got) 32 | *(.rela.sdata .rela.sdata.* .rela.gnu.linkonce.s.*) 33 | *(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*) 34 | *(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*) 35 | *(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*) 36 | *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) 37 | PROVIDE_HIDDEN (__rela_iplt_start = .); 38 | *(.rela.iplt) 39 | PROVIDE_HIDDEN (__rela_iplt_end = .); 40 | } 41 | .rela.plt : 42 | { 43 | *(.rela.plt) 44 | } 45 | .init : 46 | { 47 | KEEP (*(SORT_NONE(.init))) 48 | } 49 | .plt : { *(.plt) } 50 | .iplt : { *(.iplt) } 51 | .text : 52 | { 53 | *(.text.unlikely .text.*_unlikely .text.unlikely.*) 54 | *(.text.exit .text.exit.*) 55 | *(.text.startup .text.startup.*) 56 | *(.text.hot .text.hot.*) 57 | *(SORT(.text.sorted.*)) 58 | *(.text .stub .text.* .gnu.linkonce.t.*) 59 | /* .gnu.warning sections are handled specially by elf.em. */ 60 | *(.gnu.warning) 61 | } 62 | .fini : 63 | { 64 | KEEP (*(SORT_NONE(.fini))) 65 | } 66 | PROVIDE (__etext = .); 67 | PROVIDE (_etext = .); 68 | PROVIDE (etext = .); 69 | .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } 70 | .rodata1 : { *(.rodata1) } 71 | .sdata2 : 72 | { 73 | *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) 74 | } 75 | .sbss2 : { *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) } 76 | .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } 77 | .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } 78 | .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } 79 | .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } 80 | /* These sections are generated by the Sun/Oracle C++ compiler. */ 81 | .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } 82 | /* Adjust the address for the data segment. We want to adjust up to 83 | the same address within the page on the next page up. */ 84 | . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); 85 | /* Exception handling */ 86 | .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } 87 | .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } 88 | .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } 89 | .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } 90 | /* Thread Local Storage sections */ 91 | .tdata : 92 | { 93 | PROVIDE_HIDDEN (__tdata_start = .); 94 | *(.tdata .tdata.* .gnu.linkonce.td.*) 95 | } 96 | .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } 97 | .preinit_array : 98 | { 99 | PROVIDE_HIDDEN (__preinit_array_start = .); 100 | KEEP (*(.preinit_array)) 101 | PROVIDE_HIDDEN (__preinit_array_end = .); 102 | } 103 | .init_array : 104 | { 105 | PROVIDE_HIDDEN (__init_array_start = .); 106 | KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) 107 | KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) 108 | PROVIDE_HIDDEN (__init_array_end = .); 109 | } 110 | .fini_array : 111 | { 112 | PROVIDE_HIDDEN (__fini_array_start = .); 113 | KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) 114 | KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) 115 | PROVIDE_HIDDEN (__fini_array_end = .); 116 | } 117 | .ctors : 118 | { 119 | /* gcc uses crtbegin.o to find the start of 120 | the constructors, so we make sure it is 121 | first. Because this is a wildcard, it 122 | doesn't matter if the user does not 123 | actually link against crtbegin.o; the 124 | linker won't look for a file to match a 125 | wildcard. The wildcard also means that it 126 | doesn't matter which directory crtbegin.o 127 | is in. */ 128 | KEEP (*crtbegin.o(.ctors)) 129 | KEEP (*crtbegin?.o(.ctors)) 130 | /* We don't want to include the .ctor section from 131 | the crtend.o file until after the sorted ctors. 132 | The .ctor section from the crtend file contains the 133 | end of ctors marker and it must be last */ 134 | KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) 135 | KEEP (*(SORT(.ctors.*))) 136 | KEEP (*(.ctors)) 137 | } 138 | .dtors : 139 | { 140 | KEEP (*crtbegin.o(.dtors)) 141 | KEEP (*crtbegin?.o(.dtors)) 142 | KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) 143 | KEEP (*(SORT(.dtors.*))) 144 | KEEP (*(.dtors)) 145 | } 146 | .jcr : { KEEP (*(.jcr)) } 147 | .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } 148 | .dynamic : { *(.dynamic) } 149 | . = DATA_SEGMENT_RELRO_END (0, .); 150 | .data : 151 | { 152 | __DATA_BEGIN__ = .; 153 | *(.data .data.* .gnu.linkonce.d.*) 154 | SORT(CONSTRUCTORS) 155 | } 156 | .data1 : { *(.data1) } 157 | .got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) } 158 | /* We want the small data sections together, so single-instruction offsets 159 | can access them all, and initialized data all before uninitialized, so 160 | we can shorten the on-disk segment size. */ 161 | .sdata : 162 | { 163 | __SDATA_BEGIN__ = .; 164 | *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*) 165 | *(.sdata .sdata.* .gnu.linkonce.s.*) 166 | } 167 | _edata = .; PROVIDE (edata = .); 168 | . = .; 169 | __bss_start = .; 170 | .sbss : 171 | { 172 | *(.dynsbss) 173 | *(.sbss .sbss.* .gnu.linkonce.sb.*) 174 | *(.scommon) 175 | } 176 | .bss : 177 | { 178 | *(.dynbss) 179 | *(.bss .bss.* .gnu.linkonce.b.*) 180 | *(COMMON) 181 | /* Align here to ensure that the .bss section occupies space up to 182 | _end. Align after .bss to ensure correct alignment even if the 183 | .bss section disappears because there are no input sections. 184 | FIXME: Why do we need it? When there is no .bss section, we do not 185 | pad the .data section. */ 186 | . = ALIGN(. != 0 ? 64 / 8 : 1); 187 | } 188 | . = ALIGN(64 / 8); 189 | . = SEGMENT_START("ldata-segment", .); 190 | . = ALIGN(64 / 8); 191 | __BSS_END__ = .; 192 | __global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800, 193 | MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800)); 194 | _end = .; PROVIDE (end = .); 195 | . = DATA_SEGMENT_END (.); 196 | /* Stabs debugging sections. */ 197 | .stab 0 : { *(.stab) } 198 | .stabstr 0 : { *(.stabstr) } 199 | .stab.excl 0 : { *(.stab.excl) } 200 | .stab.exclstr 0 : { *(.stab.exclstr) } 201 | .stab.index 0 : { *(.stab.index) } 202 | .stab.indexstr 0 : { *(.stab.indexstr) } 203 | .comment 0 : { *(.comment) } 204 | .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } 205 | /* DWARF debug sections. 206 | Symbols in the DWARF debugging sections are relative to the beginning 207 | of the section so we begin them at 0. */ 208 | /* DWARF 1 */ 209 | .debug 0 : { *(.debug) } 210 | .line 0 : { *(.line) } 211 | /* GNU DWARF 1 extensions */ 212 | .debug_srcinfo 0 : { *(.debug_srcinfo) } 213 | .debug_sfnames 0 : { *(.debug_sfnames) } 214 | /* DWARF 1.1 and DWARF 2 */ 215 | .debug_aranges 0 : { *(.debug_aranges) } 216 | .debug_pubnames 0 : { *(.debug_pubnames) } 217 | /* DWARF 2 */ 218 | .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } 219 | .debug_abbrev 0 : { *(.debug_abbrev) } 220 | .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } 221 | .debug_frame 0 : { *(.debug_frame) } 222 | .debug_str 0 : { *(.debug_str) } 223 | .debug_loc 0 : { *(.debug_loc) } 224 | .debug_macinfo 0 : { *(.debug_macinfo) } 225 | /* SGI/MIPS DWARF 2 extensions */ 226 | .debug_weaknames 0 : { *(.debug_weaknames) } 227 | .debug_funcnames 0 : { *(.debug_funcnames) } 228 | .debug_typenames 0 : { *(.debug_typenames) } 229 | .debug_varnames 0 : { *(.debug_varnames) } 230 | /* DWARF 3 */ 231 | .debug_pubtypes 0 : { *(.debug_pubtypes) } 232 | .debug_ranges 0 : { *(.debug_ranges) } 233 | /* DWARF Extension. */ 234 | .debug_macro 0 : { *(.debug_macro) } 235 | .debug_addr 0 : { *(.debug_addr) } 236 | .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } 237 | /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } 238 | } 239 | -------------------------------------------------------------------------------- /lib/start.s: -------------------------------------------------------------------------------- 1 | .section .init, "ax" 2 | .global _start 3 | _start: 4 | .option push 5 | .option norelax 6 | la gp, __global_pointer$ 7 | .option pop 8 | li a1,0 9 | li a0,0 10 | jal ra, main 11 | jal ra, exit 12 | .end 13 | -------------------------------------------------------------------------------- /lib/syscalls.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | enum { 6 | SYSCALL_EXIT = 10000, 7 | SYSCALL_ABORT = 10001, 8 | 9 | // SYSCALL_MALLOC = 10002, 10 | // SYSCALL_CALLOC = 10003, 11 | // SYSCALL_REALLOC = 10004, 12 | // SYSCALL_FREE = 10005, 13 | 14 | // SYSCALL_MEMCPY = 10006, 15 | SYSCALL_MEMSET = 10007, 16 | // SYSCALL_MEMMOVE = 10008, 17 | // SYSCALL_MEMCMP = 10009, 18 | 19 | SYSCALL_PRINTS = 10101, 20 | SYSCALL_PRINTI = 10102, 21 | }; 22 | 23 | static inline long syscall0(long n) { 24 | register long a0 asm("a0") = 0; 25 | register long syscall_id asm("a7") = n; 26 | asm volatile ("scall" : "+r"(a0) : "r"(syscall_id)); 27 | return a0; 28 | } 29 | 30 | static inline long syscall1(long n, long arg0) { 31 | register long a0 asm("a0") = arg0; 32 | register long syscall_id asm("a7") = n; 33 | asm volatile ("scall" : "+r"(a0) : "r"(syscall_id)); 34 | return a0; 35 | } 36 | 37 | static inline long syscall3(long n, long arg0, long arg1, long arg2) { 38 | register long a0 asm("a0") = arg0; 39 | register long a1 asm("a1") = arg1; 40 | register long a2 asm("a2") = arg2; 41 | register long syscall_id asm("a7") = n; 42 | asm volatile ("scall" : "+r"(a0) : "r"(a1), "r"(a2), "r"(syscall_id)); 43 | return a0; 44 | } 45 | 46 | static inline void sys_exit(long status) { syscall1(SYSCALL_EXIT, status); } 47 | static inline void sys_abort() { syscall0(SYSCALL_ABORT);} 48 | 49 | static inline void sys_prints(const char* s) { syscall1(SYSCALL_PRINTS, (long)(s)); } 50 | static inline void sys_printi(long i) { syscall1(SYSCALL_PRINTI, i); } 51 | 52 | static inline void* sys_memset(void* vdest, int ch, size_t size) { syscall3(SYSCALL_MEMSET, (long)vdest, ch, (long)size); } 53 | 54 | #define sys_assert(expr) { \ 55 | if(!(expr)) { \ 56 | sys_prints("assertion failed: (" #expr ")"); \ 57 | sys_abort(); \ 58 | }} 59 | -------------------------------------------------------------------------------- /lib/tinyc.c: -------------------------------------------------------------------------------- 1 | #include "syscalls.h" 2 | 3 | void* memset(void* vdest, int ch, size_t size) { 4 | return sys_memset(vdest, ch, size); 5 | } 6 | 7 | __attribute__((noreturn)) void exit(int code) { 8 | sys_exit(code); 9 | __builtin_unreachable(); 10 | } 11 | -------------------------------------------------------------------------------- /machine.nelua: -------------------------------------------------------------------------------- 1 | require 'C.stdio' 2 | require 'C.string' 3 | 4 | global MEMORY_SIZE = 1024*1024*128 5 | global MEMORY_BASE = 0x1000 6 | global REGISTERS = @enum { 7 | ZERO = 0, RA, SP, GP, TP, T0, T1, T2, S0, S1, A0, A1, A2, A3, A4, A5, 8 | A6, A7, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, T3, T4, T5, T6 9 | } 10 | local REGNAMES: [32]string = { 11 | "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", 12 | "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" 13 | } 14 | global SYSCALLS = @enum{ 15 | EXIT = 10000, 16 | ABORT = 10001, 17 | 18 | -- MALLOC = 10002, 19 | -- CALLOC = 10003, 20 | -- REALLOC = 10004, 21 | -- FREE = 10005, 22 | 23 | -- MEMCPY = 10006, 24 | MEMSET = 10007, 25 | -- MEMMOVE = 10008, 26 | -- MEMCMP = 10009, 27 | 28 | PRINTS = 10101, 29 | PRINTI = 10102, 30 | } 31 | 32 | -- The CPU record with registers. 33 | global Machine = @record{ 34 | running: boolean, 35 | pc: int64, -- program counter 36 | regs: [32]uint64, -- program registers 37 | memory: [MEMORY_SIZE]uint8, -- program memory 38 | exitcode: int64, 39 | } 40 | 41 | -- Load instruction and data from a binary file into memory. 42 | function Machine:loadfile(filename: string) 43 | local fp = C.fopen(filename, 'rb') 44 | if not fp then error('failed to open file') end 45 | C.fseek(fp, 0, C.SEEK_END) 46 | local size = C.ftell(fp) 47 | C.fseek(fp, 0, C.SEEK_SET) 48 | C.fread(&self.memory[MEMORY_BASE], size, 1, fp) 49 | C.fclose(fp) 50 | self.regs[REGISTERS.SP] = MEMORY_SIZE 51 | self.pc = MEMORY_BASE 52 | end 53 | 54 | -- Fetch next instruction. 55 | function Machine:fetch(): uint32 56 | return $(@*uint32)(&self.memory[self.pc]) 57 | end 58 | 59 | -- Read a value from guest memory. 60 | function Machine:read(T: type, addr: uint64) 61 | local UT = #[T.value:unsigned_type()]# 62 | return (@T)($(@*UT)(&self.memory[addr])) 63 | end 64 | 65 | -- Return a host pointer to address in guest memory. 66 | function Machine:getptr(addr: uint64): pointer 67 | return (@pointer)(&self.memory[addr]) 68 | end 69 | 70 | -- Write a value to guest memory. 71 | function Machine:write(addr: uint64, val: auto) 72 | $(@*#[val.type]#)(&self.memory[addr]) = val 73 | end 74 | 75 | -- Dump all registers (for debugging). 76 | function Machine:dump_registers() 77 | ## for i=0,31,4 do 78 | ## for j=0,3 do 79 | ## local k = i + j 80 | C.printf(#['x'..k..' (%s) = 0x%08x\t'..(j==3 and '\n' or '')]#, REGNAMES[#[k]#], self.regs[#[k]#]) 81 | ## end 82 | ## end 83 | C.printf('pc = 0x%08x\n', self.pc) 84 | end 85 | 86 | -- Macro for debugging instructions. 87 | ## local function debug_inst(name, regs, vals) 88 | ## if DEBUG then 89 | C.printf(#['\t0x%x:\t\t0x%08x\t'..name]#, self.pc, inst) 90 | ## if regs then 91 | ## for _,ri in ipairs(regs) do 92 | C.printf(' %s', REGNAMES[#[ri]#]) 93 | ## end 94 | ## end 95 | ## if vals then 96 | ## for i,val in ipairs(vals) do 97 | C.printf(' %d', #[val]#) 98 | ## end 99 | ## end 100 | C.puts('') 101 | ## end 102 | ## end 103 | 104 | -- Read immediate encoding from instructions. 105 | ## local function rimm(inst, instoff, immend, immbeg) 106 | ## immbeg = immbeg or immend 107 | ## local bits = ((1 << (immend + 1)) - 1) & (~((1 << immbeg) - 1)) 108 | ## if instoff > immbeg then 109 | in (#[inst]# >> #[instoff - immbeg]#) & #[bits]# 110 | ## else 111 | in (#[inst]# << #[immbeg - instoff]#) & #[bits]# 112 | ## end 113 | ## end 114 | 115 | -- Read a range of bits from an instruction. 116 | ## local function rbits(inst, bend, bbeg) 117 | ## local bits = ((1<<(bend - bbeg + 1))-1) 118 | in (#[inst]# >> #[bbeg]#) & #[bits]# 119 | ## end 120 | 121 | -- Sign extend an integer with `n` bits to 64 bits. 122 | ## local function sext(val, n) 123 | in (@int64)(((@int32)(#[val]#) << #[32 - n]#) >>> #[32 - n]#) 124 | ## end 125 | 126 | -- Combine bits doing OR operations on many expressions. 127 | ##[[ 128 | local function orbits(...) 129 | local expr = select(1, ...) 130 | for i=2,select('#',...) do 131 | expr = aster.BinaryOp{expr, 'bor', (select(i, ...))} 132 | end 133 | return expr 134 | end 135 | ]] 136 | 137 | -- Implement all system calls. 138 | function Machine:handle_syscall(code: uint64): uint64 139 | switch code do 140 | case SYSCALLS.EXIT then 141 | self.running = false 142 | self.exitcode = (@int64)(self.regs[REGISTERS.A0]) 143 | case SYSCALLS.ABORT then 144 | error('aborted!') 145 | case SYSCALLS.MEMSET then 146 | local dest = self:getptr(self.regs[REGISTERS.A0]) 147 | local c = self.regs[REGISTERS.A1] 148 | local len = self.regs[REGISTERS.A2] 149 | local res = C.memset(dest, (@cint)(c), len) 150 | return (@uint64)(res) 151 | case SYSCALLS.PRINTS then 152 | local s = (@cstring)(self:getptr(self.regs[REGISTERS.A0])) 153 | print(s) 154 | case SYSCALLS.PRINTI then 155 | local i = (@int64)(self.regs[REGISTERS.A0]) 156 | print(i) 157 | else 158 | error('illegal system call') 159 | end 160 | return 0 161 | end 162 | 163 | -- Execute an instruction. 164 | function Machine:execute(inst: uint32) 165 | local opcode: uint32 = rbits!(inst, 6, 0) 166 | local rd: uint32 = rbits!(inst, 11, 7) 167 | local rs1: uint32 = rbits!(inst, 19, 15) 168 | local rs2: uint32 = rbits!(inst, 24, 20) 169 | 170 | switch opcode do 171 | case 0b0000011 then -- load 172 | local funct3 = rbits!(inst, 14, 12) 173 | local imm = sext!(rimm!(inst, 20, 11, 0), 12) 174 | local addr = self.regs[rs1] + (@uint64)(imm) 175 | local val: int64 176 | switch funct3 do 177 | case 0b000 then ## debug_inst('lb', {rd, rs1}, {imm}) 178 | val = self:read(@int8, addr) 179 | case 0b001 then ## debug_inst('lh', {rd, rs1}, {imm}) 180 | val = self:read(@int16, addr) 181 | case 0b010 then ## debug_inst('lw', {rd, rs1}, {imm}) 182 | val = self:read(@int32, addr) 183 | case 0b011 then ## debug_inst('ld', {rd, rs1}, {imm}) 184 | val = self:read(@int64, addr) 185 | case 0b100 then ## debug_inst('lbu', {rd, rs1}, {imm}) 186 | val = self:read(@uint8, addr) 187 | case 0b101 then ## debug_inst('lhu', {rd, rs1}, {imm}) 188 | val = self:read(@uint16, addr) 189 | case 0b110 then ## debug_inst('lwu', {rd, rs1}, {imm}) 190 | val = self:read(@uint32, addr) 191 | else 192 | error('illegal load instruction') 193 | end 194 | if likely(rd ~= 0) then 195 | self.regs[rd] = (@uint64)(val) 196 | end 197 | case 0b0100011 then -- store 198 | local funct3 = rbits!(inst, 14, 12) 199 | local imm = sext!(orbits!(rimm!(inst, 25, 11, 5),rimm!(inst, 7, 4, 0)), 12) 200 | local addr = self.regs[rs1] + (@uint64)(imm) 201 | local val = self.regs[rs2] 202 | switch funct3 do 203 | case 0b000 then ## debug_inst('sb', {rs2, rs1}, {imm}) 204 | self:write(addr, (@uint8)(val)) 205 | case 0b001 then ## debug_inst('sh', {rs2, rs1}, {imm}) 206 | self:write(addr, (@uint16)(val)) 207 | case 0b010 then ## debug_inst('sw', {rs2, rs1}, {imm}) 208 | self:write(addr, (@uint32)(val)) 209 | case 0b011 then ## debug_inst('sd', {rs2, rs1}, {imm}) 210 | self:write(addr, val) 211 | else 212 | error('illegal store instruction') 213 | end 214 | case 0b0010011 then -- op-imm 215 | local funct3 = rbits!(inst, 14, 12) 216 | local imm = sext!(rimm!(inst, 20, 11, 0), 12) 217 | local shamt = rbits!(inst, 25, 20) 218 | local val: int64 = (@int64)(self.regs[rs1]) 219 | switch funct3 do 220 | case 0b000 then ## debug_inst('addi', {rd, rs1}, {imm}) 221 | val = val + imm 222 | case 0b001 then ## debug_inst('slli', {rd, rs1}, {shamt}) 223 | val = val << shamt 224 | case 0b010 then ## debug_inst('slti', {rd, rs1}, {imm}) 225 | if val < imm then val = 1 else val = 0 end 226 | case 0b011 then ## debug_inst('sltiu', {rd, rs1}, {imm}) 227 | if (@uint64)(val) < (@uint64)(imm) then val = 1 else val = 0 end 228 | case 0b100 then ## debug_inst('xori', {rd, rs1}, {imm}) 229 | val = val ~ imm 230 | case 0b101 then 231 | local funct6 = rbits!(inst, 31, 26) 232 | switch funct6 >> 4 do 233 | case 0b00 then ## debug_inst('srli', {rd, rs1}, {shamt}) 234 | val = val >> shamt 235 | case 0b01 then ## debug_inst('srai', {rd, rs1}, {shamt}) 236 | val = val >>> shamt 237 | else 238 | error('illegal op-imm shift instruction') 239 | end 240 | case 0b110 then ## debug_inst('ori', {rd, rs1}, {imm}) 241 | val = val | imm 242 | case 0b111 then ## debug_inst('andi', {rd, rs1}, {imm}) 243 | val = val & imm 244 | else 245 | error('illegal op-imm instruction') 246 | end 247 | if likely(rd ~= 0) then 248 | self.regs[rd] = (@uint64)(val) 249 | end 250 | case 0b0011011 then -- op-imm-32 251 | local funct3 = rbits!(inst, 14, 12) 252 | local imm = sext!(rimm!(inst, 20, 11, 0), 12) 253 | local val: int64 = (@int64)(self.regs[rs1]) 254 | switch funct3 do 255 | case 0b000 then ## debug_inst('addiw', {rd, rs1}, {imm}) 256 | val = (@int32)(val + imm) 257 | case 0b001 then ## debug_inst('slliw', {rd, rs1}, {imm}) 258 | val = (@int32)(val << imm) 259 | case 0b101 then 260 | local shamt = rs2 261 | local funct7 = rbits!(inst, 31, 25) 262 | switch funct7 >> 5 do 263 | case 0b00 then ## debug_inst('srliw', {rd, rs1}, {shamt}) 264 | val = (@int32)(val >> shamt) 265 | case 0b01 then ## debug_inst('sraiw', {rd, rs1}, {shamt}) 266 | val = (@int32)(val >>> shamt) 267 | else 268 | error('illegal op-imm-32 shift instruction') 269 | end 270 | else 271 | error('illegal op-imm-32 instruction') 272 | end 273 | if likely(rd ~= 0) then 274 | self.regs[rd] = (@uint64)(val) 275 | end 276 | case 0b0110011 then -- op 277 | local funct3 = rbits!(inst, 14, 12) 278 | local funct7 = rbits!(inst, 31, 25) 279 | local val1: int64 = (@int64)(self.regs[rs1]) 280 | local val2: int64 = (@int64)(self.regs[rs2]) 281 | local val: int64 282 | switch (funct7 << 3) | funct3 do 283 | case 0b0000000000 then ## debug_inst('add', {rd, rs1, rs2}) 284 | val = val1 + val2 285 | case 0b0100000000 then ## debug_inst('sub', {rd, rs1, rs2}) 286 | val = val1 - val2 287 | case 0b0000000001 then ## debug_inst('sll', {rd, rs1, rs2}) 288 | val = val1 << (val2 & 0b11111) 289 | case 0b0000000010 then ## debug_inst('slt', {rd, rs1, rs2}) 290 | if val1 < val2 then val = 1 else val = 0 end 291 | case 0b0000000011 then ## debug_inst('sltu', {rd, rs1, rs2}) 292 | if (@uint64)(val1) < (@uint64)(val2) then val = 1 else val = 0 end 293 | case 0b0000000100 then ## debug_inst('xor', {rd, rs1, rs2}) 294 | val = val1 ~ val2 295 | case 0b0000000101 then ## debug_inst('srl', {rd, rs1, rs2}) 296 | val = val1 >> (val2 & 0b11111) 297 | case 0b0100000101 then ## debug_inst('sra', {rd, rs1, rs2}) 298 | val = val1 >>> (val2 & 0b11111) 299 | case 0b0000000110 then ## debug_inst('or', {rd, rs1, rs2}) 300 | val = val1 | val2 301 | case 0b0000000111 then ## debug_inst('and', {rd, rs1, rs2}) 302 | val = val1 & val2 303 | -- M extension 304 | case 0b0000001000 then ## debug_inst('mul', {rd, rs1, rs2}) 305 | val = val1 * val2 306 | case 0b0000001001 then ## debug_inst('mulh', {rd, rs1, rs2}) 307 | val = (@int64)((@uint64)(((@int128)(val1) * (@int128)(val2)) >> 64)) 308 | case 0b0000001010 then ## debug_inst('mulhsu', {rd, rs1, rs2}) 309 | val = (@int64)((@uint64)(((@int128)(val1) * (@int128)((@uint64)(val2))) >> 64)) 310 | case 0b0000001011 then ## debug_inst('mulhu', {rd, rs1, rs2}) 311 | val = (@int64)((@uint64)(((@int128)((@uint64)(val1)) * (@int128)((@uint64)(val2))) >> 64)) 312 | case 0b0000001100 then ## debug_inst('div', {rd, rs1, rs2}) 313 | local dividend, divisor = val1, val2 314 | if unlikely(dividend == (@int64)(0x8000000000000000) and divisor == -1) then -- overflow 315 | val = (@int64)(0x8000000000000000) 316 | elseif unlikely(divisor == 0) then -- division by zero 317 | val = -1 318 | else 319 | val = dividend /// divisor 320 | end 321 | case 0b0000001101 then ## debug_inst('divu', {rd, rs1, rs2}) 322 | local dividend, divisor = (@uint64)(val1), (@uint64)(val2) 323 | if unlikely(divisor == 0) then -- division by zero 324 | val = (@int64)(-1) 325 | else 326 | val = (@int64)(dividend /// divisor) 327 | end 328 | case 0b0000001110 then ## debug_inst('rem', {rd, rs1, rs2}) 329 | local dividend, divisor = val1, val2 330 | if unlikely(dividend == (@int64)(0x8000000000000000) and divisor == -1) then -- overflow 331 | val = 0 332 | elseif unlikely(divisor == 0) then -- division by zero 333 | val = dividend 334 | else 335 | val = dividend %%% divisor 336 | end 337 | case 0b0000001111 then ## debug_inst('remu', {rd, rs1, rs2}) 338 | local dividend, divisor = (@uint64)(val1), (@uint64)(val2) 339 | if unlikely(divisor == 0) then -- division by zero 340 | val = (@int64)(dividend) 341 | else 342 | val = (@int64)(dividend %%% divisor) 343 | end 344 | else 345 | error('illegal op instruction') 346 | end 347 | if likely(rd ~= 0) then 348 | self.regs[rd] = (@uint64)(val) 349 | end 350 | case 0b0111011 then -- op-32 351 | local funct3 = rbits!(inst, 14, 12) 352 | local funct7 = rbits!(inst, 31, 25) 353 | local val1: int64 = (@int64)(self.regs[rs1]) 354 | local val2: int64 = (@int64)(self.regs[rs2]) 355 | local val: int64 356 | switch (funct7 << 3) | funct3 do 357 | case 0b0000000000 then ## debug_inst('addw', {rd, rs1, rs2}) 358 | val = (@int32)(val1 + val2) 359 | case 0b0100000000 then ## debug_inst('subw', {rd, rs1, rs2}) 360 | val = (@int32)(val1 - val2) 361 | case 0b0000000001 then ## debug_inst('sllw', {rd, rs1, rs2}) 362 | val = (@int32)(val1 << (val2 & 0b11111)) 363 | case 0b0000000101 then ## debug_inst('srlw', {rd, rs1, rs2}) 364 | val = (@int32)(val1 >> (val2 & 0b11111)) 365 | case 0b0100000101 then ## debug_inst('sraw', {rd, rs1, rs2}) 366 | val = (@int32)(val1 >>> (val2 & 0b11111)) 367 | -- M extension 368 | case 0b0000001000 then ## debug_inst('mulw', {rd, rs1, rs2}) 369 | val = (@int32)((@int32)(val1) * (@int32)(val2)) 370 | case 0b0000001100 then ## debug_inst('divw', {rd, rs1, rs2}) 371 | local dividend, divisor = (@int32)(val1), (@int32)(val2) 372 | if unlikely(dividend == (@int32)(0x80000000) and divisor == -1) then -- overflow 373 | val = (@int32)(0x80000000) 374 | elseif unlikely(divisor == 0) then -- division by zero 375 | val = (@int32)(-1) 376 | else 377 | val = dividend /// divisor 378 | end 379 | case 0b0000001101 then ## debug_inst('divuw', {rd, rs1, rs2}) 380 | local dividend, divisor = (@uint32)(val1), (@uint32)(val2) 381 | if unlikely(divisor == 0) then -- division by zero 382 | val = (@int32)(-1) 383 | else 384 | val = (@int32)(dividend /// divisor) 385 | end 386 | case 0b0000001110 then ## debug_inst('remw', {rd, rs1, rs2}) 387 | local dividend, divisor = (@int32)(val1), (@int32)(val2) 388 | if unlikely(dividend == (@int32)(0x80000000) and divisor == -1) then -- overflow 389 | val = 0 390 | elseif unlikely(divisor == 0) then -- division by zero 391 | val = dividend 392 | else 393 | val = dividend %%% divisor 394 | end 395 | case 0b0000001111 then ## debug_inst('remuw', {rd, rs1, rs2}) 396 | local dividend, divisor = (@uint32)(val1), (@uint32)(val2) 397 | if unlikely(divisor == 0) then -- division by zero 398 | val = (@int32)(dividend) 399 | else 400 | val = (@int32)(dividend %%% divisor) 401 | end 402 | else 403 | error('illegal op-32 instruction') 404 | end 405 | if likely(rd ~= 0) then 406 | self.regs[rd] = (@uint64)(val) 407 | end 408 | case 0b0110111 then -- lui 409 | local imm = sext!(rimm!(inst, 12, 31, 12), 32) 410 | ## debug_inst('lui', {rd}, {imm}) 411 | if likely(rd ~= 0) then 412 | self.regs[rd] = (@uint64)(imm) 413 | end 414 | case 0b0010111 then -- auipc 415 | local imm = sext!(rimm!(inst, 12, 31, 12), 32) 416 | ## debug_inst('auipc', {rd}, {imm}) 417 | if likely(rd ~= 0) then 418 | self.regs[rd] = (@uint64)(self.pc + imm) 419 | end 420 | case 0b1101111 then -- jal 421 | local imm = sext!(orbits!(rimm!(inst, 31, 20),rimm!(inst, 21, 10, 1),rimm!(inst, 20, 11),rimm!(inst, 12, 19, 12)), 21) 422 | ## debug_inst('jal', {rd}, {imm}) 423 | if likely(rd ~= 0) then 424 | self.regs[rd] = (@uint64)(self.pc + 4) 425 | end 426 | self.pc = self.pc + imm 427 | return 428 | case 0b1100111 then -- jalr 429 | local imm = sext!(rimm!(inst, 20, 11, 0),12) 430 | ## debug_inst('jalr', {rd, rs1}, {imm}) 431 | local pc = self.pc + 4 432 | self.pc = ((self.regs[rs1] + imm) & ~1) 433 | if unlikely(rd ~= 0) then 434 | self.regs[rd] = (@uint64)(pc) 435 | end 436 | return 437 | case 0b1100011 then -- branches 438 | local funct3 = rbits!(inst, 14, 12) 439 | local imm = sext!(orbits!(rimm!(inst, 31, 12),rimm!(inst, 25, 10, 5),rimm!(inst, 8, 4, 1),rimm!(inst, 7, 11)), 13) 440 | local val1, val2 = self.regs[rs1], self.regs[rs2] 441 | local cond: boolean 442 | switch funct3 do 443 | case 0b000 then ## debug_inst('beq', {rs1, rs2}, {imm}) 444 | cond = val1 == val2 445 | case 0b001 then ## debug_inst('bne', {rs1, rs2}, {imm}) 446 | cond = val1 ~= val2 447 | case 0b100 then ## debug_inst('blt', {rs1, rs2}, {imm}) 448 | cond = (@int64)(val1) < (@int64)(val2) 449 | case 0b101 then ## debug_inst('bge', {rs1, rs2}, {imm}) 450 | cond = (@int64)(val1) >= (@int64)(val2) 451 | case 0b110 then ## debug_inst('bltu', {rs1, rs2}, {imm}) 452 | cond = val1 < val2 453 | case 0b111 then ## debug_inst('bgeu', {rs1, rs2}, {imm}) 454 | cond = val1 >= val2 455 | else 456 | error('illegal branch instruction') 457 | end 458 | if cond then 459 | self.pc = self.pc + imm 460 | return 461 | end 462 | case 0b0001111 then -- fence 463 | -- fence instruction does nothing because this emulator executes an 464 | -- instruction sequentially on a single thread. 465 | ## debug_inst('fence') 466 | case 0b1110011 then -- system call/break 467 | local funct11 = rbits!(inst, 31, 20) 468 | switch funct11 do 469 | case 0b000000000000 then 470 | local code = self.regs[REGISTERS.A7] 471 | ## debug_inst('ecall', {}, {code}) 472 | self.regs[REGISTERS.A0] = self:handle_syscall(code) 473 | case 0b000000000001 then ## debug_inst('ebreak') 474 | self.exitcode = -1 475 | self.running = false 476 | else 477 | error('illegal system instruction') 478 | end 479 | else 480 | ## debug_inst('invop', {}, {opcode}) 481 | error('illegal instruction') 482 | end 483 | self.pc = self.pc + 4 484 | end 485 | 486 | -- Run until the guest calls exit system call. 487 | -- Any error terminates the application. 488 | function Machine:run() 489 | self.running = true 490 | while likely(self.running) do 491 | local inst = self:fetch() 492 | self:execute(inst) 493 | end 494 | end 495 | -------------------------------------------------------------------------------- /riscvm.c: -------------------------------------------------------------------------------- 1 | /* Generated by Nelua 0.2.0-dev */ 2 | /* Compile command: gcc -x c "riscvm.c" -x none -pipe -fwrapv -fno-strict-aliasing -g -lm -o "riscvm" */ 3 | /* Compile hash: 3yRF316SvDmqYnDkveN73j7PiY2f */ 4 | /* ------------------------------ DIRECTIVES -------------------------------- */ 5 | #define NELUA_NIL (nlniltype){} 6 | #include 7 | #include 8 | #include 9 | #include 10 | /* Macro used to force inlining a function. */ 11 | #ifdef __GNUC__ 12 | #define NELUA_INLINE __attribute__((always_inline)) inline 13 | #elif defined(_MSC_VER) 14 | #define NELUA_INLINE __forceinline 15 | #elif __STDC_VERSION__ >= 199901L 16 | #define NELUA_INLINE inline 17 | #else 18 | #define NELUA_INLINE 19 | #endif 20 | #include 21 | #include 22 | /* Macro used to import/export extern C functions. */ 23 | #ifdef __cplusplus 24 | #define NELUA_EXTERN extern "C" 25 | #else 26 | #define NELUA_EXTERN extern 27 | #endif 28 | /* Macro used to generate traceback on aborts when sanitizing. */ 29 | #if defined(__clang__) && defined(__has_feature) 30 | #if __has_feature(undefined_behavior_sanitizer) 31 | #define NELUA_UBSAN_UNREACHABLE __builtin_unreachable 32 | #endif 33 | #elif defined(__gnu_linux__) && defined(__GNUC__) && __GNUC__ >= 5 34 | NELUA_EXTERN void __ubsan_handle_builtin_unreachable(void*) __attribute__((weak)); 35 | #define NELUA_UBSAN_UNREACHABLE() {if(&__ubsan_handle_builtin_unreachable) __builtin_unreachable();} 36 | #endif 37 | #ifndef NELUA_UBSAN_UNREACHABLE 38 | #define NELUA_UBSAN_UNREACHABLE() 39 | #endif 40 | /* Macro used to specify a function that never returns. */ 41 | #if __STDC_VERSION__ >= 201112L 42 | #define NELUA_NORETURN _Noreturn 43 | #elif defined(__GNUC__) 44 | #define NELUA_NORETURN __attribute__((noreturn)) 45 | #elif defined(_MSC_VER) 46 | #define NELUA_NORETURN __declspec(noreturn) 47 | #else 48 | #define NELUA_NORETURN 49 | #endif 50 | /* Macro used sign that a type punning cast may alias (related to strict aliasing). */ 51 | #ifdef __GNUC__ 52 | #define NELUA_MAYALIAS __attribute__((may_alias)) 53 | #else 54 | #define NELUA_MAYALIAS 55 | #endif 56 | /* Macro used for branch prediction. */ 57 | #if defined(__GNUC__) || defined(__clang__) 58 | #define NELUA_LIKELY(x) __builtin_expect(x, 1) 59 | #else 60 | #define NELUA_LIKELY(x) (x) 61 | #endif 62 | /* Macro used for branch prediction. */ 63 | #if defined(__GNUC__) || defined(__clang__) 64 | #define NELUA_UNLIKELY(x) __builtin_expect(x, 0) 65 | #else 66 | #define NELUA_UNLIKELY(x) (x) 67 | #endif 68 | /* Macro used to force not inlining a function. */ 69 | #ifdef __GNUC__ 70 | #define NELUA_NOINLINE __attribute__((noinline)) 71 | #elif defined(_MSC_VER) 72 | #define NELUA_NOINLINE __declspec(noinline) 73 | #else 74 | #define NELUA_NOINLINE 75 | #endif 76 | /* ------------------------------ DECLARATIONS ------------------------------ */ 77 | typedef struct nlniltype {} nlniltype; 78 | typedef struct nlniltype nltype; 79 | typedef struct machine_Machine machine_Machine; 80 | typedef machine_Machine* machine_Machine_ptr; 81 | struct machine_Machine { 82 | bool running; 83 | int64_t pc; 84 | uint64_t regs[32]; 85 | uint8_t memory[134217728]; 86 | int64_t exitcode; 87 | }; 88 | typedef struct nlstring nlstring; 89 | typedef uint8_t* nluint8_arr0_ptr; 90 | struct nlstring { 91 | nluint8_arr0_ptr data; 92 | uintptr_t size; 93 | }; 94 | static void machine_Machine_loadfile(machine_Machine_ptr self, nlstring filename); 95 | typedef FILE* FILE_ptr; 96 | static NELUA_INLINE char* nelua_string2cstring(nlstring s); 97 | static NELUA_INLINE void nelua_write_stderr(const char* msg, uintptr_t len, bool flush); 98 | static NELUA_NORETURN void nelua_abort(void); 99 | static NELUA_NORETURN void nelua_panic_string(nlstring s); 100 | typedef struct NELUA_MAYALIAS nluint8_arr134217728 {uint8_t v[134217728];} nluint8_arr134217728; 101 | typedef union NELUA_MAYALIAS nluint8_arr134217728_cast {nluint8_arr134217728 a; uint8_t p[134217728];} nluint8_arr134217728_cast; 102 | typedef struct NELUA_MAYALIAS nluint64_arr32 {uint64_t v[32];} nluint64_arr32; 103 | typedef union NELUA_MAYALIAS nluint64_arr32_cast {nluint64_arr32 a; uint64_t p[32];} nluint64_arr32_cast; 104 | static NELUA_INLINE uint32_t machine_Machine_fetch(machine_Machine_ptr self); 105 | typedef uint32_t* nluint32_ptr; 106 | static NELUA_INLINE int8_t machine_Machine_read_1(machine_Machine_ptr self, nlniltype T, uint64_t addr); 107 | static NELUA_INLINE int16_t machine_Machine_read_2(machine_Machine_ptr self, nlniltype T, uint64_t addr); 108 | typedef uint16_t* nluint16_ptr; 109 | static NELUA_INLINE int32_t machine_Machine_read_3(machine_Machine_ptr self, nlniltype T, uint64_t addr); 110 | static NELUA_INLINE int64_t machine_Machine_read_4(machine_Machine_ptr self, nlniltype T, uint64_t addr); 111 | typedef uint64_t* nluint64_ptr; 112 | static NELUA_INLINE uint8_t machine_Machine_read_5(machine_Machine_ptr self, nlniltype T, uint64_t addr); 113 | static NELUA_INLINE uint16_t machine_Machine_read_6(machine_Machine_ptr self, nlniltype T, uint64_t addr); 114 | static NELUA_INLINE uint32_t machine_Machine_read_7(machine_Machine_ptr self, nlniltype T, uint64_t addr); 115 | static NELUA_INLINE void* machine_Machine_getptr(machine_Machine_ptr self, uint64_t addr); 116 | static NELUA_INLINE void machine_Machine_write_1(machine_Machine_ptr self, uint64_t addr, uint8_t val); 117 | static NELUA_INLINE void machine_Machine_write_2(machine_Machine_ptr self, uint64_t addr, uint16_t val); 118 | static NELUA_INLINE void machine_Machine_write_3(machine_Machine_ptr self, uint64_t addr, uint32_t val); 119 | static NELUA_INLINE void machine_Machine_write_4(machine_Machine_ptr self, uint64_t addr, uint64_t val); 120 | static uint64_t machine_Machine_handle_syscall(machine_Machine_ptr self, uint64_t code); 121 | static void nelua_print_1(char* a1); 122 | static void nelua_print_2(int64_t a1); 123 | static NELUA_INLINE void machine_Machine_execute(machine_Machine_ptr self, uint32_t inst); 124 | static NELUA_INLINE int64_t nelua_shl_nlint64(int64_t a, int64_t b); 125 | static NELUA_INLINE int64_t nelua_shr_nlint64(int64_t a, int64_t b); 126 | static NELUA_INLINE int64_t nelua_asr_nlint64(int64_t a, int64_t b); 127 | static NELUA_INLINE __int128 nelua_shr_nlint128(__int128 a, __int128 b); 128 | static NELUA_NOINLINE void machine_Machine_run(machine_Machine_ptr self); 129 | static int nelua_argc; 130 | typedef char** nlcstring_arr0_ptr; 131 | static nlcstring_arr0_ptr nelua_argv; 132 | static void nelua_print_3(nlstring a1); 133 | static char* riscvm_filename; 134 | static machine_Machine riscvm_machine; 135 | static NELUA_INLINE nlstring nelua_cstring2string(const char* s); 136 | static int nelua_main(int argc, char** argv); 137 | /* ------------------------------ DEFINITIONS ------------------------------- */ 138 | char* nelua_string2cstring(nlstring s) { 139 | return (s.size == 0) ? (char*)"" : (char*)s.data; 140 | } 141 | void nelua_write_stderr(const char* msg, uintptr_t len, bool flush) { 142 | if(len > 0 && msg) { 143 | fwrite(msg, 1, len, stderr); 144 | } 145 | if(flush) { 146 | fwrite("\n", 1, 1, stderr); 147 | fflush(stderr); 148 | } 149 | } 150 | void nelua_abort(void) { 151 | NELUA_UBSAN_UNREACHABLE(); 152 | abort(); 153 | } 154 | void nelua_panic_string(nlstring s) { 155 | if(s.size > 0) { 156 | nelua_write_stderr((const char*)s.data, s.size, true); 157 | } 158 | nelua_abort(); 159 | } 160 | void machine_Machine_loadfile(machine_Machine_ptr self, nlstring filename) { 161 | FILE_ptr fp = fopen(nelua_string2cstring(filename), "rb"); 162 | if((!(fp != NULL))) { 163 | nelua_panic_string(((nlstring){(uint8_t*)"failed to open file", 19})); 164 | } 165 | fseek(fp, 0, SEEK_END); 166 | long size = ftell(fp); 167 | fseek(fp, 0, SEEK_SET); 168 | fread((void*)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[4096]), (size_t)size, 1U, fp); 169 | fclose(fp); 170 | (((nluint64_arr32_cast*)&self->regs)->a).v[2] = 134217728U; 171 | self->pc = 4096; 172 | } 173 | uint32_t machine_Machine_fetch(machine_Machine_ptr self) { 174 | return (*(nluint32_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[self->pc])); 175 | } 176 | int8_t machine_Machine_read_1(machine_Machine_ptr self, nlniltype T, uint64_t addr) { 177 | return (int8_t)(*(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])); 178 | } 179 | int16_t machine_Machine_read_2(machine_Machine_ptr self, nlniltype T, uint64_t addr) { 180 | return (int16_t)(*(nluint16_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])); 181 | } 182 | int32_t machine_Machine_read_3(machine_Machine_ptr self, nlniltype T, uint64_t addr) { 183 | return (int32_t)(*(nluint32_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])); 184 | } 185 | int64_t machine_Machine_read_4(machine_Machine_ptr self, nlniltype T, uint64_t addr) { 186 | return (int64_t)(*(nluint64_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])); 187 | } 188 | uint8_t machine_Machine_read_5(machine_Machine_ptr self, nlniltype T, uint64_t addr) { 189 | return (*(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])); 190 | } 191 | uint16_t machine_Machine_read_6(machine_Machine_ptr self, nlniltype T, uint64_t addr) { 192 | return (*(nluint16_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])); 193 | } 194 | uint32_t machine_Machine_read_7(machine_Machine_ptr self, nlniltype T, uint64_t addr) { 195 | return (*(nluint32_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])); 196 | } 197 | void* machine_Machine_getptr(machine_Machine_ptr self, uint64_t addr) { 198 | return (void*)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr]); 199 | } 200 | void machine_Machine_write_1(machine_Machine_ptr self, uint64_t addr, uint8_t val) { 201 | (*(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])) = val; 202 | } 203 | void machine_Machine_write_2(machine_Machine_ptr self, uint64_t addr, uint16_t val) { 204 | (*(nluint16_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])) = val; 205 | } 206 | void machine_Machine_write_3(machine_Machine_ptr self, uint64_t addr, uint32_t val) { 207 | (*(nluint32_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])) = val; 208 | } 209 | void machine_Machine_write_4(machine_Machine_ptr self, uint64_t addr, uint64_t val) { 210 | (*(nluint64_ptr)(&(((nluint8_arr134217728_cast*)&self->memory)->a).v[addr])) = val; 211 | } 212 | void nelua_print_1(char* a1) { 213 | fputs(a1 != NULL ? a1 : "(null cstring)", stdout); 214 | fputs("\n", stdout); 215 | fflush(stdout); 216 | } 217 | void nelua_print_2(int64_t a1) { 218 | fprintf(stdout, "%lli", (long long)a1); 219 | fputs("\n", stdout); 220 | fflush(stdout); 221 | } 222 | uint64_t machine_Machine_handle_syscall(machine_Machine_ptr self, uint64_t code) { 223 | switch(code) { 224 | case 10000: { 225 | self->running = false; 226 | self->exitcode = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[10]; 227 | break; 228 | } 229 | case 10001: { 230 | nelua_panic_string(((nlstring){(uint8_t*)"aborted!", 8})); 231 | break; 232 | } 233 | case 10007: { 234 | void* dest = machine_Machine_getptr(self, (((nluint64_arr32_cast*)&self->regs)->a).v[10]); 235 | uint64_t c = (((nluint64_arr32_cast*)&self->regs)->a).v[11]; 236 | uint64_t len = (((nluint64_arr32_cast*)&self->regs)->a).v[12]; 237 | void* res = memset(dest, (int)c, (size_t)len); 238 | return (uint64_t)res; 239 | } 240 | case 10101: { 241 | char* s = (char*)machine_Machine_getptr(self, (((nluint64_arr32_cast*)&self->regs)->a).v[10]); 242 | nelua_print_1(s); 243 | break; 244 | } 245 | case 10102: { 246 | int64_t i = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[10]; 247 | nelua_print_2(i); 248 | break; 249 | } 250 | default: { 251 | nelua_panic_string(((nlstring){(uint8_t*)"illegal system call", 19})); 252 | break; 253 | } 254 | } 255 | return 0U; 256 | } 257 | int64_t nelua_shl_nlint64(int64_t a, int64_t b) { 258 | if(NELUA_LIKELY(b >= 0 && b < 64)) { 259 | return ((uint64_t)a) << b; 260 | } else if(NELUA_UNLIKELY(b < 0 && b > -64)) { 261 | return (uint64_t)a >> -b; 262 | } else { 263 | return 0; 264 | } 265 | } 266 | int64_t nelua_shr_nlint64(int64_t a, int64_t b) { 267 | if(NELUA_LIKELY(b >= 0 && b < 64)) { 268 | return (uint64_t)a >> b; 269 | } else if(NELUA_UNLIKELY(b < 0 && b > -64)) { 270 | return (uint64_t)a << -b; 271 | } else { 272 | return 0; 273 | } 274 | } 275 | int64_t nelua_asr_nlint64(int64_t a, int64_t b) { 276 | if(NELUA_LIKELY(b >= 0 && b < 64)) { 277 | return a >> b; 278 | } else if(NELUA_UNLIKELY(b >= 64)) { 279 | return a < 0 ? -1 : 0; 280 | } else if(NELUA_UNLIKELY(b < 0 && b > -64)) { 281 | return a << -b; 282 | } else { 283 | return 0; 284 | } 285 | } 286 | __int128 nelua_shr_nlint128(__int128 a, __int128 b) { 287 | if(NELUA_LIKELY(b >= 0 && b < 128)) { 288 | return (unsigned __int128)a >> b; 289 | } else if(NELUA_UNLIKELY(b < 0 && b > -128)) { 290 | return (unsigned __int128)a << -b; 291 | } else { 292 | return 0; 293 | } 294 | } 295 | void machine_Machine_execute(machine_Machine_ptr self, uint32_t inst) { 296 | uint32_t opcode = ((inst >> 0) & 127); 297 | uint32_t rd = ((inst >> 7) & 31); 298 | uint32_t rs1 = ((inst >> 15) & 31); 299 | uint32_t rs2 = ((inst >> 20) & 31); 300 | switch(opcode) { 301 | case 0x3: { 302 | uint32_t funct3 = ((inst >> 12) & 7); 303 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)((inst >> 20) & 4095) << 20)) >> 20); 304 | uint64_t addr = ((((nluint64_arr32_cast*)&self->regs)->a).v[rs1] + (uint64_t)imm); 305 | int64_t val; 306 | switch(funct3) { 307 | case 0x0: { 308 | val = (int64_t)machine_Machine_read_1(self, NELUA_NIL, addr); 309 | break; 310 | } 311 | case 0x1: { 312 | val = (int64_t)machine_Machine_read_2(self, NELUA_NIL, addr); 313 | break; 314 | } 315 | case 0x2: { 316 | val = (int64_t)machine_Machine_read_3(self, NELUA_NIL, addr); 317 | break; 318 | } 319 | case 0x3: { 320 | val = machine_Machine_read_4(self, NELUA_NIL, addr); 321 | break; 322 | } 323 | case 0x4: { 324 | val = (int64_t)machine_Machine_read_5(self, NELUA_NIL, addr); 325 | break; 326 | } 327 | case 0x5: { 328 | val = (int64_t)machine_Machine_read_6(self, NELUA_NIL, addr); 329 | break; 330 | } 331 | case 0x6: { 332 | val = (int64_t)machine_Machine_read_7(self, NELUA_NIL, addr); 333 | break; 334 | } 335 | default: { 336 | nelua_panic_string(((nlstring){(uint8_t*)"illegal load instruction", 24})); 337 | break; 338 | } 339 | } 340 | if(NELUA_LIKELY((rd != 0))) { 341 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)val; 342 | } 343 | break; 344 | } 345 | case 0x23: { 346 | uint32_t funct3 = ((inst >> 12) & 7); 347 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)(((inst >> 20) & 4064) | ((inst >> 7) & 31)) << 20)) >> 20); 348 | uint64_t addr = ((((nluint64_arr32_cast*)&self->regs)->a).v[rs1] + (uint64_t)imm); 349 | uint64_t val = (((nluint64_arr32_cast*)&self->regs)->a).v[rs2]; 350 | switch(funct3) { 351 | case 0x0: { 352 | machine_Machine_write_1(self, addr, (uint8_t)val); 353 | break; 354 | } 355 | case 0x1: { 356 | machine_Machine_write_2(self, addr, (uint16_t)val); 357 | break; 358 | } 359 | case 0x2: { 360 | machine_Machine_write_3(self, addr, (uint32_t)val); 361 | break; 362 | } 363 | case 0x3: { 364 | machine_Machine_write_4(self, addr, val); 365 | break; 366 | } 367 | default: { 368 | nelua_panic_string(((nlstring){(uint8_t*)"illegal store instruction", 25})); 369 | break; 370 | } 371 | } 372 | break; 373 | } 374 | case 0x13: { 375 | uint32_t funct3 = ((inst >> 12) & 7); 376 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)((inst >> 20) & 4095) << 20)) >> 20); 377 | uint32_t shamt = ((inst >> 20) & 63); 378 | int64_t val = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[rs1]; 379 | switch(funct3) { 380 | case 0x0: { 381 | val = (val + imm); 382 | break; 383 | } 384 | case 0x1: { 385 | val = nelua_shl_nlint64(val, shamt); 386 | break; 387 | } 388 | case 0x2: { 389 | if((val < imm)) { 390 | val = 1; 391 | } else { 392 | val = 0; 393 | } 394 | break; 395 | } 396 | case 0x3: { 397 | if(((uint64_t)val < (uint64_t)imm)) { 398 | val = 1; 399 | } else { 400 | val = 0; 401 | } 402 | break; 403 | } 404 | case 0x4: { 405 | val = (val ^ imm); 406 | break; 407 | } 408 | case 0x5: { 409 | uint32_t funct6 = ((inst >> 26) & 63); 410 | switch((funct6 >> 4)) { 411 | case 0x0: { 412 | val = nelua_shr_nlint64(val, shamt); 413 | break; 414 | } 415 | case 0x1: { 416 | val = nelua_asr_nlint64(val, shamt); 417 | break; 418 | } 419 | default: { 420 | nelua_panic_string(((nlstring){(uint8_t*)"illegal op-imm shift instruction", 32})); 421 | break; 422 | } 423 | } 424 | break; 425 | } 426 | case 0x6: { 427 | val = (val | imm); 428 | break; 429 | } 430 | case 0x7: { 431 | val = (val & imm); 432 | break; 433 | } 434 | default: { 435 | nelua_panic_string(((nlstring){(uint8_t*)"illegal op-imm instruction", 26})); 436 | break; 437 | } 438 | } 439 | if(NELUA_LIKELY((rd != 0))) { 440 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)val; 441 | } 442 | break; 443 | } 444 | case 0x1b: { 445 | uint32_t funct3 = ((inst >> 12) & 7); 446 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)((inst >> 20) & 4095) << 20)) >> 20); 447 | int64_t val = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[rs1]; 448 | switch(funct3) { 449 | case 0x0: { 450 | val = (int64_t)(int32_t)(val + imm); 451 | break; 452 | } 453 | case 0x1: { 454 | val = (int64_t)(int32_t)nelua_shl_nlint64(val, imm); 455 | break; 456 | } 457 | case 0x5: { 458 | uint32_t shamt = rs2; 459 | uint32_t funct7 = ((inst >> 25) & 127); 460 | switch((funct7 >> 5)) { 461 | case 0x0: { 462 | val = (int64_t)(int32_t)nelua_shr_nlint64(val, shamt); 463 | break; 464 | } 465 | case 0x1: { 466 | val = (int64_t)(int32_t)nelua_asr_nlint64(val, shamt); 467 | break; 468 | } 469 | default: { 470 | nelua_panic_string(((nlstring){(uint8_t*)"illegal op-imm-32 shift instruction", 35})); 471 | break; 472 | } 473 | } 474 | break; 475 | } 476 | default: { 477 | nelua_panic_string(((nlstring){(uint8_t*)"illegal op-imm-32 instruction", 29})); 478 | break; 479 | } 480 | } 481 | if(NELUA_LIKELY((rd != 0))) { 482 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)val; 483 | } 484 | break; 485 | } 486 | case 0x33: { 487 | uint32_t funct3 = ((inst >> 12) & 7); 488 | uint32_t funct7 = ((inst >> 25) & 127); 489 | int64_t val1 = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[rs1]; 490 | int64_t val2 = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[rs2]; 491 | int64_t val; 492 | switch(((funct7 << 3) | funct3)) { 493 | case 0x0: { 494 | val = (val1 + val2); 495 | break; 496 | } 497 | case 0x100: { 498 | val = (val1 - val2); 499 | break; 500 | } 501 | case 0x1: { 502 | val = nelua_shl_nlint64(val1, (val2 & 0x1f)); 503 | break; 504 | } 505 | case 0x2: { 506 | if((val1 < val2)) { 507 | val = 1; 508 | } else { 509 | val = 0; 510 | } 511 | break; 512 | } 513 | case 0x3: { 514 | if(((uint64_t)val1 < (uint64_t)val2)) { 515 | val = 1; 516 | } else { 517 | val = 0; 518 | } 519 | break; 520 | } 521 | case 0x4: { 522 | val = (val1 ^ val2); 523 | break; 524 | } 525 | case 0x5: { 526 | val = nelua_shr_nlint64(val1, (val2 & 0x1f)); 527 | break; 528 | } 529 | case 0x105: { 530 | val = nelua_asr_nlint64(val1, (val2 & 0x1f)); 531 | break; 532 | } 533 | case 0x6: { 534 | val = (val1 | val2); 535 | break; 536 | } 537 | case 0x7: { 538 | val = (val1 & val2); 539 | break; 540 | } 541 | case 0x8: { 542 | val = (val1 * val2); 543 | break; 544 | } 545 | case 0x9: { 546 | val = (int64_t)(uint64_t)nelua_shr_nlint128(((__int128)val1 * (__int128)val2), 64); 547 | break; 548 | } 549 | case 0xa: { 550 | val = (int64_t)(uint64_t)nelua_shr_nlint128(((__int128)val1 * (__int128)(uint64_t)val2), 64); 551 | break; 552 | } 553 | case 0xb: { 554 | val = (int64_t)(uint64_t)nelua_shr_nlint128(((__int128)(uint64_t)val1 * (__int128)(uint64_t)val2), 64); 555 | break; 556 | } 557 | case 0xc: { 558 | int64_t dividend = val1; 559 | int64_t divisor = val2; 560 | if(NELUA_UNLIKELY(((dividend == (-9223372036854775807LL-1)) && (divisor == -1)))) { 561 | val = (-9223372036854775807LL-1); 562 | } else if(NELUA_UNLIKELY((divisor == 0))) { 563 | val = -1; 564 | } else { 565 | val = (dividend / divisor); 566 | } 567 | break; 568 | } 569 | case 0xd: { 570 | uint64_t dividend = (uint64_t)val1; 571 | uint64_t divisor = (uint64_t)val2; 572 | if(NELUA_UNLIKELY((divisor == 0))) { 573 | val = -1; 574 | } else { 575 | val = (int64_t)(dividend / divisor); 576 | } 577 | break; 578 | } 579 | case 0xe: { 580 | int64_t dividend = val1; 581 | int64_t divisor = val2; 582 | if(NELUA_UNLIKELY(((dividend == (-9223372036854775807LL-1)) && (divisor == -1)))) { 583 | val = 0; 584 | } else if(NELUA_UNLIKELY((divisor == 0))) { 585 | val = dividend; 586 | } else { 587 | val = (dividend % divisor); 588 | } 589 | break; 590 | } 591 | case 0xf: { 592 | uint64_t dividend = (uint64_t)val1; 593 | uint64_t divisor = (uint64_t)val2; 594 | if(NELUA_UNLIKELY((divisor == 0))) { 595 | val = (int64_t)dividend; 596 | } else { 597 | val = (int64_t)(dividend % divisor); 598 | } 599 | break; 600 | } 601 | default: { 602 | nelua_panic_string(((nlstring){(uint8_t*)"illegal op instruction", 22})); 603 | break; 604 | } 605 | } 606 | if(NELUA_LIKELY((rd != 0))) { 607 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)val; 608 | } 609 | break; 610 | } 611 | case 0x3b: { 612 | uint32_t funct3 = ((inst >> 12) & 7); 613 | uint32_t funct7 = ((inst >> 25) & 127); 614 | int64_t val1 = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[rs1]; 615 | int64_t val2 = (int64_t)(((nluint64_arr32_cast*)&self->regs)->a).v[rs2]; 616 | int64_t val; 617 | switch(((funct7 << 3) | funct3)) { 618 | case 0x0: { 619 | val = (int64_t)(int32_t)(val1 + val2); 620 | break; 621 | } 622 | case 0x100: { 623 | val = (int64_t)(int32_t)(val1 - val2); 624 | break; 625 | } 626 | case 0x1: { 627 | val = (int64_t)(int32_t)nelua_shl_nlint64(val1, (val2 & 0x1f)); 628 | break; 629 | } 630 | case 0x5: { 631 | val = (int64_t)(int32_t)nelua_shr_nlint64(val1, (val2 & 0x1f)); 632 | break; 633 | } 634 | case 0x105: { 635 | val = (int64_t)(int32_t)nelua_asr_nlint64(val1, (val2 & 0x1f)); 636 | break; 637 | } 638 | case 0x8: { 639 | val = (int64_t)((int32_t)val1 * (int32_t)val2); 640 | break; 641 | } 642 | case 0xc: { 643 | int32_t dividend = (int32_t)val1; 644 | int32_t divisor = (int32_t)val2; 645 | if(NELUA_UNLIKELY(((dividend == (-2147483647-1)) && (divisor == -1)))) { 646 | val = -2147483648LL; 647 | } else if(NELUA_UNLIKELY((divisor == 0))) { 648 | val = -1; 649 | } else { 650 | val = (int64_t)(dividend / divisor); 651 | } 652 | break; 653 | } 654 | case 0xd: { 655 | uint32_t dividend = (uint32_t)val1; 656 | uint32_t divisor = (uint32_t)val2; 657 | if(NELUA_UNLIKELY((divisor == 0))) { 658 | val = -1; 659 | } else { 660 | val = (int64_t)(int32_t)(dividend / divisor); 661 | } 662 | break; 663 | } 664 | case 0xe: { 665 | int32_t dividend = (int32_t)val1; 666 | int32_t divisor = (int32_t)val2; 667 | if(NELUA_UNLIKELY(((dividend == (-2147483647-1)) && (divisor == -1)))) { 668 | val = 0; 669 | } else if(NELUA_UNLIKELY((divisor == 0))) { 670 | val = (int64_t)dividend; 671 | } else { 672 | val = (int64_t)(dividend % divisor); 673 | } 674 | break; 675 | } 676 | case 0xf: { 677 | uint32_t dividend = (uint32_t)val1; 678 | uint32_t divisor = (uint32_t)val2; 679 | if(NELUA_UNLIKELY((divisor == 0))) { 680 | val = (int64_t)(int32_t)dividend; 681 | } else { 682 | val = (int64_t)(int32_t)(dividend % divisor); 683 | } 684 | break; 685 | } 686 | default: { 687 | nelua_panic_string(((nlstring){(uint8_t*)"illegal op-32 instruction", 25})); 688 | break; 689 | } 690 | } 691 | if(NELUA_LIKELY((rd != 0))) { 692 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)val; 693 | } 694 | break; 695 | } 696 | case 0x37: { 697 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)((inst << 0) & 4294963200LL) << 0)) >> 0); 698 | if(NELUA_LIKELY((rd != 0))) { 699 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)imm; 700 | } 701 | break; 702 | } 703 | case 0x17: { 704 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)((inst << 0) & 4294963200LL) << 0)) >> 0); 705 | if(NELUA_LIKELY((rd != 0))) { 706 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)(self->pc + imm); 707 | } 708 | break; 709 | } 710 | case 0x6f: { 711 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)(((((inst >> 11) & 1048576) | ((inst >> 20) & 2046)) | ((inst >> 9) & 2048)) | ((inst << 0) & 1044480)) << 11)) >> 11); 712 | if(NELUA_LIKELY((rd != 0))) { 713 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)(self->pc + 4); 714 | } 715 | self->pc = (self->pc + imm); 716 | return; 717 | } 718 | case 0x67: { 719 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)((inst >> 20) & 4095) << 20)) >> 20); 720 | int64_t pc = (self->pc + 4); 721 | self->pc = ((int64_t)((((nluint64_arr32_cast*)&self->regs)->a).v[rs1] + imm) & -2); 722 | if(NELUA_UNLIKELY((rd != 0))) { 723 | (((nluint64_arr32_cast*)&self->regs)->a).v[rd] = (uint64_t)pc; 724 | } 725 | return; 726 | } 727 | case 0x63: { 728 | uint32_t funct3 = ((inst >> 12) & 7); 729 | int64_t imm = (int64_t)(((int32_t)((uint32_t)(int32_t)(((((inst >> 19) & 4096) | ((inst >> 20) & 2016)) | ((inst >> 7) & 30)) | ((inst << 4) & 2048)) << 19)) >> 19); 730 | uint64_t val1 = (((nluint64_arr32_cast*)&self->regs)->a).v[rs1]; 731 | uint64_t val2 = (((nluint64_arr32_cast*)&self->regs)->a).v[rs2]; 732 | bool cond; 733 | switch(funct3) { 734 | case 0x0: { 735 | cond = (val1 == val2); 736 | break; 737 | } 738 | case 0x1: { 739 | cond = (val1 != val2); 740 | break; 741 | } 742 | case 0x4: { 743 | cond = ((int64_t)val1 < (int64_t)val2); 744 | break; 745 | } 746 | case 0x5: { 747 | cond = ((int64_t)val1 >= (int64_t)val2); 748 | break; 749 | } 750 | case 0x6: { 751 | cond = (val1 < val2); 752 | break; 753 | } 754 | case 0x7: { 755 | cond = (val1 >= val2); 756 | break; 757 | } 758 | default: { 759 | nelua_panic_string(((nlstring){(uint8_t*)"illegal branch instruction", 26})); 760 | break; 761 | } 762 | } 763 | if(cond) { 764 | self->pc = (self->pc + imm); 765 | return; 766 | } 767 | break; 768 | } 769 | case 0xf: { 770 | break; 771 | } 772 | case 0x73: { 773 | uint32_t funct11 = ((inst >> 20) & 4095); 774 | switch(funct11) { 775 | case 0x0: { 776 | uint64_t code = (((nluint64_arr32_cast*)&self->regs)->a).v[17]; 777 | (((nluint64_arr32_cast*)&self->regs)->a).v[10] = machine_Machine_handle_syscall(self, code); 778 | break; 779 | } 780 | case 0x1: { 781 | self->exitcode = -1; 782 | self->running = false; 783 | break; 784 | } 785 | default: { 786 | nelua_panic_string(((nlstring){(uint8_t*)"illegal system instruction", 26})); 787 | break; 788 | } 789 | } 790 | break; 791 | } 792 | default: { 793 | nelua_panic_string(((nlstring){(uint8_t*)"illegal instruction", 19})); 794 | break; 795 | } 796 | } 797 | self->pc = (self->pc + 4); 798 | } 799 | void machine_Machine_run(machine_Machine_ptr self) { 800 | self->running = true; 801 | while(NELUA_LIKELY(self->running)) { 802 | uint32_t inst = machine_Machine_fetch(self); 803 | machine_Machine_execute(self, inst); 804 | } 805 | } 806 | void nelua_print_3(nlstring a1) { 807 | if(a1.size > 0) { 808 | fwrite(a1.data, 1, a1.size, stdout); 809 | } 810 | fputs("\n", stdout); 811 | fflush(stdout); 812 | } 813 | nlstring nelua_cstring2string(const char* s) { 814 | if(s == NULL) { 815 | return (nlstring){0}; 816 | } 817 | uintptr_t size = strlen(s); 818 | if(size == 0) { 819 | return (nlstring){0}; 820 | } 821 | return (nlstring){(uint8_t*)s, size}; 822 | } 823 | int nelua_main(int argc, char** argv) { 824 | nelua_argc = argc; 825 | nelua_argv = argv; 826 | if((nelua_argc != 2)) { 827 | nelua_print_3(((nlstring){(uint8_t*)"please supply a RV64I program to run!", 37})); 828 | exit(1); 829 | } 830 | riscvm_filename = nelua_argv[1]; 831 | machine_Machine_loadfile((&riscvm_machine), nelua_cstring2string(riscvm_filename)); 832 | machine_Machine_run((&riscvm_machine)); 833 | exit((int)riscvm_machine.exitcode); 834 | return 0; 835 | } 836 | int main(int argc, char** argv) { 837 | return nelua_main(argc, argv); 838 | } 839 | -------------------------------------------------------------------------------- /riscvm.nelua: -------------------------------------------------------------------------------- 1 | -- Uncomment this to debug instructions being run. 2 | -- ## DEBUG = true 3 | 4 | require 'machine' 5 | require 'C.arg' 6 | require 'C.stdlib' 7 | 8 | if C.argc ~= 2 then 9 | print('please supply a RV64I program to run!') 10 | C.exit(1) 11 | end 12 | local filename = C.argv[1] 13 | 14 | local machine: Machine 15 | 16 | -- Load RISC-V program. 17 | machine:loadfile(filename) 18 | 19 | -- Run the instructions until exit or on error. 20 | machine:run() 21 | 22 | -- Exit returning the program exit code. 23 | C.exit((@cint)(machine.exitcode)) 24 | --------------------------------------------------------------------------------