#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;
