├── .gitignore ├── LICENSE ├── README.md ├── docs ├── binarybytecode.md └── syntax.md ├── dub.sdl ├── source └── navm │ ├── bytecode.d │ ├── common.d │ └── navm.d ├── stackvm ├── .gitignore ├── README.md ├── dub.sdl └── source │ └── app.d └── tests ├── default ├── fib ├── recfib └── str /.gitignore: -------------------------------------------------------------------------------- 1 | .dub/* 2 | .vscode/* 3 | /*-test-default 4 | /demo 5 | dub.selections.json 6 | lib*.a 7 | sample 8 | tempcode 9 | callgrind* 10 | svm 11 | *-test-library 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2023 Nafees Hassan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NaVM 2 | 3 | A barebones VM, intended to be used for scripting applications. 4 | 5 | Created primarily for use in [qscript](https://github.com/nafees10/qscript). 6 | 7 | --- 8 | 9 | ## Getting Started 10 | 11 | NaVM comes with a demo stack based VM `stackvm` or `svm`. These instructions 12 | will build that. 13 | 14 | See the documents: 15 | 16 | * `docs/binarybytecode.md` - Binary ByteCode format 17 | * `syntax.md` - Syntax description 18 | * `tests/*` - Some test bytecodes, to be run using the NaVM demo 19 | 20 | ### Prerequisites 21 | 22 | You need to have these present on your machine to build NaVM: 23 | 24 | 1. dub 25 | 2. dlang compiler (`ldc` usually generates best performing code) 26 | 3. git 27 | 28 | ### Building 29 | 30 | ```bash 31 | git clone https://github.com/Nafees10/navm.git 32 | cd navm 33 | dub build :stackvm -b=release -c=demo # --compiler=ldc 34 | ``` 35 | 36 | This will compile the NaVM demo binary named `svm`. 37 | 38 | You can now run NaVM bytecode using: 39 | 40 | ```bash 41 | ./svm tests/default [numberOfTimesToRun] 42 | ``` 43 | 44 | Replace `tests/default` with a path to a bytecode file. 45 | 46 | --- 47 | 48 | ## Creating a VM 49 | 50 | A VM using NaVM is created using simple functions. A very basic VM with only 2 51 | instructions, `printSum int int`, and `print int`, can be created as: 52 | 53 | ```d 54 | import navm.navm; 55 | void printSum(ptrdiff_t a, ptrdiff_t b){ 56 | writeln(a + b); 57 | } 58 | void print(ptrdiff_t a){ 59 | writeln(a); 60 | } 61 | string[] bytecodeSource = [ 62 | "print 2", 63 | "printSum 1 2" 64 | ]; 65 | // load bytecode. bytecodeSource is the bytecode in a string array of lines 66 | ByteCode code; 67 | try { 68 | code = parseByteCode!(printSum, print)(bytecodeSource); 69 | } catch (Exception e){ 70 | // probably an error in the bytecode 71 | } 72 | // execute the code 73 | execute!(printSum, print)(code); 74 | ``` 75 | 76 | ### Starting at a label 77 | 78 | ```d 79 | import std.algorithm : countUntil, canFind; 80 | ByteCode code; 81 | // locate the index of the label 82 | ptrdiff_t index = code.labelNames.countUntil("labelName"); 83 | if (index == -1){ 84 | throw new Exception("labelName does not exist"); 85 | } 86 | execute!(..)(code, index); 87 | ``` 88 | 89 | ### Jumps 90 | 91 | An instruction can cause execution to jump to another place in the bytecode, by 92 | receiving references to the instruction counter `_ic`. 93 | 94 | The instruction counter stores index of the next instruction to execute. 95 | 96 | The most common use case for jumps would be jumping to some label. A jump to a 97 | label could be implemented as: 98 | 99 | ```d 100 | void jump(ref size_t _ic, size_t label){ 101 | _ic = label; 102 | } 103 | ``` 104 | 105 | Example usage: 106 | 107 | ``` 108 | start: 109 | printS "Hello\n" 110 | loop: 111 | printS "Looping forever\n" 112 | jump @loop # @loop is replaced with the index of instruction after loop 113 | ``` 114 | 115 | ### Shared state 116 | 117 | Since the instruction functions have to be functions and not delegates, there 118 | was one way to have shared data between instructions: global variables. 119 | 120 | However that would get tricky with multiple execute calls, so an alternative is 121 | to use the `_state` parameter. An overload of `execute` can be passed a ref of 122 | a struct instance, which it will pass on to any instruction that needs it: 123 | 124 | ```d 125 | struct Stack { 126 | ptrdiff_t[512] arr; 127 | size_t top; 128 | } 129 | void push(ref Stack _state, size_t value){ 130 | _state.arr[top ++] = value; 131 | } 132 | void pop(ref Stack _state){ 133 | _state.top --; 134 | } 135 | import std.meta : AliasSeq; 136 | alias InstrucionSet = AliasSeq!(push, pop); 137 | // load 138 | ByteCode code = parseByteCode!InstrucionSet(bytecodeSource); 139 | Stack stack; 140 | // execute 141 | execute!(Stack, InstrucionSet)(code, stack); 142 | ``` 143 | 144 | --- 145 | 146 | ## License 147 | NaVM is licensed under the MIT License - see [LICENSE](LICENSE) for details 148 | -------------------------------------------------------------------------------- /docs/binarybytecode.md: -------------------------------------------------------------------------------- 1 | # Binary Bytecode 2 | 3 | This document describes how `NaBytecodeBinary` class stores bytecode in binary 4 | format. The version described in this document is the latest, see table below. 5 | 6 | The binary file contains these sections of bytecode in the this order: 7 | 8 | 1. magic number 9 | 2. version bytes 10 | 3. magic number postfix 11 | 4. metadata 12 | 7. labels 13 | 5. instruction & data 14 | 15 | ## Magic Number & Version & Magic Number Postfix 16 | 17 | This part is always 17 bytes. 18 | 19 | The first 7 bytes are to be: 20 | 21 | ASCII: 22 | ``` 23 | NAVMBC- 24 | ``` 25 | 26 | hexadecimal: 27 | ``` 28 | 4E 41 56 4D 42 43 2D 29 | ``` 30 | These are followed by 2 bytes, which are used to identify version information. 31 | 32 | 8 bytes after these bytes are ignored, these are the magic number postfix. 33 | 34 | ### Version Identifying Bytes 35 | 36 | | Version number, ushort| First NaVM Version | 37 | | --------------------- | --------------------- | 38 | | `0x0001` | v1.2 | 39 | | `0x0002` | v2.0 | 40 | 41 | Since all integers are stored in little endian, `0x0001` will be stored as: 42 | `01 00` 43 | 44 | Byte combinations not present in table above are reserved for future versions. 45 | 46 | ## Metadata 47 | 48 | An 8 byte (64 bit) unsigned integer used to store number of bytes in metadata, 49 | followed by the metadata. 50 | 51 | ## Labels 52 | 53 | An 8 byte (64 bit) unsigned integer stores the number of labels. This is 54 | followed by the labels. 55 | 56 | A label is stored as: 57 | 58 | 1. CodeIndex - fixed length, 8 bytes 59 | 2. Name - variable length (`char` array). 60 | 61 | ## Instructions & Data 62 | 63 | An 8 byte (64 bit) unsigned integer stores the _number of bytes_ used for 64 | storing instruction codes and their data. Following bytes are the `code` part 65 | of the ByteCode. 66 | 67 | --- 68 | 69 | **All integers are stored in little endian encoding** 70 | -------------------------------------------------------------------------------- /docs/syntax.md: -------------------------------------------------------------------------------- 1 | # NaVM syntax 2 | 3 | ## Statements 4 | 5 | A statement is a single line. A statement in NaVM can be: 6 | 7 | * Empty or whitespace 8 | * Label 9 | * Instruction + optional args 10 | * Label + Instruction + optional args 11 | 12 | ## Instructions 13 | 14 | Instructions are written as instructionName followed by any argument(s), 15 | separeted by whitespace. The instructionName is case sensitive. 16 | 17 | ``` 18 | instructionName argument argument 19 | ``` 20 | 21 | ## Labels 22 | 23 | Label names are case sensitive as well. 24 | 25 | ``` 26 | SomeLabel: 27 | SomeInstruction 28 | MoreInstructions 29 | OtherLabel: AnotherInstruction withSomeArg andAnotherArg # comment 30 | Jump SomeLabel 31 | ``` 32 | 33 | ## Whitespace 34 | 35 | Whitespace can be either tab(s) or space(s), and is ignored. 36 | At least 1 character of whitespace is necessary between labels, instruction 37 | names, and arguments. 38 | 39 | ## Instruction Arguments 40 | 41 | An instruction can have any number of arguments. These can be of following 42 | types: 43 | 44 | * Integer - signed integer (`ptrdiff_t`) 45 | * Double - a float 46 | * Boolean - true (a non zero ubyte), or false (0) 47 | * String - a string, enclosed between `"` 48 | * Label - position in code. Written as `@LabelName`. 49 | 50 | ### Strings 51 | 52 | These are written like: `"someString"`. 53 | 54 | The back-slash character can be used to include characters like `"` or tab by 55 | doing: `"tab: \t, quotationMark: \""` 56 | 57 | ### Characters 58 | 59 | These are written like: `'c'`. 60 | 61 | ### Hexadecimal Integer 62 | 63 | These are read as of type Integer, and are written like: `0xFFF`, where `FFF` 64 | is the number. 65 | 66 | ### Binary Integer 67 | 68 | Integer can be written in binary as: `0b1111`, where `1111` is the number. 69 | 70 | ## Comments 71 | 72 | Anything following a `#` (including the `#`) are considered comments and are 73 | ignored: 74 | 75 | ``` 76 | #comment 77 | someInstruction someArgument # this is a comment 78 | ``` 79 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "navm" 2 | description "A barebones VM for use in scripting languages" 3 | authors "Nafees Hassan" 4 | copyright "Copyright © 2019-2024, Nafees Hassan" 5 | license "MIT" 6 | dependency "utils" version="~>0.8.0" 7 | subPackage "./stackvm" 8 | -------------------------------------------------------------------------------- /source/navm/bytecode.d: -------------------------------------------------------------------------------- 1 | module navm.bytecode; 2 | 3 | import utils.misc : readHexadecimal, readBinary, isNum; 4 | import utils.ds : FIFOStack; 5 | 6 | import navm.common; 7 | 8 | import std.conv, 9 | std.uni, 10 | std.meta, 11 | std.array, 12 | std.string, 13 | std.traits, 14 | std.typecons, 15 | std.algorithm; 16 | 17 | /// Position Independent Code 18 | /// (label names, labels, instructions, data, & indexes of relative address) 19 | public struct ByteCode{ 20 | string[] labelNames; /// labelNames, index corresponds `ByteCode.labels` 21 | size_t[] labels; /// index in code for each label 22 | ubyte[] code; /// instructions and their data 23 | size_t end; /// index+1 of last instruction in code 24 | } 25 | 26 | /// ByteCode version 27 | public enum ushort NAVMBC_VERSION = 0x02; 28 | 29 | public ByteCode parseByteCode(T...)(string[] lines) 30 | if (allSatisfy!(isCallable, T)){ 31 | ByteCode ret; 32 | string[][] argsAll; 33 | 34 | // pass 1: split args, and read labels 35 | foreach (lineNo, line; lines){ 36 | string[] splits = line.separateWhitespace.filter!(a => a.length > 0).array; 37 | if (splits.length == 0) continue; 38 | if (splits[0].length && splits[0][$ - 1] == ':'){ 39 | string name = splits[0][0 .. $ - 1]; 40 | if (ret.labelNames.canFind(name)) 41 | throw new Exception(format!"line %d: label `%s` redeclared"( 42 | lineNo + 1, name)); 43 | ret.labelNames ~= name; 44 | ret.labels ~= ret.code.length; 45 | splits = splits[1 .. $]; 46 | if (splits.length == 0) 47 | continue; 48 | } 49 | immutable string inst = splits[0]; 50 | splits = splits[1 .. $]; 51 | pass1S: switch (inst){ 52 | static foreach (ind, Inst; T){ 53 | case __traits(identifier, Inst): 54 | if (splits.length!= InstArity!Inst) 55 | throw new Exception(format! 56 | "line %d: `%s` expects %d arguments, got %d" 57 | (lineNo + 1, inst, InstArity!Inst, splits.length)); 58 | ret.code ~= (cast(ushort)ind).asBytes; 59 | ret.code.length += SizeofSum!(InstArgs!Inst); 60 | break pass1S; 61 | } 62 | default: 63 | throw new Exception(format!"line %d: Instruction expected, got `%s`" 64 | (lineNo + 1, inst)); 65 | } 66 | argsAll ~= splits; 67 | } 68 | ret.end = ret.code.length; 69 | 70 | // pass 2: read args 71 | size_t pos = 0; 72 | foreach (args; argsAll){ 73 | immutable ushort inst = ret.code[pos .. $].as!ushort; 74 | pos += ushort.sizeof; 75 | pass2S: final switch (inst){ 76 | static foreach (ind, Inst; T){ 77 | case ind: 78 | ubyte[] updated = parseArgs!Inst(ret, args); 79 | ret.code[pos .. pos + SizeofSum!(InstArgs!Inst)] = updated; 80 | pos += SizeofSum!(InstArgs!Inst); 81 | break pass2S; 82 | } 83 | } 84 | } 85 | return ret; 86 | } 87 | 88 | private ubyte[] parseArgs(alias Inst)(ref ByteCode code, string[] args){ 89 | ubyte[] ret; 90 | static foreach (i, Arg; InstArgs!Inst){ 91 | static if (is (Arg == string)){ 92 | if (args[i].length && args[i][0] == '"'){ 93 | ubyte[] data = cast(ubyte[])parseData!Arg(args[i]); 94 | if (data == null) 95 | throw new Exception(format!"Instruction `%s` expected %s, got `%s`" 96 | (__traits(identifier, Inst), Arg.stringof, args[i])); 97 | ret ~= code.code.length.asBytes; 98 | ret ~= (code.code.length + data.length).asBytes; 99 | code.code ~= data; 100 | } else { 101 | throw new Exception(format! 102 | "Instruction `%s` expected string for %d-th arg, got `%s`" 103 | (__traits(identifier, Inst), i + 1, args[i])); 104 | } 105 | 106 | } else static if (isIntegral!Arg){ 107 | if (args[i].length && args[i][0] == '@'){ 108 | if (!code.labelNames.canFind(args[i][1 .. $])) 109 | throw new Exception(format!"Label `%s` used but not declared" 110 | (args[i][1 .. $])); 111 | ret ~= (cast(Arg) 112 | code.labels[code.labelNames.countUntil(args[i][1 .. $])]).asBytes; 113 | } else { 114 | ret ~= parseData!Arg(args[i]).asBytes; 115 | } 116 | } else { 117 | ret ~= parseData!Arg(args[i]).asBytes; 118 | } 119 | } 120 | return ret; 121 | } 122 | 123 | /// 124 | unittest{ 125 | void push(ushort){} 126 | void push2(ushort, ushort){} 127 | void pop(){} 128 | void add(){} 129 | void print(){} 130 | alias parse = parseByteCode!(push, push2, pop, add, print); 131 | string[] source = [ 132 | "data: push 50", 133 | "start: push 50", 134 | "push @data", 135 | "push2 1 2", 136 | "add", 137 | "print" 138 | ]; 139 | ByteCode code = parse(source); 140 | assert(code.labels.length == 2); 141 | assert(code.labelNames.canFind("data")); 142 | assert(code.labelNames.canFind("start")); 143 | assert(code.labels[0] == 0); 144 | assert(code.labels[1] == 4); 145 | } 146 | 147 | /// Returns: Expected stream size 148 | private size_t binStreamExpectedSize( 149 | size_t metadataLen = 0, 150 | size_t labelsCount = 0, 151 | size_t dataLen = 0){ 152 | return 17 + 8 + metadataLen + 153 | 8 + ((8 + 8) * labelsCount) + 154 | 8 + dataLen; 155 | } 156 | 157 | /// Writes ByteCode to a binary stream 158 | /// 159 | /// Returns: binary date in a ubyte[] 160 | public ubyte[] toBin(ref ByteCode code, ubyte[8] magicPostfix = 0, 161 | ubyte[] metadata = null){ 162 | // figure out expected length 163 | size_t expectedSize = binStreamExpectedSize( 164 | metadata.length, code.labels.length, code.code.length); 165 | // count label names sizes, add those 166 | foreach (name; code.labelNames) 167 | expectedSize += name.length; 168 | ubyte[] stream = new ubyte[expectedSize]; 169 | 170 | // header 171 | stream[0 .. 7] = cast(ubyte[])"NAVMBC-"; 172 | stream[7 .. 9] = ByteUnion!ushort(NAVMBC_VERSION).bytes; 173 | stream[9 .. 17] = magicPostfix; 174 | 175 | // metadata 176 | stream[17 .. 25] = ByteUnion!(size_t, 8)(metadata.length).bytes; 177 | stream[25 .. 25 + metadata.length] = metadata; 178 | size_t seek = 25 + metadata.length; 179 | 180 | // labels 181 | stream[seek .. seek + 8] = ByteUnion!(size_t, 8)(code.labels.length).bytes; 182 | seek += 8; 183 | foreach (i, name; code.labelNames){ 184 | stream[seek .. seek + 8] = ByteUnion!(size_t, 8)(code.labels[i]).bytes; 185 | seek += 8; 186 | stream[seek .. seek + 8] = ByteUnion!(size_t, 8)(name.length).bytes; 187 | seek += 8; 188 | stream[seek .. seek + name.length] = cast(ubyte[])cast(char[])name; 189 | seek += name.length; 190 | } 191 | 192 | // instructions data 193 | stream[seek .. seek + 8] = ByteUnion!(size_t, 8)(code.end).bytes; 194 | seek += 8; 195 | stream[seek .. seek + code.code.length] = code.code; 196 | seek += code.code.length; 197 | 198 | return stream; 199 | } 200 | 201 | /// 202 | unittest{ 203 | ByteCode code;/// empty code 204 | ubyte[] bin = code.toBin([1, 2, 3, 4, 5, 6, 7, 8], [8, 9, 10]); 205 | assert(bin.length == 17 + 8 + 3 + 8 + 8); 206 | assert(bin[0 .. 7] == "NAVMBC-"); // magic bytes 207 | assert(bin[7 .. 9] == [2, 0]); // version 208 | assert(bin[9 .. 17] == [1, 2, 3, 4, 5, 6, 7, 8]); // magic postfix 209 | assert(bin[17 .. 25] == [3, 0, 0, 0, 0, 0, 0, 0]); // length of metadata 210 | assert(bin[25 .. 28] == [8, 9, 10]); // metadata 211 | } 212 | 213 | /// Reads ByteCode from a byte stream in ubyte[] 214 | /// Throws: Exception in case of error 215 | /// Returns: ByteCode 216 | public ByteCode fromBin(ubyte[] stream, ref ubyte[8] magicPostfix, 217 | ref ubyte[] metadata){ 218 | if (stream.length < binStreamExpectedSize) 219 | throw new Exception("Stream size if less than minimum possible size"); 220 | if (stream[0 .. 7] != "NAVMBC-") 221 | throw new Exception("Invalid header in stream"); 222 | if (stream[7 .. 9] != ByteUnion!(ushort, 2)(NAVMBC_VERSION).bytes) 223 | throw new Exception("Stream is of different ByteCode version.\n" ~ 224 | "\tStream: " ~ ByteUnion!ushort(stream[7 .. 9]).data.to!string ~ 225 | "\tSupported: " ~ NAVMBC_VERSION); 226 | magicPostfix = stream[9 .. 17]; 227 | size_t len = ByteUnion!(size_t, 8)(stream[17 .. 25]).data; 228 | if (binStreamExpectedSize(len) > stream.length) 229 | throw new Exception("Invalid stream length"); 230 | metadata = stream[25 .. 25 + len]; 231 | size_t seek = 25 + len; 232 | 233 | ByteCode code; 234 | 235 | // labels 236 | len = ByteUnion!(size_t, 8)(stream[seek .. seek + 8]).data; 237 | if (binStreamExpectedSize(metadata.length, len) > stream.length) 238 | throw new Exception("Invalid stream length"); 239 | seek += 8; 240 | code.labels.length = len; 241 | code.labelNames.length = len; 242 | foreach (i, ref label; code.labels){ 243 | label = ByteUnion!(size_t, 8)(stream[seek .. seek + 8]).data; 244 | seek += 8; 245 | len = ByteUnion!(size_t, 8)(stream[seek .. seek + 8]).data; 246 | seek += 8; 247 | if (seek + len > stream.length) 248 | throw new Exception("Invalid stream length"); 249 | code.labelNames[i] = cast(immutable char[])stream[seek .. seek + len].dup; 250 | seek += len; 251 | } 252 | 253 | // data 254 | code.end = ByteUnion!(size_t, 8)(stream[seek .. seek + 8]).data; 255 | if (binStreamExpectedSize(metadata.length, code.labels.length, code.end) 256 | > stream.length) 257 | throw new Exception("Invalid stream length"); 258 | seek += 8; 259 | code.code = stream[seek .. $].dup; 260 | return code; 261 | } 262 | 263 | /// 264 | unittest{ 265 | import std.functional, std.range; 266 | ByteCode code; 267 | ubyte[] data = iota(cast(ubyte)0, ubyte.max).cycle.take(0).array; 268 | code.code = data.dup; 269 | code.labelNames = ["data", "start", "loop", "end"]; 270 | code.labels = [ 271 | 0, 272 | 2, 273 | 1025, 274 | 1300, 275 | ]; 276 | 277 | ubyte[] bin = code.toBin([1, 2, 3, 4, 5, 6, 7, 8], [1, 2, 3]).dup; 278 | ubyte[8] postfix; 279 | ubyte[] metadata; 280 | ByteCode decoded = bin.fromBin(postfix, metadata); 281 | assert(postfix == [1, 2, 3, 4, 5, 6, 7, 8]); 282 | assert(metadata == [1, 2, 3]); 283 | assert(decoded.labels.length == 4); 284 | assert(decoded.labels == [0, 2, 1025, 1300]); 285 | assert(decoded.labelNames[0] == "data"); 286 | assert(decoded.labelNames[1] == "start"); 287 | assert(decoded.labelNames[2] == "loop"); 288 | assert(decoded.labelNames[3] == "end"); 289 | assert(decoded.code == data, decoded.code.to!string); 290 | } 291 | 292 | /// Parses data 293 | /// Throws: Exception if incorrect format 294 | /// Returns: parsed data. 295 | private T parseData(T)(string s){ 296 | static if (isIntegral!T){ 297 | // can be just an int 298 | if (isNum(s, false)) 299 | return s.to!T; 300 | // can be a binary or hex literal 301 | if (s.length > 2 && s[0] == '0'){ 302 | if (s[1] == 'b') 303 | return (cast(T)readBinary(s[2 .. $])); 304 | else if (s[1] == 'x') 305 | return (cast(T)readHexadecimal(s[2 .. $])); 306 | } 307 | throw new Exception(format!"`%s` is not an integer"(s)); 308 | 309 | } else static if (isFloatingPoint!T){ 310 | if (isNum(s, true)) 311 | return s.to!T; 312 | throw new Exception(format!"`%s` is not a float"(s)); 313 | 314 | } else static if (is (T == bool)){ 315 | if (s == "true") 316 | return true; 317 | if (s == "false") 318 | return false; 319 | throw new Exception(format!"`%s` is not a boolean"(s)); 320 | 321 | } else static if (isSomeChar!T){ 322 | if (s.length < 2 || s[0] != s[$ - 1] || s[0] != '\'') 323 | return null; 324 | s = s[1 .. $ - 1].unescape; 325 | return s.to!T; 326 | 327 | } else static if (is (T == string)){ 328 | if (s.length < 2 || s[0] != s[$ - 1] || s[0] != '\"') 329 | throw new Exception(format!"`%s` is not a string"(s)); 330 | s = s[1 .. $ - 1].unescape; 331 | return s.to!T; 332 | 333 | } else { 334 | static assert(false, "Unsupported argument type " ~ T.stringof); 335 | } 336 | } 337 | 338 | /// 339 | unittest{ 340 | assert("true".parseData!bool == true); 341 | assert("false".parseData!bool == false); 342 | assert("0x50".parseData!size_t == 0x50); 343 | assert("0b101010".parseData!size_t == 0b101010); 344 | assert("12345".parseData!size_t == 1_2345); 345 | assert("\"bla bla\"".parseData!string == "bla bla"); 346 | assert("5.5".parseData!double == "5.5".to!double); 347 | } 348 | -------------------------------------------------------------------------------- /source/navm/common.d: -------------------------------------------------------------------------------- 1 | module navm.common; 2 | 3 | import std.meta, 4 | std.conv, 5 | std.traits; 6 | 7 | /// Whether an instruction is stateful 8 | package template InstIsStateful(alias T) if (isCallable!T){ 9 | enum InstIsStateful = getInstIsStateful; 10 | private bool getInstIsStateful(){ 11 | foreach (name; ParameterIdentifierTuple!T){ 12 | if (name == "_state") 13 | return true; 14 | } 15 | return false; 16 | } 17 | } 18 | 19 | /// Whether any of the instructions in a set require state 20 | package template InstsIsStateful(T...) if (allSatisfy!(isCallable, T)){ 21 | enum InstsIsStateful = 22 | EraseAll!(false, staticMap!(InstIsStateful, T)).length > 0; 23 | } 24 | 25 | /// Whether N'th parameter of an Instruction is an argument 26 | package template InstParamIsArg(alias T, size_t N) if (isCallable!T){ 27 | enum InstParamIsArg = 28 | ParameterIdentifierTuple!T[N] != "_code" && 29 | ParameterIdentifierTuple!T[N] != "_ic" && 30 | ParameterIdentifierTuple!T[N] != "_state"; 31 | } 32 | 33 | /// Instruction Function's argument types (these exclude stuff like _ic...) 34 | package template InstArgs(alias T) if (isCallable!T){ 35 | alias InstArgs = AliasSeq!(); 36 | static foreach (i; 0 .. Parameters!T.length){ 37 | static if (InstParamIsArg!(T, i)) 38 | InstArgs = AliasSeq!(InstArgs, Parameters!T[i]); 39 | } 40 | } 41 | 42 | /// Function arity (instruction arguments only) 43 | package template InstArity(alias T){ 44 | enum InstArity = InstArgs!T.length; 45 | } 46 | 47 | /// ditto 48 | package template InstArities(T...) if (allSatisfy!(isCallable, T)){ 49 | alias InstArities = AliasSeq!(); 50 | static foreach (sym; T) 51 | InstArities = AliasSeq!(InstArities, InstArity!sym); 52 | } 53 | 54 | /// If a T can be .sizeof'd 55 | package enum HasSizeof(alias T) = __traits(compiles, T.sizeof); 56 | 57 | /// sum of sizes 58 | package template SizeofSum(T...) if (allSatisfy!(HasSizeof, T)){ 59 | enum SizeofSum = calculateSizeofSum(); 60 | private size_t calculateSizeofSum(){ 61 | size_t ret = 0; 62 | foreach (sym; T) 63 | ret += sym.sizeof; 64 | return ret; 65 | } 66 | } 67 | 68 | /// Mapping of Args to Params for an instruction. size_t.max for unmapped 69 | package template InstParamArgMapping(alias T) if (isCallable!T){ 70 | enum InstParamArgMapping = getMapping; 71 | size_t[Parameters!T.length] getMapping(){ 72 | size_t[Parameters!T.length] ret; 73 | size_t count = 0; 74 | static foreach (i; 0 .. Parameters!T.length){ 75 | static if (InstParamIsArg!(T, i)){ 76 | ret[i] = count ++; 77 | } else { 78 | ret[i] = size_t.max; 79 | } 80 | } 81 | return ret; 82 | } 83 | } 84 | 85 | /// Instruction's Parameters alias for calling 86 | package template InstCallStatement(alias Inst) if (isCallable!Inst){ 87 | enum InstCallStatement = getStatement(); 88 | private string getStatement(){ 89 | string ret = "Inst("; 90 | static foreach (i, mapTo; InstParamArgMapping!Inst){ 91 | static if (mapTo == size_t.max){ 92 | static if (ParameterIdentifierTuple!Inst[i] == "_ic"){ 93 | ret ~= "ic, "; 94 | } else static if (ParameterIdentifierTuple!Inst[i] == "_state"){ 95 | ret ~= "state, "; 96 | } else static if (ParameterIdentifierTuple!Inst[i] == "_code"){ 97 | ret ~= "code, "; 98 | } 99 | } else { 100 | ret ~= "p[" ~ mapTo.to!string ~ "], "; 101 | } 102 | } 103 | if (ret[$ - 1] == '(') 104 | return ret ~ ");"; 105 | return ret[0 .. $ - 2] ~ ");"; 106 | } 107 | } 108 | 109 | 110 | /// Union with array of ubytes 111 | package union ByteUnion(T, ubyte N = T.sizeof){ 112 | T data; 113 | ubyte[N] bytes; 114 | this(ubyte[N] bytes){ 115 | this.bytes = bytes; 116 | } 117 | this(ubyte[] bytes){ 118 | assert(bytes.length >= N); 119 | this.bytes = bytes[0 .. N]; 120 | } 121 | this(T data){ 122 | this.data = data; 123 | } 124 | } 125 | 126 | 127 | /// Reads a ubyte[] as a type 128 | /// Returns: value in type T 129 | pragma(inline, true) package T as(T)(ubyte[] data) { 130 | assert(data.length >= T.sizeof); 131 | return *(cast(T*)data.ptr); 132 | } 133 | 134 | /// Returns: ubyte[] against a value of type T 135 | pragma(inline, true) package ubyte[] asBytes(T)(T val) { 136 | ubyte[] ret; 137 | ret.length = T.sizeof; 138 | return ret[] = (cast(ubyte*)&val)[0 .. T.sizeof]; 139 | } 140 | 141 | /// 142 | unittest{ 143 | assert((cast(ptrdiff_t)1025).asBytes.as!ptrdiff_t == 1025); 144 | assert("hello".asBytes.as!string == "hello"); 145 | assert((cast(double)50.5).asBytes.as!double == 50.5); 146 | assert('a'.asBytes.as!char == 'a'); 147 | assert(true.asBytes.as!bool == true); 148 | } 149 | 150 | 151 | /// reads a string into substrings separated by whitespace. Strings are read 152 | /// as a whole 153 | /// 154 | /// Returns: substrings 155 | /// 156 | /// Throws: Exception if string not closed 157 | package string[] separateWhitespace(string line){ 158 | string[] r; 159 | size_t i, start; 160 | for (; i < line.length; i++){ 161 | immutable char c = line[i]; 162 | if (c == '#'){ 163 | if (start < i) 164 | r ~= line[start .. i]; 165 | break; 166 | } 167 | if (c == '"' || c == '\''){ 168 | if (start < i) 169 | r ~= line[start .. i]; 170 | start = i; 171 | immutable ptrdiff_t endIndex = i + line[i .. $].strEnd; 172 | if (endIndex <= i) 173 | throw new Exception("string not closed"); 174 | r ~= line[start .. endIndex + 1]; 175 | start = endIndex + 1; 176 | i = endIndex; 177 | continue; 178 | } 179 | 180 | if (c == ' ' || c == '\t'){ 181 | if (start < i) 182 | r ~= line[start .. i]; 183 | while (i < line.length && (line[i] == ' ' || line[i] == '\t')) 184 | i ++; 185 | start = i; 186 | i --; // back to whitespace, i++ in for(..;..;) exists 187 | continue; 188 | } 189 | 190 | } 191 | if (i == line.length && start <= i - 1) 192 | r ~= line[start .. $].dup; 193 | return r; 194 | } 195 | /// 196 | unittest{ 197 | assert("potato".separateWhitespace == ["potato"]); 198 | assert("potato potato".separateWhitespace == ["potato", "potato"]); 199 | assert(" a b \"str\"".separateWhitespace == ["a", "b", "\"str\""]); 200 | assert("a b 'c' \"str\"".separateWhitespace == ["a", "b", "'c'", "\"str\""]); 201 | assert("\ta \t b\"str\"".separateWhitespace == ["a", "b", "\"str\""]); 202 | assert(" a b 'c'\"str\"'c'".separateWhitespace == 203 | ["a", "b", "'c'", "\"str\"", "'c'"]); 204 | assert("a 'b'#c".separateWhitespace == ["a", "'b'"]); 205 | assert("a: a b#c".separateWhitespace == ["a:","a", "b"]); 206 | assert("a 'b' #c".separateWhitespace == ["a", "'b'"]); 207 | } 208 | 209 | /// Returns: the index where a string ends, -1 if not terminated 210 | package ptrdiff_t strEnd(string s){ 211 | if (s.length == 0) 212 | return -1; 213 | immutable char strTerminator = s[0]; 214 | size_t i; 215 | for (i = 1; i < s.length; i ++){ 216 | if (s[i] == strTerminator) 217 | return i; 218 | i += s[i] == '\\'; 219 | } 220 | return -1; 221 | } 222 | /// 223 | unittest{ 224 | assert(2 + "st\"sdfsdfsd\"0"[2 .. $].strEnd == 11); 225 | } 226 | 227 | /// Returns: unescaped string 228 | package string unescape(string s){ 229 | if (s.length == 0) 230 | return null; 231 | char[] r = []; 232 | for (size_t i = 0; i < s.length; i ++){ 233 | if (s[i] != '\\'){ 234 | r ~= s[i]; 235 | continue; 236 | } 237 | if (i + 1 < s.length){ 238 | char c = s[i + 1]; 239 | switch (c){ 240 | case 't': r ~= '\t'; i ++; continue; 241 | case 'n': r ~= '\n'; i ++; continue; 242 | case '\\': r ~= '\\'; i ++; continue; 243 | default: break; 244 | } 245 | } 246 | r ~= s[i]; 247 | } 248 | return cast(string)r; 249 | } 250 | /// 251 | unittest{ 252 | assert("newline:\\ntab:\\t".unescape == 253 | "newline:\ntab:\t", "newline:\\ntab:\\t".unescape); 254 | } 255 | -------------------------------------------------------------------------------- /source/navm/navm.d: -------------------------------------------------------------------------------- 1 | module navm.navm; 2 | 3 | import std.conv, 4 | std.meta, 5 | std.traits; 6 | 7 | import navm.common; 8 | 9 | public import navm.bytecode; 10 | 11 | /// Execute a Code 12 | public void execute(S, T...)( 13 | ref ByteCode code, 14 | ref S state, 15 | size_t label = size_t.max) if (allSatisfy!(isCallable, T)){ 16 | size_t ic; 17 | if (label < code.labels.length) 18 | ic = code.labels[label]; 19 | while (ic < code.end){ 20 | immutable ushort inst = code.code[ic .. $].as!ushort; 21 | ic += ushort.sizeof; 22 | switcher: switch (inst){ 23 | foreach (ind, Inst; T){ 24 | case ind: 25 | { 26 | InstArgs!Inst p; 27 | static foreach (i, Arg; InstArgs!Inst){ 28 | static if (is (Arg == string)){ 29 | immutable size_t 30 | start = *(cast(size_t*)(code.code.ptr + ic)), 31 | end = *(cast(size_t*)(code.code.ptr + ic + size_t.sizeof)); 32 | p[i] = cast(string)(code.code[start .. end]); 33 | ic += size_t.sizeof * 2; 34 | } else { 35 | p[i] = *(cast(Arg*)(code.code.ptr + ic)); 36 | ic += Arg.sizeof; 37 | } 38 | } 39 | mixin(InstCallStatement!Inst); 40 | } 41 | break switcher; 42 | } 43 | default: 44 | break; 45 | } 46 | } 47 | } 48 | 49 | /// 50 | unittest{ 51 | struct State{ int i; } 52 | void inc1(ref State _state){ 53 | _state.i += 1; 54 | } 55 | void inc2(ref State _state){ 56 | _state.i += 2; 57 | } 58 | ByteCode code = parseByteCode!(inc1, inc2)([ 59 | "inc1", "inc2", "inc1" 60 | ]); 61 | State state; 62 | execute!(State, inc1, inc2)(code, state); 63 | assert(state.i == 4); 64 | } 65 | 66 | /// ditto 67 | public void execute(T...)(ref ByteCode code, size_t label = size_t.max) if ( 68 | allSatisfy!(isCallable, T) && !InstsIsStateful!T){ 69 | ubyte dummyState; 70 | execute!(ubyte, T)(code, dummyState, label); 71 | } 72 | 73 | /// 74 | unittest{ 75 | int i; 76 | void inc1(){ 77 | i += 1; 78 | } 79 | void inc2(){ 80 | i += 2; 81 | } 82 | ByteCode code = parseByteCode!(inc1, inc2)([ 83 | "inc1", "inc2", "inc1" 84 | ]); 85 | execute!(inc1, inc2)(code); 86 | assert(i == 4); 87 | } 88 | -------------------------------------------------------------------------------- /stackvm/.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | docs/ 5 | /stackvm 6 | stackvm.so 7 | stackvm.dylib 8 | stackvm.dll 9 | stackvm.a 10 | stackvm.lib 11 | stackvm-test-* 12 | *.exe 13 | *.pdb 14 | *.o 15 | *.obj 16 | *.lst 17 | -------------------------------------------------------------------------------- /stackvm/README.md: -------------------------------------------------------------------------------- 1 | # StackVM 2 | 3 | A Demo VM built using NaVM. 4 | 5 | ## Instructions 6 | 7 | ### Arithmetic 8 | 9 | * `addI` - pushes sum of top 2 integers 10 | * `subI` - pops a, b integers. Pushes a - b 11 | * `mulI` - pushes product of top 2 integers 12 | * `divI` - pops a, b integers. Pushes a / b 13 | * `modI` - pops a, b integers. Pushes a % b 14 | * `addF` - pushes sum of top 2 floats 15 | * `subF` - pops a, b floats. Pushes a - b 16 | * `mulF` - pushes product of top 2 floats 17 | * `divF` - pops a, b floats. Pushes a / b 18 | 19 | ### Comparison 20 | 21 | * `cmp` - Compares top 2 32bit values. Pushes 1 (integer) if same, else 0 22 | * `lesI` - Pops a, b integers. Pushes 1 (integer) if a < b, else 0 23 | * `lesF` - Pops a, b floats. Pushes 1 (integer) if a < b, else 0 24 | * `notB` - Pushes 1 (integer) if top integer is 0, else 0 25 | * `andB` - Pushes 1 (integer) if top 2 integers are non-zero, else 0 26 | * `orB` - Pushes 1 (integer) if either of top 2 integers are non-zero, else 0 27 | 28 | ### Bitwise 29 | 30 | * `not` - Pushes NOT of top integer 31 | * `and` - Pushes AND of top 2 integers 32 | * `or` - Pushes OR of top 2 integers 33 | 34 | ### Stack manipulation 35 | 36 | * `pshI a` - Pushes integer `a` 37 | * `pshF a` - Pushes float `a` 38 | * `pop` - Pops 1 integer/float 39 | * `popN n` - Pops `n` integers/floats 40 | * `seek` - Pushes top integer/float 41 | * `off n` - Adds `n` to stack read/write offset 42 | * `off0` - Sets stack read/write offset to 0 43 | * `pshO` - pushes stack offset 44 | * `popO` - Pops integer, sets it as stack offset 45 | * `get n` - Pushes 32 bits from `offset + n` 46 | * `getR n` - Pushes address of `offset + n` 47 | * `put n` - Pops 32 bit value. Writes to `offset + n` 48 | * `putR` - Pops a 32 bit value, address. Writes value to stack at address. 49 | * `incA n` - Increments integer at `offset + n` 50 | * `incR` - Pops a 32 bit address. Increments integer at that address 51 | 52 | ### Jumps 53 | 54 | * `jmp label` - Jumps execution to label 55 | * `jmpC label` - Jumps execution to label, if top integer is non-zero. 56 | * `call label` - Pushes stack offset, `_ic`, and `_dc`, sets offset to top, 57 | and jumps to label. Pushed data equals 12 bytes or 3 ints. 58 | * `ret` - pops `_dc`, `_ic`, and stack offset and sets them. 59 | 60 | ### Printing 61 | 62 | * `dbg` - Prints debug info. 63 | * `printI` - Prints top integer popped from stack 64 | * `printF` - Prints top float popped from stack 65 | * `printS s` - Prints the string `s` 66 | -------------------------------------------------------------------------------- /stackvm/dub.sdl: -------------------------------------------------------------------------------- 1 | name "stackvm" 2 | description "A basic stack based VM using NaVM" 3 | authors "Nafees Hassan" 4 | copyright "Copyright © 2023, Nafees Hassan" 5 | license "MIT" 6 | dependency "navm" path="../" 7 | targetPath "../" 8 | targetName "svm" 9 | -------------------------------------------------------------------------------- /stackvm/source/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio, 2 | std.datetime.stopwatch, 3 | std.traits, 4 | std.meta, 5 | std.conv : to; 6 | 7 | import utils.misc; 8 | 9 | import navm.navm; 10 | import navm.bytecode; 11 | 12 | struct Stack{ 13 | ubyte[4096] stack; 14 | ushort seek; 15 | ushort base; 16 | pragma(inline, true) T pop(T)() if (!isArray!T){ 17 | assert(seek >= T.sizeof, "Cannot pop `" ~ T.stringof ~ "` " ~ " seek is " 18 | ~ seek.to!string); 19 | seek -= T.sizeof; 20 | return *(cast(T*)(stack.ptr + seek)); 21 | } 22 | pragma(inline, true) T top(T)() if (!isArray!T){ 23 | assert(seek >= T.sizeof, "Cannot pop `" ~ T.stringof ~ "` " ~ " seek is " 24 | ~ seek.to!string); 25 | return *(cast(T*)(stack.ptr + seek - T.sizeof)); 26 | } 27 | pragma(inline, true) void push(T)(T val) if (!isArray!T){ 28 | assert(seek + T.sizeof <= stack.length, "Cannot push `" ~ T.stringof ~ 29 | "` seek is " ~ seek.to!string); 30 | stack[seek .. seek + T.sizeof] = (cast(ubyte*)&val)[0 .. T.sizeof]; 31 | seek += T.sizeof; 32 | } 33 | } 34 | 35 | /// 36 | unittest{ 37 | Stack stack; 38 | stack.push(cast(ubyte)127); 39 | stack.push(cast(ubyte)128); 40 | stack.push(cast(ptrdiff_t)ptrdiff_t.max); 41 | assert(stack.pop!ptrdiff_t == ptrdiff_t.max); 42 | assert(stack.pop!ubyte == 128); 43 | assert(stack.pop!ubyte == 127); 44 | } 45 | 46 | static assert(float.sizeof == int.sizeof); 47 | 48 | // math instructions 49 | 50 | void addI(ref Stack _state){ 51 | _state.push!int(_state.pop!int + _state.pop!int); 52 | } 53 | 54 | void subI(ref Stack _state){ 55 | immutable int a = _state.pop!int; 56 | _state.push!int(a - _state.pop!int); 57 | } 58 | 59 | void mulI(ref Stack _state){ 60 | _state.push!int(_state.pop!int * _state.pop!int); 61 | } 62 | 63 | void divI(ref Stack _state){ 64 | immutable int a = _state.pop!int; 65 | _state.push!int(a / _state.pop!int); 66 | } 67 | 68 | void modI(ref Stack _state){ 69 | immutable int a = _state.pop!int; 70 | _state.push!int(a % _state.pop!int); 71 | } 72 | 73 | void addF(ref Stack _state){ 74 | _state.push!float(_state.pop!float + _state.pop!float); 75 | } 76 | 77 | void subF(ref Stack _state){ 78 | immutable float a = _state.pop!float; 79 | _state.push!float(a - _state.pop!float); 80 | } 81 | 82 | void mulF(ref Stack _state){ 83 | _state.push!float(_state.pop!float * _state.pop!float); 84 | } 85 | 86 | void divF(ref Stack _state){ 87 | immutable float a = _state.pop!float; 88 | _state.push!float(a / _state.pop!float); 89 | } 90 | 91 | // comparison 92 | 93 | void cmp(ref Stack _state){ 94 | _state.push!int(_state.pop!int == _state.pop!int); 95 | } 96 | 97 | void lesI(ref Stack _state){ 98 | immutable int a = _state.pop!int; 99 | _state.push!int(a < _state.pop!int); 100 | } 101 | 102 | void lesF(ref Stack _state){ 103 | immutable float a = _state.pop!float; 104 | _state.push!int(a < _state.pop!float); 105 | } 106 | 107 | // boolean 108 | 109 | void notB(ref Stack _state){ 110 | _state.push!int(_state.pop!int == 0); 111 | } 112 | 113 | void andB(ref Stack _state){ 114 | immutable int a = _state.pop!int, b = _state.pop!int; 115 | _state.push!int(a && b); 116 | } 117 | 118 | void orB(ref Stack _state){ 119 | immutable int a = _state.pop!int, b = _state.pop!int; 120 | _state.push!int(a || b); 121 | } 122 | 123 | // bitwise 124 | 125 | void not(ref Stack _state){ 126 | _state.push!int(~(_state.pop!int)); 127 | } 128 | 129 | void and(ref Stack _state){ 130 | immutable int a = _state.pop!int, b = _state.pop!int; 131 | _state.push!int(a & b); 132 | } 133 | 134 | void or(ref Stack _state){ 135 | immutable int a = _state.pop!int, b = _state.pop!int; 136 | _state.push!int(a | b); 137 | } 138 | 139 | // stack manipulation 140 | 141 | void pshI(ref Stack _state, int val){ 142 | _state.push!int(cast(int)val); 143 | } 144 | 145 | void pshF(ref Stack _state, float val){ 146 | _state.push!float(val); 147 | } 148 | 149 | void pop(ref Stack _state){ 150 | _state.seek -= int.sizeof; 151 | } 152 | 153 | void popN(ref Stack _state, int n){ 154 | _state.seek -= int.sizeof * n; 155 | } 156 | 157 | void seek(ref Stack _state){ 158 | _state.push!int(_state.top!int); 159 | } 160 | 161 | void off(ref Stack _state, int n){ 162 | _state.base += n; 163 | } 164 | 165 | void off0(ref Stack _state){ 166 | _state.base = 0; 167 | } 168 | 169 | void pshO(ref Stack _state){ 170 | _state.push!int(_state.base); 171 | } 172 | 173 | void popO(ref Stack _state){ 174 | _state.base = cast(ushort)_state.pop!int; 175 | } 176 | 177 | void get(ref Stack _state, int offset){ 178 | _state.push!int(*(cast(int*)(_state.stack.ptr + _state.base + offset))); 179 | } 180 | 181 | void getR(ref Stack _state){ 182 | immutable int offset = _state.pop!int; 183 | _state.push!int(_state.base + offset); 184 | } 185 | 186 | void put(ref Stack _state, int offset){ 187 | *cast(int*)(_state.stack.ptr + _state.base + offset) = _state.pop!int; 188 | } 189 | 190 | void putR(ref Stack _state){ 191 | immutable int val = _state.pop!int, addr = _state.pop!int; 192 | *cast(int*)(_state.stack.ptr + addr) = val; 193 | } 194 | 195 | void incA(ref Stack _state, int offset){ 196 | int *ptr = cast(int*)(_state.stack.ptr + _state.base + offset); 197 | *ptr = *ptr + 1; 198 | } 199 | 200 | void incR(ref Stack _state){ 201 | int *ptr = cast(int*)(_state.stack.ptr + _state.pop!int); 202 | *ptr = *ptr + 1; 203 | } 204 | 205 | // jumps 206 | 207 | void jmp(ref size_t _ic, ref ByteCode _code, uint label){ 208 | _ic = label; 209 | } 210 | 211 | void jmpC(ref size_t _ic, ref ByteCode _code, ref Stack _state, 212 | uint label){ 213 | if (_state.pop!int != 0) 214 | _ic = label; 215 | } 216 | 217 | void call(ref size_t _ic, ref ByteCode _code, ref Stack _state, uint label){ 218 | _state.push!int(_state.base); 219 | _state.push!int(cast(int)_ic); 220 | _state.base = _state.seek; 221 | _ic = label; 222 | } 223 | 224 | void ret(ref size_t _ic, ref ByteCode _code, ref Stack _state){ 225 | _ic = _state.pop!int; 226 | _state.base = cast(ushort)_state.pop!int; 227 | } 228 | 229 | void dbg(ref Stack _state){ 230 | writefln!"base: %d\tseek: %d"(_state.base, _state.seek); 231 | } 232 | 233 | void printI(ref Stack _state){ 234 | write(_state.pop!int); 235 | } 236 | 237 | void printF(ref Stack _state){ 238 | write(_state.pop!float); 239 | } 240 | 241 | void printS(string s){ 242 | write(s); 243 | } 244 | 245 | alias InstructionSet = AliasSeq!(addI, subI, mulI, divI, modI, addF, subF, 246 | mulF, divF, cmp, lesI, lesF, notB, andB, orB, not, and, or, pshI, pshF, 247 | pop, popN, seek, off, pshO, popO, off0, get, getR, put, putR, incA, incR, 248 | jmp, jmpC, call, ret, dbg, printI, printF, printS); 249 | 250 | void main(string[] args){ 251 | if (args.length < 2) 252 | args = [args[0], "tests/default"]; 253 | immutable size_t count = args.length > 2 && args[2].isNum 254 | ? args[2].to!size_t : 1; 255 | StopWatch sw; 256 | ByteCode code = parseByteCode!InstructionSet(fileToArray(args[1])); 257 | debug { 258 | writeln("Code: "); 259 | writeln(code); 260 | } 261 | 262 | Stack state; 263 | immutable ptrdiff_t startIndex = code.labelNames.indexOf("start"); 264 | if (startIndex == -1){ 265 | writeln("label `start` not found"); 266 | return; 267 | } 268 | 269 | size_t min = size_t.max ,max = 0 ,avg = 0; 270 | sw = StopWatch(AutoStart.no); 271 | foreach (i; 0 .. count){ 272 | state = Stack.init; 273 | sw.start; 274 | execute!(Stack, InstructionSet)(code, state, startIndex); 275 | sw.stop; 276 | immutable size_t currentTime = sw.peek.total!"msecs" - avg; 277 | min = currentTime < min ? currentTime : min; 278 | max = currentTime > max ? currentTime : max; 279 | avg = sw.peek.total!"msecs"; 280 | } 281 | avg = sw.peek.total!"msecs" / count; 282 | 283 | writeln("executed `",args[1],"` ",count," times:"); 284 | writeln("min\tmax\tavg\ttotal"); 285 | writeln(min,'\t',max,'\t',avg,'\t',sw.peek.total!"msecs"); 286 | } 287 | -------------------------------------------------------------------------------- /tests/default: -------------------------------------------------------------------------------- 1 | start: 2 | pshI 0 3 | loop: 4 | get 0 5 | printI 6 | printS "\n" 7 | 8 | incA 0 9 | pshI 100000 10 | get 0 11 | lesI 12 | jmpC @loop 13 | end: 14 | -------------------------------------------------------------------------------- /tests/fib: -------------------------------------------------------------------------------- 1 | fib: 2 | get -12 3 | pshI 1 4 | lesI 5 | jmpC @fibCalc 6 | ret 7 | fibCalc: 8 | # decrement n by 2 9 | get -12 10 | pshI -1 11 | addI # reverse counter at 0 12 | pshI 0 # prev, at 4 13 | pshI 1 # current, at 8 14 | fibLoopCond: 15 | get 0 16 | jmpC @fibLoop 17 | get 8 18 | put -12 19 | popN 3 20 | ret 21 | fibLoop: 22 | # decrement n 23 | pshI -1 24 | get 0 25 | addI 26 | put 0 27 | 28 | # prev + pprev, hold in stack 29 | get 4 30 | get 8 31 | addI 32 | # prev = current 33 | get 8 34 | put 4 35 | # save current 36 | put 8 37 | jmp @fibLoopCond 38 | 39 | start: 40 | pshI 36 41 | call @fib 42 | printI 43 | -------------------------------------------------------------------------------- /tests/recfib: -------------------------------------------------------------------------------- 1 | fib: 2 | get -12 3 | pshI 1 4 | lesI 5 | jmpC @fibRecCall 6 | ret 7 | fibRecCall: 8 | pshI 1 9 | get -12 10 | subI 11 | call @fib 12 | 13 | pshI 2 14 | get -12 15 | subI 16 | call @fib 17 | 18 | addI 19 | put -12 20 | ret 21 | start: 22 | pshI 36 23 | call @fib 24 | printI 25 | -------------------------------------------------------------------------------- /tests/str: -------------------------------------------------------------------------------- 1 | start: 2 | printS "Hello\t" 3 | printS "World\n" 4 | --------------------------------------------------------------------------------