MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 238 lines 5.2 kB view raw
1const STACK_SIZE = 256; 2const PROGRAM_SIZE = 1024; 3 4const Opcode = { 5 OP_PUSH: 0, 6 OP_POP: 1, 7 OP_ADD: 2, 8 OP_SUB: 3, 9 OP_MUL: 4, 10 OP_DIV: 5, 11 OP_PRINT: 6, 12 OP_JMP: 7, 13 OP_JZ: 8, 14 OP_LT: 9, 15 OP_EQ: 10, 16 OP_NEG: 11, 17 OP_HALT: 12 18}; 19 20const opcodeNames = { 21 [Opcode.OP_PUSH]: 'OP_PUSH', 22 [Opcode.OP_POP]: 'OP_POP', 23 [Opcode.OP_ADD]: 'OP_ADD', 24 [Opcode.OP_SUB]: 'OP_SUB', 25 [Opcode.OP_MUL]: 'OP_MUL', 26 [Opcode.OP_DIV]: 'OP_DIV', 27 [Opcode.OP_PRINT]: 'OP_PRINT', 28 [Opcode.OP_JMP]: 'OP_JMP', 29 [Opcode.OP_JZ]: 'OP_JZ', 30 [Opcode.OP_LT]: 'OP_LT', 31 [Opcode.OP_EQ]: 'OP_EQ', 32 [Opcode.OP_NEG]: 'OP_NEG', 33 [Opcode.OP_HALT]: 'OP_HALT' 34}; 35 36// Debug settings 37const DEBUG_ENABLED = true; 38 39class VM { 40 constructor() { 41 this.sp = -1; 42 this.pc = 0; 43 this.stack = new Array(STACK_SIZE).fill(0); 44 this.program = new Uint8Array(PROGRAM_SIZE); 45 } 46 47 free() { 48 this.sp = -1; 49 this.pc = 0; 50 } 51 52 checkStackHasValues(count) { 53 if (this.sp < count - 1) { 54 throw new Error(`Stack Underflow! Need ${count} value(s), but stack has ${this.sp + 1}`); 55 } 56 } 57 58 push(value) { 59 if (this.sp >= STACK_SIZE - 1) { 60 throw new Error('Stack Overflow!'); 61 } 62 this.sp++; 63 this.stack[this.sp] = value; 64 this.tracePush(value); 65 } 66 67 pop() { 68 this.checkStackHasValues(1); 69 const result = this.stack[this.sp]; 70 this.sp--; 71 this.tracePop(result); 72 return result; 73 } 74 75 performBinary(op) { 76 this.checkStackHasValues(2); 77 const b = this.pop(); 78 const a = this.pop(); 79 let result; 80 switch (op) { 81 case '+': 82 result = a + b; 83 break; 84 case '-': 85 result = a - b; 86 break; 87 case '*': 88 result = a * b; 89 break; 90 case '/': 91 result = Math.trunc(a / b); 92 break; 93 } 94 this.push(result); 95 } 96 97 performUnary(op) { 98 this.checkStackHasValues(1); 99 const a = this.pop(); 100 let result; 101 switch (op) { 102 case '-': 103 result = -a; 104 break; 105 } 106 this.push(result); 107 } 108 109 execute() { 110 this.traceHeader(); 111 112 while (true) { 113 const op = this.program[this.pc]; 114 this.traceInstruction(this.pc, op); 115 this.pc++; 116 117 switch (op) { 118 case Opcode.OP_PUSH: { 119 const val = this.readInt64(); 120 this.push(val); 121 break; 122 } 123 case Opcode.OP_POP: 124 this.pop(); 125 break; 126 case Opcode.OP_HALT: 127 this.traceHalt(); 128 return; 129 case Opcode.OP_PRINT: 130 this.checkStackHasValues(1); 131 console.log(this.stack[this.sp]); 132 break; 133 case Opcode.OP_ADD: 134 this.performBinary('+'); 135 break; 136 case Opcode.OP_SUB: 137 this.performBinary('-'); 138 break; 139 case Opcode.OP_MUL: 140 this.performBinary('*'); 141 break; 142 case Opcode.OP_DIV: 143 this.performBinary('/'); 144 break; 145 case Opcode.OP_NEG: 146 this.performUnary('-'); 147 break; 148 default: 149 throw new Error('Unknown Opcode!'); 150 } 151 152 this.printStack(); 153 this.traceSeparator(); 154 } 155 } 156 157 readInt64() { 158 // Read 8 bytes as little-endian 64-bit integer 159 // JS doesn't handle 64-bit ints natively, but for small values this works 160 let val = 0; 161 for (let i = 0; i < 8; i++) { 162 val |= this.program[this.pc + i] << (i * 8); 163 } 164 this.pc += 8; 165 return val; 166 } 167 168 // Debug/trace methods 169 traceHeader() { 170 if (!DEBUG_ENABLED) return; 171 console.log('\n\x1b[96m\x1b[1m=== VM Execution Trace ===\x1b[0m\n'); 172 } 173 174 traceInstruction(pc, opcode) { 175 if (!DEBUG_ENABLED) return; 176 const name = opcodeNames[opcode] || 'UNKNOWN'; 177 console.log(`\x1b[94m[PC=${pc}]\x1b[0m \x1b[32m\x1b[1m${name}\x1b[0m`); 178 } 179 180 tracePush(value) { 181 if (!DEBUG_ENABLED) return; 182 console.log(`\x1b[2m \x1b[35mPUSH\x1b[0m \x1b[93m${value}\x1b[0m`); 183 } 184 185 tracePop(value) { 186 if (!DEBUG_ENABLED) return; 187 console.log(`\x1b[2m \x1b[31mPOP \x1b[0m \x1b[93m${value}\x1b[0m`); 188 } 189 190 traceHalt() { 191 if (!DEBUG_ENABLED) return; 192 console.log('\n\x1b[91m\x1b[1m=== Execution Halted ===\x1b[0m'); 193 } 194 195 traceSeparator() { 196 if (!DEBUG_ENABLED) return; 197 console.log(); 198 } 199 200 printStack() { 201 if (!DEBUG_ENABLED) return; 202 let stackStr = this.stack.slice(0, this.sp + 1).join(' '); 203 console.log(`\x1b[2m Stack \x1b[36m[sp=${this.sp}]\x1b[0m: [ \x1b[33m${stackStr}\x1b[0m ]`); 204 } 205} 206 207// Helper to write int64 to program 208function writeInt(program, pc, val) { 209 for (let i = 0; i < 8; i++) { 210 program[pc.value] = (val >> (i * 8)) & 0xff; 211 pc.value++; 212 } 213} 214 215// Main 216function main() { 217 const v = new VM(); 218 const values = [10, 13, 6, 25, 42]; 219 const pc = { value: 0 }; 220 221 for (const val of values) { 222 v.program[pc.value++] = Opcode.OP_PUSH; 223 writeInt(v.program, pc, val); 224 } 225 226 v.program[pc.value++] = Opcode.OP_MUL; 227 v.program[pc.value++] = Opcode.OP_ADD; 228 v.program[pc.value++] = Opcode.OP_NEG; 229 v.program[pc.value++] = Opcode.OP_DIV; 230 v.program[pc.value++] = Opcode.OP_SUB; 231 v.program[pc.value++] = Opcode.OP_PRINT; 232 v.program[pc.value++] = Opcode.OP_HALT; 233 234 v.execute(); 235 v.free(); 236} 237 238main();