blob: 1a1cec12c27ac0148cf1c5f9e64bccb9a06d412e [file] [log] [blame]
/*
* Code generator for UNSP CPU core
* Copyright (C) 2022 Adrien Destugues <pulkomandy@pulkomandy.tk>
*
* Distributed under terms of the MIT license.
*/
#include "supp.h"
#include <stdbool.h>
static char FILE_[] = __FILE__;
char cg_copyright[]="vbcc code-generator for unsp V0.2 (c) 2022-2024 by Adrien Destugues";
int g_flags[MAXGF] = { VALFLAG };
char* g_flags_name[MAXGF] = { "abi" }; // TODO support ABIs 1.0, 1.1 and 1.2.
union ppi g_flags_val[MAXGF];
#define USE_COMMONS (g_flags[19]&USEDFLAG)
struct Typ ityp={INT};
// Values in target endianness, initialized by init_cg
/* Alignment-requirements for all types in bytes. */
zmax align[MAX_TYPE+1];
/* Alignment that is sufficient for every object. */
zmax maxalign;
/* CHAR_BIT of the target machine. */
zmax char_bit;
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];
/* Alignment-requirements for all types in bytes. */
static long malign[MAX_TYPE+1] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 };
/* Sizes of all elementary types in bytes. */
static long msizetab[MAX_TYPE+1] = { 0, 1, 1, 1, 2, 2, 2, 4, 4, 0, 1, 0, 0, 0, 1, 0 };
char* regnames[MAXR+1] = {"noreg", "SP", "R1", "R2", "R3", "R4", "BP", "SR", "PC" };
/* The Size of each register in bytes. */
zmax regsize[MAXR+1];
/* Type which can store each register. */
struct Typ *regtype[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 which registers may be scratched by functions. */
int regscratch[MAXR+1] = {0, 0, 1, 1, 1, 1, 0, 0, 0};
/* Names of target-specific variable attributes. */
char *g_attr_name[]={"__interrupt",0};
#define INTERRUPT 1
#define DATA 0
#define BSS 1
#define CODE 2
#define RODATA 3
#define SPECIAL 4
static char *codename="\t.text\n\t.align\t2\n",
*dataname="\t.data\n\t.align\t2\n",
*bssname="",
*rodataname="\t.section\t.rodata\n\t.align\t2\n",
*tocname="\t.tocd\n\t.align\t2\n",
*sdataname="\t.section\t\".sdata\",\"aw\"\n\t.align\t2\n",
*sdata2name="\t.section\t\".sdata2\",\"a\"\n\t.align\t2\n",
*sbssname="\t.section\t\".sbss\",\"auw\"\n\t.align\t2\n";
static char *labprefix="l",*idprefix="_",*tocprefix="@_";
static int newobj;
int current_section;
static void emit_obj(FILE *,obj *,int);
static int special_section(FILE *f,struct Var *v)
{
char *sec;
if(!v->vattr) return 0;
sec=strstr(v->vattr,"section(");
if(!sec) return 0;
sec+=strlen("section(");
emit(f,"\t.section\t");
while(*sec&&*sec!=')') emit_char(f,*sec++);
emit(f,"\n");
if(f) current_section=SPECIAL;
return 1;
}
int init_cg(void)
{
maxalign=l2zm(1L);
char_bit = l2zm(16L);
current_section = 0;
for (int i = 0; i <= MAX_TYPE; i++) {
sizetab[i] = l2zm(msizetab[i]);
align[i] = l2zm(malign[i]);
}
for (int i = 1; i <= MAXR; i++) {
regsize[i] = l2zm(1);
regtype[i]=&ityp;
}
/* Initialize the min/max-settings. */
t_min[CHAR]=l2zm(-32767);
t_min[SHORT]= t_min[CHAR];
t_min[INT]=t_min[CHAR];
t_min[LONG]=l2zm(-2147483647);
t_min[LLONG]=t_min[LONG];
t_min[MAXINT]=t_min[LONG];
t_max[CHAR]=ul2zum(32768);
t_max[SHORT]=t_max[CHAR];
t_max[INT]=t_max[CHAR];
t_max[LONG]=l2zm(2147483647);
t_max[LLONG]=t_max[LONG];
t_max[MAXINT]=t_max[LONG];
tu_max[CHAR]=ul2zum(65535);
tu_max[SHORT]=tu_max[CHAR];
tu_max[INT]=tu_max[CHAR];
tu_max[LONG]=ul2zum(4294967295);
tu_max[LLONG]=tu_max[LONG];
tu_max[MAXINT]=tu_max[LONG];
declare_builtin("__div", INT, INT, 0, INT, 0, 0, 0);
declare_builtin("__mod", INT, INT, 0, INT, 0, 0, 0);
regsa[0+1] = 1; // Reserve SP for the code generator
regsa[5+1] = 1; // Reserve BP for the code generator
regsa[6+1] = 1; // Reserve SR for the code generator
regsa[7+1] = 1; // Reserve PC for the code generator
return 1;
}
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. */
{
return 1; // return values in R1, why not?
}
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. */
{
// Only 16 bit values can be stored in registers
return msizetab[t & NQ] <= 1;
}
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. */
{
// FIXME handle MR (R3 pair R4)
return 0;
}
int shortcut(int c, int t)
{
// All implicit conversions are allowed
return 1;
}
void cleanup_cg(FILE *f)
{
}
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;
}
static long real_offset(struct obj *o)
{
long off;
if(zm2l(o->v->offset)>=0){
return zm2l(o->v->offset)+zm2l(o->val.vmax);
}else{
return zm2l(o->v->offset)-zm2l(maxalign)+zm2l(o->val.vmax);
}
}
static void emit_obj(FILE *f,struct obj *p,int t)
/* Prints an object. */
{
#if 0
if(p->am){
if(p->am->flags&REG_IND) emit(f,"%s,%s",mregnames[p->am->offset],mregnames[p->am->base]);
if(p->am->flags&IMM_IND) emit(f,"%ld(%s)",p->am->offset,mregnames[p->am->base]);
return;
}
#endif
/* if(p->flags&DREFOBJ) emit(f,"(");*/
if(p->flags&VAR) {
if(p->v->storage_class==AUTO||p->v->storage_class==REGISTER){
if(p->flags&REG){
emit(f,"%s",regnames[p->reg]);
}else{
emit(f,"%ld(%s)",real_offset(p),"BP");
}
}else{
if(!zmeqto(l2zm(0L),p->val.vmax)){emitval(f,&p->val,MAXINT);emit(f,"+");}
if(p->v->storage_class==STATIC){
emit(f,"%s%ld",labprefix,zm2l(p->v->offset));
}else{
emit(f,"%s%s",idprefix,p->v->identifier);
}
}
}
if((p->flags&REG)&&!(p->flags&VAR)) emit(f,"%s",regnames[p->reg]);
if(p->flags&KONST){
emitval(f,&p->val,t&NU);
}
/* if(p->flags&DREFOBJ) emit(f,")");*/
}
static int exists_freereg(struct IC *p,int reg)
/* Test if there is a sequence of FREEREGs containing FREEREG reg. */
{
while(p&&(p->code==FREEREG||p->code==ALLOCREG)){
if(p->code==FREEREG&&p->q1.reg==reg) return 1;
p=p->next;
}
return 0;
}
int must_convert(int o,int t,int const_expr)
/* Returns zero if code for converting np to type t */
/* can be omitted. */
{
int op=o&NQ,tp=t&NQ;
if(ISFLOAT(op) != ISFLOAT(tp)) return 1;
return 0; // all types are the same
}
static void title(FILE *f)
/* set file symbol with input file name */
{
extern char *inname;
static int done;
if (!done && f) {
done = 1;
emit(f,"\t.file\t\"%s\"\n",inname);
}
}
static long pof2(zumax x)
/* Yields log2(x)+1 oder 0. */
{
zumax p;int ln=1;
p=ul2zum(1L);
while(ln<=32&&zumleq(p,x)){
if(zumeqto(x,p)) return ln;
ln++;p=zumadd(p,p);
}
return 0;
}
void gen_ds(FILE *f,zmax size,struct Typ *t)
/* This function has to create <size> bytes of storage */
/* initialized with zero. */
{
title(f);
if (newobj && current_section!=SPECIAL)
emit(f,"%ld\n",zm2l(size));
else
emit(f,"\t.space\t%ld\n",zm2l(size));
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. */
{
title(f);
if(!optsize&&(zm2l(align)<4))
emit(f,"\t.align\t2\n");
else
if(zm2l(align)>1)
emit(f,"\t.align\t%ld\n",pof2(align)-1);
}
void gen_dc(FILE *f,int t,struct const_list *p)
/* This function has to create static storage */
/* initialized with const-list p. */
{
title(f);
if ((t&NQ) == POINTER)
t = UNSIGNED|LONG;
emit(f,"\t.2byte\t");
if (!p->tree) {
if (ISFLOAT(t)) {
unsigned char *ip = (unsigned char *)&p->val.vdouble;
emit(f,"0x%02x%02x%02x%02x",ip[0],ip[1],ip[2],ip[3]);
if ((t&NQ) != FLOAT)
emit(f,",0x%02x%02x%02x%02x",ip[4],ip[5],ip[6],ip[7]);
}
else {
emitval(f,&p->val,t&NU);
}
}
else {
emit_obj(f,&p->tree->o,t&NU);
}
emit(f, "\n");
newobj = 0;
}
void init_db(FILE *f)
{
}
void cleanup_db(FILE *f)
{
}
char* use_libcall(int c, int t, int t2)
{
// TODO separate signed/unsigned
if (c == MOD)
return "__mod";
if (c == DIV)
return "__div";
return 0;
}
const char* currentseg = "codeseg";
const char* const codeseg = "codeseg";
const char* const dataseg = "dataseg";
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;
char *sec;
title(f);
if(v->clist)
constflag = is_const(v->vtyp);
if (isstatic(v->storage_class)) {
if (ISFUNC(v->vtyp->flags))
return;
if (!special_section(f,v)) {
if (v->clist && (!constflag) && current_section!=DATA) {
emit(f,dataname);
if (f)
current_section = DATA;
}
if (v->clist && constflag && current_section!=RODATA) {
emit(f,rodataname);
if (f)
current_section = RODATA;
}
if (!v->clist && current_section!=BSS) {
emit(f,bssname);
if (f)
current_section = BSS;
}
}
if (v->clist || current_section==SPECIAL) {
gen_align(f,falign(v->vtyp));
emit(f,"%s%ld:\n",labprefix,zm2l(v->offset));
}
else
emit(f,"\t.lcomm\t%s%ld,",labprefix,zm2l(v->offset));
newobj = 1;
}
if (isextern(v->storage_class)) {
emit(f,"\t.globl\t%s%s\n",idprefix,v->identifier);
if (v->flags & (DEFINED|TENTATIVE)) {
if (!special_section(f,v)) {
if (v->clist && (!constflag) && current_section!=DATA) {
emit(f,dataname);
if(f)
current_section = DATA;
}
if (v->clist && constflag && current_section!=RODATA) {
emit(f,rodataname);
if (f)
current_section = RODATA;
}
if (!v->clist && current_section!=BSS) {
emit(f,bssname);
if (f)
current_section = BSS;
}
}
if (v->clist || current_section==SPECIAL) {
gen_align(f,falign(v->vtyp));
emit(f,"%s%s:\n",idprefix,v->identifier);
}
else {
emit(f,"\t.global\t%s%s\n\t.%scomm\t%s%s,",idprefix,
v->identifier,(USE_COMMONS?"":"l"),idprefix,v->identifier);
}
newobj = 1;
}
}
}
static int localslot(int localslots, int vo)
{
if (vo < 0) {
return localslots + 2 - vo;
} else {
return vo;
}
}
/** Emit appropriate code to access a variable.
*
* f: output assembler file
* p: instruction being generated (TODO: remove, used for printic in case of errors only)
* op: operand containing the variable being generated
* localslots: size of local variables area for computing BP relative offsets
* store: determine if this is a store to the variable or a read (FIXME this may be wrong and we should rely on DREFOBJ instead?)
* word: word offset (1 to access the high word of long values, otherwise should be 0)
*/
static void emitvar(FILE* f, IC* p, struct obj* op, int localslots, bool store, int word)
{
// FIXME handle DREFOBJ and VARADR correctly in all cases
// DREFOBJ: the variable is a pointer and we need to access the pointed data
// VARADR: only for static and extern variables, get the address of the variable
if (op->flags & DREFOBJ) {
if (op->flags & REG) {
emit(f, "(%s)", regnames[op->reg]);
} else if (isextern(op->v->storage_class)) {
emit(f, "(_%s)", op->v->identifier);
} else if (isauto(op->v->storage_class))
emit(f, "(BP+%d)", localslot(localslots, op->v->offset) + word);
else {
emit(f, "(%s%d)", labprefix, op->v->offset);
}
} else if (op->flags & VARADR) {
if (isauto(op->v->storage_class))
emit(f, "BP, %d", op->v->offset);
else if (isstatic(op->v->storage_class))
emit(f, "%s%d + %d", labprefix, op->v->offset, op->val.vlong);
else if (isextern(op->v->storage_class)) {
emit(f, "_%s + %d", op->v->identifier, word);
} else {
printic(stderr, p);
ierror(0);
}
} else {
if (op->flags & REG) {
emit(f, "%s", regnames[op->reg]);
} else if (isauto(op->v->storage_class))
emit(f, "(BP+%d)", localslot(localslots, op->v->offset) + word);
else if (isstatic(op->v->storage_class)) {
emit(f, "(%s%d + %d)", labprefix, op->v->offset, op->val.vlong);
} else if (isextern(op->v->storage_class))
if (store)
emit(f, "(_%s)", op->v->identifier);
else
emit(f, "%c_%s", word ? '>' : '<', op->v->identifier);
else {
printic(stderr, p);
ierror(0);
}
}
}
static int cg_getreg(FILE* f, struct IC* ic)
{
for (int i = 2; i <= MAXR; i++)
{
if (regs[i] == 0) {
regs[i] = 2;
return i;
}
}
// no register is available, we need to push/pop one, making sure it is not used by the IC
for (int reg = 2; reg <= 5; reg++) {
if ((ic->q1.flags & REG) && (ic->q1.reg == reg))
continue;
if ((ic->q2.flags & REG) && (ic->q2.reg == reg))
continue;
if ((ic->z.flags & REG) && (ic->z.reg == reg))
continue;
if (regs[reg] < 3)
regs[reg] = 3;
else
regs[reg]++;
emit(f, "\tPUSH %s, %s, (SP)\n", regnames[reg], regnames[reg]);
return reg;
}
fprintf(stderr, "No more registers!\n");
return 0;
}
static void cg_freereg(FILE* f, int r)
{
if (regs[r] < 2) {
fprintf(stderr, "oops\n");
//ierror(0);
return;
}
if (regs[r] >= 3) {
regs[r] --;
emit(f, "\tPOP %s, %s, (SP)\n", regnames[r], regnames[r]);
} else {
regs[r] = 0;
}
}
static int emitoperand(FILE* f, IC* p, struct obj* op, int offset, bool store, int word)
{
if (p->q1.flags & VAR) {
emitvar(f, p, op, offset, store, word);
} else if (p->q1.flags & KONST) {
if (p->q1.flags & DREFOBJ) {
emit(f, "(");
emitval(f, &op->val, store ? ztyp(p) : q1typ(p));
emit(f, ")");
} else {
emitval(f, &op->val, store ? ztyp(p) : q1typ(p));
}
} else {
ierror(0);
}
}
static int cg_aluop(const char* op, FILE* f, IC* p, zmax offset)
{
if ((p->z.flags & REG) && !(p->z.flags & DREFOBJ) && (p->q1.flags & VAR) && (p->q2.flags & VAR|KONST)) {
// Target is register
if (p->q1.flags & REG) {
if (p->z.reg != p->q1.reg) {
if (p->q1.flags & DREFOBJ)
emit(f, "\tLD\t%s, (%s)\n", regnames[p->z.reg], regnames[p->q1.reg]);
else
emit(f, "\tLD\t%s, %s\n", regnames[p->z.reg], regnames[p->q1.reg]);
}
} else {
emit(f, "\tLD\t%s, ", regnames[p->z.reg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
}
emit(f, "\t%s\t%s, ", op, regnames[p->z.reg]);
if (p->q2.flags & REG) {
emit(f, "%s\n", regnames[p->q2.reg]);
} else if (p->q2.flags & KONST) {
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
} else {
emit(f, "(BP+%d)\n", localslot(offset, p->q2.v->offset));
}
return 1;
}
// Target is not a register or q1 is const or VARADR or DREFOBJ are involved?
if ((p->z.flags & VAR)) {
if ((p->q1.flags & REG) && !(p->q1.flags & DREFOBJ) && (p->z.flags & REG) && (p->q2.flags & KONST)) {
emit(f, "\t%s\t%s, %s, ", op, regnames[p->z.reg], regnames[p->q1.reg]);
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
} else {
// TODO if z is not REG but q1 is REG and there is a freereg for it just after this IC,
// we should use q1 register here instead of allocating a temporary
int tmpreg;
if ((p->z.flags & REG) && !(p->z.flags & DREFOBJ)) {
// We can use the destination register directly
tmpreg = p->z.reg;
} else {
tmpreg = cg_getreg(f, p);
}
// Load the source
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitoperand(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
// Do the operation
emit(f, "\t%s\t%s, ", op, regnames[tmpreg]);
if (p->q2.flags & VAR) {
emitvar(f, p, &p->q2, offset, false, 0);
} else if (p->q2.flags & KONST) {
emitval(f, &p->q2.val, q2typ(p));
} else {
ierror(0);
}
emit(f, "\n");
// Store the result
if ((p->z.flags & REG) && !(p->z.flags & DREFOBJ)) {
// Operation was done directly in target register, no need to store
} else {
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
}
}
return 1;
}
if ((p->q2.flags & KONST) && (p->z.flags & (KONST | DREFOBJ)))
{
int tmpreg = cg_getreg(f, p);
if (p->q1.flags & (KONST | DREFOBJ)) {
emit(f, "\tLD\t%s, (", regnames[tmpreg]);
emitval(f, &p->q1.val, q1typ(p));
emit(f, ")\n");
} else if (p->q1.flags & VAR) {
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
}
emit(f, "\t%s\t%s, ", op, regnames[tmpreg]);
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
emit(f, "\tST\t%s, (", regnames[tmpreg]);
emitval(f, &p->z.val, ztyp(p));
emit(f, ")\n");
cg_freereg(f, tmpreg);
return 1;
}
printf("ALUOP OPS %d %d %d\n", p->q1.flags, p->q2.flags, p->z.flags);
return 0;
}
static int cg_ptrop(const char* op, FILE* f, IC* p, zmax offset)
{
if ((p->q1.flags & REG) && (p->q2.flags & REG) && (p->z.flags & REG) && (p->q1.reg == p->z.reg)) {
if (p->q1.flags & DREFOBJ) {
emit(f, "\t%s\t%s, (%s)\n", op, regnames[p->z.reg], regnames[p->q2.reg]);
} else {
emit(f, "\t%s\t%s, %s\n", op, regnames[p->z.reg], regnames[p->q2.reg]);
}
return 1;
}
if ((p->q1.flags & VAR) && (p->q2.flags & VAR) && isauto(p->q2.v->storage_class) && (p->z.flags & REG)) {
emit(f, "\tLD\t%s, ", regnames[p->z.reg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
if (p->q2.flags & REG) {
if (p->q2.flags & DREFOBJ) {
emit(f, "\t%s\t%s, (%s)\n", op, regnames[p->z.reg], regnames[p->q2.reg]);
} else {
emit(f, "\t%s\t%s, %s\n", op, regnames[p->z.reg], regnames[p->q2.reg]);
}
} else {
emit(f, "\t%s\t%s, (BP+%d)\n", op, regnames[p->z.reg], localslot(offset, p->q2.v->offset));
}
return 1;
}
if ((p->q1.flags & REG) && (p->z.flags & REG) && (p->q2.flags & KONST)) {
emit(f, "\t%s\t%s, %s, ", op, regnames[p->z.reg], regnames[p->q1.reg]);
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
return 1;
}
if (!strcmp(op, "ADD") && (p->q1.flags & VAR) && (p->q2.flags & KONST) && (p->z.flags & VAR)) {
int tmpreg;
if (p->z.flags & REG) {
tmpreg = p->z.reg;
} else {
tmpreg = cg_getreg(f, p);
}
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
emit(f, "\t%s\t%s, ", op, regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
if (!(p->z.flags & REG)) {
emit(f, "# PTR1\n");
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
}
return 1;
}
if ((p->q1.flags & VAR) && (p->q2.flags & VAR) && (p->z.flags & VAR)) {
int tmpreg;
if (p->z.flags & REG) {
tmpreg = p->z.reg;
} else {
tmpreg = cg_getreg(f, p);
}
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\t%s\t%s, ", op, regnames[tmpreg]);
if (p->q2.flags & REG) {
emit(f, "%s", regnames[p->q2.reg]);
} else {
emitvar(f, p, &p->q2, offset, false, 0);
}
emit(f, "\n");
if (!(p->z.flags & REG)) {
emit(f, "# PTR2\n");
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
}
return 1;
}
return 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)
{
emit(f, "\t.type\t%s%s,@function\n%s%s:\n", idprefix, v->identifier, idprefix, v->identifier);
if(v->storage_class==EXTERN){
if((v->flags&(INLINEFUNC|INLINEEXT))!=INLINEFUNC)
emit(f,"\t.globl\t%s%s\n", idprefix, v->identifier);
}
// different prologue and epilogue for interrupts: save all registers, use RETI instead RETF
if (v->tattr & INTERRUPT) {
// FIXME we can be smarter about which registers to save, by scanning all the ICs to see
// what's really used.
emit(f, "\tPUSH\tR1, BP, (SP)\n"); // Save previous SP
if (offset > 0) {
emit(f, "\tSUB\tSP, %d\n", offset); // Make space for local variables
// There are no parameters, so we need to set BP only if there are local variables
emit(f, "\tADD\tBP, SP, 1\n\n"); // BP points on first local variable
}
} else {
// TODO we can avoid saving and restoring BP if: offset is 0 AND there are no parameters
// There does't seem to be an easy way to know if the function has parameters so we may
// need to scan ICs for any access to auto variables?
emit(f, "\tPUSH\tBP, BP, (SP)\n"); // Save previous SP
if (offset > 0) {
emit(f, "\tSUB\tSP, %d\n", offset); // Make space for local variables
}
emit(f, "\tADD\tBP, SP, 1\n\n"); // BP points on first local variable
}
// Start with the default register set
for (int i = 1; i <= MAXR; i++)
regs[i] = regsa[i];
for(; p != NULL; p = p->next) {
switch (p->code) {
case ASSIGN:
{
// Copy Q1 to Z.
if ((p->q1.flags & REG) && (p->z.flags & REG)) {
emit(f, "; ASSIGN %d -> %d\n", p->q1.flags, p->z.flags);
if (p->z.flags & DREFOBJ) {
emit(f, "\tST\t%s, (%s)\t; ASSIGN to ptr\n", regnames[p->q1.reg], regnames[p->z.reg]);
} else if (p->q1.flags & DREFOBJ) {
emit(f, "\tLD\t%s, (%s)\t; ASSIGN from ptr\n", regnames[p->z.reg], regnames[p->q1.reg]);
} else {
emit(f, "\tLD\t%s, %s\t; ASSIGN T1\n", regnames[p->z.reg], regnames[p->q1.reg]);
}
continue;
}
if (!(p->q1.flags & KONST)) {
// Q1 is not constant
if ((p->z.flags & REG) && (p->q1.flags & VAR) && !(p->q1.flags & REG)) {
emit(f, "\tLD\t%s, ", regnames[p->z.reg]);
emitvar(f, p, &p->q1, offset, true, 0);
emit(f, "\n");
continue;
}
if ((p->q1.flags & REG) && (p->z.flags & VAR)) {
// REG to VAR is a simple ST instruction
emit(f, "; MOVE1\n");
emit(f, "\tST\t%s, ", regnames[p->q1.reg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
continue;
}
if ((p->q1.flags & REG) || (p->z.flags & REG)) {
fprintf(stderr, "error3 q1 %d z %d\n", p->q1.flags, p->z.flags);
printic(stderr, p);
break;
}
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, true, 0);
emit(f, "\n");
if (p->z.flags & DREFOBJ) {
if (p->z.flags & REG) {
emit(f, "\tST\t%s, (%s)\n", regnames[tmpreg], regnames[p->z.reg]);
} else {
int tmpreg2 = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg2]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
emit(f, "\tST\t%s, (%s)\n", regnames[tmpreg], regnames[tmpreg2]);
cg_freereg(f, tmpreg2);
}
} else {
emit(f, "# MOVE2 %d\n", p->z.flags);
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
}
cg_freereg(f, tmpreg);
continue;
}
// For all cases below Q1 is a constant
if (p->z.flags == VAR) {
// Memory variables
int tmpreg = cg_getreg(f, p);
if (p->q1.flags & DREFOBJ) {
emit(f, "\tLD\t%s, (", regnames[tmpreg]);
emitval(f, &p->q1.val, q1typ(p));
emit(f, ")\n");
} else {
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitval(f, &p->q1.val, q1typ(p));
emit(f, "\n");
}
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
continue;
}
if (p->z.flags == (REG|DREFOBJ)) {
// Constant to memory pointed by register
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]); emitval(f, &p->q1.val, q1typ(p)); emit(f, " # ASSIGNT6\n");
emit(f, "\tST\t%s, (%s)\n", regnames[tmpreg], regnames[p->z.reg]);
cg_freereg(f, tmpreg);
continue;
}
if (p->z.flags & REG) {
// Constant to register or to variable
if (p->z.flags == (VAR|REG|DREFOBJ)) {
// FIXME isn't this the same as above?
// TODO look in the next ICs if there is an ADDI2P on the same pointer,
// if so, we can use postincrement here and skip the ADDI2P
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]); emitval(f, &p->q1.val, q1typ(p)); emit(f, "\t# ASSIGN T7\n");
emit(f, "\tST\t%s, (%s)\n", regnames[tmpreg], regnames[p->z.reg]);
cg_freereg(f, tmpreg);
} else {
if (p->q1.flags & DREFOBJ) {
emit(f, "\tLD\t%s, (", regnames[p->z.reg]);
emitval(f, &p->q1.val, q1typ(p));
emit(f, ")\n");
} else {
emit(f, "\tLD\t%s, ", regnames[p->z.reg]);
emitval(f, &p->q1.val, q1typ(p));
emit(f, "\n");
}
}
continue;
}
if (p->z.flags == (VAR|DREFOBJ)) {
// Constant to external variable
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]); emitval(f, &p->q1.val, q1typ(p)); emit(f, " # ASSIGNT8\n");
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
continue;
}
if ((p->q1.flags == KONST) && (p->z.flags == KONST | DREFOBJ)) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitval(f, &p->q1.val, q1typ(p));
emit(f, "\n");
emit(f, "\tST\t%s, (", regnames[tmpreg]);
emitval(f, &p->z.val, ztyp(p));
emit(f, ")\n");
cg_freereg(f, tmpreg);
continue;
}
printf("ASSIGN q1flags: %d zflags %d\n", p->q1.flags, p->z.flags);
break;
}
case OR:
{
if (cg_aluop("OR", f, p, offset))
continue;
break;
}
case XOR:
{
if (cg_aluop("XOR", f, p, offset))
continue;
break;
}
case AND:
{
if (cg_aluop("AND", f, p, offset))
continue;
break;
}
case LSHIFT:
{
if (p->q2.flags & KONST) {
int shift = p->q2.val.vint;
// TODO handle all possible shift values
// TODO check type of Q1 and Z, int and long should be handled differently
if (shift == 16) {
if ((p->z.flags & REG) && !(p->z.flags & DREFOBJ)) {
emit(f, "\tLD\t%s, ", regnames[p->z.reg]);
if (p->q1.flags & VAR) {
emitvar(f, p, &p->q1, offset, false, 1);
} else if (p->q1.flags & KONST) {
emit(f, "#");
emitval(f, &p->q1.val, q1typ(p));
}
emit(f, "\n");
continue;
}
if (msizetab[ztyp(p) & NQ] > 1) {
int tmpreg;
tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitoperand(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 1);
emit(f, "\n");
emit(f, "\tLD\t%s, 0\n", regnames[tmpreg]);
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
continue;
}
}
if ((p->q1.flags & REG) && (p->z.flags & REG)) {
int shift = p->q2.val.vint;
if ((p->q1.reg == p->z.reg)) {
while (shift > 4) {
emit(f, "\tLD\t%s, %s LSL %d\n", regnames[p->z.reg], regnames[p->z.reg], 4);
shift -= 4;
}
emit(f, "\tLD\t%s, %s LSL %d\n", regnames[p->z.reg], regnames[p->z.reg], shift);
} else {
emit(f, "\tLD\t%s, %s LSL %d\n", regnames[p->q1.reg], regnames[p->z.reg], shift > 4 ? 4 : shift);
if (shift > 4) {
shift -= 4;
while (shift > 4) {
emit(f, "\tLD\t%s, %s LSL %d\n", regnames[p->z.reg], regnames[p->z.reg], 4);
shift -= 4;
}
if (shift > 0)
emit(f, "\tLD\t%s, %s LSL %d\n", regnames[p->z.reg], regnames[p->z.reg], shift);
}
}
continue;
}
if (!(p->q1.flags & REG)) {
int tmpreg;
if (p->z.flags & REG) {
tmpreg = p->z.reg;
} else {
tmpreg = cg_getreg(f, p);
}
int shift = p->q2.val.vint;
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
while (shift > 0) {
emit(f, "\tLD\t%s, %s LSL %d\n", regnames[tmpreg], regnames[tmpreg], (shift >= 4) ? 4 : shift);
shift -= 4;
}
if (!(p->z.flags & REG)) {
emit(f, "# LSHIFT\n");
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
}
continue;
}
}
break;
}
case RSHIFT:
{
if ((p->q1.flags & VAR) && (p->q2.flags & KONST) && (p->z.flags & VAR)) {
if ((p->q1.flags & REG) && (p->z.flags & REG)) {
int shift = p->q2.val.vint;
if ((p->q1.reg == p->z.reg)) {
while (shift > 4) {
emit(f, "\tLD\t%s, %s LSR %d\n", regnames[p->z.reg], regnames[p->z.reg], 4);
shift -= 4;
}
emit(f, "\tLD\t%s, %s LSR %d\n", regnames[p->z.reg], regnames[p->z.reg], shift);
} else {
emit(f, "\tLD\t%s, %s LSR %d\n", regnames[p->q1.reg], regnames[p->z.reg], shift > 4 ? 4 : shift);
if (shift > 4) {
shift -= 4;
while (shift > 4) {
emit(f, "\tLD\t%s, %s LSR %d\n", regnames[p->z.reg], regnames[p->z.reg], 4);
shift -= 4;
}
if (shift > 0)
emit(f, "\tLD\t%s, %s LSR %d\n", regnames[p->z.reg], regnames[p->z.reg], shift);
}
}
continue;
}
if (!(p->q1.flags & REG)) {
int tmpreg;
if (p->z.flags & REG) {
tmpreg = p->z.reg;
} else {
tmpreg = cg_getreg(f, p);
}
int shift = p->q2.val.vint;
if ((ztyp(p) & NQ) >= LONG) {
struct obj* source = &p->q1;
// 32bit shifts. 32bit values are not allowed to be in registers, which
// simplifies this a little
if (shift >= 16) {
// Special case for the common operation shifting to get the high
// byte of a long value
// TODO is this useful also for shifts larger than 16? Then we can
// finish the shifting with just the low byte (the high word will
// be all 0)
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, source, offset, false, 1);
emit(f, "\n");
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
emit(f, "\tLD\t%s, 0\n", regnames[tmpreg]);
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 1);
emit(f, "\n");
shift -= 16;
source = &p->z;
}
while (shift > 0) {
// TODO this can be done more efficiently if we allocate two
// temporary registers for the high and low bytes. There would be
// a lot less load-store, especially for shifts greater than 4.
// Shift the high word first
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, source, offset, false, 1);
emit(f, "\n");
emit(f, "\tLD\t%s, %s LSR %d\n", regnames[tmpreg], regnames[tmpreg], (shift >= 4) ? 4 : shift);
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 1);
emit(f, "\n");
// Then do the low word. The ROR addressing mode gets the bits we
// just removed from the high bit, using the shift buffer
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, source, offset, false, 0);
emit(f, "\n");
emit(f, "\tLD\t%s, %s ROR %d\n", regnames[tmpreg], regnames[tmpreg], (shift >= 4) ? 4 : shift);
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
shift -= 4;
source = &p->z;
}
cg_freereg(f, tmpreg);
continue;
} else {
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
while (shift > 0) {
emit(f, "\tLD\t%s, %s LSR %d\n", regnames[tmpreg], regnames[tmpreg], (shift >= 4) ? 4 : shift);
shift -= 4;
}
if (!(p->z.flags & REG)) {
emit(f, "# RSHIFT\n");
emit(f, "\tST\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
}
continue;
}
}
}
break;
}
case ADD:
{
if (cg_aluop("ADD", f, p, offset))
continue;
break;
}
case SUB:
{
if (cg_aluop("SUB", f, p, offset))
continue;
break;
}
case MULT:
{
// FIXME properly handle long-to-long multiplication (do the 3 crossed multiplications and add them up)
if ((p->q2.flags & KONST) && (p->z.flags & VAR) && isauto(p->z.v->storage_class)) {
int mult = p->q2.val.vuint;
if ((mult == 64) || (mult == 4)) {
// Special case for power-of-two multiplications, use shift operations
// FIXME adjust the code to work for 2, 8, 16, 128, ... as well
// FIXME check instructions timing, at which point is it faster to use a multiplication? (including the register transfers)
int tmpreg;
if (p->z.flags & REG)
tmpreg = p->z.reg;
else
tmpreg = cg_getreg(f, p);
// TODO if p->q1 is REG, we can just push it and reuse the same register
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
while (mult > 16) {
emit(f, "\tLD\t%s, %s LSL 4\n", regnames[tmpreg], regnames[tmpreg]);
mult /= 16;
}
emit(f, "\tLD\t%s, %s LSL 2\n", regnames[tmpreg], regnames[tmpreg]);
if (!(p->z.flags & REG)) {
emit(f, "# MULT64\n");
emit(f, "\tST\t%s, (BP+%d)\n", regnames[tmpreg], localslot(offset, p->z.v->offset));
cg_freereg(f, tmpreg);
}
continue;
}
// FIXME make sure the source or dest isn't already in R3 or R4
emit(f, "\tPUSH R3,R4,\t(SP)\n"); // FIXME check if R3 and R4 are allocated
emit(f, "\tLD\tR3, ");
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\tLD\tR4, ");
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
emit(f, "\tMUL\tR3, R4\n");
if (p->z.flags & REG) {
emit(f, "\tLD\t%s, R4\n", regnames[p->z.reg]);
} else {
break;
}
emit(f, "\tPOP R3,R4,\t(SP)\n"); // FIXME check if R3 and R4 are allocated
continue;
}
// (REG|VAR|SCRATCH * VAR|SCRATCH -> REG|VAR|SCRATCH (Q1 and Z samereg)
if (((p->q1.flags & ~(REG|SCRATCH)) == VAR) && ((p->q2.flags & ~SCRATCH) == VAR))
{
// FIXME make sure the source or dest isn't already in R3 or R4
emit(f, "\tPUSH R3,R4,\t(SP)\n"); // FIXME check if R3 and R4 are allocated
emit(f, "\tLD\tR3, ");
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\tLD\tR4, ");
emitvar(f, p, &p->q2, offset, false, 0);
emit(f, "\n");
emit(f, "\tPOP R3,R4,\t(SP)\n"); // FIXME check if R3 and R4 are allocated
emit(f, "\tMUL\tR3, R4\n");
if (p->z.flags & REG)
emit(f, "\tLD\t%s, R4\n", regnames[p->z.reg]);
else {
emit(f, "\tST\tR4, ");
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
}
continue;
}
emit(f, "# ??MUL %d %d -> %d\n", p->q1.flags, p->q2.flags, p->z.flags);
break;
}
case ADDRESS:
{
// FIXME check if q1 is a local variable, otherwise we need completely different code
int tmpreg;
if (p->z.flags & REG) {
tmpreg = p->z.reg;
emit(f, "\tADD\t%s, BP, %d\n", regnames[tmpreg], localslot(offset, p->q1.v->offset));
} else {
tmpreg = cg_getreg(f, p);
emit(f, "\tADD\t%s, BP, %d\n", regnames[tmpreg], localslot(offset, p->q1.v->offset));
if ((p->z.flags & VAR) && isauto(p->z.v->storage_class)) {
emit(f, "# ADDRESS\n");
emit(f, "\tST\t%s, (BP+%d)\n", regnames[tmpreg], localslot(offset, p->z.v->offset));
} else {
printf("flags: %x class %x\n", p->z.flags, p->z.v->storage_class);
ierror(0);
}
cg_freereg(f, tmpreg);
}
continue;
}
case CALL:
{
if ((p->q1.flags & (VAR|DREFOBJ)) == VAR && p->q1.v->fi && p->q1.v->fi->inline_asm) {
emit_inline_asm(f, p->q1.v->fi->inline_asm);
} else {
if (p->q1.flags & REG) {
emit(f, "\tPUSH\tSR, PC, (SP)\n");
emit(f, "\tLD\tPC, %s\n", regnames[p->q1.reg]);
} else if (p->q1.flags == (VAR|DREFOBJ|SCRATCH)) {
if (isauto(p->q1.v->storage_class)) {
// LD PC, (BP+Imm6) is not allowed, so we need an intermediate register
// FIXME we're not allowed to spill registers to the stack here!
// We have to do it before pushing the arguments. So using cg_getreg isn't too wise.
int tmpreg = cg_getreg(f, p);
emit(f, "\tPUSH\tSR, PC, (SP)\n");
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\tLD\tPC, %s\n", regnames[tmpreg]);
cg_freereg(f, tmpreg);
} else {
emit(f, "\tPUSH\tSR, PC, (SP)\n");
emit(f, "\tLD\tPC, ");
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
}
} else {
emit(f, "\tCALL\t_%s ; Q1: %d\n", p->q1.v->identifier, p->q1.flags);
}
emit(f, "\tADD\tSP, ");
emitval(f, &p->q2.val, INT);
emit(f, "\n");
}
continue;
}
case CONVERT:
{
// FIXME also check if q1 and z are DREFOBJ to use this code, otherwise it's simpler
if ((p->q1.flags & REG) && (p->z.flags & REG)) {
int tmpreg = cg_getreg(f, p);
emit(f, "# CONVERT\n");
emit(f, "\tLD\t%s, (%s)\n", regnames[tmpreg], regnames[p->q1.reg]);
emit(f, "\tST\t%s, (%s)\n", regnames[tmpreg], regnames[p->z.reg]);
cg_freereg(f, tmpreg);
continue;
}
bool allocreg = false;
bool store = true;
if ((p->q1.flags & VAR)) {
int tmpreg;
if ((p->q1.flags & REG) && !(p->q1.flags & DREFOBJ)) {
tmpreg = p->q1.reg;
} else {
if ((p->z.flags & REG) && !(p->z.flags & DREFOBJ)) {
tmpreg = p->z.reg;
store = false;
} else {
tmpreg = cg_getreg(f, p);
allocreg = true;
}
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
if (p->q1.flags & VAR) {
emitvar(f, p, &p->q1, offset, false, 0);
} else if (p->q1.flags & KONST) {
emit(f, "#");
emitval(f, &p->q1.val, q1typ(p));
}
emit(f, "\n");
}
if (store) {
emit(f, "\tST\t%s, ", regnames[tmpreg]);
// TODO use emitoperand?
if (p->z.flags & VAR) {
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
} else if (p->z.flags & (KONST | DREFOBJ)) {
emit(f, "(");
emitval(f, &p->z.val, ztyp(p));
emit(f, ")\n");
}
}
if (allocreg) {
cg_freereg(f, tmpreg);
}
continue;
}
if ((p->q1.flags == KONST|DREFOBJ) && (p->z.flags & ~SCRATCH == REG|VAR)) {
emit(f, "\tLD\t%s, (", regnames[p->z.reg]);
emitval(f, &p->q1.val, ztyp(p));
emit(f, ")\n");
continue;
}
fprintf(stderr, "MISSING IMPL CONVERT %d %d\n", p->q1.flags, p->z.flags);
break;
}
case ALLOCREG:
{
// TODO sometimes vbcc does an allocreg, some operations that don't use the register
// then a freereg (I guess as a consequence of optimizations).
// Since we don't have a lot of registers, it's wise to clean that up...
int reallyUsed = 0;
for (struct IC* p2 = p->next; p2 != NULL; p2 = p2->next) {
if (p2->code == FREEREG) {
if (p2->q1.reg == p->q1.reg)
break;
} else {
if ((p2->z.flags & REG) && (p2->z.reg == p->q1.reg)) {
reallyUsed = 1;
break;
}
if ((p2->q1.flags & REG) && (p2->q1.reg == p->q1.reg)) {
reallyUsed = 1;
break;
}
if ((p2->q2.flags & REG) && (p2->q2.reg == p->q1.reg)) {
reallyUsed = 1;
break;
}
}
}
// remember that the reg is in use
if (reallyUsed)
regs[p->q1.reg] = 1;
else
emit(f, "\t# Fake allocreg %s\n", regnames[p->q1.reg]);
continue;
}
case FREEREG:
{
// remember that the reg is available
regs[p->q1.reg] = 0;
continue;
}
case COMPARE:
{
if (p->q2.flags & KONST) {
if (p->q1.flags & REG) {
emit(f, "\tCMP\t%s, ", regnames[p->q1.reg]);
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
} else if ((p->q1.flags & VAR) && isauto(p->q1.v->storage_class)) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, (BP+%d)\n", regnames[tmpreg], localslot(offset, p->q1.v->offset));
emit(f, "\tCMP\t%s, ", regnames[tmpreg]);
emitval(f, &p->q2.val, q2typ(p));
emit(f, "\n");
cg_freereg(f, tmpreg);
}
continue;
}
if (((p->q1.flags & ~SCRATCH) == VAR) && ((p->q2.flags & ~SCRATCH) == (VAR|REG))) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\tCMP\t%s, %s\n", regnames[tmpreg], regnames[p->q2.reg]);
cg_freereg(f, tmpreg);
continue;
}
if (((p->q1.flags & ~(VARADR|DREFOBJ|SCRATCH)) == VAR) && (p->q2.flags & VAR)) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\tCMP\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q2, offset, false, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
continue;
}
if ((p->q1.flags & REG) && (p->q1.flags & DREFOBJ) && (p->q2.flags & VAR)) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, (%s)\n", regnames[tmpreg], regnames[p->q1.reg]);
emit(f, "\tCMP\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q2, offset, false, 0);
emit(f, "\n");
cg_freereg(f, tmpreg);
continue;
}
if ((p->q1.flags & REG) && (p->q2.flags & REG)) {
emit(f, "\tCMP\t%s, %s\n", regnames[p->q1.reg], regnames[p->q2.reg]);
continue;
}
if (p->q1.flags & REG) {
emit(f, "\tCMP\t%s, ", regnames[p->q1.reg]);
emitvar(f, p, &p->q2, offset, false, 0);
emit(f, "\n");
continue;
}
emit(f, "; CMP %d %d\n", p->q1.flags, p->q2.flags);
break;
}
case TEST:
{
// TODO check if a previous IC already set the flags for the same register
// (for example we just loaded it with LD). In that case, emit no instruction.
if (p->q1.flags & REG) {
if (p->q1.flags & DREFOBJ) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, (%s)\n", regnames[tmpreg], regnames[p->q1.reg]);
// LD already sets the Z flag, no need to do an explicit compare here
//emit(f, "\tCMP\t%s, 0\n", regnames[tmpreg]);
cg_freereg(f, tmpreg);
} else {
emit(f, "\tCMP\t%s, 0\n", regnames[p->q1.reg]);
}
continue;
}
if (p->q1.flags & VAR) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, true, 0);
emit(f, "\n");
// LD already sets the Z flag, no need to do an explicit compare here
//emit(f, "\tCMP\t%s, 0\n", regnames[tmpreg]);
cg_freereg(f, tmpreg);
continue;
}
break;
}
case LABEL:
{
emit(f, "f%d:\n", p->typf);
continue;
}
case BEQ:
{
if (p->q1.flags == 0) {
emit(f, "\tLJE\tf%d\n", iclabel(p));
continue;
}
break;
}
case BNE:
{
if (p->q1.flags == 0) {
emit(f, "\tLJNE\tf%d\n", iclabel(p));
continue;
}
break;
}
case BLT:
{
if (p->q1.flags == 0) {
emit(f, "\tLJB\tf%d\n", iclabel(p));
continue;
}
break;
}
case BGE:
{
if (p->q1.flags == 0) {
emit(f, "\tLJGE\tf%d\n", iclabel(p));
continue;
}
break;
}
case BLE:
{
if (p->q1.flags == 0) {
emit(f, "\tJLE\tf%d\n", iclabel(p));
continue;
}
break;
}
case BGT:
{
if (p->q1.flags == 0) {
emit(f, "\tLJG\tf%d\n", iclabel(p));
continue;
}
break;
}
case BRA:
{
// TODO decide if the target is close enough that we can use JMP (but really that's
// a job for the assembler)
emit(f, "\tGOTO\tf%d\n", iclabel(p));
continue;
}
case PUSH:
{
if ((p->q1.flags & REG)) {
emit(f, "\tPUSH\t%s, %s, (SP)\n", regnames[p->q1.reg], regnames[p->q1.reg]);
continue;
}
if ((p->q1.flags & VAR)) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, ", regnames[tmpreg]);
emitvar(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
emit(f, "\tPUSH\t%s, %s, (SP)\n", regnames[tmpreg], regnames[tmpreg]);
cg_freereg(f, tmpreg);
continue;
}
if ((p->q1.flags & KONST)) {
int tmpreg = cg_getreg(f, p);
emit(f, "\tLD\t%s, %d\n", regnames[tmpreg], p->q1.val.vint);
emit(f, "\tPUSH\t%s, %s, (SP)\n", regnames[tmpreg], regnames[tmpreg]);
cg_freereg(f, tmpreg);
continue;
}
break;
}
case ADDI2P:
{
if (cg_ptrop("ADD", f, p, offset))
continue;
break;
}
case SUBIFP:
{
if (cg_ptrop("SUB", f, p, offset))
continue;
break;
}
case SETRETURN:
{
if (!(p->q1.flags & REG)) {
emit(f, "\tLD\tR1, ");
emitoperand(f, p, &p->q1, offset, false, 0);
emit(f, "\n");
continue;
}
if (p->q1.reg != 2) {
emit(f, "\tLD\tR1, %s\n", regnames[p->q1.reg]);
continue;
}
// Result is already in R1
continue;
}
case GETRETURN:
{
if (p->z.flags & REG) {
if (p->z.reg != 2) {
// Transfer return code from R1 into another register
emit(f, "\tLD\t%s, R1\n", regnames[p->z.reg]);
} else {
// Return code is already in R1, nothing to do
}
continue;
} else {
// Transfer return code into memory
if (p->z.flags & VAR) {
emit(f, "; GETRETURN\n");
emit(f, "\tST\t%s, ", regnames[p->q1.reg]);
emitvar(f, p, &p->z, offset, true, 0);
emit(f, "\n");
continue;
}
}
break;
}
case NOP:
{
continue;
}
}
emit(f, "*** UNHANDLED IC: %d\n", p->code);
if (f)
printic(f, p);
}
emit(f, "\n");
if (v->tattr & INTERRUPT) {
if (offset > 0) {
emit(f, "\tADD SP, %d\n", offset); // Free stack space used by local variables
}
emit(f, "\tPOP R1, BP, (SP)\n");
emit(f, "\tRETI\n"); // Exit function
} else {
if (offset > 0) {
emit(f, "\tADD SP, %d\n", offset); // Free stack space used by local variables
}
emit(f, "\tPOP BP, BP, (SP)\n"); // Restore caller BP
emit(f, "\tRETF\n\n"); // Exit function
}
}