#toy.g #externs.g /* * codeGen.d: routines for code generation. */ uint CODE_BUFFER_SIZE = 10000; /* DETAILS OF THE MC68000 CPU */ /* register limits */ uint D_REG_FIRST = 7, D_REG_LAST = 2; /* special register values */ uint R_FP = 6, R_SP = 7; /* addressing modes and submodes */ uint M_DDIR = 0o0, M_ADIR = 0o1, M_INDIR = 0o2, M_INC = 0o3, M_DEC = 0o4, M_DISP = 0o5, M_INDEX = 0o6, M_SPECIAL = 0o7, M_ABS_SHORT = 0o0, M_ABS_LONG = 0o1, M_PC_DISP = 0o2, M_PC_INDEX = 0o3, M_IMM = 0o4, M_SR = 0o4; /* mode values for binary ops */ uint OM_REG = 0b0 << 2, OM_EA = 0b1 << 2; /* operand size values */ uint S_BYTE = 0b00, S_WORD = 0b01, S_LONG = 0b10, S_S_ADDR = 0b011, S_L_ADDR = 0b111; /* condition code values */ uint CC_T = 0x0, CC_F = 0x1, CC_HI = 0x2, CC_LS = 0x3, CC_HS = 0x4, CC_CC = 0x4, CC_LO = 0x5, CC_CS = 0x5, CC_NE = 0x6, CC_EQ = 0x7, CC_VC = 0x8, CC_VS = 0x9, CC_PL = 0xa, CC_MI = 0xb, CC_GE = 0xc, CC_LT = 0xd, CC_GT = 0xe, CC_LE = 0xf; /* opcodes */ uint OP_ANDI = 0x0200, /* opSingle */ OP_MOVEL = 0x2000, /* opMove */ OP_MOVEW = 0x3000, /* opMove */ OP_CLR = 0x4200, /* opSingle */ OP_NEG = 0x4400, /* opSingle */ OP_TST = 0x4a00, /* opSingle */ OP_SUB = 0x9000, /* opModed */ OP_CMP = 0xb000, /* opModed */ OP_ADD = 0xd000; /* opModed */ uint OP_EXT = 0x4800, /* opSpecial */ OP_SAVEM = 0x48c0, /* opSpecial */ OP_RESTM = 0x4cc0, /* opSpecial */ OP_LINK = 0x4e50, /* opSpecial */ OP_UNLK = 0x4e58, /* opSpecial */ OP_RTS = 0x4e75, /* opSpecial */ OP_JSR = 0x4e80, /* opEA */ OP_JMP = 0x4ec0, /* opEA */ OP_Scc = 0x50c0, /* opSpecial */ OP_Bcc = 0x6000, /* opBranch */ OP_DIVS = 0x81c0, /* opRegister */ OP_MULS = 0xc1c0; /* opRegister */ /* variables to hold the proc's code, and allocate its variables */ [CODE_BUFFER_SIZE]byte CodeBuffer; uint CodeBufferPos; uint NextRegister; int GlobalVariableSize, ParameterSize, LocalVariableSize; /* INTERFACE TO 'system.d' FOR ALLOCATION OF RUN-TIME SYSTEM VARIABLES */ /* * setGlobalSize - set the initial size for the global variables. */ proc setGlobalSize(int size)void: GlobalVariableSize := size; corp; /* * getGlobalSize - get the final size for the global variables. */ proc getGlobalSize()int: GlobalVariableSize corp; /* LOW-LEVEL ROUTINES TO PUT BYTES OF CODE INTO THE CODE BUFFER */ /* * genWord - generate a 16 bit word of code. */ proc genWord(uint w)void: if CodeBufferPos + 2 > CODE_BUFFER_SIZE then abort("code buffer overflow"); fi; CodeBuffer[CodeBufferPos] := w >> 8; CodeBuffer[CodeBufferPos + 1] := w; CodeBufferPos := CodeBufferPos + 2; corp; /* INTERFACE ROUTINES TO BUILD PROPER MC68000 INSTRUCTIONS FOR US */ /* * opSpecial - generate an instruction that doesn't fit another form. */ proc opSpecial(uint opCode)void: genWord(opCode); corp; /* * opSingle - generate a single-operand instruction. */ proc opSingle(uint opCode, size, destEA)void: genWord(opCode | size << 6 | destEA); corp; /* * opEA - generate an instruction which only has an EA operand. */ proc opEA(uint opCode, sourceEA)void: genWord(opCode | sourceEA); corp; /* * opRegister - generate a register/EA type instruction. */ proc opRegister(uint opCode, reg, sourceEA)void: genWord(opCode | reg << 9 | sourceEA); corp; /* * opModed - generate an instruction with an "op-mode". */ proc opModed(uint opCode, reg, mode, sourceEA)void: genWord(opCode | reg << 9 | mode << 6 | sourceEA); corp; /* * opMove - generate a MOVE instruction. */ proc opMove(uint opCode, source, dest)void: genWord(opCode | (dest & 0o07) << 9 | (dest & 0o70) << 3 | source); corp; /* * opBranch - generate a branch instruction. */ proc opBranch(uint condition, offset)void: genWord(OP_Bcc | condition << 8 | (offset & 0xff)); corp; /* ROUTINES CALLED FROM PARSER TO DO CODE GENERATION, ETC. */ /* * allocateVariable - allocate and set up a variable or parameter. */ proc allocateVariable(*SymbolEntry_t se)void: /* note: we use a size of 2 for everything. Variables of type 'bool' really only need 1 byte, but we use 2 to avoid any problems with alignment */ case se*.se_kind incase sk_globalVariable: se*.se_value := GlobalVariableSize; GlobalVariableSize := GlobalVariableSize + 2; incase sk_parameter: ParameterSize := ParameterSize + 2; se*.se_value := -ParameterSize; incase sk_localVariable: LocalVariableSize := LocalVariableSize + 2; se*.se_value := -LocalVariableSize; esac; corp; /* * procStartParameters - set up for declaring parameters. */ proc procStartParameters()void: ParameterSize := 0; corp; /* * procEndParameters - parameters done, start with locals. */ proc procEndParameters()void: LocalVariableSize := ParameterSize + 8; corp; /* * procEndLocals - all done with locals - final offset fixup. */ proc procEndLocals()void: fixLocals(ParameterSize + 8); LocalVariableSize := LocalVariableSize - ParameterSize - 8; corp; /* * codeInit - initialize code generation for the current proc. */ proc codeInit(*SymbolEntry_t se)void: CodeBufferPos := 0; codeStartProc(se); opSpecial(OP_LINK | R_FP); genWord(- LocalVariableSize); opSpecial(OP_SAVEM | M_DEC << 3 | R_SP); genWord(0b0111111100000000); NextRegister := D_REG_FIRST; corp; /* * codeTerm - terminate code generation for the current proc. */ proc codeTerm(*SymbolEntry_t se)void: opSpecial(OP_RESTM | M_INC << 3 | R_SP); genWord(0b0000000011111110); opSpecial(OP_UNLK | R_FP); /* optimization: easier exit code if no parameters */ if ParameterSize = 0 then genWord(OP_RTS); else opMove(OP_MOVEL, M_INC << 3 | R_SP, M_ADIR << 3 | 0); opModed(OP_ADD, R_SP, OM_REG | S_S_ADDR, M_SPECIAL << 3 | M_IMM); genWord(ParameterSize); opEA(OP_JMP, M_INDIR << 3 | 0); fi; codeEndProc(se, &CodeBuffer[0], CodeBufferPos); corp; /* * pushConstant - push a constant onto the 'stack'. */ proc pushConstant(int n)void: if NextRegister = D_REG_LAST - 1 then abort("out of registers"); fi; /* optimization: use a CLR for the value 0 */ if n = 0 then opSingle(OP_CLR, S_WORD, M_DDIR << 3 | NextRegister); else opMove(OP_MOVEW, M_SPECIAL << 3 | M_IMM, M_DDIR << 3 | NextRegister); genWord(n); fi; NextRegister := NextRegister - 1; corp; /* * pushVariable - push a variable onto the 'stack'. */ proc pushVariable(register *SymbolEntry_t se)void: if NextRegister = D_REG_LAST - 1 then abort("out of registers"); fi; opMove( OP_MOVEW, if se*.se_kind = sk_globalVariable then M_SPECIAL << 3 | M_ABS_LONG else /* sk_parameter or sk_localVariable */ M_DISP << 3 | R_FP fi, M_DDIR << 3 | NextRegister ); if se*.se_kind = sk_globalVariable then codeReference(rc_globalVariable, nil, CodeBufferPos); genWord(0); fi; genWord(se*.se_value); NextRegister := NextRegister - 1; corp; /* * popVariable - pop a variable from the 'stack'. */ proc popVariable(register *SymbolEntry_t se)void: NextRegister := NextRegister + 1; opMove( OP_MOVEW, M_DDIR << 3 | NextRegister, if se*.se_kind = sk_globalVariable then M_SPECIAL << 3 | M_ABS_LONG else /* sk_parameter or sk_localVariable */ M_DISP << 3 | R_FP fi ); if se*.se_kind = sk_globalVariable then codeReference(rc_globalVariable, nil, CodeBufferPos); genWord(0); fi; genWord(se*.se_value); corp; /* * emitString - add a string constant to the code. */ proc emitString(*char st; ulong length)void: register *char p; register ulong l; /* round the length up to an even value. We are cheating ever so slightly here in that we will reference the byte in memory beyond the allocated length of the string. We know, however, that any memory allocator that we are likely to encounter will round requests up to at least an even number of bytes, so we should be OK */ l := length; if l & 1 ~= 0 then l := l + 1; fi; /* we do a JSR around the string, which results in it's address being on the stack - just where we want it to call 'toy_printString' */ opEA(OP_JSR, M_SPECIAL << 3 | M_PC_DISP); genWord(l * sizeof(char) + 2); p := st; while l ~= 0 do l := l - 1; if CodeBufferPos = CODE_BUFFER_SIZE then abort("code buffer overflow"); fi; CodeBuffer[CodeBufferPos] := p* - '\e'; CodeBufferPos := CodeBufferPos + 1; p := p + sizeof(char); od; memFree(st, length * sizeof(char)); corp; /* * callSpecial - call one of the special run-time routines. */ proc callSpecial(RelocCode_t rc)void: if rc = rc_printInt then NextRegister := NextRegister + 1; opMove(OP_MOVEW, M_DDIR << 3 | NextRegister, M_DEC << 3 | R_SP); fi; opEA(OP_JSR, M_SPECIAL << 3 | M_ABS_LONG); codeReference(rc, nil, CodeBufferPos); genWord(0); genWord(0); if rc = rc_readInt then if NextRegister = D_REG_LAST - 1 then abort("out of registers"); fi; opMove(OP_MOVEW, M_DDIR << 3 | 0, M_DDIR << 3 | NextRegister); NextRegister := NextRegister - 1; fi; corp; /* * pushParameter - push a parameter to a proc call. */ proc pushParameter()void: NextRegister := NextRegister + 1; opMove(OP_MOVEW, M_DDIR << 3 | NextRegister, M_DEC << 3 | R_SP); corp; /* * callProc - generate a call to the given procedure or function. */ proc callProc(*SymbolEntry_t se)void: opEA(OP_JSR, M_SPECIAL << 3 | M_ABS_LONG); if se*.se_kind = sk_procedure then codeReference(rc_none, se, CodeBufferPos); genWord(0); genWord(0); fi; if se*.se_type ~= rt_void then if NextRegister = D_REG_LAST - 1 then abort("out of registers"); fi; opMove(OP_MOVEW, M_DDIR << 3 | 0, M_DDIR << 3 | NextRegister); NextRegister := NextRegister - 1; fi; corp; /* * procResult - put a proc's result in the proper place (D0). */ proc procResult()void: NextRegister := NextRegister + 1; opMove(OP_MOVEW, M_DDIR << 3 | NextRegister, M_DDIR << 3 | 0); corp; /* * unaryOp - perform a unary operation on the current 'top-of-stack'. * (only unary operation currently is negate) */ proc unaryOp(TokenKind_t opToken)void: opSingle(OP_NEG, S_WORD, M_DDIR << 3 | (NextRegister + 1)); corp; /* * binaryOp - perform a binary operation on the current 'top-of-stack'. */ proc binaryOp(TokenKind_t opToken)void: NextRegister := NextRegister + 1; if opToken = tk_star or opToken = tk_slash then if opToken = tk_slash then opSpecial(OP_EXT | 0b011 << 6 | (NextRegister + 1)); fi; opRegister( if opToken = tk_star then OP_MULS else OP_DIVS fi, NextRegister + 1, M_DDIR << 3 | NextRegister ); else opModed( if opToken = tk_plus then OP_ADD else OP_SUB fi, NextRegister + 1, OM_REG | S_WORD, M_DDIR << 3 | NextRegister ); fi; corp; /* * compareValues - compare two values, setting result accordingly. */ proc compareValues(TokenKind_t opToken)void: NextRegister := NextRegister + 1; opModed(OP_CMP, NextRegister + 1, OM_REG | S_WORD, M_DDIR << 3 | NextRegister); opSpecial(OP_Scc | case opToken incase tk_equal: CC_EQ incase tk_notEqual: CC_NE incase tk_less: CC_LT incase tk_lessEqual: CC_LS incase tk_greater: CC_GT incase tk_greaterEqual: CC_GE esac << 8 | M_DDIR << 3 | (NextRegister + 1) ); opSingle(OP_ANDI, S_WORD, M_DDIR << 3 | (NextRegister + 1)); genWord(1); corp; /* * getPos - return the current code position, for later backpatching. */ proc getPos()uint: CodeBufferPos corp; /* * branchForward - branch to some as-yet-unknown forward location. Return * the position to be used later to fill in the offset. */ proc branchForward(bool isConditional)uint: if isConditional then NextRegister := NextRegister + 1; opSingle(OP_TST, S_WORD, M_DDIR << 3 | NextRegister); opBranch(CC_EQ, 0); else opBranch(CC_T, 0); fi; genWord(0); CodeBufferPos - 2 corp; /* * patchBranch - patch a previous branch to come to the current position. */ proc patchBranch(register uint offset)void: CodeBuffer[offset] := (CodeBufferPos - offset) >> 8; CodeBuffer[offset + 1] := (CodeBufferPos - offset); corp; /* * branchTo - generate a backwards unconditional branch. */ proc branchTo(uint offset)void: register uint distance; distance := CodeBufferPos + 2 - offset; /* optimization: use a short branch if possible */ if distance <= 128 then opBranch(CC_T, - distance); else opBranch(CC_T, 0); genWord(- distance); fi; corp;