mirror of
https://github.com/nArnoSNES/tcc-65816.git
synced 2024-10-08 07:54:59 +00:00
1297 lines
41 KiB
C
1297 lines
41 KiB
C
/*
|
|
* WDC 65816 code generator for TCC
|
|
*
|
|
* Copyright (c) 2007 Ulrich Hecht
|
|
*
|
|
* Based on arm-gen.c by Daniel Glöckner
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#define LDOUBLE_SIZE 12 // not actually supported
|
|
#define LDOUBLE_ALIGN 4
|
|
#define MAX_ALIGN 8
|
|
|
|
#define NB_REGS 15
|
|
|
|
#define RC_INT 0x0001
|
|
#define RC_FLOAT 0x0002
|
|
#define RC_R0 0x0004
|
|
#define RC_R1 0x0008
|
|
#define RC_R2 0x0010
|
|
#define RC_R3 0x0020
|
|
#define RC_R4 0x0040
|
|
#define RC_R5 0x0080
|
|
#define RC_R9 0x0100
|
|
#define RC_R10 0x0200
|
|
#define RC_F0 0x0400
|
|
#define RC_F1 0x0800
|
|
#define RC_F2 0x1000
|
|
#define RC_F3 0x2000
|
|
#define RC_NONE 0x8000
|
|
|
|
#define RC_IRET RC_R0
|
|
#define RC_LRET RC_R1
|
|
#define RC_FRET RC_F0
|
|
|
|
enum {
|
|
TREG_R0 = 0,
|
|
TREG_R1,
|
|
TREG_R2,
|
|
TREG_R3,
|
|
TREG_R4,
|
|
TREG_R5,
|
|
TREG_R9 = 9,
|
|
TREG_R10,
|
|
TREG_F0,
|
|
TREG_F1,
|
|
TREG_F2,
|
|
TREG_F3,
|
|
};
|
|
|
|
int reg_classes[NB_REGS] = {
|
|
RC_INT | RC_R0,
|
|
RC_INT | RC_R1,
|
|
RC_INT | RC_R2,
|
|
RC_INT | RC_R3,
|
|
RC_INT | RC_R4,
|
|
RC_INT | RC_R5,
|
|
RC_NONE,
|
|
RC_NONE,
|
|
RC_NONE,
|
|
RC_R9,
|
|
RC_R10,
|
|
RC_FLOAT | RC_F0,
|
|
RC_FLOAT | RC_F1,
|
|
RC_FLOAT | RC_F2,
|
|
RC_FLOAT | RC_F3,
|
|
};
|
|
|
|
#define REG_IRET TREG_R0
|
|
#define REG_LRET TREG_R1
|
|
#define REG_FRET TREG_F0
|
|
|
|
#define R_DATA_32 1 // whatever
|
|
#define R_JMP_SLOT 2 // whatever
|
|
#define R_COPY 3 // whatever
|
|
|
|
#define ELF_PAGE_SIZE 0x1000 // whatever
|
|
#define ELF_START_ADDR 0x400 // made up
|
|
|
|
#define PTR_SIZE 4
|
|
|
|
#define EM_TCC_TARGET EM_W65
|
|
|
|
#define LOCAL_LABEL "__local_%d"
|
|
|
|
char current_fn[256] = "";
|
|
|
|
/* yet another terrible workaround
|
|
WLA does not have file-local symbols, only section-local and global.
|
|
thus, everything that is file-local must be made global and given a
|
|
unique name. not knowing how to choose one deterministically (filename?
|
|
could be more than one file with the same name...), I chose to use a
|
|
random number, saved to static_prefix. */
|
|
char* static_prefix = "__tccs_";
|
|
|
|
char* label_workaround = NULL;
|
|
struct labels_816 {
|
|
char* name;
|
|
int pos;
|
|
};
|
|
struct labels_816 label[1000];
|
|
int labels = 0;
|
|
|
|
char* get_sym_str(Sym* sym)
|
|
{
|
|
static char name[256];
|
|
char* symname;
|
|
|
|
symname = get_tok_str(sym->v, NULL);
|
|
|
|
name[0] = 0;
|
|
/* if static, add prefix */
|
|
if(sym->type.t & VT_STATIC) {
|
|
//fprintf(stderr,"sym %s type 0x%x current_fn %s token %d\n",symname,sym->type.t,current_fn,sym->v);
|
|
if((sym->type.t & VT_STATICLOCAL) && current_fn[0] != 0 && !((sym->type.t & VT_BTYPE) == VT_FUNC))
|
|
sprintf(name, "%s_FUNC_%s_", static_prefix, current_fn);
|
|
else
|
|
strcpy(name, static_prefix);
|
|
}
|
|
|
|
/* add symbol name */
|
|
strcat(name, symname);
|
|
|
|
//fprintf(stderr,"symbol %s type 0x%x\n", name, sym->type.t);
|
|
return name;
|
|
}
|
|
|
|
/* XXX: make it faster ? */
|
|
void g(int c)
|
|
{
|
|
int ind1;
|
|
ind1 = ind + 1;
|
|
if (ind1 > cur_text_section->data_allocated)
|
|
section_realloc(cur_text_section, ind1);
|
|
cur_text_section->data[ind] = c;
|
|
ind = ind1;
|
|
}
|
|
|
|
void s(char* str)
|
|
{
|
|
for(;*str;str++) g(*str);
|
|
}
|
|
|
|
char line[256];
|
|
#define pr(x...) do { sprintf(line, x); s(line); } while(0)
|
|
|
|
int jump[1000][2];
|
|
int jumps = 0;
|
|
|
|
|
|
void gsym_addr(int t, int a)
|
|
{
|
|
/* code at t wants to jump to a */
|
|
//fprintf(stderr, "gsymming t 0x%x a 0x%x\n", t, a);
|
|
pr("; gsym_addr t %d a %d ind %d\n",t,a,ind);
|
|
/* the label generation code sets this for us so we know when a symbol
|
|
is a label and what its name is, so that we can remember its name
|
|
and position so the output code can insert it correctly */
|
|
if(label_workaround) {
|
|
//fprintf("setting label %s to a %d (t %d)\n", label_workaround, a, t);
|
|
label[labels].name = label_workaround;
|
|
label[labels].pos = a;
|
|
labels++;
|
|
label_workaround = NULL;
|
|
}
|
|
int i;
|
|
// pair up the jump with the target address
|
|
// the tcc_output_... function will add a
|
|
// label __local_<i> at a when writing the output
|
|
int found = 0;
|
|
for(i = 0; i < jumps; i++) {
|
|
if(jump[i][0] == t) jump[i][1] = a;
|
|
found = 1;
|
|
}
|
|
if(!found) pr("; ERROR no jump found to patch\n");
|
|
}
|
|
|
|
void gsym(int t)
|
|
{
|
|
gsym_addr(t,ind);
|
|
}
|
|
|
|
int stack_back = 0;
|
|
int adjust_stack(int fc, int disp)
|
|
{
|
|
pr("; stack adjust: fc + disp - loc %d\n", fc + disp - loc);
|
|
if(fc + disp - loc < 256) { stack_back = 0; return fc; }
|
|
stack_back = -loc + fc;
|
|
pr("tsc\nclc\nadc.w #%d\ntcs\n", stack_back);
|
|
return fc - stack_back;
|
|
}
|
|
|
|
int restore_stack(int fc)
|
|
{
|
|
if(!stack_back) return fc;
|
|
pr("tsc\nsec\nsbc.w #%d\ntcs\n", stack_back);
|
|
return fc + stack_back;
|
|
}
|
|
|
|
// this used to be local to gfunc_call, but we need it to get the correct
|
|
// stack pointer displacement while building the argument stack for
|
|
// a function call
|
|
int args_size = 0;
|
|
|
|
int ll_workaround = 0;
|
|
|
|
void load(int r, SValue* sv)
|
|
{
|
|
int fr,ft,fc;
|
|
int length;
|
|
int align;
|
|
int v,sign,t;
|
|
SValue v1;
|
|
pr("; load %d\n",r);
|
|
pr("; type %d reg 0x%x extra 0x%x\n",sv->type.t,sv->r,sv->type.extra);
|
|
fr = sv->r;
|
|
ft = sv->type.t;
|
|
fc = sv->c.ul;
|
|
|
|
length = type_size(&sv->type, &align);
|
|
if((ft & VT_BTYPE) == VT_LLONG) length = 2; // long longs are handled word-wise
|
|
if(ll_workaround) length = 4;
|
|
|
|
//pr("; load r 0x%x fr 0x%x ft 0x%x fc 0x%x\n",r,fr,ft,fc);
|
|
|
|
int base = -1;
|
|
v = fr & VT_VALMASK;
|
|
if(fr & VT_LVAL) {
|
|
if(v == VT_LLOCAL) {
|
|
v1.type.t = VT_PTR;
|
|
v1.r = VT_LOCAL | VT_LVAL;
|
|
v1.c.ul = sv->c.ul;
|
|
load(base=10 /* lr */, &v1);
|
|
fc=sign=0;
|
|
v=VT_LOCAL;
|
|
}
|
|
else if(v == VT_CONST) {
|
|
if(fr & VT_SYM) { // deref symbol + displacement
|
|
char* sy = get_sym_str(sv->sym);
|
|
if(is_float(ft)) {
|
|
pr("; fld%d [%s + %d], tcc__f%d\n", length, sy, fc, r - TREG_F0);
|
|
switch(length) {
|
|
case 4: pr("lda.l %s + %d\nsta.b tcc__f%d\nlda.l %s + %d + 2\nsta.b tcc__f%dh\n", sy, fc, r - TREG_F0, sy, fc, r - TREG_F0); break;
|
|
default: error("ICE 1");
|
|
}
|
|
}
|
|
else {
|
|
pr("; ld%d [%s + %d], tcc__r%d\n", length, sy, fc, r);
|
|
// FIXME: This implementation is moronic
|
|
if(fc > 65535) error("index too big");
|
|
switch(length) {
|
|
case 1:
|
|
pr("lda.w #0\nsep #$20\nlda.l %s + %d\nrep #$20\n", sy, fc);
|
|
if(!(ft & VT_UNSIGNED)) pr("xba\nxba\nbpl +\nora.w #$ff00\n+\n");
|
|
pr("sta.b tcc__r%d\n", r);
|
|
break;
|
|
case 2: pr("lda.l %s + %d\nsta.b tcc__r%d\n", sy, fc, r); break;
|
|
case 4: pr("lda.l %s + %d\nsta.b tcc__r%d\nlda.l %s + %d + 2\nsta.b tcc__r%dh\n", sy, fc, r, sy, fc, r); break;
|
|
default: error("ICE 1");
|
|
}
|
|
}
|
|
}
|
|
else { // deref constant pointer
|
|
//error("ld [%d],tcc__r%d\n",fc,r);
|
|
pr("; deref constant ptr ld [%d],tcc__r%d\n", fc, r);
|
|
if(is_float(ft)) {
|
|
error("dereferencing constant float pointers unimplemented\n");
|
|
}
|
|
else {
|
|
switch(length) {
|
|
case 1:
|
|
pr("lda.w #0\nsep #$20\nlda.l %d\nrep #$20\n", fc);
|
|
if(!(ft & VT_UNSIGNED)) pr("xba\nxba\nbpl +\nora.w #$ff00\n+\n");
|
|
pr("sta.b tcc__r%d\n", r);
|
|
break;
|
|
case 2: pr("lda.l %d\nsta.b tcc__r%d\n", fc, r); break;
|
|
case 4: pr("lda.l %d\nsta.b tcc__r%d\nlda.l %d + 2\nsta.b tcc__r%dh\n", fc, r, fc, r); break;
|
|
default: error("ICE 1");
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if(v < VT_CONST) { // deref pointer in register
|
|
base = v;
|
|
fc = sign = 0;
|
|
v = VT_LOCAL;
|
|
}
|
|
|
|
if(v == VT_LOCAL) {
|
|
if(is_float(ft)) {
|
|
if(base == -1) {
|
|
pr("; fld%d [sp,%d],tcc__f%d\n", length, fc, r - TREG_F0);
|
|
if(length != 4) error("ICE 2f");
|
|
fc = adjust_stack(fc, args_size + 2);
|
|
pr("lda %d + __%s_locals + 1,s\nsta.b tcc__f%d\nlda %d + __%s_locals + 1,s\nsta.b tcc__f%dh\n", fc+args_size, current_fn, r - TREG_F0, fc+args_size+2, current_fn, r - TREG_F0);
|
|
fc = restore_stack(fc);
|
|
}
|
|
else {
|
|
pr("; fld%d [tcc__r%d,%d],tcc__f%d\n", length, base, fc, r - TREG_F0);
|
|
if(length != 4) error("ICE 3f");
|
|
pr("ldy #%d\nlda.b [tcc__r%d],y\nsta.b tcc__f%d\niny\niny\nlda.b [tcc__r%d], y\nsta.b tcc__f%dh\n", fc, base, r - TREG_F0, base, r - TREG_F0);
|
|
}
|
|
}
|
|
else {
|
|
if(base == -1) { // value of local at fc
|
|
pr("; ld%d [sp,%d],tcc__r%d\n",length,fc,r);
|
|
fc = adjust_stack(fc, args_size + 2);
|
|
switch(length) {
|
|
case 1:
|
|
pr("lda.w #0\nsep #$20\nlda %d + __%s_locals + 1,s\nrep #$20\n", fc+args_size, current_fn);
|
|
if(!(ft & VT_UNSIGNED)) pr("xba\nxba\nbpl +\nora.w #$ff00\n+\n");
|
|
pr("sta.b tcc__r%d\n", r);
|
|
break;
|
|
case 2: pr("lda %d + __%s_locals + 1,s\nsta.b tcc__r%d\n", fc+args_size, current_fn, r); break;
|
|
case 4: pr("lda %d + __%s_locals + 1,s\nsta.b tcc__r%d\nlda %d + __%s_locals + 1,s\nsta.b tcc__r%dh\n", fc+args_size, current_fn, r, fc+args_size + 2, current_fn, r); break;
|
|
default: error("ICE 2"); break;
|
|
}
|
|
fc = restore_stack(fc);
|
|
}
|
|
else { // value of array member r[fc]
|
|
pr("; ld%d [tcc__r%d,%d],tcc__r%d\n",length, base, fc, r);
|
|
switch(length) {
|
|
case 1:
|
|
pr("lda.w #0\n");
|
|
if(!fc) pr("sep #$20\nlda.b [tcc__r%d]\nrep #$20\n", base);
|
|
else pr("ldy #%d\nsep #$20\nlda.b [tcc__r%d],y\nrep #$20\n", fc, base);
|
|
if(!(ft & VT_UNSIGNED)) pr("xba\nxba\nbpl +\nora.w #$ff00\n+\n");
|
|
pr("sta.b tcc__r%d\n", r);
|
|
break;
|
|
case 2:
|
|
if(!fc) pr("lda.b [tcc__r%d]\nsta.b tcc__r%d\n", base, r);
|
|
else pr("ldy #%d\nlda.b [tcc__r%d],y\nsta.b tcc__r%d\n", fc, base, r);
|
|
break;
|
|
case 4: pr("ldy #%d\nlda.b [tcc__r%d],y\nsta.b tcc__r%d\niny\niny\nlda.b [tcc__r%d],y\nsta.b tcc__r%dh\n", fc, base, r, base, r); break;
|
|
default: error("ICE 3"); break;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
} else { // VT_LVAL
|
|
if(v == VT_CONST) {
|
|
if(fr & VT_SYM) { // symbolic constant
|
|
char* sy = get_sym_str(sv->sym);
|
|
pr("; ld%d #%s + %d, tcc__r%d (type 0x%x)\n", length,sy, fc, r, ft);
|
|
if(length != PTR_SIZE) pr("; FISHY! length <> PTR_SIZE! (may be an array)\n");
|
|
pr("lda.w #:%s\nsta.b tcc__r%dh\nlda.w #%s + %d\nsta.b tcc__r%d\n", sy, r, sy, fc, r);
|
|
}
|
|
else { // numeric constant
|
|
pr("; ld%d #%d,tcc__r%d\n",length,sv->c.ul,r);
|
|
if((ft & VT_BTYPE) == VT_BOOL) {
|
|
sv->c.ul = sv->c.ul? 1: 0;
|
|
}
|
|
switch(length) {
|
|
case 1:
|
|
if (ft & VT_UNSIGNED) {
|
|
pr("lda.w #%d\n", sv->c.ul & 0xff);
|
|
}
|
|
else {
|
|
pr("lda.w #%d\n", ((short)((sv->c.ul & 0xff) << 8)) >> 8);
|
|
}
|
|
pr("sta.b tcc__r%d\n", r);
|
|
break;
|
|
case 2: pr("lda.w #%d\nsta.b tcc__r%d\n", sv->c.ul & 0xffff, r); break;
|
|
case 4: pr("lda.w #%d\nsta.b tcc__r%d\nlda.w #%d\nsta.b tcc__r%dh\n", sv->c.ul & 0xffff, r, sv->c.ul >> 16, r); break;
|
|
default: error("ICE 4");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
else if(v == VT_LOCAL) {
|
|
if(fr & VT_SYM) {
|
|
error("symbol");
|
|
char* sy = get_sym_str(sv->sym);
|
|
pr("; LOCAL ld%d #%s, tcc__r%d (type 0x%x)\n", length,sy, r, ft);
|
|
}
|
|
else { // local pointer
|
|
pr("; ld%d #(sp) + %d,tcc__r%d (fr 0x%x ft 0x%x fc 0x%x)\n",length,sv->c.ul,r,fr,ft,fc);
|
|
// pointer; have to ensure the upper word is correct (page 0)
|
|
pr("stz.b tcc__r%dh\ntsa\nclc\nadc #(%d + __%s_locals + 1)\nsta.b tcc__r%d\n", r, sv->c.ul + args_size, current_fn, r);
|
|
}
|
|
return;
|
|
}
|
|
else if(v == VT_CMP) {
|
|
error("cmp");
|
|
return;
|
|
}
|
|
else if(v == VT_JMP || v == VT_JMPI) {
|
|
t = v & 1; // inverted or not
|
|
pr("; jmpr(i) v 0x%x r 0x%x fc 0x%x\n",v,r,fc);
|
|
pr("lda #%d\nbra +\n", t);
|
|
gsym(fc);
|
|
pr("lda #%d\n+\nsta.b tcc__r%d\n", t^1, r); // stz rXh seems to be unnecessary (we only look at the lower word)
|
|
return;
|
|
}
|
|
else if(v < VT_CONST) { // register value
|
|
if(is_float(ft)) {
|
|
v -= TREG_F0;
|
|
r -= TREG_F0;
|
|
pr("; fmov tcc__f%d, tcc__f%d\n", v, r);
|
|
pr("lda.b tcc__f%d\nsta.b tcc__f%d\nlda.b tcc__f%dh\nsta.b tcc__f%dh\n", v, r, v, r);
|
|
}
|
|
else {
|
|
pr("; mov tcc__r%d, tcc__r%d\n",v,r);
|
|
pr("lda.b tcc__r%d\nsta.b tcc__r%d\nlda.b tcc__r%dh\nsta.b tcc__r%dh\n", v, r, v, r);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
error("load unimplemented");
|
|
}
|
|
|
|
void store(int r, SValue* sv)
|
|
{
|
|
int v, ft, fc, fr, sign;
|
|
int base;
|
|
int length, align;
|
|
SValue v1;
|
|
|
|
fr = sv->r;
|
|
ft = sv->type.t;
|
|
fc = sv->c.i;
|
|
|
|
length = type_size(&sv->type, &align);
|
|
if((ft & VT_BTYPE) == VT_LLONG) length = 2; // long longs are handled word-wise
|
|
|
|
pr("; store r 0x%x fr 0x%x ft 0x%x fc 0x%x\n",r,fr,ft,fc);
|
|
|
|
v = fr & VT_VALMASK;
|
|
base = -1;
|
|
if ((fr & VT_LVAL) || fr == VT_LOCAL) {
|
|
if(v < VT_CONST) { // deref register
|
|
base = v;
|
|
v=VT_LOCAL;
|
|
fc=sign=0; // no clue...
|
|
}
|
|
if(v == VT_CONST) {
|
|
if(fr & VT_SYM) { // deref symbol
|
|
char* sy = get_sym_str(sv->sym);
|
|
if(r >= TREG_F0) pr("; fst%d tcc__f%d, [%s,%d]\n", length, r - TREG_F0, sy, fc);
|
|
else pr("; st%d tcc__r%d, [%s,%d]\n", length, r, sy, fc);
|
|
if(r >= TREG_F0 && length != 4) error("illegal float store of length %d", length);
|
|
switch(length) {
|
|
case 1: pr("sep #$20\nlda.b tcc__r%d\nsta.l %s + %d\nrep #$20\n", r, sy, fc); break;
|
|
case 2: pr("lda.b tcc__r%d\nsta.l %s + %d\n", r, sy, fc); break;
|
|
case 4:
|
|
if(r >= TREG_F0)
|
|
pr("lda.b tcc__f%d\nsta.l %s + %d\nlda.b tcc__f%dh\nsta.l %s + %d + 2\n", r - TREG_F0, sy, fc, r - TREG_F0, sy, fc);
|
|
else
|
|
pr("lda.b tcc__r%d\nsta.l %s + %d\nlda.b tcc__r%dh\nsta.l %s + %d + 2\n", r, sy, fc, r, sy, fc);
|
|
break;
|
|
default: error("ICE 5"); break;
|
|
}
|
|
return;
|
|
}
|
|
else {
|
|
v1.type.t = VT_PTR; //ft;
|
|
v1.r = fr & ~VT_LVAL;
|
|
v1.c.ul = sv->c.ul;
|
|
v1.sym = sv->sym;
|
|
load(base=9,&v1);
|
|
fc = sign = 0;
|
|
v = VT_LOCAL;
|
|
}
|
|
}
|
|
if(v == VT_LOCAL) {
|
|
if(r >= TREG_F0) {
|
|
if(base < 0) {
|
|
pr("; fst%d tcc__f%d, [sp,%d]\n", length, r - TREG_F0, fc);
|
|
fc = adjust_stack(fc, args_size + 2);
|
|
switch(length) {
|
|
case 4: pr("lda.b tcc__f%d\nsta %d + __%s_locals + 1,s\nlda.b tcc__f%dh\nsta %d + __%s_locals + 1,s\n", r - TREG_F0, fc+args_size, current_fn, r - TREG_F0, fc+args_size + 2, current_fn); break;
|
|
default: error("ICE 6f"); break;
|
|
}
|
|
fc = restore_stack(fc);
|
|
}
|
|
else {
|
|
pr("; fst%d tcc__f%d, [tcc__r%d,%d]\n", length, r - TREG_F0, base, fc);
|
|
switch(length) {
|
|
case 4: pr("ldy.w #0\nlda.b tcc__f%d\nsta.b [tcc__r%d],y\niny\niny\nlda.b tcc__f%dh\nsta.b [tcc__r%d],y\n", r - TREG_F0, base, r - TREG_F0, base); break;
|
|
default: error("ICE 7f"); break;
|
|
}
|
|
}
|
|
return;
|
|
} else {
|
|
if(base < 0) { // write to local at fc
|
|
pr("; st%d tcc__r%d, [sp,%d]\n",length,r,fc);
|
|
fc = adjust_stack(fc, args_size + 2);
|
|
switch(length) {
|
|
case 1: pr("sep #$20\nlda.b tcc__r%d\nsta %d + __%s_locals + 1,s\nrep #$20\n", r, fc+args_size, current_fn); break;
|
|
case 2: pr("lda.b tcc__r%d\nsta %d + __%s_locals + 1,s\n", r, fc+args_size, current_fn); break;
|
|
case 4: pr("lda.b tcc__r%d\nsta %d + __%s_locals + 1,s\nlda.b tcc__r%dh\nsta %d + __%s_locals + 1,s\n", r, fc+args_size, current_fn, r, fc+args_size + 2, current_fn); break;
|
|
default: error("ICE 6"); break;
|
|
}
|
|
fc = restore_stack(fc);
|
|
}
|
|
else { // write to array member r[fc]
|
|
pr("; st%d tcc__r%d, [tcc__r%d,%d]\n",length,r,base,fc);
|
|
switch(length) {
|
|
case 1:
|
|
pr("sep #$20\nlda.b tcc__r%d\n", r);
|
|
if(!fc) pr("sta.b [tcc__r%d]\nrep #$20\n", base);
|
|
else pr("ldy #%d\nsta.b [tcc__r%d],y\nrep #$20\n", fc, base);
|
|
break;
|
|
case 2:
|
|
pr("lda.b tcc__r%d\n", r);
|
|
if(!fc) pr("sta.b [tcc__r%d]\n", base);
|
|
else pr("ldy #%d\nsta.b [tcc__r%d],y\n", fc, base);
|
|
break;
|
|
case 4: pr("lda.b tcc__r%d\nldy #%d\nsta.b [tcc__r%d],y\nlda.b tcc__r%dh\niny\niny\nsta.b [tcc__r%d],y\n", r, fc, base, r, base); break;
|
|
default: error("ICE 7"); break;
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
error("store unimplemented");
|
|
|
|
}
|
|
|
|
void gfunc_call(int nb_args)
|
|
{
|
|
int align, r, i, func_call;
|
|
Sym *func_sym;
|
|
|
|
int length;
|
|
|
|
/* args_size is the size of the function call arguments already
|
|
pushed on the stack. needed so that loads and stores to
|
|
locals on the stack still work while building an argument
|
|
list. needs to be restored before returning to make
|
|
nested function calls work (passing structs by value causes
|
|
a memcpy call) */
|
|
int restore_args_size = args_size;
|
|
|
|
for(i = 0;i < nb_args; i++) {
|
|
|
|
length = type_size(&vtop->type, &align);
|
|
if(vtop->type.t & VT_ARRAY) length = PTR_SIZE;
|
|
|
|
if ((vtop->type.t & VT_BTYPE) == VT_STRUCT) {
|
|
/* allocate the necessary size on stack */
|
|
pr("; sub sp, #%d\n",length);
|
|
pr("tsa\nsec\nsbc #%d\ntas\n", length);
|
|
args_size += length;
|
|
|
|
/* generate structure store */
|
|
r = get_reg(RC_INT);
|
|
/* put the pointer to struct store on the stack
|
|
always remember: the 65xxs are off-by-one processors
|
|
there is always something to increment or decrement to
|
|
get the value you actually want. (cf. mvn/mvp) */
|
|
pr("stz.b tcc__r%dh\ntsa\nina\nsta.b tcc__r%d\n",r,r);
|
|
|
|
/* here, TCC generates a memcpy call
|
|
this recursion makes the restore_args_size stuff necessary */
|
|
vset(&vtop->type, r | VT_LVAL, 0);
|
|
vswap();
|
|
vstore();
|
|
} else if (is_float(vtop->type.t)) {
|
|
if(length != 4) error("unknown float size %d\n", length);
|
|
r = gv(RC_FLOAT);
|
|
pr("; fldpush%d (type 0x%x reg 0x%x) tcc__f%d\n", length, vtop->type.t, vtop->r, r - TREG_F0);
|
|
pr("pei (tcc__f%dh)\npei (tcc__f%d)\n", r - TREG_F0, r - TREG_F0);
|
|
args_size += length;
|
|
} else {
|
|
/* simple type (currently always same size) */
|
|
/* XXX: implicit cast ? */
|
|
if(((vtop->r & VT_VALMASK) == VT_CONST) && ((vtop->r & VT_LVAL) == 0)) {
|
|
// push immediate
|
|
pr("; push%d imm r 0x%x\n",length,vtop->r);
|
|
if(vtop->r & VT_SYM) {
|
|
char* sy = get_sym_str(vtop->sym);
|
|
if(length != PTR_SIZE) pr("; FISHY! length <> PTR_SIZE! (may be an array)\n");
|
|
pr("pea.w :%s\npea.w %s %c %d\n", sy, sy, vtop->c.i < 0 ? '-' : '+', abs(vtop->c.i));
|
|
length = PTR_SIZE;
|
|
}
|
|
else {
|
|
switch(length) {
|
|
case 1: pr("sep #$20\nlda #%d\npha\nrep #$20\n", vtop->c.ul & 0xff); break;
|
|
case 2: pr("pea.w %d\n", vtop->c.ul & 0xffff); break;
|
|
case 4:
|
|
pr("pea.w %d\npea.w %d\n", vtop->c.ul >> 16, vtop->c.ul & 0xffff);
|
|
break;
|
|
default: error("cannot push %d-byte immediate", length); break;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// load to register, then push
|
|
pr("; ldpush before load type 0x%x reg 0x%x\n", vtop->type.t, vtop->r);
|
|
r = gv(RC_INT);
|
|
pr("; ldpush%d (type 0x%x reg 0x%x) tcc__r%d\n", length, vtop->type.t, vtop->r, r);
|
|
switch(length) {
|
|
case 1: pr("sep #$20\nlda.b tcc__r%d\npha\nrep #$20\n", r); break;
|
|
//case 2: pr("lda.b tcc__r%d\npha\n", r); break;
|
|
case 2: pr("pei (tcc__r%d)\n", r); break;
|
|
case 4:
|
|
if((vtop->type.t & VT_BTYPE) == VT_LLONG) {
|
|
//pr("lda.b tcc__r%d\npha\nlda.b tcc__r%d\npha\n", vtop->r2, r);
|
|
pr("pei (tcc__r%d)\npei (tcc__r%d)\n", vtop->r2, r);
|
|
}
|
|
//else pr("lda.b tcc__r%dh\npha\nlda.b tcc__r%d\npha\n", r, r);
|
|
else pr("pei (tcc__r%dh)\npei (tcc__r%d)\n", r, r);
|
|
break;
|
|
default: error("cannot push %d-byte from register", length); break;
|
|
}
|
|
}
|
|
args_size += length;
|
|
}
|
|
vtop--;
|
|
}
|
|
save_regs(0); /* save used temporary registers */
|
|
func_sym = vtop->type.ref;
|
|
func_call = func_sym->r;
|
|
|
|
/* fast call case */
|
|
if (func_call >= FUNC_FASTCALL1 && func_call <= FUNC_FASTCALL3) {
|
|
error("fastcall");
|
|
}
|
|
|
|
pr("; call r 0x%x\n",vtop->r);
|
|
if(vtop->r & VT_LVAL) {
|
|
// call a function pointer
|
|
if((vtop->r & VT_VALMASK) == VT_LLOCAL) {
|
|
SValue v1;
|
|
v1.type.t = VT_PTR;
|
|
v1.r = VT_LOCAL | VT_LVAL;
|
|
v1.c.ul = vtop->c.ul;
|
|
load(9, &v1);
|
|
// the 65816 is two stoopid to do a jsl [r10], so we have to jump thru a hoop here
|
|
pr("; eins\njsr.l tcc__jsl_ind_r9\n");
|
|
}
|
|
else { // call a symbolic function pointer
|
|
pr("; symfpcall vtop->sym %p vtop->r 0x%x vtop->type.t 0x%x c 0x%x\n", vtop->sym, vtop->r, vtop->type.t, vtop->c.ui);
|
|
gv(RC_R10);
|
|
pr("; zwei\njsr.l tcc__jsl_r10\n");
|
|
}
|
|
}
|
|
else
|
|
pr("jsr.l %s\n", get_sym_str(vtop->sym));
|
|
|
|
if (args_size - restore_args_size && func_sym->r != FUNC_STDCALL) {
|
|
pr("; add sp, #%d\n",args_size - restore_args_size);
|
|
// pull the arguments off the stack
|
|
if(args_size - restore_args_size == 2) pr("pla\n");
|
|
else pr("tsa\nclc\nadc #%d\ntas\n",args_size - restore_args_size);
|
|
}
|
|
args_size = restore_args_size;
|
|
vtop--;
|
|
}
|
|
|
|
int gjmp(int t)
|
|
{
|
|
int r;
|
|
int i;
|
|
// remember this jump so we can insert a label before the destination later
|
|
pr("; gjmp_addr %d at %d\n",t,ind);
|
|
pr("jmp.w " LOCAL_LABEL "\n",jumps);
|
|
r = ind;
|
|
jump[jumps][0] = r;
|
|
for(i = 0; i < jumps; i++) {
|
|
if(jump[i][0] == t) { // the jump target is a jump itself; make it go to same place as this one
|
|
jump[i][0] = r;
|
|
}
|
|
}
|
|
jumps++;
|
|
gsym_addr(r,t);
|
|
return r;
|
|
}
|
|
|
|
void gjmp_addr(int a)
|
|
{
|
|
gjmp(a);
|
|
}
|
|
|
|
int gtst(int inv, int t)
|
|
{
|
|
int v,r;
|
|
v = vtop->r & VT_VALMASK;
|
|
r = ind;
|
|
pr("; gtst inv %d t %d v %d r %d ind %d\n",inv,t,v,r,ind);
|
|
if(v == VT_CMP) {
|
|
pr("; cmp op 0x%x inv %d v %d r %d\n",vtop->c.i,inv,v,r);
|
|
//gsym(t);
|
|
switch(vtop->c.i) {
|
|
case TOK_NE:
|
|
// remember that we need a label to jump to
|
|
jump[jumps][0] = r;
|
|
pr("; cmp ne\n");
|
|
// branches (too short) pr("b%s " LOCAL_LABEL "\n", inv?"eq":"ne", jumps++);
|
|
pr("b%s +\n", inv?"ne":"eq");
|
|
gsym(t);
|
|
pr("brl " LOCAL_LABEL "\n+\n", jumps++);
|
|
break;
|
|
default:
|
|
error("unknown compare");
|
|
break;
|
|
}
|
|
t = r;
|
|
}
|
|
else if(v == VT_JMP || v == VT_JMPI) {
|
|
pr("; VT_jmp r %d t %d ji %d inv %d vtop->c.i %d\n",r,t, v&1, inv, vtop->c.i);
|
|
if((v & 1) == inv) {
|
|
gsym(t);
|
|
t = vtop->c.i;
|
|
}
|
|
else {
|
|
t = gjmp(t);
|
|
gsym(vtop->c.i);
|
|
}
|
|
}
|
|
else {
|
|
if(is_float(vtop->type.t)) {
|
|
pr("; float 4\n");
|
|
v = gv(RC_FLOAT);
|
|
gsym(t);
|
|
pr("lda.b tcc__f%d\nand.w #$ff00\nora.b tcc__f%dh\n", v - TREG_F0, v - TREG_F0);
|
|
vtop->r = VT_CMP;
|
|
vtop->c.i = TOK_NE;
|
|
return gtst(inv, t);
|
|
}
|
|
else if((vtop->r & (VT_VALMASK | VT_LVAL | VT_SYM)) == VT_CONST) {
|
|
if((vtop->type.t & VT_BTYPE) == VT_LLONG) error("zweiundvierzig");
|
|
if((vtop->c.i != 0) != inv) {
|
|
pr("; uncond jump: go! (vtop->c.i %d, inv %d)\n",vtop->c.i, inv);
|
|
/* set flags as if we had a false compare result */
|
|
pr("lda.w #0\n");
|
|
t = gjmp(t);
|
|
}
|
|
else pr("; uncond jump: nop\n");
|
|
}
|
|
else {
|
|
v = gv(RC_INT);
|
|
gsym(t);
|
|
pr("; tcc__r%d to compare reg\n", v);
|
|
pr("lda.b tcc__r%d ; DON'T OPTIMIZE\n",v);
|
|
if((vtop->type.t & VT_BTYPE) == VT_LLONG) pr("ora.b tcc__r%d\n", vtop->r2);
|
|
vtop->r = VT_CMP;
|
|
vtop->c.i = TOK_NE;
|
|
return gtst(inv, t);
|
|
}
|
|
}
|
|
vtop--;
|
|
pr("; gtst finished; t %d\n",t);
|
|
return t;
|
|
}
|
|
|
|
// generate an integer operation
|
|
void gen_opi(int op)
|
|
{
|
|
int r, fr, fc, ft, c, r5;
|
|
char* opcrem = 0, *opcalc = 0, *opcarry = 0;
|
|
int optone;
|
|
int docarry;
|
|
int sign;
|
|
int div;
|
|
int length, align;
|
|
int isconst = 0;
|
|
int timesshift, i;
|
|
|
|
length = type_size(&vtop[0].type, &align);
|
|
r = vtop[-1].r;
|
|
fr = vtop[0].r;
|
|
fc = vtop[0].c.ul;
|
|
|
|
// get the actual values
|
|
if((fr & VT_VALMASK) == VT_CONST && op != TOK_UMULL && !(fr & VT_SYM)) {
|
|
// vtop is const, only need to load the other one
|
|
ft = vtop[0].type.t;
|
|
vtop--;
|
|
r = gv(RC_INT);
|
|
isconst = 1;
|
|
if(length <= 2 && (fc < -32768 || fc > 65535)) {
|
|
warning("large integer implicitly truncated");
|
|
fc = (short)fc;
|
|
}
|
|
}
|
|
else {
|
|
// have to load both operands to registers
|
|
gv2(RC_INT,RC_INT);
|
|
r = vtop[-1].r;
|
|
fr = vtop[0].r;
|
|
ft = vtop[0].type.t;
|
|
vtop--;
|
|
}
|
|
|
|
pr("; gen_opi len %d op %c\n",length,op);
|
|
switch(op) {
|
|
// multiplication
|
|
case '*':
|
|
if(isconst) {
|
|
pr("; mul #%d, tcc__r%d\n", fc, r);
|
|
pr("lda.w #%d\nsta.b tcc__r9\n", fc);
|
|
}
|
|
else {
|
|
pr("; mul tcc__r%d,tcc__r%d\n",fr,r);
|
|
pr("lda.b tcc__r%d\nsta.b tcc__r9\n", fr);
|
|
}
|
|
pr("lda.b tcc__r%d\nsta.b tcc__r10\n", r);
|
|
pr("jsr.l tcc__mul\n");
|
|
pr("sta.b tcc__r%d\n", r);
|
|
break;
|
|
|
|
case TOK_UMULL:
|
|
r = vtop[0].r2 = get_reg(RC_INT);
|
|
c = vtop[0].r;
|
|
vtop[0].r = get_reg(RC_INT);
|
|
pr("; umull tcc__r%d, tcc__r%d => tcc__r%d/tcc__r%d\n", c, vtop[1].r, vtop->r, r);
|
|
pr("lda.b tcc__r%d\nsta.b tcc__r9\nstz.b tcc__r9h\nlda.b tcc__r%d\nsta.b tcc__r10\nstz.b tcc__r10h\n", c, vtop[1].r);
|
|
pr("jsr.l tcc__mull\n");
|
|
pr("stx.b tcc__r%d\nsty.b tcc__r%d\n", r, vtop->r);
|
|
break;
|
|
// division and friends
|
|
case TOK_PDIV:
|
|
op = TOK_UDIV;
|
|
case '/':
|
|
case TOK_UDIV:
|
|
case '%':
|
|
case TOK_UMOD:
|
|
if(op == '/' || op == '%') sign = 1;
|
|
else sign = 0;
|
|
if(op == '/' || op == TOK_UDIV) div = 1;
|
|
else div = 0;
|
|
|
|
if(isconst) {
|
|
pr("; div #%d, tcc__r%d\n", fc, r);
|
|
pr("ldx.b tcc__r%d\n", r);
|
|
pr("lda.w #%d\n", fc);
|
|
}
|
|
else {
|
|
pr("; div tcc__r%d,tcc__r%d\n", fr, r);
|
|
|
|
pr("ldx.b tcc__r%d\n", r); // dividend to x
|
|
pr("lda.b tcc__r%d\n", fr); // divisor to accu
|
|
}
|
|
pr("jsr.l tcc__%s\n",sign?"div":"udiv");
|
|
|
|
if(div) pr("lda.b tcc__r9\nsta.b tcc__r%d\n", r); // quotient in r9...
|
|
else pr("stx.b tcc__r%d\n", r); // ...remainder in x
|
|
|
|
break;
|
|
|
|
// intops the 65816 can do in hardware
|
|
case '+':
|
|
case TOK_ADDC1:
|
|
case TOK_ADDC2:
|
|
case '-':
|
|
case TOK_SUBC1:
|
|
case TOK_SUBC2:
|
|
case '&':
|
|
case TOK_LAND:
|
|
case '|':
|
|
case '^':
|
|
optone = 1;
|
|
docarry = 1;
|
|
if(isconst && fc < 0) {
|
|
if(op == '+') { op = '-'; fc = -fc; }
|
|
else if(op == '-') { op = '+'; fc = -fc; }
|
|
else if(op == TOK_ADDC1) { op = TOK_SUBC1; fc = -fc; }
|
|
else if(op == TOK_SUBC1) { op = TOK_ADDC1; fc = -fc; }
|
|
else if(op == TOK_ADDC2) { op = TOK_SUBC2; fc = -fc; }
|
|
else if(op == TOK_SUBC2) { op = TOK_ADDC2; fc = -fc; }
|
|
}
|
|
if(op == '+' || op == TOK_ADDC1 || op == TOK_ADDC2) {
|
|
opcalc = "adc"; // insn to use
|
|
opcrem = "inc"; // *crement to use when doing this op with *crements
|
|
opcarry = "clc"; // how to initialize the carry properly
|
|
if(op == TOK_ADDC2) docarry = 0;
|
|
}
|
|
else if(op == '-' || op == TOK_SUBC1 || op == TOK_SUBC2) {
|
|
opcalc = "sbc";
|
|
opcrem = "dec";
|
|
opcarry = "sec";
|
|
if(op == TOK_SUBC2) docarry = 0;
|
|
}
|
|
else if(op == '&' || op == TOK_LAND) {
|
|
optone = 0; // optimize the "constant 1" operand case by using a *crement?
|
|
docarry = 0; // touch the carry?
|
|
opcalc = "and";
|
|
}
|
|
else if(op == '|') {
|
|
optone = 0;
|
|
docarry = 0;
|
|
opcalc = "ora";
|
|
}
|
|
else if(op == '^') {
|
|
optone = 0;
|
|
docarry = 0;
|
|
opcalc = "eor";
|
|
}
|
|
else error("ICE 42");
|
|
|
|
pr("; %s tcc__r%d (0x%x), tcc__r%d (0x%x) (fr type 0x%x c %d r type 0x%x)\n",opcalc, fr, fr,r,r,vtop[0].type.t,vtop[0].c.ul, vtop[-1].type.t);
|
|
if(isconst) {
|
|
pr("; length xxy %d vtop->type 0x%x\n", type_size(&vtop->type, &align),vtop->type.t);
|
|
if (length == 4) {
|
|
/* probably pointer arithmetic... */
|
|
pr("; assuming pointer arith\n");
|
|
if ((fc >> 16) == 0) pr("stz.b tcc__r%dh\n", r);
|
|
else pr("lda.w #%d\nsta.b tcc__r%dh\n", fc >> 16, r);
|
|
}
|
|
if(fc == 1 && optone) pr("%s.b tcc__r%d\n", opcrem, r);
|
|
else if(fc == 2 && optone) pr("%s.b tcc__r%d\n%s.b tcc__r%d\n", opcrem, r, opcrem, r);
|
|
else pr("%s\nlda.b tcc__r%d\n%s.w #%d\nsta.b tcc__r%d\n", docarry?opcarry:"; nop", r, opcalc, fc & 0xffff, r);
|
|
}
|
|
else {
|
|
pr("; length xxy %d vtop->type 0x%x\n", type_size(&vtop->type, &align),vtop->type.t);
|
|
pr("%s\nlda.b tcc__r%d\n%s.b tcc__r%d\nsta.b tcc__r%d\n", docarry?opcarry:"; nop", r, opcalc, fr, r);
|
|
}
|
|
break;
|
|
|
|
case TOK_EQ:
|
|
case TOK_NE:
|
|
r5 = get_reg(RC_R5);
|
|
if(isconst) {
|
|
pr("; cmpr(n)eq tcc__r%d, #%d\n", r, fc);
|
|
pr("ldx #1\nlda.b tcc__r%d\nsec\nsbc #%d\n", r, fc);
|
|
}
|
|
else {
|
|
pr("; cmpr(n)eq tcc__r%d, tcc__r%d\n", r, fr);
|
|
pr("ldx #1\nlda.b tcc__r%d\nsec\nsbc.b tcc__r%d\n", r, fr);
|
|
}
|
|
pr("tay\n"); // save for long long comparisons
|
|
if(op == TOK_EQ) pr("beq +\n");
|
|
else pr("bne +\n");
|
|
pr("dex\n+\nstx.b tcc__r%d\n", r5); // long long code does some wild branching and fucks up
|
|
// if the results of the compares it generates do not
|
|
// end up in the same register; unlikely to be a performance
|
|
// impediment: TCC does not usually use this register anyway
|
|
vtop->r = r5;
|
|
break;
|
|
|
|
case TOK_GT:
|
|
case TOK_LE:
|
|
case TOK_LT:
|
|
case TOK_GE:
|
|
// 65xxx signed compare logic from here: http://www.6502.org/tutorials/compare_beyond.html#5
|
|
r5 = get_reg(RC_R5);
|
|
if(isconst) {
|
|
pr("; cmpcd tcc__r%d, #%d\n", r, fc);
|
|
pr("ldx #1\nlda.b tcc__r%d\nsec\nsbc.w #%d\n",r,fc);
|
|
}
|
|
else {
|
|
pr("; cmpcd tcc__r%d, tcc__r%d\n", r, fr);
|
|
pr("ldx #1\nlda.b tcc__r%d\nsec\nsbc.b tcc__r%d\n", r, fr);
|
|
}
|
|
pr("tay\n"); // may need that later for long long
|
|
if(op == TOK_GT) pr("beq ++\n"); // greater than => equality not good, result 0
|
|
else if(op == TOK_LE) pr("beq +++\n"); // less than or equal => equality good, result 1
|
|
pr("bvc +\neor #$8000\n+\n");
|
|
switch(op) {
|
|
case TOK_GT: pr("bpl +++\n"); break;
|
|
case TOK_LE: pr("bmi +++\n"); break;
|
|
case TOK_LT: pr("bmi +++\n"); break;
|
|
case TOK_GE: pr("bpl +++\n"); break;
|
|
default: error("don't know how to handle signed 0x%x\n", op);
|
|
}
|
|
pr("++\ndex\n+++\nstx.b tcc__r%d\n", r5); // see TOK_EQ/TOK_NE
|
|
vtop->r = r5;
|
|
break;
|
|
case TOK_UGT:
|
|
case TOK_ULE:
|
|
case TOK_ULT:
|
|
case TOK_UGE:
|
|
r5 = get_reg(RC_R5);
|
|
if(isconst) {
|
|
pr("; ucmpcd tcc__r%d, #%d\n", r, fc);
|
|
pr("ldx #1\nlda.b tcc__r%d\nsec\nsbc.w #%d\n", r, fc);
|
|
}
|
|
else {
|
|
pr("; ucmpcd tcc__r%d, tcc__r%d\n", r, fr);
|
|
pr("ldx #1\nlda.b tcc__r%d\nsec\nsbc.b tcc__r%d\n", r, fr);
|
|
}
|
|
pr("tay\n"); // needed for long long comparisons
|
|
switch(op) {
|
|
case TOK_UGT: pr("beq +\nbcs ++\n"); break;
|
|
case TOK_ULE: pr("beq ++\nbcc ++\n"); break;
|
|
case TOK_ULT: pr("bcc ++\n"); break;
|
|
case TOK_UGE: pr("bcs ++\n"); break;
|
|
default: error("don't know how to handle 0x%x\n", op);
|
|
}
|
|
pr("+ dex\n++\nstx.b tcc__r%d\n", r5); // see TOK_EQ/TOK_NE
|
|
vtop->r = r5;
|
|
break;
|
|
case TOK_SAR:
|
|
case TOK_SHR:
|
|
case TOK_SHL:
|
|
timesshift = 1;
|
|
#define UNROLL_SHIFT_MAX 4
|
|
#define SHIFT_IN_PLACE_MAX 2
|
|
if(isconst) {
|
|
pr("; %s tcc__r%d, #%d\n", op==TOK_SAR?"sar":op==TOK_SHR?"shr":"shl", r, fc);
|
|
if(!fc) return; // 0 -> nothing to do
|
|
if(fc == 8 && (op == TOK_SHR || op == TOK_SHL)) {
|
|
pr("lda.b tcc__r%d\nxba\n", r);
|
|
switch(op) {
|
|
case TOK_SHL: pr("and #$ff00\n"); break;
|
|
case TOK_SHR: pr("and #$00ff\n"); break;
|
|
default: error("ICE 43");
|
|
}
|
|
pr("sta.b tcc__r%d\n", r);
|
|
return;
|
|
}
|
|
else if(fc > UNROLL_SHIFT_MAX) // too many shifts -> need a loop
|
|
pr("lda.b tcc__r%d\nldy.w #%d\n-\n", r, fc);
|
|
else if(fc > SHIFT_IN_PLACE_MAX) {
|
|
pr("lda.b tcc__r%d\n", r);
|
|
timesshift = fc;
|
|
}
|
|
else {
|
|
// very few shifts; don't bother loading the value to the accu
|
|
for(i = 0; i < fc; i++) {
|
|
switch(op) {
|
|
case TOK_SAR:
|
|
pr("cmp #$8000\n"); // carry <= number negative?
|
|
pr("ror.b tcc__r%d\n", r);
|
|
break;
|
|
case TOK_SHR:
|
|
pr("lsr.b tcc__r%d\n",r); break;
|
|
case TOK_SHL:
|
|
// for left shifts, signedness is irrelevant (in spite of what the mnemonic seems to suggest)
|
|
pr("asl.b tcc__r%d\n", r); break;
|
|
default: error("unknown shift");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
pr("; %s tcc__r%d, tcc__r%d\n", op==TOK_SAR?"sar":op==TOK_SHR?"shr":"shl", r, fr);
|
|
pr("lda.b tcc__r%d\nldy.b tcc__r%d\nbeq +\n-\n", r, fr);
|
|
}
|
|
for(i = 0; i < timesshift; i++)
|
|
switch(op) {
|
|
case TOK_SAR:
|
|
pr("cmp #$8000\n"); // carry <= number negative?
|
|
pr("ror a\n");
|
|
break;
|
|
case TOK_SHR:
|
|
pr("lsr a\n"); break;
|
|
case TOK_SHL:
|
|
// for left shifts, signedness is irrelevant (in spite of what the mnemonic seems to suggest)
|
|
pr("asl a\n"); break;
|
|
default: error("unknown shift");
|
|
}
|
|
if(isconst && fc <= UNROLL_SHIFT_MAX) pr("sta.b tcc__r%d\n", r);
|
|
else pr("dey\nbne -\n+\nsta.b tcc__r%d\n", r);
|
|
break;
|
|
default:
|
|
error("opi 0x%x (%c) unimplemented\n",op,op);
|
|
}
|
|
}
|
|
|
|
void float_to_woz(float, unsigned char*);
|
|
|
|
void gen_opf(int op)
|
|
{
|
|
int r, fr, ft;
|
|
float fcf;
|
|
int length, align;
|
|
int ir;
|
|
|
|
length = type_size(&vtop[0].type, &align);
|
|
r = vtop[-1].r;
|
|
fr = vtop[0].r;
|
|
fcf = vtop[0].c.f;
|
|
|
|
// get the actual values
|
|
gv2(RC_F1,RC_F0);
|
|
r = vtop[-1].r;
|
|
fr = vtop[0].r;
|
|
ft = vtop[0].type.t;
|
|
vtop--;
|
|
|
|
pr("; gen_opf len %d op 0x%x ('%c')\n",length,op,op);
|
|
|
|
switch(op) {
|
|
case '*':
|
|
pr("jsr.l tcc__fmul\n");
|
|
break;
|
|
|
|
case '/':
|
|
pr("jsr.l tcc__fdiv\n");
|
|
break;
|
|
|
|
case '+':
|
|
pr("jsr.l tcc__fadd\n");
|
|
break;
|
|
|
|
case '-':
|
|
pr("jsr.l tcc__fsub\n");
|
|
break;
|
|
|
|
case TOK_EQ:
|
|
case TOK_NE:
|
|
ir = get_reg(RC_INT);
|
|
pr("jsr.l tcc__fcmp\n");
|
|
pr("dec a\n");
|
|
if(op == TOK_EQ) pr("beq +\n");
|
|
else pr("bne +\n");
|
|
pr("dex\n+\nstx.b tcc__r%d\n", ir);
|
|
vtop->r = ir;
|
|
return;
|
|
break;
|
|
|
|
case TOK_GT:
|
|
case TOK_LE:
|
|
case TOK_LT:
|
|
case TOK_GE:
|
|
ir = get_reg(RC_INT); // register to store the result flag in
|
|
pr("jsr.l tcc__fcmp\n");
|
|
// assuming tcc__fcmp returns signum + 1 in accu and inits X with 1
|
|
// subtracting one to set overflow flag
|
|
pr("sec\nsbc.w #1\n");
|
|
if(op == TOK_GT) pr("beq ++\n"); // greater than => equality not good, result 0
|
|
else if(op == TOK_LE) pr("beq +++\n"); // less than or equal => equality good, result 1
|
|
pr("bvc +\neor #$8000\n");
|
|
switch(op) {
|
|
case TOK_GT: pr("+ bpl +++\n"); break;
|
|
case TOK_LE: pr("+ bmi +++\n"); break;
|
|
case TOK_LT: pr("+ bmi +++\n"); break;
|
|
case TOK_GE: pr("+ bpl +++\n"); break;
|
|
default: error("don't know how to handle signed 0x%x\n", op);
|
|
}
|
|
pr("++\ndex\n+++\nstx.b tcc__r%d\n", ir);
|
|
vtop->r = ir;
|
|
return;
|
|
break;
|
|
|
|
default:
|
|
error("opf 0x%x (%c) unimplemented\n",op,op);
|
|
}
|
|
vtop->r = TREG_F0;
|
|
}
|
|
|
|
void gen_cvt_itof(int t)
|
|
{
|
|
int r, r2, it;
|
|
gv(RC_INT); // load integer to convert
|
|
r = vtop->r; // register with int
|
|
r2 = vtop->r2; // register with high word (for long longs)
|
|
it = vtop->type.t; // type of int
|
|
|
|
pr("; itof tcc__r%d, f0\n", r);
|
|
if((vtop->type.t & VT_BTYPE) == VT_LLONG) {
|
|
pr("pei (tcc__r%d)\npei (tcc__r%d)\n", r2, r);
|
|
if(it & VT_UNSIGNED) error("jsr.l tcc__ulltof\n"); // this is probably dead code
|
|
else pr("jsr.l tcc__lltof\n");
|
|
pr("pla\npla\n");
|
|
}
|
|
else {
|
|
get_reg(RC_F0); // result will go to f0
|
|
pr("lda.b tcc__r%d\n", r);
|
|
pr("xba\nsta.b tcc__f0 + 1\n"); // convert to big-endian and load to upper 2 bytes of mantissa
|
|
if(it & VT_UNSIGNED) pr("jsr.l tcc__ufloat\n");
|
|
else pr("jsr.l tcc__float\n");
|
|
}
|
|
vtop->r = TREG_F0; // tell TCC that the result is in f0
|
|
}
|
|
|
|
void gen_cvt_ftoi(int t)
|
|
{
|
|
int r = 0;
|
|
gv(RC_F0);
|
|
if((t & VT_BTYPE) == VT_LLONG) {
|
|
get_reg(RC_R0);
|
|
get_reg(RC_R1);
|
|
}
|
|
else r = get_reg(RC_INT);
|
|
pr("; ftoi tcc__f0, tcc__r%d(type 0x%x)\n", r, t);
|
|
if(t & VT_UNSIGNED) pr("lda #0\nsta.b tcc__r9\n");
|
|
else pr("lda #1\nsta.b tcc__r9\n");
|
|
|
|
// use llfix for any unsigned type to avoid overflow
|
|
if((t & VT_BTYPE) == VT_LLONG || (t & VT_UNSIGNED)) {
|
|
pr("jsr.l tcc__llfix\n");
|
|
vtop->r2 = TREG_R1;
|
|
vtop->r = TREG_R0;
|
|
return;
|
|
}
|
|
else {
|
|
pr("jsr.l tcc__fix\n");
|
|
pr("lda.b tcc__f0 + 1\nxba\nsta.b tcc__r%d\n", r);
|
|
}
|
|
|
|
vtop->r = r;
|
|
}
|
|
|
|
void gen_cvt_ftof(int t)
|
|
{
|
|
error("gen_cvt_ftof 0x%x\n",t);
|
|
}
|
|
|
|
void ggoto(void)
|
|
{
|
|
int r = gv(RC_INT);
|
|
int t = vtop->type.t;
|
|
pr("; ggoto r 0x%x t 0x%x\n",r,t);
|
|
pr("lda.b tcc__r%d\nsta.b tcc__r9 + 1\nsep #$20\nlda.b tcc__r%dh\nsta.b tcc__r9h + 1\nlda.b #$5c\nsta.b tcc__r9\nrep #$20\n", r, r);
|
|
pr("jml.l tcc__r9\n");
|
|
}
|
|
|
|
int section_count = 0;
|
|
int ind_before_section = 0;
|
|
int section_closed = 1;
|
|
|
|
void gfunc_prolog(CType* func_type)
|
|
{
|
|
Sym* sym; //, *sym2;
|
|
Sym* symf;
|
|
int n,addr,size,align;
|
|
//fprintf(stderr,"gfunc_prolog t %d sym %p\n",func_type->t,func_type->ref);
|
|
|
|
sym = func_type->ref;
|
|
func_vt = sym->type;
|
|
|
|
n=0;
|
|
addr=3; // skip 24-bit return address
|
|
loc = 0;
|
|
if((func_vt.t & VT_BTYPE) == VT_STRUCT) {
|
|
func_vc = addr;
|
|
addr += PTR_SIZE;
|
|
n += PTR_SIZE;
|
|
}
|
|
|
|
/* super-dirty hack to get the function name */
|
|
symf = (Sym*) ( ((void*)func_type) - offsetof(Sym, type) );
|
|
strcpy(current_fn, get_sym_str(symf));
|
|
|
|
/* wlalink does not cut up sections, so it is desirable to have a section
|
|
for each function to keep the amount of unused memory in the ROM banks
|
|
low. WLA DX barfs, however, if fed more than 255 sections, so we have
|
|
to be a bit economical: gfunc_epilog() only closes the section if more
|
|
than 50K of assembler code have been written */
|
|
if(section_closed) {
|
|
ind_before_section = ind;
|
|
pr("\n.section \".text_0x%x\" superfree\n", section_count++);
|
|
section_closed = 0;
|
|
}
|
|
|
|
pr("\n%s:\n",current_fn);
|
|
|
|
while((sym = sym->next)) {
|
|
CType* type;
|
|
type = &sym->type;
|
|
sym_push(sym->v & ~SYM_FIELD, type, VT_LOCAL | VT_LVAL, addr);
|
|
size = type_size(type, &align);
|
|
//fprintf(stderr,"pushed sym type 0x%x size %d addr 0x%x\n",type->t,size,addr);
|
|
addr += size;
|
|
n += size;
|
|
}
|
|
pr("; sub sp,#__%s_locals\n",current_fn);
|
|
pr(".ifgr __%s_locals 0\ntsa\nsec\nsbc #__%s_locals\ntas\n.endif\n",current_fn,current_fn);
|
|
loc = 0; // huh squared?
|
|
}
|
|
|
|
char locals[1000][80];
|
|
int localnos[1000];
|
|
int localno=0;
|
|
|
|
void gfunc_epilog(void)
|
|
{
|
|
pr("; add sp, #__%s_locals\n",current_fn);
|
|
pr(".ifgr __%s_locals 0\ntsa\nclc\nadc #__%s_locals\ntas\n.endif\n", current_fn, current_fn);
|
|
pr("rtl\n");
|
|
|
|
pr(".ends\n");
|
|
section_closed = 1;
|
|
|
|
if(-loc > 0x1f00) error("stack overflow");
|
|
/* simply putting a ".define __<current_fn>_locals -<loc>" after the
|
|
function does not work in some cases for unknown reasons (wla-dx
|
|
complains about unresolved symbols); putting them before the reference
|
|
works, but this has to be done by the output code, so we have to save
|
|
the various locals sizes somewhere */
|
|
strcpy(locals[localno], current_fn);
|
|
localnos[localno] = -loc;
|
|
localno++;
|
|
current_fn[0] = 0;
|
|
}
|