blob: 91ada14a074f6f9fc5da0f4c0ca65c11cf003a3e [file] [log] [blame]
/*
FALCO16 v3
*/
/* TO DO
- If a FREEREG follows an IC for Q1, the register can be used as temporary.
e.g if we get a = R0 + c, and there's a FREEREG R0 following the IC, we can generate
add r0, [c]
mov [a], r0
instead of
mov r1, r0
add r1, [c]
mov [a], r1
-- floating point capability must also be implemented
- data alignement doesn't work, g1 should be aligned below.
-- Is this supposed to be handled by the assembler?
char global = 4;
int g1 = 5;
-- conditional branching for LONG works, but temporary register handling is not ideal.
Register are loaded more than required.
-- inline optimization must be done, e.g shift by const, mul/div by 2
-- call ptr not implemented.
-- register parameter passing
-- 1arg operation are currently only using reg arguments
-- could take advantage of [reg+ofs] addressing mode by combining IC
-- IRQs should only save registers which are actually used, in case of function call all regs must be saved
-- implement "naked" keyword for RTOS use.
-- right now lib_mov is done by messing with the IC operands changing them into pointers. It is probably better to
use ptr_push instead of messign around with val_push.
-- must update arg_push to support double, long long and such, not using more temperary registers
-- compare with KONST not correct when KONST = maxint
*/
#include "supp.h"
#include "vbc.h"
//static char FILE_[]=__FILE__;
/* Public data that MUST be there. */
/* Name and copyright. */
char cg_copyright[]="falco16_v3 code generator v0.1 by Daniel Schoch";
// command line
//--------------
/* Commandline-flags the code-generator accepts:
0: just a flag
VALFLAG: a value must be specified
STRINGFLAG: a string can be specified
FUNCFLAG: a function will be called
apart from FUNCFLAG, all other versions can only be specified once */
int g_flags[MAXGF] = {VALFLAG, VALFLAG, VALFLAG};
/* the flag-name, do not use names beginning with l, L, I, D or U, because
they collide with the frontend */
char *g_flags_name[MAXGF]={"regs", "rsave", "rtmp"};
/* Description of command line options:
regs=<val>
DEFAULT = 6
Specify the number of registers to use. Must be >=3 and <= 14.
The value should match the actual CPU implementation, the default is 6.
Values < 6 don't make much practical sense since there won't be
enough registers available for efficient code generation.
rsave=<val>
DEFAULT = automatic
0 <= val <= regs-rtmp
Specify the number of non-scratch registers to use.
Non-scratch registers are registers that are saved across
function calls. They are used for efficient register
variable implementation.
rtmp=<val>
DEFAULT = 0
0 <= val <= 3
Specify the number of temporary registers to reserve.
Temporary registers are used by the code generator.
In situation where there is no free register is available
a register must be saved and restored.
/* the results of parsing the command-line-flags will be stored here */
union ppi g_flags_val[MAXGF];
// data types
//======================
/* CHAR_BIT for the target machine. */
zmax char_bit;
/* Typenames (needed because of HAVE_EXT_TYPES). */
char *typname[MAX_TYPE+1];
/* Alignment-requirements for all types in bytes. */
zmax align[MAX_TYPE+1];
/* Alignment that is sufficient for every object. */
zmax maxalign;
/* sizes of the basic types (in bytes) */
zmax sizetab[MAX_TYPE+1];
/* Minimum and Maximum values each type can have. */
/* Must be initialized in init_cg(). */
zmax t_min[MAX_TYPE+1];
zumax t_max[MAX_TYPE+1];
zumax tu_max[MAX_TYPE+1];
// registers
//==========================
/* Names of all registers. will be initialized in init_cg(),
register number 0 is invalid, valid registers start at 1 */
char *regnames[MAXR+1];
/* The Size of each register in bytes. */
zmax regsize[MAXR+1];
/* Specifies which registers may be scratched by functions. */
int regscratch[MAXR+1];
/* regsa[reg]!=0 if a certain register is allocated and should */
/* not be used by the compiler pass. */
int regsa[MAXR+1];
/* specifies the priority for the register-allocator, if the same
estimated cost-saving can be obtained by several registers, the
one with the highest priority will be used */
int reg_prio[MAXR+1];
/* a type which can store each register. */
struct Typ *regtype[MAXR+1];
/* an empty reg-handle representing initial state */
struct reg_handle empty_reg_handle = {0, 0};
/****************************************/
/* Private data and functions. */
/****************************************/
#define ISCOMPOSITE(t) (ISARRAY(t) || ISSTRUCT(t) || ISUNION(t))
// number of registers to use, this can be a ny value from 3 to 14.
// A value < 6 is not recommended as it does not leave any registers for
// efficient code generation.
// The default is 6.
int nregs;
// register names
#define REG_FLAGS 1
#define REG_SP 2
#define REG_R0 3
#define REG_R1 4
#define REG_R2 5
#define REG_R3 6
#define REG_R4 7
#define REG_R5 8
#define REG_R6 9
#define REG_R7 10
#define REG_R8 11
#define REG_R9 12
#define REG_R10 13
#define REG_R11 14
#define REG_R12 15
#define REG_R13 16
// register usage tracking system
// Allocated by ALLOCEG IC
#define TRACK_ALLOCREG 1
// used by IC, not available as temporary
#define TRACK_IC 2
// allocated as temporary by tracker
#define TRACK_TMP 4
// saved
#define TRACK_SAVED 8
// not usable as indicated by CPU register layout
#define TRACK_OFFLIMITS 16
// dedicated temporary register
#define TRACK_DEDICATED 32
// bits indicating register is used and cannot be used no matter what
#define TRACK_NOGO (TRACK_IC | TRACK_TMP | TRACK_OFFLIMITS)
int track_status[MAXR+1];
// We provide 3 slots for temporary registers.
// In here we keep track if which slot contains which register.
#define TRACK_SLOTS 3
int track_slot[TRACK_SLOTS];
/* used to initialize regtyp[] */
static struct Typ ltyp = { INT };
/* macros defined by the backend */
// extended data types attribute strings
#define EXTDATA_INTERRUPT "interrupt"
static char *marray[] = {
"__FALCO16__",
"__interrupt(x)=__vattr(\"interrupt(\"#x\")\")",
0
};
/* sections */
#define SEC_TEXT 0 // code
#define SEC_RODATA 1 // constant data
#define SEC_DATA 2 // initialized data
#define SEC_BSS 3 // data initialized to 0
#define SEC_VECTOR 4 // irq vectors
static int section = -1;
static char *sec_textname = "code",
*sec_rodataname = "const",
*sec_dataname = "idata",
*sec_bssname = "zdata",
*sec_vectorname = "vector";
/* assembly-prefixes for labels and external identifiers */
static char *label_prefix = "p";
static char *ident_prefix = "_";
static int label_count;
// assemly data storage strings
static char *dct[] = {"", "db", "dw", "dw", "dd", "dd", "dd", "dd", "dd"};
static long loff;
static long stackoffset;
static int newobj;
static long pof2(zumax x)
/* Yields log2(x)+1 oder 0. */
{
zumax p;
int ln = 1;
p = ul2zum(1UL);
while (ln <= 32 && zumleq(p, x)) {
if (zumeqto(x, p)) return ln;
ln++; p = zumadd(p, p);
}
return 0;
}
static long const_get(struct obj *x, int typ, int n)
{
long ret;
eval_const(&x->val, typ);
ret = (vmax >> (n * char_bit)) & 0xffff;
return ret;
}
static long ofs_get(struct obj *x)
{
long ret;
ret = 0;
if (x->flags & VAR) {
if (x->v->offset < 0) {
ret = (long)(loff-(x->v->offset+2)+(x->val.vmax))-stackoffset+2;
} else {
ret = (long)((x->v->offset)+(x->val.vmax)-stackoffset);
}
}
return ret;
}
static void emit_object(FILE *f, struct obj *x, int n, int typ)
{
long ofs;
// calculate offset into stack
ofs = ofs_get(x);
// access mode
eval_const(&x->val, typ);
if (x->flags & REG) {
if (x->flags & DREFOBJ) {
// register pointer '[reg+ofs]'
emit(f, "[%s+%ld]", regnames[x->reg], n);
} else {
// register 'reg'
emit(f, "%s", regnames[x->reg]);
}
} else if (x->flags & KONST) {
if (x->flags & VARADR) {
// This is the special case for which the address of a constant must be generated.
// KONST| VARADR is never generated by the front-end, but it's a modified
// object by the back-end. The address of a constant is used for FLOAT/DOUBLE
// library call operations involving a constant. An address to a constant is generated by
// placing the constant into section SEC_RODATA and generating a pointer to
// the object.
// NOT COMPLETE, must have some kind of konst counter.
// 1st nject konst into ROM section
emit(f, "__KONST_");
} else if (x->flags & DREFOBJ) {
// constant address, e.g. mov [123], r0
emit(f, "[%ld]", const_get(x, typ, n));
} else {
// regular KONST value
emit(f, "%ld", const_get(x, typ, n));
}
} else if (x->flags & VAR) {
// REGULAR VARIABLE ACCESS
if (isauto(x->v->storage_class)) {
if (x->flags & VARADR) {
// this is a special case not generated by the front-end, it's a modified object
// by the back end needing to generate the address of a auto variable.
// This is used by 'ptr_push'. What eventually must be generated is
// mov reg, sp
// add reg, ofs+n
// Since this is the emit_object function we only generate sp here, the
// add reg, ofs+n must be generated by the callee.
emit(f, "sp");
} else {
// stack based value '[sp+ofs]'
emit(f, "[sp+%ld]", (long)ofs + n);
}
} else if (isextern(x->v->storage_class)) {
if (x->flags & VARADR) {
// address of global variable
emit(f, "%s%s+%ld", ident_prefix, (long)x->v->identifier, (long)x->val.vmax + n);
} else {
// value of global '[ident+ofs]'
emit(f, "[%s%s+%ld]", ident_prefix, (long)x->v->identifier, (long)x->val.vmax + n);
}
} else if (isstatic(x->v->storage_class)) {
if (x->flags & VARADR) {
// address of static variable
emit(f, "%s%ld+%ld", label_prefix, (long)x->v->offset, (long)x->val.vmax + n);
} else {
// value of static variable
emit(f, "[%s%ld+%ld]", label_prefix, (long)x->v->offset, (long)x->val.vmax + n);
}
} else terror("-- emit_object: unexpected storage class");
} else {
terror("-- emit_object: unexpected access mode");
}
}
/*
Load a temporary ptr register to be used by the main emit_load
function.
returns 1, if code was generated to load the pointer.
returns 0, if no code was necessary since no temporary ptr is required.
*/
static int emit_tmpptr(FILE *f, struct obj *x, int n)
{
struct AddressingMode *am;
char *regptr_name;
int ret;
ret = 0;
am = x->am;
if (am && am->regptr) {
ret = 1;
regptr_name = regnames[x->am->regptr];
if (n == 0) {
emit(f, "\tmov\t%s, ", regptr_name);
emit_object(f, x, 0, POINTER);
emit(f, "\n");
} else {
// emit(f, "\tadd\t%s, %d\n", regptr_name, n);
}
}
return ret;
}
// Generate code to perform a given operation.
// The code generated is:
// op t, s
// typ = operand type (e.g CHAR, POINTER, INT ...)
// n = the offset when dealing with multi-world objects (e.g long, float)
// EITHER t OR s MUST BE A REGISTER!
// For the non-register operand, temporaries are used and loaded
// according to am-data.
static void emit_op(FILE *f, struct obj *t, struct obj *s, int typ, int n, char *op)
{
long ofs;
char *mode;
// calculate offset into stack
ofs = ofs_get(t);
// determine access mode (8-bit or 16-bit instruction)
mode = "";
if (sizetab[typ&NQ] == 1) mode = ".8";
if (emit_tmpptr(f, s, n)) {
// we use a temporary pointer and it's loaded in register s->am->regptr
// Now we have to generate the code to load the data using the temporary pointer.
emit(f, "\t%s%s\t%s, [%s+%d]\n", op, mode, regnames[t->reg], regnames[s->am->regptr], n);
} else if (emit_tmpptr(f, t, n)) {
// get value into target register
emit(f, "\t%s%s\t[%s+%d], %s\n", op, mode, regnames[t->am->regptr], n, regnames[s->reg]);
} else {
emit(f, "\t%s%s\t", op, mode);
emit_object(f, t, n, typ);
emit(f, ", ");
emit_object(f, s, n, typ);
emit(f, "\n");
}
}
// Generate register loading code in the following format:
// op reg, x
// Meaning it is always code generated, which loads into a register.
// typ = operand type (e.g CHAR, POINTER, INT ...)
// n = the offset when dealing with multi-world objects (e.g long, float)
static void emit_load(FILE *f, int reg, struct obj *s, int typ, int n, char *op)
{
struct obj *x;
int ofs;
// if target register is blank, we quit
if (reg == 0) return;
x = mymalloc(sizeof(struct obj));
// build a target register object
x->flags = REG;
x->reg = reg;
x->dtyp = 0;
x->v = 0;
x->am = 0;
// generate operation
emit_op(f, x, s, typ, n, op);
// we have to take care of the speical case of address of auto variable
if ((s->flags & (VAR|VARADR)) == (VAR|VARADR)) {
if (isauto(s->v->storage_class)) {
// calculate offset into stack
ofs = ofs_get(s) + n;
if (ofs) {
emit(f, "\tadd\t%s, %d\n", regnames[reg], ofs);
}
}
}
myfree(x);
}
// Generate the following code:
// mov t, reg
// typ = operand type (e.g CHAR, POINTER, INT ...)
// n = the offset when dealing with multi-world objects (e.g long, float)
static void emit_store(FILE *f, struct obj *t, int reg, int typ, int n)
{
struct obj *x;
// if source register is blank, we quit
if (reg == 0) return;
x = mymalloc(sizeof(struct obj));
// build a source register object
x->flags = REG;
x->reg = reg;
x->dtyp = 0;
x->v = 0;
x->am = 0;
// generate operation
emit_op(f, t, x, typ, n, "mov");
myfree(x);
}
void am_alloc(struct obj *x)
{
// if no object, return
if (x == 0) return;
// if already allocated, return
if (x->am) return;
x->am = mymalloc(sizeof(struct AddressingMode));
x->am->regptr = 0;
x->am->regval[0] = 0;
x->am->regval[1] = 0;
}
// Compare if 2 objects are the same.
int obj_eq(struct obj *x1, struct obj *x2)
{
int ret;
// we assume the objects are equal to start with
ret = 1;
if (x1==0 || x2==0) {
ret = 0;
} else {
if (x1->flags != x2->flags) ret = 0;
if (x1->reg != x2->reg) ret = 0;
}
return ret;
}
/*
Initialize register tracking system.
The tracking systemis is used to keep track of what register is used and holds
what at any given time. Depending on availability, temporary registers can be
allocated with best efficiency.
*/
static void track_init(void)
{
int i;
// pre-fill
for(i = 1; i <= MAXR; i++) {
// clear all unnecessary bits
track_status[i] &= (TRACK_OFFLIMITS | TRACK_DEDICATED);
}
// all slots are empty at the beginning.
for (i = 0; i < TRACK_SLOTS; i++) {
// indicate slot is not used
track_slot[i] = 0;
}
}
// Mark any register used by the IC
static void track_obj_claim(struct obj *x)
{
// analyze object and flag already used registers as 'no-go'
if (x && (x->flags & REG)) {
track_status[x->reg] |= TRACK_IC;
}
}
/*
Allocate the required temporary registers
to be able to generate code for the given IC.
Each operand gets an 'am' datastructure, which
is filled with the temporary registers to use.
And it works like this
z = q1 <op> q2
ptr ptr ptr
val val
There's only one ptr that satisfies all three operand.
There's only one val that is used for both q1 and z.
So for example in if we have q1(ptr, val) that means:
Use register ptr to get q1 into register val.
Or for z(ptr, val): Use register ptr to store register val
to the target location.
*/
/*
0. determine if we need to free previously allocated temps
a. we do this if we got a branch or if the current IC uses any temp reg
1. determine how many temporaries we need
2. check if we can reuse previously reserved temps
3. reserve temps if necessary (and make it smart, pick the ones that are not used in the next IC)
*/
static int track_alloc(FILE *f)
{
int reg;
int i;
int save;
reg = 0;
save = 0;
// try to find a register
// If we can't find one, there's some serious issue.
for (i = 1; i < MAXR+1; i++) {
if ((track_status[i] & (TRACK_NOGO|TRACK_ALLOCREG|TRACK_SAVED)) == TRACK_ALLOCREG) {
// found a register, but it must be saved, keep looking maybe
// we find a better one, only use this if we haven't found anything useful yet
if (reg == 0) {
reg = i;
save = 1;
}
} else if ((track_status[i] & (TRACK_NOGO|TRACK_ALLOCREG|TRACK_SAVED)) == (TRACK_ALLOCREG|TRACK_SAVED)) {
// found a register, it's in use by ALLOCREG, but it has already been saved
// We use it and quit.
reg = i;
save = 0;
break;
} else if ((track_status[i] & (TRACK_NOGO|TRACK_ALLOCREG|TRACK_SAVED|TRACK_DEDICATED)) == 0) {
// found an unallocated register which is not a dedicated temp, we use it
reg = i;
save = 0;
break;
} else if ((track_status[i] & (TRACK_NOGO|TRACK_DEDICATED)) == TRACK_DEDICATED) {
// found a dedicated temporary, we could use it, but we try to find a better one
reg = i;
save = 0;
}
}
if (reg == 0) terror("-- track_alloc: can't find temporary register");
// flag register as taken
track_status[reg] |= TRACK_TMP;
emit(f, "; allocate temporary: %s\n", regnames[reg]);
if (save) {
track_status[reg] |= TRACK_SAVED;
// find an unused storage slot
for (i = 0; i < TRACK_SLOTS; i++) {
if (!track_slot[i]) break;
}
if (i >= TRACK_SLOTS) terror("--track_alloc: out of storage slots");
track_slot[i] = reg;
emit(f, "\tmov\t[_TSLOT+%d], %s\n", i*2, regnames[reg]);
}
return reg;
}
/*
Temporary register tracking and assignement.
*/
/*
Check if access to object x requires temporary registers.
Allocate all required temporary registers.
*/
static void track_obj(FILE *f, struct obj *x, int typ)
{
int i;
int reg, reg2;
int need_tmpval;
int size;
// object must have 'am' structure.
am_alloc(x);
// determine if we need to free previously allocated temporaries
// claim registers used by object
track_obj_claim(x);
// see if one of them was allocated, and free it if necessary
for (i = 0; i < TRACK_SLOTS; i++) {
reg = track_slot[i];
if ((track_status[reg] & (TRACK_IC|TRACK_SAVED)) == (TRACK_IC|TRACK_SAVED)) {
track_status[reg] &= ~TRACK_SAVED;
track_slot[i] = 0;
emit(f, "; restore temporary\n");
emit(f, "\tmov\t%s, [_TSLOT+%d]\n", regnames[reg], i*2);
}
}
// determine how many temporary registers we need
need_tmpval = 1;
if (x && (x->flags & (REG|DREFOBJ)) == REG) need_tmpval = 0;
if (need_tmpval) {
// we need temporary value register, allocate one
reg = track_alloc(f);
// In case this is a 32-bit operand we need 2 temporaries
size = sizetab[typ&NQ];
reg2 = 0;
if (size == 4) {
reg2 = track_alloc(f);
}
x->am->regval[0] = reg;
x->am->regval[1] = reg2;
}
// next step is to check if pointer memory access requires a register
if (x && (x->flags & (DREFOBJ | VAR | REG)) == (DREFOBJ | VAR)) {
reg = track_alloc(f);
x->am->regptr = reg;
}
}
/*
Check the IC for temporary register requirements.
Allocate all required temporaries and save them if required.
*/
static void track_ic(FILE *f, struct IC *p)
{
struct obj *z;
struct obj *q1;
struct obj *q2;
int i;
int reg, reg2;
int need_tmpval;
int size;
int code;
// get objects
z = &p->z;
q1 = &p->q1;
q2 = &p->q2;
// get instruction code
code = p->code;
// each object must have 'am' structure.
am_alloc(z);
am_alloc(q1);
am_alloc(q2);
// determine if we need to free previously allocated temporaries
// claim registers used by IC
track_obj_claim(z);
track_obj_claim(q1);
track_obj_claim(q2);
// see if one of them was allocated, and free it if necessary
for (i = 0; i < TRACK_SLOTS; i++) {
reg = track_slot[i];
if ((track_status[reg] & (TRACK_IC|TRACK_SAVED)) == (TRACK_IC|TRACK_SAVED)) {
track_status[reg] &= ~TRACK_SAVED;
track_slot[i] = 0;
emit(f, "; restore temporary\n");
emit(f, "\tmov\t%s, [_TSLOT+%d]\n", regnames[reg], i*2);
}
}
// determine how many temporary registers we need
need_tmpval = 1;
if (z && (z->flags & (REG|DREFOBJ)) == REG) need_tmpval = 0;
if (q1 && (q1->flags & (REG|DREFOBJ)) == REG) need_tmpval = 0;
if (q2 && (q2->flags & (REG|DREFOBJ)) == REG) need_tmpval = 0;
// if target Z is NOT the same as Q1 we will always need a temporary
if (code == ASSIGN || code == CONVERT) {
;
} else if (code == COMPARE) {
// in case of COMPARE we need a temporary if Q1 == KONST
if (q1 && (q1->flags & KONST) == KONST) need_tmpval = 1;
} else {
if (!obj_eq(z, q1)) {
need_tmpval = 1;
}
}
if (need_tmpval) {
reg = 0;
// check if we can use target register as temporary
// We can do so if Q2 is not the same as Z
if (z) {
if (((z->flags & (REG|DREFOBJ)) == REG) && !obj_eq(z, q2)) {
reg = z->reg;
track_status[reg] |= TRACK_TMP;
} else {
// we need temporary value register, allocate one
reg = track_alloc(f);
}
// In case this is a 32-bit operand we need 2 temporaries
size = sizetab[ztyp(p)&NQ];
reg2 = 0;
if (size >= 4) {
reg2 = track_alloc(f);
}
z->am->regval[0] = reg;
z->am->regval[1] = reg2;
}
// Q1 need the same temp as Z, but maybe only 16-bit
if (q1) {
if (!reg) {
reg = track_alloc(f);
}
q1->am->regval[0] = reg;
if (sizetab[q1typ(p)&NQ] <= 2) {
reg2 = 0;
}
q1->am->regval[1] = reg2;
}
}
// next step is to check if pointer memory access requires a register
reg = 0;
if (z && (z->flags & (DREFOBJ | VAR | REG)) == (DREFOBJ | VAR)) {
if (!reg) reg = track_alloc(f);
z->am->regptr = reg;
}
if (q1 && (q1->flags & (DREFOBJ | VAR | REG)) == (DREFOBJ | VAR)) {
if (!reg) reg = track_alloc(f);
q1->am->regptr = reg;
}
if (q2 && (q2->flags & (DREFOBJ | VAR | REG)) == (DREFOBJ | VAR)) {
if (!reg) reg = track_alloc(f);
q2->am->regptr = reg;
}
}
static void track_release(void)
{
int i;
for (i = 1; i <= MAXR; i++) {
track_status[i] &= ~(TRACK_TMP|TRACK_IC);
}
}
/*
Restores all saved temporary registers.
This must be executed before any branch, call
or function exit.
*/
static void track_restore(FILE *f)
{
int i;
track_release();
for (i = 0; i < TRACK_SLOTS; i++) {
if (track_slot[i]) {
track_status[track_slot[i]] &= ~(TRACK_SAVED);
emit(f, "\tmov\t%s, [_TSLOT+%d]\n", regnames[track_slot[i]], i*2);
track_slot[i] = 0;
}
}
}
// same as above, but don't clear TRACK_SAVED and don't release the temps.
// This is used in branch instructions.
static void track_restore2(FILE *f)
{
int i;
for (i = 0; i < TRACK_SLOTS; i++) {
if (track_slot[i]) {
emit(f, "\tmov\t%s, [_TSLOT+%d]\n", regnames[track_slot[i]], i*2);
}
}
}
/* generates the function entry code */
static void cd_function_entry(FILE *f, struct Var *v, long offset)
{
int i;
if (section != SEC_TEXT) {
emit(f, "\n\tsection\t%s\n", sec_textname);
section = SEC_TEXT;
}
if (v->storage_class == EXTERN) emit(f,"\tpublic\t%s%s\n", ident_prefix, v->identifier);
emit(f,"%s%s:\n", ident_prefix,v->identifier);
if (v->vattr && strstr(v->vattr, EXTDATA_INTERRUPT)) {
// interrupt service routine
emit(f, "\tpush\tflags\n");
for (i = REG_R0; i < REG_R0+nregs; i++) {
emit(f, "\tpush\t%s\n", regnames[i]);
}
}
// allocate required stack
if (offset > 0) emit(f,"\tsub\tsp, %ld\n", offset);
// store non-scratch register used in function
for (i = 1; i <= MAXR; i++) {
if (!regsa[i] && !regscratch[i] && regused[i]) {
emit(f, "\tpush\t%s\n", regnames[i]);
// adjust stack offset
stackoffset -= 2;
}
}
}
/* generates the function exit code */
static void cg_function_exit(FILE *f, struct Var *v, long offset)
{
int i;
// restore any saved temporary registers
// I don't think this is necessary as long as we don't have global register variables.
// What does vbcc do? Global regs?
track_restore(f);
// restore non-scratch register used in function
for (i = MAXR; i >= 1; i--) {
if (!regsa[i] && !regscratch[i] && regused[i]) {
emit(f, "\tpop\t%s\n", regnames[i]);
stackoffset += 2;
}
}
if (offset > 0) {
emit(f,"\tadd\tsp, %ld\n", offset);
}
if (v->vattr && strstr(v->vattr, EXTDATA_INTERRUPT)) {
// interrupt service routine
for (i = REG_R0+nregs-1; i >= REG_R0; i--) {
emit(f, "\tpop\t%s\n", regnames[i]);
}
emit(f, "\tpop\tflags\n");
emit(f, "\tiret\n\n");
} else {
emit(f, "\tret\n\n");
}
}
/*
some blurp:
There's an IC, which contains a target(z), and 2 sources(q1 and q2).
Macros ztyp, q1typ and q2typ return the type of the operands
e.g. (char, int, short, double, struct, array....).
Also contains the qualifiers UNSIGNED, CONST
Most of the time it's the same for all operands. Exceptions are
CONVERT or adding int to pointer.
Using NU and NQ will remove qualifiers and only leave base type.
Of interest are:
ISPOINTER, ISINT, ISFLOAT, ISFUNC, ISSTRUCT, ISUNION, ISARRAY,
ISSCALAR, ISARITH
Each operand can be one of the following:
----------------------------------------
KONST constant number
KONST|DREFOBJ absolute pointer
REG register
VAR variable (can be auto, register, static, extern)
VAR|REG a variable which was put in a register
REG|DREFOBJ indirect [reg]
VAR|DREFOBJ indirect [var]
VAR|REG|DREFOBJ indirect [reg], where the register is a variable
VAR|VARADR address of a variable
Temporaries generated by the compiler don't have the VAR flag set.
It is only a VAR if it is so in the source code.
Each variable then has information regarding the storage class,
so each VAR is on of the following:
AUTO
REGISTER
STATIC
EXTERN
Macros of interest:
isauto, isextern, isstatic.
*/
/* val_push
Pushing the value of the object onto the stack.
*/
static void val_push(FILE *f, struct obj *x, int typ)
{
// handle required temporaries
track_obj(f, x, typ);
emit(f, "; push val\n");
// Load Q1 into temporary register val if necessary.
// Automatically use temporary x->am->regptr if necessary.
emit_load(f, x->am->regval[0], x, typ, 0, "mov");
emit_load(f, x->am->regval[1], x, typ, 2, "mov");
// emit operation
if (x->am->regval[1]) {
emit(f, "\tpush\t%s\n", regnames[x->am->regval[1]]);
stackoffset -= 2;
}
if (x->am->regval[0]) {
// operation is performed on temporaries
emit(f, "\tpush\t%s\n", regnames[x->am->regval[0]]);
stackoffset -= 2;
} else {
// operation is performed directly on target (z == q1 for this to work)
emit(f, "\tpush\t%s\n", regnames[x->reg]);
stackoffset -= 2;
}
// release temporaries
track_release();
}
/* ptr_push
Pushing the ptr to the object onto the stack.
In case the object is a KONST, a copy of the KONST is made
in romsection and a pointer to the constant is pushed.
WARNING WARNING WARNING
This function MODIFIES the object *x.
*/
static void ptr_push(FILE *f, struct obj *x)
{
// handle required temporaries
track_obj(f, x, POINTER);
emit(f, "; push ptr to object\n");
// modify the IC such that we can generate code from it
// what we should do here is: if DREFOBJ is set, clear it. If DREFOBJ is not set, set VARADR.
if (x->flags & DREFOBJ) {
x->flags &= ~DREFOBJ;
} else {
x->flags |= VARADR;
}
x->dtyp = POINTER;
// Load object into temporary register val if necessary.
// Automatically use temporary x->am->regptr if necessary.
emit_load(f, x->am->regval[0], x, POINTER, 0, "mov");
// emit operation
if (x->am->regval[0]) {
// operation is performed on temporaries
emit(f, "\tpush\t%s\n", regnames[x->am->regval[0]]);
stackoffset -= 2;
} else {
// operation is performed directly on target (z == q1 for this to work)
emit(f, "\tpush\t%s\n", regnames[x->reg]);
stackoffset -= 2;
}
// release temporaries
track_release();
}
/*
Get the object from the stack. It is assumed the object is
located at [sp]. It is subsequently transfered to x
[sp] --> x
This function can be implemented either using 'pop' or using '[sp+xx]'.
Either one has advantage or disadvantage, also depending on CPU
implementation.
*/
void val_pop(FILE *f, struct obj *x, int typ)
{
emit(f, "; pop val\n");
// handle required temporaries
track_obj(f, x, typ);
// Load [sp] value into temporary register val if necessary.
if (x->am->regval[0]) {
// operation is performed on temporaries
emit(f, "\tpop\t%s\n", regnames[x->am->regval[0]]);
stackoffset += 2;
if (x->am->regval[1]) {
emit(f, "\tpop\t%s\n", regnames[x->am->regval[1]]);
stackoffset += 2;
}
} else {
// operation is performed directly on target (z == q1 for this to work)
emit(f, "\tpop\t%s\n", regnames[x->reg]);
stackoffset += 2;
}
// store to target
emit_store(f, x, x->am->regval[0], typ, 0);
emit_store(f, x, x->am->regval[1], typ, 2);
// release temporaries
track_release();
}
/*
Library call for ALU functions.
*/
static void lib_alu(FILE *f, struct IC *p, char *call)
{
char *modifier;
char *dtype;
int size;
// do some checking first
if (q1typ(p) != q2typ(p)) terror("--lib_alu: type mismatch");
modifier = "S";
if (q1typ(p) & UNSIGNED) {
modifier = "U";
}
switch (q1typ(p)) {
case CHAR:
dtype = "I8"; break;
case SHORT:
case INT:
dtype = "I16"; break;
case LONG:
dtype = "I32"; break;
case FLOAT:
dtype = "F32"; break;
case DOUBLE:
dtype = "F64"; break;
default:
dtype = ""; break;
terror("--lib_alu: data type not supported");
break;
}
// handle required temporaries
// temporary allocation is handled in val_push instead.
// The idea here is to allocate all required temporaries now,
// such that the return value can be easily grabbed.
// This is not implemented right now due to complications.
// track_ic(f, p);
// push the spaceholder for the result
size = sizetab[ztyp(p)&NQ];
if (size <= 2) {
// stack is 2-byte minimum
if (size < 2) size = 2;
emit(f, "\tsub\tsp, %d\n", size);
stackoffset -= size;
// Push all arguments
val_push(f, &p->q2, q2typ(p));
val_push(f, &p->q1, q1typ(p));
} else {
ptr_push(f, &p->q2);
ptr_push(f, &p->q1);
}
track_restore(f);
// emit call to library function
emit(f, "\tcall\t_%s_%s%s\n", call, modifier, dtype);
// restore stack state
// use 2*size in case 'val_pop' actually pops,
// use 3*size in case 'val_pop uses [sp+xx]
emit(f, "\tadd\tsp, %d\n", (int)2*size);
stackoffset += 2*size;
// last step is to grab the return value
// Return value is at [sp].
val_pop(f, &p->z, ztyp(p));
}
/*
Library call for mempry copy functions.
*/
static void lib_mov(FILE *f, struct IC *p)
{
// modify the IC such that we can generate code from it
p->q2.flags = KONST;
// Push Q2 (number of bytes to copy)
emit(f, "; push array size\n");
val_push(f, &p->q2, INT);
// Push pointer to Q1 (source)
emit(f, "; push source pointer\n");
ptr_push(f, &p->q1);
// push pointer to target (Z)
emit(f, "; push target pointer\n");
ptr_push(f, &p->z);
track_restore(f);
// emit call to library function
emit(f, "\tcall\t_MOV\n");
// stack cleanup, we should use callee clean-up
emit(f, "\tadd\tsp, %d\n", (int)6);
stackoffset += 6;
}
/*
*/
static void cg_assign(FILE *f, struct IC *p)
{
struct obj *x;
int typ;
int size;
// any struct or array goes to library function
// anything > 4 bytes goes to library
// size is only possible with ASSIGN, not CONVERT.
if (p->code == ASSIGN) {
size = opsize(p);
if (size > 4 || ISCOMPOSITE(ztyp(p))) {
lib_mov(f, p);
return;
}
}
// handle required temporaries
track_ic(f, p);
// We also get here for CONVERT, so test don't work.
// if (q1typ(p) != ztyp(p)) terror(0);
emit(f, "; mov\n");
// emit operation
typ = q1typ(p);
x = &p->q1;
if (x->am->regval[0]) {
// operation is performed on temporaries
emit_load(f, x->am->regval[0], x, typ, 0, "mov");
emit_load(f, x->am->regval[1], x, typ, 2, "mov");
} else {
// operation is performed directly, no temporaries involved
emit_op(f, &p->z, x, typ, 0, "mov");
}
// check if this is a CONVERT operation and inject sign extension
// if necessary.
if (p->code == CONVERT) {
// if Q1 is CHAR we need to blow it up to INT
if (sizetab[q1typ(p) & NQ] == 1) {
if (typ & UNSIGNED) {
// clear high byte
if (p->z.am->regval[0]) {
emit(f, "\tand\t%s, 0xff\n", regnames[p->z.am->regval[0]]);
} else {
emit(f, "\tand\t%s, 0xff\n", regnames[p->z.reg]);
}
} else {
// SXT high byte
if (p->z.am->regval[0]) {
emit(f, "\tsxt.8\t%s\n", regnames[p->z.am->regval[0]]);
} else {
emit(f, "\tsxt.8\t%s\n", regnames[p->z.reg]);
}
}
}
// if Z is LONG we need to blow it up from INT to LONG
if (sizetab[ztyp(p) & NQ] == 4) {
if (typ & UNSIGNED) {
emit(f, "\tmov\t%s, 0\n", regnames[p->z.am->regval[1]]);
} else {
emit(f, "\tmov\t%s, %s\n", regnames[p->z.am->regval[1]], regnames[p->z.am->regval[0]]);
emit(f, "\tsxt\t%s\n", regnames[p->z.am->regval[1]]);
}
}
}
// emit store if necessary
typ = ztyp(p);
x = &p->z;
emit_store(f, x, x->am->regval[0], typ, 0);
emit_store(f, x, x->am->regval[1], typ, 2);
// release temporaries
track_release();
}
static void cg_push(FILE *f, struct IC *p)
{
int size;
// any object larger than 4 bytes goes to a library function
size = opsize(p); // might need to use pushsize here instead of opsize?
if (size > 4 || ISCOMPOSITE(q1typ(p))) {
// allocate required stack
emit(f, "\tsub\tsp, %d\n", size);
stackoffset -= size;
// Push Z (number of bytes to push)
emit(f, "; push array size\n");
// modify z such that it becomes a KONST
p->z.flags = KONST;
val_push(f, &p->z, INT);
// Push pointer to Q1 (source)
emit(f, "; push source pointer\n");
ptr_push(f, &p->q1);
// emit call to library function
emit(f, "\tcall\t_PUSH\n");
// stack cleanup, we should use callee clean-up
emit(f, "\tadd\tsp, %d\n", (int)4);
stackoffset += 4;
return;
}
// handle required temporaries of 1op instructions
// track_push(f, p); // this must go, needs to be part of val_push
val_push(f, &p->q1, q1typ(p));
}
static void cg_call(FILE *f, struct IC *p)
{
long size = pushedargsize(p);
long ofs;
// calculate offset into stack
ofs = ofs_get(&p->q1);
emit(f, "; call\n");
// restore any temporaries
track_restore(f);
if ((p->q1.flags & (VAR|DREFOBJ)) == VAR) {
// call to label
emit(f, "\tcall\t%s%s\n", ident_prefix, p->q1.v->identifier);
} else if ((p->q1.flags & (VAR|DREFOBJ)) == (VAR|DREFOBJ) && p->q1.dtyp == POINTER) {
terror("-- call: pointer argument not yet supported");
} else terror("-- call: unexpected call mode\n");
if (size > 0) {
emit(f,"\tadd\tsp, %ld\n", size);
stackoffset += size;
}
}
static void cg_alu(FILE *f, struct IC *p)
{
struct obj *x;
int typ;
int c;
char *instr1, *instr2;
c = p->code;
switch (c) {
case ADD: instr1 = "add"; instr2 = "addc"; break;
case SUB: instr1 = "sub"; instr2 = "subc"; break;
case ADDI2P: instr1 = "add"; instr2 = "addc"; break;
case SUBIFP: instr1 = "sub"; instr2 = "subc"; break;
case OR: instr1 = instr2 = "or"; break;
case AND: instr1 = instr2 = "and"; break;
case XOR: instr1 = instr2 = "xor"; break;
default: instr1 = "xxx"; break;
};
emit(f, "; %s\n", instr1);
// get type of argument
typ = ztyp(p);
// try to get z and q1 the same, if possible
switch_IC(p);
// handle required temporaries of 2op instructions
track_ic(f, p);
if (!ISINT(typ) && !ISPOINTER(typ)) terror("-- addsub: unexpected operands");
// Load Q1 into temporary register val if necessary.
// Automatically use temporary x->am->regptr if necessary.
x = &p->q1;
emit_load(f, x->am->regval[0], x, typ, 0, "mov");
emit_load(f, x->am->regval[1], x, typ, 2, "mov");
// emit operation
x = &p->z;
if (x->am->regval[0]) {
// operation is performed on temporaries
emit_load(f, x->am->regval[0], &p->q2, typ, 0, instr1);
emit_load(f, x->am->regval[1], &p->q2, typ, 2, instr2);
} else {
// operation is performed directly on target (z == q1 for this to work)
emit_op(f, x, &p->q2, typ, 0, instr1);
}
// emit store if necessary
x = &p->z;
if (x->am->regval[0]) {
if (((x->flags & (REG|DREFOBJ)) != REG) && x->reg != x->am->regval[0]) {
// only write store if target is not a register or in case it is a register it
// is not the same as the source.
emit_store(f, x, x->am->regval[0], typ, 0);
emit_store(f, x, x->am->regval[1], typ, 2);
}
}
// release temporaries
track_release();
}
static void cg_lshift(FILE *f, struct IC *p)
{
// First we check if we can take a short-cut and don't need to
// call the library.
// [not done]
lib_alu(f, p, "SHL");
}
static void cg_rshift(FILE *f, struct IC *p)
{
// First we check if we can take a short-cut and don't need to
// call the library.
// [not done]
lib_alu(f, p, "SHR");
}
static void cg_mult(FILE *f, struct IC *p)
{
// First we check if we can take a short-cut and don't need to
// call the library.
// [not done]
lib_alu(f, p, "MUL");
}
static void cg_div(FILE *f, struct IC *p)
{
// First we check if we can take a short-cut and don't need to
// call the library.
// [not done]
lib_alu(f, p, "DIV");
}
static void cg_mod(FILE *f, struct IC *p)
{
// First we check if we can take a short-cut and don't need to
// call the library.
// [not done]
lib_alu(f, p, "MOD");
}
/*
modify IC such that we get
z = q1 xor 0xffff
*/
static void cg_komplement(FILE *f, struct IC *p)
{
struct obj *x;
emit(f, "; komplement\n");
p->code = XOR;
// change q2 to const 0
x = &p->q2;
x->flags = KONST;
x->reg = 0;
x->dtyp = 0;
// insert 0xffff
gval.vmax = -1;
eval_const(&gval, MAXINT);
insert_const(&x->val, ztyp(p));
// transfer to alu
cg_alu(f, p);
}
/*
modify IC such that we get
z = 0 - q1
*/
static void cg_minus(FILE *f, struct IC *p)
{
struct obj *x;
emit(f, "; minus\n");
p->code = SUB;
p->q2 = p->q1;
// change q1 to const 0
x = &p->q1;
x->flags = KONST;
x->reg = 0;
x->dtyp = 0;
// is this the right way of generating a constant?
gval.vmax = 0;
eval_const(&gval, MAXINT);
insert_const(&x->val, ztyp(p));
// transfer to alu
cg_alu(f, p);
}
static void cg_address(FILE *f, struct IC *p)
{
struct obj *x;
int typ;
long ofs;
int reg;
// calculate offset into stack
ofs = ofs_get(&p->q1);
// handle required temporaries of 1op instructions
track_ic(f, p);
emit(f, "; address\n");
// emit operation
typ = ztyp(p);
x = &p->z;
reg = x->reg;
if (x->am->regval[0]) {
reg = x->am->regval[0];
}
// emit operation
emit(f, "\tmov\t%s, sp\n", regnames[reg]);
if (ofs != 0) emit(f, "\tadd\t%s, %ld\n", regnames[reg], ofs);
// emit store if necessary
typ = ztyp(p);
x = &p->z;
if (x->am->regval[0]) {
if ((x->flags & (REG|DREFOBJ)) != REG) {
// only write store if target is not a register
emit_store(f, x, x->am->regval[0], typ, 0);
}
}
// release temporaries
track_release();
}
/*
modify IC such that we get
COMPARE q1, 0
*/
static void cg_test(FILE *f, struct IC *p)
{
struct obj *x;
emit(f, "; test\n");
p->code = COMPARE;
// change q2 to const 0
x = &p->q2;
x->flags = KONST;
x->reg = 0;
x->dtyp = 0;
// insert 0
gval.vmax = 0;
eval_const(&gval, MAXINT);
insert_const(&x->val, ztyp(p));
}
/*
Swap operands q1 <-> q2 of an IC. This is used by the branch IC
to map the operation to the available comaprisons.
*/
static void swap_operands(struct IC *p)
{
struct obj y;
y = p->q1;
p->q1 = p->q2;
p->q2 = y;
}
static void cg_branch_modify(struct IC *cmp, struct IC *p)
{
int c;
int q2_inc;
int swap;
int q2_konst;
// grab the branch instruction
c = p->code;
/*
In case Q1 == KONST we need to swap operands, however
this only makes a difference in case Q2 == REG.
This whole logic here might not be necessary if the front-end
always puts KONST in Q2. Test seem to confirm this, however for
the time being we leave the code in.
*/
if (((cmp->q1.flags & (KONST|DREFOBJ)) == KONST) && ((cmp->q2.flags & (REG|DREFOBJ)) == REG)) {
// swap operands
swap_operands(cmp);
// modify branch condition to match operand swap
switch (c) {
case BLT: c = BGT; break;
case BGE: c = BLE; break;
case BLE: c = BGE; break;
case BGT: c = BLT; break;
}
}
// Check if Q2 is a KONST. If it is we shouldn't swap operands.
q2_konst = 0;
if ((cmp->q2.flags & (KONST|DREFOBJ)) == KONST) {
q2_konst = 1;
}
// initialize modification falgs
swap = 0;
q2_inc = 0;
// decode jump and decide what modifications must be undertaken.
switch (c) {
// equal ==, generate 'je'
case BEQ: break;
// not equal !=, generate 'jne'
case BNE: break;
// less than '<'
case BLT: break;
// greater equal '>='
case BGE: break;
// less equal '<='
// This must be changed into BLT or BGE
case BLE:
if (q2_konst) {
// Q2 is KONST, can't swap operands but must modify the constant
c = BLT;
q2_inc = 1;
} else {
// must swap operands to map to available comparison instructions
c = BGE;
swap = 1;
}
break;
// greater than '>'
case BGT:
if (q2_konst) {
// Q2 is KONST, can't swap operands but must modify the constant
c = BGE;
q2_inc = 1;
} else {
// must swap operands to map to available comparison instructions
c = BLT;
swap = 1;
}
break;
}
/*
MODIFY IC if neccessary.
Modification looks like this:
If q2_inc == 1, we increment KONST q2 by 1.
If swap == 1, we swap operands.
Either way, we replace the exisiting condition code with the new one.
*/
if (swap) swap_operands(cmp);
if (q2_inc) {
// partial constant
struct obj *x;
// increment Q2
x = &cmp->q2;
// not sure if this is the way I'm supposed to do this
eval_const(&x->val, q2typ(cmp));
gval.vmax = zmadd(vmax, 1);
eval_const(&gval, MAXINT);
insert_const(&x->val, q2typ(cmp));
}
// insert new condition code
p->code = c;
}
//static void cg_branch
static void cg_branch(FILE *f, struct IC *p)
{
struct IC *cmp;
int c;
// size in bytes
long size;
int typ;
char *sign;
// jump target
// The target in case the condition is satisfied
long target_true;
// the target in case the condition is violated
long target_false;
// partial constant
struct obj *x;
if (q1typ(p) != q2typ(p)) terror(0);
emit(f, "; compare\n");
// try to find compare IC
cmp = p->prev;
while (cmp && cmp->code == FREEREG) cmp = cmp->prev;
if (!cmp || (cmp->code != COMPARE && cmp->code != TEST)) terror("-- cg_branch: compare not found");
typ = q1typ(cmp);
size = sizetab[typ&NQ];
if (!ISINT(typ)) terror("-- cg_branch: unexpected object type");
// get jump target
target_true = (long)p->typf;
target_false = label_count++;
cg_branch_modify(cmp, p);
// grab the branch instruction
c = p->code;
// determine sign/unsigned modifier
sign = "s";
if (typ & UNSIGNED) {
sign = "u";
}
// 1. step we allcoate necessay temporaries
track_ic(f, cmp);
// We need to distinguish different operand sizes
if (size <= 2) {
// First case takes care of CHAR and INT/SHORT
x = &cmp->q1;
emit_load(f, x->am->regval[0], x, typ, 0, "mov");
// emit operation
if (x->am->regval[0]) {
// operation is performed on temporaries
emit_load(f, x->am->regval[0], &cmp->q2, typ, 0, "cmp");
} else {
// operation is performed directly on target (z == q1 for this to work)
emit_op(f, x, &cmp->q2, typ, 0, "cmp");
}
// must restore temporaries here,
track_restore(f);
switch (c) {
case BLT:
emit(f, "\tj%sl\t%s%ld\n", sign, label_prefix, target_true);
break;
case BGE:
emit(f, "\tj%sge\t%s%ld\n", sign, label_prefix, target_true);
break;
case BEQ:
emit(f, "\tje\t%s%ld\n", label_prefix, target_true);
break;
case BNE:
emit(f, "\tjne\t%s%ld\n", label_prefix, target_true);
break;
}
} else if (size == 4) {
// LONG
x = &cmp->q1;
emit_load(f, x->am->regval[0], x, typ, 0, "mov");
emit_load(f, x->am->regval[1], x, typ, 2, "mov");
// emit operation
if (x->am->regval[1]) {
// operation is performed on temporaries
emit_load(f, x->am->regval[1], &cmp->q2, typ, 2, "cmp");
} else {
// operation is performed directly on target (z == q1 for this to work)
emit_op(f, x, &cmp->q2, typ, 2, "cmp");
}
// must restore temporaries here,
track_restore2(f);// (but don't release)
switch (c) {
case BLT:
emit(f, "\tj%sl\t%s%ld\n", sign, label_prefix, target_true);
emit(f, "\tjne\t%sn%ld\n", label_prefix, target_false);
break;
case BGE:
emit(f, "\tj%sl\t%sn%ld\n", sign, label_prefix, target_false);
emit(f, "\tjne\t%s%ld\n", label_prefix, target_true);
break;
case BEQ:
emit(f, "\tjne\t%sn%ld\n", label_prefix, target_false);
break;
case BNE:
emit(f, "\tjne\t%sn%ld\n", label_prefix, target_true);
break;
}
// here we have to reallocate temporary. HOWEVER if done cleverly we don't have
// to reload it, I have to think about the whole thing a litte bit.
// (reload what is needed, use same temps as above)
emit_load(f, x->am->regval[0], x, typ, 0, "mov");
// emit operation
if (x->am->regval[0]) {
// operation is performed on temporaries
emit_load(f, x->am->regval[0], &cmp->q2, typ, 0, "cmp");
} else {
// operation is performed directly on target (z == q1 for this to work)
emit_op(f, x, &cmp->q2, typ, 0, "cmp");
}
track_restore(f);
switch (c) {
case BLT:
emit(f, "\tjuge\t%s%ld\n", label_prefix, target_true);
break;
case BGE:
emit(f, "\tjul\t%s%ld\n", label_prefix, target_true);
break;
case BEQ:
emit(f, "\tje\t%s%ld\n", label_prefix, target_true);
break;
case BNE:
emit(f, "\tjne\t%s%ld\n", label_prefix, target_true);
break;
}
} else {
terror("branch size not supported");
}
// emit "not taken" label
emit(f, "%sn%ld:\n", label_prefix, target_false);
// release temporaries
track_release();
}
static void cg_setreturn(FILE *f, struct IC *p)
{
emit(f, "; set return\n");
if (sizetab[ztyp(p)&NQ] > regsize[p->z.reg]) terror("-- cg_setreturn: size mismatch");
emit_load(f, REG_R0, &p->q1, q1typ(p), 0, "mov");
}
static void cg_getreturn(FILE *f, struct IC *p)
{
emit(f, "; get return\n");
if (p->q1.reg) {
emit_store(f, &p->z, p->q1.reg, ztyp(p), 0);
}
}
static void cg_movefromreg(FILE *f, struct IC *p)
{
emit_store(f, &p->z, p->q1.reg, ztyp(p), 0);
}
static void cg_movetoreg(FILE *f, struct IC *p)
{
emit_load(f, p->z.reg, &p->q1, ztyp(p), 0, "mov");
}
/****************************************/
/* End of private data and functions. */
/****************************************/
/* Does necessary initializations for the code-generator. Gets called */
/* once at the beginning and should return 0 in case of problems. */
int init_cg(void)
{
int i;
int nregs;
int rtmp;
int rsave;
/* Initialize some values which cannot be statically initialized */
/* because they are stored in the target's arithmetic. */
maxalign = l2zm(2L);
char_bit = l2zm(8L);
sizetab[0] = 1; typname[0] = "strange"; align[0] = 1;
sizetab[CHAR] = 1; typname[CHAR] = "char"; align[CHAR] = 1;
sizetab[SHORT] = 2; typname[SHORT] = "short"; align[SHORT] = 2;
sizetab[INT] = 2; typname[INT] = "int"; align[INT] = 2;
sizetab[LONG] = 4; typname[LONG] = "long"; align[LONG] = 2;
sizetab[LLONG] = 8; typname[LLONG] = "long long"; align[LLONG] = 2;
sizetab[FLOAT] = 4; typname[FLOAT] = "float"; align[FLOAT] = 2;
sizetab[DOUBLE] = 8; typname[DOUBLE] = "double"; align[DOUBLE] = 2;
sizetab[LDOUBLE] = 8; typname[LDOUBLE] = "long double"; align[LDOUBLE] = 2;
sizetab[VOID] = 0; typname[VOID] = "void"; align[VOID] = 1;
sizetab[POINTER] = 2; typname[POINTER] = "pointer"; align[POINTER] = 2;
sizetab[ARRAY] = 0; typname[ARRAY] = "array"; align[ARRAY] = 1;
sizetab[STRUCT] = 0; typname[STRUCT] = "struct"; align[STRUCT] = 1;
sizetab[UNION] = 0; typname[UNION] = "union"; align[UNION] = 1;
sizetab[ENUM] = 2; typname[ENUM] = "enum"; align[ENUM] = 2;
sizetab[FUNKT] = 0; typname[FUNKT] = "function"; align[FUNKT] = 1;
sizetab[MAXINT] = 0;
// fill in the default register description
regnames[0] = "noreg";
regnames[REG_R0] = "r0";
regnames[REG_R1] = "r1";
regnames[REG_R2] = "r2";
regnames[REG_R3] = "r3";
regnames[REG_R4] = "r4";
regnames[REG_R5] = "r5";
regnames[REG_R6] = "r6";
regnames[REG_R7] = "r7";
regnames[REG_R8] = "r8";
regnames[REG_R9] = "r9";
regnames[REG_R10] = "r10";
regnames[REG_R11] = "r11";
regnames[REG_R12] = "r12";
regnames[REG_R13] = "r13";
for (i = 1; i < MAXR+1; i++) {
regsize[i] = l2zm(2L);
regtype[i] = &ltyp;
regscratch[i] = 1;
regsa[i] = 0;
reg_prio[i] = 1;
// private register tracking system
// Indicate register is available as temporary.
track_status[i] = 0;
}
// default numbers of registers to use
nregs = 6;
// check command line flags for number of registers
if (g_flags[0] & USEDFLAG) {
nregs = g_flags_val[0].l;
}
if (nregs < 3) nregs = 3;
if (nregs > 14) nregs = 14;
// limit available registers to 'nregs'
for (i = REG_R0+nregs; i <= REG_R13; i++) {
regsa[i] = 1;
regscratch[i] = 0;
track_status[i] |= TRACK_OFFLIMITS;
}
// get the number of temporary registers to reserve
if (g_flags[2] & USEDFLAG) {
rtmp = g_flags_val[1].l;
} else {
rtmp = 0;
if (nregs >= 6) rtmp = 1;
if (nregs >= 9) rtmp = 2;
if (nregs >= 12) rtmp = 3;
}
if (rtmp < 0) rtmp = 0;
if (rtmp > 3) rtmp = 3;
// remove temporary registers from code generator pool (on the top side)
for (i = nregs-rtmp; i < nregs; i++) {
regsa[REG_R0+i] = 1;
regscratch[REG_R0+i] = 0;
// indicate to tracked that this is a dedicated temporary register
track_status[REG_R0+i] |= TRACK_DEDICATED;
}
// get numbers of registers to save across function calls
if (g_flags[1] & USEDFLAG) {
rsave = g_flags_val[1].l;
} else {
rsave = 0;
// automatically select
if (nregs >= 6) rsave = 2;
if (nregs >= 10) rsave = 4;
}
if (rsave < 0) rsave = 0;
if (rsave > nregs-rtmp) rsave = nregs-rtmp;
for (i = 0; i < rsave; i++) {
regscratch[REG_R0+nregs-rtmp-rsave+i] = 0;
}
// SP is special
regnames[REG_SP] = "sp";
regscratch[REG_SP] = 0;
regsa[REG_SP] = 1;
track_status[REG_SP] |= TRACK_OFFLIMITS;
// and FLAGS is special
regnames[REG_FLAGS] = "flags";
regscratch[REG_FLAGS] = 0;
regsa[REG_FLAGS] = 1;
track_status[REG_FLAGS] |= TRACK_OFFLIMITS;
// print some status to the screen
printf("Falco16 CPU information\n");
printf(" GP registers: %d\n", nregs);
printf(" temporaries: %d\n", rtmp);
printf(" saves: %d\n", rsave);
/* Don't use multiple ccs. */
multiple_ccs = 0;
/* Initialize the min/max-settings. Note that the types of the */
/* host system may be different from the target system and you may */
/* only use the smallest maximum values ANSI guarantees if you */
/* want to be portable. */
/* That's the reason for the subtraction in t_min[INT]. Long could */
/* be unable to represent -2147483648 on the host system. */
t_min[CHAR]=l2zm(-128L);
t_min[SHORT]=l2zm(-32768L);
t_min[INT]=t_min(SHORT);
t_min[LONG]=zmsub(l2zm(-2147483647L),l2zm(1L));
t_min[LLONG]=zmlshift(l2zm(1L),l2zm(63L));
t_min[MAXINT]=t_min(LLONG);
t_max[CHAR]=ul2zum(127L);
t_max[SHORT]=ul2zum(32767UL);
t_max[INT]=t_max(SHORT);
t_max[LONG]=ul2zum(2147483647UL);
t_max[LLONG]=zumrshift(zumkompl(ul2zum(0UL)),ul2zum(1UL));
t_max[MAXINT]=t_max(LLONG);
tu_max[CHAR]=ul2zum(255UL);
tu_max[SHORT]=ul2zum(65535UL);
tu_max[INT]=t_max(UNSIGNED|SHORT);
tu_max[LONG]=ul2zum(4294967295UL);
tu_max[LLONG]=zumkompl(ul2zum(0UL));
tu_max[MAXINT]=t_max(UNSIGNED|LLONG);
target_macros=marray;
// local label counter
label_count = 0;
return 1;
}
void init_db(FILE *f)
{
}
int freturn(struct Typ *t)
/* Returns the register in which variables of type t are returned. */
/* If the value cannot be returned in a register returns 0. */
/* A pointer MUST be returned in a register. The code-generator */
/* has to simulate a pseudo register if necessary. */
{
int ret;
int p;
ret = 0;
p = t->flags & NQ;
if (sizetab[p] <= regsize[REG_R0]) ret = REG_R0;
return ret;
}
int reg_pair(int r, struct rpair *p)
/* Returns 0 if the register is no register pair. If r */
/* is a register pair non-zero will be returned and the */
/* structure pointed to p will be filled with the two */
/* elements. */
{
return 0;
}
int regok(int r, int t, int mode)
/* Returns 0 if register r cannot store variables of */
/* type t. If t==POINTER and mode!=0 then it returns */
/* non-zero only if the register can store a pointer */
/* and dereference a pointer to mode. */
{
// simple paramtere check
if(r == 0) return 0;
// if floating point, we don't keep them in registers at all
if (ISFLOAT(t)) return 0;
// Must be integer or pointer, keep them in register if possible.
t &= NQ;
if (sizetab[t] <= regsize[r]) return 1;
return 0;
}
int dangerous_IC(struct IC *p)
/* Returns zero if the IC p can be safely executed */
/* without danger of exceptions or similar things. */
/* vbcc may generate code in which non-dangerous ICs */
/* are sometimes executed although control-flow may */
/* never reach them (mainly when moving computations */
/* out of loops). */
/* Typical ICs that generate exceptions on some */
/* machines are: */
/* - accesses via pointers */
/* - division/modulo */
/* - overflow on signed integer/floats */
{
return 0;
}
int must_convert(int src, int dst, int const_expr)
{
int srcp = src & NQ;
int dstp = dst & NQ;
if (ISINT(src) && ISINT(dst)) {
if (sizetab[srcp] >= sizetab[dstp]) return 0;
// if (sizetab[srcp] == 1 && sizetab[dstp] == 2) return 0;
} else if (ISINT(src) && ISPOINTER(dst)) {
if (sizetab[srcp] >= sizetab[dstp]) return 0;
// if (sizetab[srcp] == 1 && sizetab[dstp] == 2) return 0;
} else if (ISPOINTER(src) && ISINT(dst)) {
if (sizetab[srcp] >= sizetab[dstp]) return 0;
// if (sizetab[srcp] == 1 && sizetab[dstp] == 2) return 0;
}
// no conversion when src and dst are same type
if (src == dst) return 0;
return 1;
}
/* Return name of library function, if this node should be
implemented via libcall. */
char *use_libcall(np p)
{
/*
following operations go into library
N - native
L - library call (some paramters are implemented native)
A - library call (all the time)
X - not supported
- - not valid (not generated by front-end)
? - what to do?
CHAR SHORT/INT LONG LLONG FLOAT DOUBLE LDOUBLE
CMP N N N X ? X X
OR N N N X - - -
XOR N N N X - - -
AND N N N X - - -
LSHIFT L L L X - - -
RSHIFT L L L X - - -
ADD N N N X A X X
SUB N N N X A X X
MULT L L L X A X X
DIV L L L X A X X
MOD L L L X A X X
*/
static char fname[16];
char *ret;
int f;
char *sign;
char *dtype;
int code;
if(p->flags>=EQUAL&&p->flags<=GREATEREQ){
extern struct Typ *arith_typ();
struct Typ *t=arith_typ(p->left->ntyp,p->right->ntyp);
f=t->flags&NU;
freetyp(t);
if((f&NQ)==LLONG){
sprintf(fname,"__cmp%s%sll",ename[p->flags],(f&UNSIGNED)&&p->flags!=EQUAL&&p->flags!=INEQUAL?"u":"");
ret=fname;
}else if((f&NQ)==FLOAT){
sprintf(fname,"__cmp%s%sf",ename[p->flags],(f&UNSIGNED)&&p->flags!=EQUAL&&p->flags!=INEQUAL?"u":"");
ret=fname;
}else if((f&NQ)==DOUBLE||(f&NQ)==LDOUBLE){
sprintf(fname,"__cmp%s%sd",ename[p->flags],(f&UNSIGNED)&&p->flags!=EQUAL&&p->flags!=INEQUAL?"u":"");
ret=fname;
}
}else{
// get code of operation
code = p->flags;
// get data type
f = p->ntyp->flags & NU;
sign = "s";
if (f & UNSIGNED) {
sign = "u";
}
switch (f & NQ) {
case CHAR: dtype = "i8"; break;
case SHORT:
case INT: dtype = "i16"; break;
case LONG: dtype = "i32"; break;
case FLOAT: dtype = "f32"; break;
case DOUBLE: dtype = "f64"; break;
default: dtype = ""; break;
terror("--use_libcall: data type not supported");
break;
}
// default is no function
ret = 0;
switch (code) {
case LSHIFT: sprintf(fname, "__shl_%s%s", sign, dtype); ret = fname; break;
case RSHIFT: sprintf(fname, "__shr_%s%s", sign, dtype); ret = fname; break;
case ADD:
if (ISFLOAT(f)) { sprintf(fname, "__add_%s", dtype); ret = fname; }
break;
case SUB:
if (ISFLOAT(f)) { sprintf(fname, "__sub_%s", dtype); ret = fname; }
break;
case MINUS:
if (ISFLOAT(f)) { sprintf(fname, "__neg_%s", dtype); ret = fname; }
break;
case MULT: sprintf(fname, "__mul_%s%s", sign, dtype); ret = fname; break;
case DIV:
sprintf(fname, "__div_%s%s", sign, dtype); ret = fname;
break;
case MOD:
sprintf(fname, "__mod_%s%s", sign, dtype); ret = fname;
break;
}
}
if(ret){
/* declare function if necessary */
struct struct_declaration *sd;struct Typ *t;
if(!find_ext_var(ret)){
sd = mymalloc(sizeof(*sd));
sd->count = 0;
t = new_typ();
t->flags = FUNKT;
t->exact = add_sd(sd, FUNKT);
t->next = clone_typ(p->ntyp);
add_var(ret, t, EXTERN, 0);
}
}
return ret;
}
void gen_ds(FILE *f, zmax size, struct Typ *t)
/* This function has to create <size> bytes of storage */
/* initialized with zero. */
{
if (newobj) {
emit(f, "\tdb\tdup(%ld, 0)\n", zm2l(size));
} else {
emit (f, "-- somehting's wrong\n");
}
newobj = 0;
}
void gen_align(FILE *f, zmax align)
/* This function has to make sure the next data is */
/* aligned to multiples of <align> bytes. */
{
int p;
p = p;
}
void gen_var_head(FILE *f, struct Var *v)
/* This function has to create the head of a variable */
/* definition, i.e. the label and information for */
/* linkage etc. */
{
int constflag;
if (v->clist) constflag = is_const(v->vtyp);
if (v->storage_class == STATIC) {
if ((v->vtyp->flags&NQ) == FUNKT) return;
if (v->clist && (!constflag) && section != SEC_DATA) {
emit(f, "\n\tsection\t%s\n", sec_dataname);
if (f) section = SEC_DATA;
}
if (v->clist && constflag && section != SEC_RODATA) {
emit(f, "\n\tsection\t%s\n", sec_rodataname);
if (f) section = SEC_RODATA;
}
if (!v->clist && section != SEC_BSS) {
emit(f, "\n\tsection\t%s\n", sec_bssname);
if (f) section = SEC_BSS;
}
emit(f, "%s%ld:", label_prefix, zm2l(v->offset));
newobj = 1;
}
if (v->storage_class == EXTERN) {
if (v->flags & (DEFINED|TENTATIVE)) {
if (v->clist && (!constflag) && section != SEC_DATA) {
emit(f, "\n\tsection\t%s\n", sec_dataname);
if (f) section=SEC_DATA;
}
if (v->clist && constflag && section != SEC_RODATA) {
emit(f, "\n\tsection\t%s\n", sec_rodataname);
if(f) section = SEC_RODATA;
}
if (!v->clist && section != SEC_BSS) {
emit(f, "\n\tsection\t%s\n", sec_bssname);
if (f) section = SEC_BSS;
}
emit(f, "\tpublic\t%s%s\n", ident_prefix, v->identifier);
emit(f, "%s%s:", ident_prefix, v->identifier);
newobj = 1;
}
}
}
void gen_dc(FILE *f, int t, struct const_list *p)
/* This function has to create static storage */
/* initialized with const-list p. */
{
emit(f, "\t%s\t", dct[t&NQ]);
if (!p->tree) {
if ((t&NQ) == FLOAT || (t&NQ) == DOUBLE) {
emit(f, "-- floating point not supported\n");
} else {
emitval(f, &p->val, t&NU);
}
} else {
emit(f, "-- gen_dc: what the hell\n");
}
emit(f, "\n");
newobj = 0;
}
/* The main code-generation routine. */
/* f is the stream the code should be written to. */
/* p is a pointer to a doubly linked list of ICs */
/* containing the function body to generate code for. */
/* v is a pointer to the function. */
/* offset is the size of the stackframe the function */
/* needs for local variables. */
void gen_code(FILE *f, struct IC *p, struct Var *v, zmax offset)
/* The main code-generation. */
{
struct IC *head;
int c;
if (DEBUG&1) printf("gen_code()\n");
loff = ((zm2l(offset) + 1) / 2) * 2;
stackoffset = 0;
cd_function_entry(f, v, loff);
// initialize temporary register tracking
track_init();
head = p;
for(; p; p = p->next){
// print: this doesn't work right, due to emit buffer
printic(f, p);
emit(f, "; stackoffset: %d\n", stackoffset);
// get operation code
c = p->code;
switch (c) {
case NOP:
break;
case ASSIGN:
case CONVERT:
cg_assign(f, p);
break;
case LSHIFT:
cg_lshift(f, p);
break;
case RSHIFT:
cg_rshift(f, p);
break;
case ADD:
case ADDI2P:
case SUB:
case SUBIFP:
case OR:
case XOR:
case AND:
cg_alu(f, p);
break;
case MULT:
cg_mult(f, p);
break;
case DIV:
cg_div(f, p);
break;
case MOD:
cg_mod(f, p);
break;
case KOMPLEMENT:
cg_komplement(f, p);
break;
case MINUS:
cg_minus(f, p);
break;
case ADDRESS:
cg_address(f, p);
break;
case TEST:
// change TEST into COMPARE
cg_test(f, p);
break;
case COMPARE:
// This is handled by the branch IC. Nothing to do here.
break;
case LABEL:
// must restore all temporaries before any label
// This is not good, as vbcc seems to be generating more labels than necessaty,
// pretty much defeating our clever system. We must do some more processing here.
track_restore(f);
emit(f, "%s%ld:\n", label_prefix, (long)p->typf);
break;
case BEQ:
case BNE:
case BLT:
case BGE:
case BLE:
case BGT:
cg_branch(f, p);
break;
case BRA:
// restore temporaries
track_restore(f);
emit(f, "\tjmp\t%s%ld\n", label_prefix, (long)p->typf);
break;
case PUSH:
cg_push(f, p);
break;
case CALL:
cg_call(f, p);
break;
case SETRETURN:
cg_setreturn(f, p);
break;
case GETRETURN:
cg_getreturn(f, p);
break;
case ALLOCREG:
// register is being used, indicate it can only be used if saved
track_status[p->q1.reg] |= TRACK_ALLOCREG;
break;
case FREEREG:
// register is again free
track_status[p->q1.reg] &= ~(TRACK_ALLOCREG);
break;
case MOVEFROMREG:
cg_movefromreg(f, p);
break;
case MOVETOREG:
cg_movetoreg(f, p);
break;
default:
emit(f, "unhandled IC\n");
}
emit(f, "\n");
}
cg_function_exit(f, v, loff);
}
int shortcut(int code, int typ)
{
if (sizetab[typ&NQ] > 2) return 0;
return 1;
}
void cleanup_cg(FILE *f)
{
}
void cleanup_db(FILE *f)
{
}