1 module crisc; 2 import std.stdio; 3 import std.string; 4 import std.array; 5 import std.conv; 6 import std.file; 7 import std.algorithm; 8 9 public enum DBGOpCode : uint { 10 // PRINT a register 11 PRT_REG = 0, 12 13 // PRINT the program counter 14 PRT_CTR = 1, 15 16 // PRINT the program cycles 17 PRT_CYC = 2, 18 19 // PRINT the writable memory 20 PRT_WMEM = 3, 21 22 // PRINT the full memory. 23 PRT_FMEM = 4, 24 25 // PRINT memory range 26 PRT_MEMR = 5, 27 28 // PRINT the callstack. 29 PRT_CSTK = 6, 30 31 // PRINT the datastack. 32 PRT_DSTK = 7, 33 34 // SET verbose logging output 35 SET_VEB = 8, 36 37 // SET verbose logging for data stack operations. 38 SET_VSTK = 9, 39 } 40 41 public enum OpCode : ubyte { 42 // Kill program 43 HALT = 0, 44 45 // Move REG A to REG B 46 MOV = 1, 47 48 // Move CONST A to REG B 49 MOVC = 2, 50 51 // Move REG (Referenced by CONST) A to REG B 52 MOVR = 3, 53 54 ADD = 4, 55 56 // Add CONST A to REG B 57 ADDC = 5, 58 59 // Add REG (Referenced by CONST) A to REG B 60 ADDR = 6, 61 62 // Subtract REG A to REG B 63 SUB = 7, 64 65 // Subtract CONST A to REG B 66 SUBC = 8, 67 68 // Subtract REG (Referenced by CONST) A to REG B 69 SUBR = 9, 70 71 // Multiply REG A to REG B 72 MUL = 10, 73 74 // Multiply CONST A to REG B 75 MULC = 11, 76 77 // Multiply REG (Referenced by CONST) A to REG B 78 MULR = 12, 79 80 // Divide REG A to REG B 81 DIV = 13, 82 83 // Divide CONST A to REG B 84 DIVC = 14, 85 86 // Divide REG (Referenced by CONST) A to REG B 87 DIVR = 15, 88 89 // JUMP TO ADDRESS A 90 JMP = 16, 91 92 // JUMP TO CONST A 93 JMPC = 17, 94 95 // JUMP TO ADDRESS A IF STATUS register is NOT EQUAL to CONST 96 JMPNEQ = 18, 97 98 // JUMP TO ADDRESS A IF STATUS register is EQUAL to CONST 99 JMPEQ = 19, 100 101 // JUMP TO ADDRESS A IF STATUS register is EQUAL or LARGER than CONST B 102 JMPLEQ = 20, 103 104 // JUMP TO ADDRESS A IF STATUS register is EQUAL or SMALLER than CONST B 105 JMPSEQ = 21, 106 107 // JUMP TO CONST A IF STATUS register is NOT EQUAL to CONST 108 JMPNEQC = 22, 109 110 // JUMP TO CONST A IF STATUS register is EQUAL to CONST 111 JMPEQC = 23, 112 113 // JUMP TO CONST A IF STATUS register is EQUAL or LARGER than CONST B 114 JMPLEQC = 24, 115 116 // JUMP TO CONST A IF STATUS register is EQUAL or SMALLER than CONST B 117 JMPSEQC = 25, 118 119 // LOAD VALUE to REG A from (Referenced by CONST) MEMORY ADDRESS B 120 LDR = 26, 121 122 // LOAD VALUE TO REG A from (Referenced by CONST) MEMORY ADDRESS B 123 LDRC = 27, 124 125 // STORE VALUE from REG A to (Referenced by CONST) MEMORY ADDRESS B 126 STR = 28, 127 128 // STORE VALUE OF REG A to (Referenced by CONST) MEMORY ADDRESS B 129 STRC = 29, 130 131 // CALL jump to address referenced by CONST A and set stack return pointer 132 CALL = 30, 133 134 // CALL system level function. 135 SCALL = 31, 136 137 // PUSH value to stack 138 PUSH = 32, 139 140 // PUSH value to stack 141 PUSHC = 33, 142 143 // POP value from stack 144 POP = 34, 145 146 // RETURN returns to the last stack pointer with values 147 RET = 35, 148 149 // DBG debugging functionality 150 DBG = 36 151 } 152 153 struct Instruction { 154 OpCode opCode; 155 size_t[2] data; 156 } 157 158 struct CPUStack { 159 private { 160 size_t size; 161 ubyte* stackptr; 162 size_t stackoffset; 163 } 164 165 public { 166 void clearStack() { 167 stackoffset = 0; 168 while (stackoffset < size) { 169 stackpointer_t[stackoffset] = 0; 170 stackoffset++; 171 } 172 stackoffset = 0; 173 } 174 175 ubyte* stackpointer() { 176 return stackptr+stackoffset; 177 } 178 179 size_t* stackpointer_t() { 180 return (cast(size_t*)stackptr)+stackoffset; 181 } 182 183 void push(size_t item) { 184 checkBounds(1); 185 *((cast(size_t*)stackpointer_t)) = item; 186 stackoffset += 1; 187 } 188 189 size_t pop() { 190 checkBounds(-1); 191 stackoffset -= 1; 192 size_t item = *((cast(size_t*)stackpointer_t)); 193 return item; 194 } 195 196 void checkBounds(long offset) { 197 if (offset < 0) { 198 if (stackoffset == 0 || cast(long)(stackoffset)-offset < 0) throw new Exception("Stack underflow"); 199 } else { 200 if (stackoffset+offset > size) 201 throw new Exception("Stack overflow!\nStack:\n"~stackStr); 202 } 203 } 204 205 string stackStr() { 206 return to!string((stackpointer_t-stackoffset)[0..size]) ~ "; OFFSET=" ~ to!string(stackoffset); 207 } 208 } 209 } 210 211 class SysCall { 212 public string name; 213 214 this(string name) { 215 this.name = name; 216 } 217 218 this(string name, CPU owner) { 219 this.name = name; 220 this.valueptr = &owner.datastack; 221 } 222 223 this(string name, CPUStack* stack) { 224 this.name = name; 225 this.valueptr = stack; 226 } 227 228 protected CPUStack* valueptr; 229 public abstract void execute(); 230 } 231 232 class SysCallGetSafeMem : SysCall { 233 CPU cpu; 234 235 this() { 236 super("gsfm"); 237 } 238 239 this(CPU cpu) { 240 super("gsfm", cpu); 241 this.cpu = cpu; 242 } 243 244 public override void execute() { 245 valueptr.push(cast(size_t)cpu.safeMemOffset); 246 } 247 } 248 249 class CPU { 250 // Program counter/program pointer. 251 Instruction* progptr; 252 253 // Pointer to start of program 254 Instruction* progstart; 255 256 size_t progctr() { 257 return progptr-progstart; 258 } 259 260 // List of system calls (NOTE NEEDS TO BE IN SAME ORDER AS ASSEMBLED) 261 SysCall[] syscalls; 262 263 // Registers 264 ulong[256] REGISTERS; 265 266 // Pointer to status register (0xFF) 267 ulong* STATUS_REG; 268 269 // Call stack 270 CPUStack callstack; 271 272 // Data stack 273 CPUStack datastack; 274 275 // The memory in which the program resides. 276 ubyte[] memory; 277 278 // safe memory space offset 279 size_t safeMemOffset; 280 281 // Amount of operations/instructions program has run 282 ulong cOps = 0; 283 284 // Sizes for stacks 285 ulong callstackSize; 286 ulong stackStoreSize; 287 288 // Verbose and Stack Verbose 289 bool VEB = false; 290 bool SVEB = false; 291 292 bool running() { 293 return (progptr !is null); 294 } 295 296 297 this(ubyte[] program, size_t stackSize, size_t memorySize) { 298 // Set up status register pointer. 299 STATUS_REG = ®ISTERS[255]; 300 301 // Prepare memory. 302 memory = program; 303 memory.length += stackSize; 304 memory.length += memorySize; 305 306 // Set stack pointer. 307 CPUStack cstack = { size:stackSize, stackptr:memory.ptr+program.length, stackoffset:0 }; 308 callstack = cstack; 309 310 // Set stack pointer for data. 311 CPUStack dstack = { size:stackSize, stackptr:(memory.ptr+program.length)+(size_t.sizeof*stackSize), stackoffset:0 }; 312 datastack = dstack; 313 314 // Set program counter and program start pointer 315 progstart = cast(Instruction*)memory.ptr; 316 progptr = progstart; 317 318 // Clear stacks 319 callstack.clearStack(); 320 datastack.clearStack(); 321 322 safeMemOffset = (program.length)+((size_t.sizeof*stackSize)*2); 323 324 // Add ptrc and rdc syscalls. 325 this.syscalls = cast(SysCall[])[new SysCallGetSafeMem(this)]; 326 } 327 328 public void PushSyscalls(SysCall[] syscalls) { 329 this.syscalls ~= syscalls; 330 } 331 332 void runCycle() { 333 if (progptr is null) return; 334 REGISTERS[254] = cast(size_t)callstack.stackpointer; 335 debugInstr(*progptr); 336 switch (progptr.opCode) { 337 338 case(OpCode.MOV): REGISTERS[progptr.data[1]] = REGISTERS[progptr.data[0]]; break; 339 case(OpCode.MOVC): REGISTERS[progptr.data[1]] = progptr.data[0]; break; 340 case(OpCode.MOVR): REGISTERS[REGISTERS[progptr.data[1]]] = progptr.data[0]; break; 341 342 case(OpCode.ADD): REGISTERS[progptr.data[1]] += REGISTERS[progptr.data[0]]; break; 343 case(OpCode.ADDC): REGISTERS[progptr.data[1]] += progptr.data[0]; break; 344 case(OpCode.ADDR): REGISTERS[REGISTERS[progptr.data[1]]] += progptr.data[0]; break; 345 346 case(OpCode.SUB): REGISTERS[progptr.data[1]] -= REGISTERS[progptr.data[0]]; break; 347 case(OpCode.SUBC): REGISTERS[progptr.data[1]] -= progptr.data[0]; break; 348 case(OpCode.SUBR): REGISTERS[REGISTERS[progptr.data[1]]] -= progptr.data[0]; break; 349 350 case(OpCode.MUL): REGISTERS[progptr.data[1]] *= REGISTERS[progptr.data[0]]; break; 351 case(OpCode.MULC): REGISTERS[progptr.data[1]] *= progptr.data[0]; break; 352 case(OpCode.MULR): REGISTERS[REGISTERS[progptr.data[1]]] *= progptr.data[0]; break; 353 354 case(OpCode.DIV): REGISTERS[progptr.data[1]] /= REGISTERS[progptr.data[0]]; break; 355 case(OpCode.DIVC): REGISTERS[progptr.data[1]] /= progptr.data[0]; break; 356 case(OpCode.DIVR): REGISTERS[REGISTERS[progptr.data[1]]] /= progptr.data[0]; break; 357 358 case(OpCode.JMP): progptr = progstart+(REGISTERS[progptr.data[0]])-1; break; 359 360 case(OpCode.JMPEQ): 361 if (*STATUS_REG == REGISTERS[progptr.data[1]]) progptr = progstart+(progptr.data[0])-1; 362 break; 363 364 case(OpCode.JMPNEQ): 365 if (*STATUS_REG != REGISTERS[progptr.data[1]]) progptr = progstart+(progptr.data[0])-1; 366 break; 367 368 case(OpCode.JMPLEQ): 369 if (*STATUS_REG >= REGISTERS[progptr.data[1]]) progptr = progstart+(progptr.data[0])-1; 370 break; 371 372 case(OpCode.JMPSEQ): 373 if (*STATUS_REG <= REGISTERS[progptr.data[1]]) progptr = progstart+(progptr.data[0])-1; 374 break; 375 376 case(OpCode.JMPC): progptr = progstart+(progptr.data[0])-1; break; 377 378 case(OpCode.JMPEQC): 379 if (*STATUS_REG == progptr.data[1]) progptr = progstart+(progptr.data[0])-1; 380 break; 381 382 case(OpCode.JMPNEQC): 383 if (*STATUS_REG != progptr.data[1]) progptr = progstart+(progptr.data[0])-1; 384 break; 385 386 case(OpCode.JMPLEQC): 387 if (*STATUS_REG >= progptr.data[1]) progptr = progstart+(progptr.data[0])-1; 388 break; 389 390 case(OpCode.JMPSEQC): 391 if (*STATUS_REG <= progptr.data[1]) progptr = progstart+(progptr.data[0])-1; 392 break; 393 394 case(OpCode.DBG): 395 if (progptr.data[0] == DBGOpCode.PRT_REG) writeln("REG_", progptr.data[1], "=", REGISTERS[progptr.data[1]]); 396 if (progptr.data[0] == DBGOpCode.PRT_CTR) writeln("PROG_CTR=", progptr); 397 if (progptr.data[0] == DBGOpCode.PRT_CYC) writeln("CYCLES=", cOps); 398 if (progptr.data[0] == DBGOpCode.PRT_WMEM) writeln("MEMORY MAP=", to!string(memory)); 399 if (progptr.data[0] == DBGOpCode.PRT_DSTK) writeln("DATASTACK=", datastack.stackStr); 400 if (progptr.data[0] == DBGOpCode.PRT_CSTK) writeln("CALLSTACK=", callstack.stackStr); 401 if (progptr.data[0] == DBGOpCode.PRT_MEMR) writeln(to!string(readMemRange(REGISTERS[253], progptr.data[1]))); 402 403 404 if (progptr.data[0] == DBGOpCode.SET_VEB) VEB = cast(bool)progptr.data[1]; 405 if (progptr.data[0] == DBGOpCode.SET_VSTK) SVEB = cast(bool)progptr.data[1]; 406 break; 407 408 case(OpCode.CALL): 409 if (progptr.data[0] < 0) throw new Exception("CPU HALT; ACCESS OUT OF BOUNDS"); 410 callstack.push(progctr+1); 411 if (SVEB) writeln("DATASTACK=", datastack.stackStr); 412 progptr = progstart+(progptr.data[0])-1; 413 break; 414 415 case(OpCode.SCALL): syscalls[progptr.data[0]].execute(); break; 416 417 case(OpCode.RET): 418 size_t tptr = callstack.pop(); 419 progptr = progstart+tptr-1; 420 if (SVEB) writeln("DATASTACK=", datastack.stackStr); 421 break; 422 423 case(OpCode.PUSHC): 424 datastack.push(progptr.data[0]); 425 if (SVEB) writeln("DATASTACK=", datastack.stackStr); 426 break; 427 428 case(OpCode.PUSH): 429 datastack.push(REGISTERS[progptr.data[0]]); 430 if (SVEB) writeln("DATASTACK=", datastack.stackStr); 431 break; 432 433 case(OpCode.POP): 434 REGISTERS[progptr.data[0]] = datastack.pop(); 435 if (SVEB) writeln("DATASTACK=", datastack.stackStr); 436 break; 437 438 case(OpCode.LDR): REGISTERS[progptr.data[1]] = readMem(REGISTERS[progptr.data[0]]); break; 439 case(OpCode.LDRC): REGISTERS[progptr.data[1]] = readMem(progptr.data[0]); break; 440 441 case(OpCode.STR): writeMem(REGISTERS[progptr.data[1]], REGISTERS[progptr.data[0]]); break; 442 case(OpCode.STRC): writeMem(REGISTERS[progptr.data[1]], progptr.data[0]); break; 443 444 case(OpCode.HALT): 445 writeln("PROGRAM HALTED."); 446 progptr = null; 447 break; 448 449 default: 450 writeln("INVALID OPERATION @", progptr-progstart); 451 break; 452 } 453 if (progptr is null) return; 454 progptr++; 455 cOps++; 456 } 457 458 private size_t readMem(size_t address) { 459 size_t value = *cast(size_t*)(cast(ubyte*)memory+address); 460 //writeln("Reading ", value, " fom ", address); 461 return value; 462 } 463 464 private size_t[] readMemRange(size_t address, size_t length) { 465 //writeln("Reading address ", address, " to ", address+length); 466 return (cast(size_t*)((cast(ubyte*)memory+address)[0..length]))[0..length/size_t.sizeof]; 467 } 468 469 private void writeMem(size_t address, size_t value) { 470 //writeln("Writing ", value, " to ", address); 471 *cast(size_t*)(cast(ubyte*)memory+address) = value; 472 } 473 474 private void debugInstr(Instruction instr) { 475 if (VEB) writeln(to!string((cast(OpCode)instr.opCode)), " ", instr.data[0], " ", instr.data[1]); 476 } 477 } 478 479 struct Label { 480 string name; 481 size_t offset; 482 } 483 484 struct LabelRef { 485 string name; 486 size_t doffset; 487 size_t offset; 488 } 489 490 class Compiler { 491 private LabelRef[] labelRefs; 492 private Instruction[] code; 493 private Label[] labels; 494 private bool doInfer; 495 496 private SysCall[] syscalls; 497 498 public this(bool infer, SysCall[] syscalls) { 499 this.doInfer = infer; 500 501 // Add ptrc and rdc syscalls. 502 this.syscalls = cast(SysCall[])[new SysCallGetSafeMem()] ~ syscalls; 503 } 504 505 public ubyte[] compile(string asmCode) { 506 // Process lines in program, removing/ignoring comments. 507 string[] lines = asmCode.split('\n'); 508 string[] keywords; 509 foreach(string line; lines) { 510 string lo = ""; 511 bool isComment = false; 512 foreach (char c; line) { 513 if (c == ';') isComment = true; 514 if (!isComment) lo ~= c; 515 } 516 foreach (string keyword; lo.split) { 517 if (keyword != "") keywords ~= keyword; 518 } 519 } 520 521 522 // Convert text/asm to instructions. 523 bool parsingCompleted = false; 524 int i = 0; 525 uint instr_pos = 0; 526 while (!parsingCompleted) { 527 try { 528 // Generate labels. 529 if (i >= keywords.length) break; 530 if (keywords[i].endsWith(":")) { 531 Label l = { keywords[i][0..$-1], instr_pos }; 532 labels ~= l; 533 } else { 534 // Preprocessing 535 OpCode opCode = getOp(keywords[i]); 536 string kw = keywords[i]; 537 538 // Specifies whether the assembler should infer which instruction to use based on how the arguments are structured. 539 if (doInfer) { 540 if (opCode != OpCode.HALT && opCode != OpCode.RET && opCode != OpCode.DBG && opCode != OpCode.CALL && opCode != OpCode.SCALL) { 541 int r = 1; 542 if (kw.toUpper.startsWith("JMP")) { 543 r = 2; 544 } 545 if (!keywords[i+r].startsWith("@")) { 546 if (opCode == OpCode.POP) throw new Exception("Invalid operation, \""~kw~"\" [arg a] takes an register, not a constant!"); 547 // Handle references. 548 kw ~= "C"; 549 } 550 if (!kw.toUpper.startsWith("JMP")) { 551 if (opCode != OpCode.POP && opCode != OpCode.JMP && opCode != OpCode.PUSH) { 552 if (!keywords[i+2].startsWith("@")) throw new Exception("Invalid operation, \""~kw~"\" [arg b] takes an register, not a constant!"); 553 } 554 } 555 } 556 } 557 558 // Generate instructions. 559 opCode = getOp(kw); 560 561 string argAStr = ""; 562 if (i+1 < keywords.length) argAStr = keywords[i+1]; 563 uint argA = 0; 564 565 string argBStr = ""; 566 if (i+2 < keywords.length) argBStr = keywords[i+2]; 567 uint argB = 0; 568 if (opCode != OpCode.HALT && opCode != OpCode.RET) { 569 if (opCode == OpCode.DBG) { 570 argA = getDBGOp(argAStr); 571 } else if (opCode == OpCode.SCALL) { 572 argA = getSyscall(argAStr); 573 } else { 574 argA = getVal(labels, 0, argAStr); 575 } 576 577 // Iterate 578 i++; 579 580 if (opCode != OpCode.CALL && opCode != OpCode.SCALL && opCode != OpCode.PUSH && opCode != OpCode.PUSHC && opCode != OpCode.POP && opCode != OpCode.JMP && opCode != OpCode.JMPC) { 581 argB = getVal(labels, 1, argBStr); 582 583 // Iterate 584 i++; 585 } 586 587 } 588 Instruction instr = { opCode, [argA, argB] }; 589 code ~= instr; 590 instr_pos++; 591 } 592 // Iterate 593 i++; 594 if (i >= keywords.length) parsingCompleted = true; 595 } catch (Exception ex) { 596 writeln("Error @ index ", i, ", token=", keywords[i], " message=", ex.message); 597 return null; 598 } 599 } 600 601 // Post processing (label conversion) 602 foreach(Label l; labels) { 603 size_t x = 0; 604 while (true) { 605 if (x >= labelRefs.length) break; 606 LabelRef lref = labelRefs[x]; 607 Instruction* instr = &code[lref.offset]; 608 if (lref.name == l.name) { 609 instr.data[lref.doffset] = cast(uint)l.offset; 610 labelRefs = labelRefs.remove(x); 611 continue; 612 613 //throw new Exception("Invalid reference to label for OPCode "~to!string(cast(OpCode)(instr.opCode))); 614 } 615 x++; 616 } 617 } 618 619 return cast(ubyte[])code; 620 } 621 622 uint getValLabels(Label[] labels, string label) { 623 foreach(Label l; labels) { 624 if (l.name == label) return cast(uint)l.offset; 625 } 626 return -1; 627 } 628 629 // # - Label 630 // @ - Reference/Register 631 // TODO: * - Address 632 633 uint getSyscall(string t) { 634 if (t.startsWith("#")) { 635 foreach(i; 0 .. syscalls.length) { 636 if (syscalls[i].name.toLower == t[1..$].toLower) 637 return cast(uint)i; 638 } 639 throw new Exception("syscall "~t~" not found!"); 640 } 641 if (t.startsWith("@")) { 642 return to!uint(t[1..$]); 643 } 644 return to!uint(t); 645 } 646 647 uint getVal(Label[] labels, size_t owner_offset, string t) { 648 if (t.startsWith("#")) { 649 LabelRef r = {t[1..$], owner_offset, code.length}; 650 labelRefs ~= r; 651 return 0; 652 } 653 if (t.startsWith("@")) { 654 t = t[1..$]; 655 } 656 if (t.startsWith("0x")) { 657 return to!uint(t[2..$], 16); 658 } 659 return to!uint(t); 660 } 661 662 OpCode getOp(string name) { 663 return to!OpCode(name.toUpper); 664 } 665 666 DBGOpCode getDBGOp(string name) { 667 return to!DBGOpCode(name.toUpper); 668 } 669 670 void printLabels() { 671 writeln("Labels:"); 672 foreach(Label l; labels) { 673 writeln("\t", l.name, "@", l.offset); 674 } 675 } 676 } 677 678 public string binToASMDESC(ubyte[] data) { 679 Instruction[] instructions = (cast(Instruction*)data)[0..data.length/Instruction.sizeof]; 680 string oi = ""; 681 int line = 0; 682 foreach(Instruction i; instructions) { 683 oi ~= "\t" ~ to!string(line) ~ "\t" ~ to!string(i.opCode) ~ " " ~ to!string(i.data[0]) ~ " " ~ to!string(i.data[1]) ~ "\n"; 684 line++; 685 } 686 return oi; 687 }