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 = &REGISTERS[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 }