blob: a242fff8f020b87950644b51ef5711923e2e5d70 [file] [log] [blame]
/* z/machine.c
* Code generator for the Z-machine.
* (C) David Given 2001
* conversion to vbcc 0.8 by Volker Barthelmann
*/
/* This code is licensed under the MIT open source license.
*
* Copyright (c) 2001, David Given
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
/* This code generator produces code for the Z-machine. The Z-machine is the
* highly peculiar virtual machine used for the old Infocom text adventures;
* these days, an extended form is still used in the interactive fiction genre
* of games. Usually, a dedicated compiler called Inform is used to generate
* code, but it would be nice to be able to use real C, so here we are.
*
* The Z-machine is (mostly) a semi-stack-based Harvard architecture machine.
* (Split code and data space, although they can share if you're clever. And
* mad.) It has no registers, but it does have procedure local variables which
* will do instead. It has a dedicated stack but as it's not accessible by
* ordinary memory it's not useful for C. It uses 8 and 16 bit words, so we'll
* have to emulate 32-bit arithmetic.
*
* For more information, including code for Inform, various interpreters, more
* documentation than you can shake a stick at, and the full technical reference
* for the Z-machine, check out the Interactive Fiction archive, at
* http://www.ifarchive.org.
*
* Things to note: there is no Z-machine assembler. (Well, there's zasm, but it's
* really just a rumour.) Luckily, Inform has an assembler mode, where it'll
* generate raw Z-machine opcodes. Unluckily, it's horribly buggy... So we're
* going to have to generate Inform source, which seems at first to be rather
* silly, but as Inform is quite a simple compiler we can make sure that it's
* only going to generate the instructions we want it to generate.
*/
/* vbcc-mandated header. */
#include "supp.h"
static char FILE_[]=__FILE__;
char cg_copyright[]="vbcc code-generator for Z-machine V0.0a (c) in 2001 by David Given";
/* Command-line flags. */
int g_flags[MAXGF] = {
STRINGFLAG,
0,
0,
0,
0,
0
};
char *g_flags_name[MAXGF] = {
"module-name",
"trace-calls",
"trace-all",
"safe-branches",
"comment-ic",
"comment-misc"
};
union ppi g_flags_val[MAXGF];
/* Type alignment. Much better code is generated if we can use even alignment.
*/
zmax align[MAX_TYPE+1] = {
0, /* 0: unused */
1, /* 1: CHAR */
2, /* 2: SHORT */
2, /* 3: INT */
2, /* 4: LONG */
2, /* 5: LLONG */
2, /* 6: FLOAT */
2, /* 7: DOUBLE */
2, /* 8: LDOUBLE */
2, /* 9: VOID */
2, /* 10: POINTER */
1, /* 11: ARRAY */
1, /* 12: STRUCT */
1, /* 13: UNION */
1, /* 14: ENUM */
1, /* 15: FUNKT */
};
/* Alignment that is valid for all types. */
zmax maxalign = 2;
/* Number of bits in a char. */
zmax char_bit = 8;
/* Sizes of all elementary types, in bytes. */
zmax sizetab[MAX_TYPE+1] = {
0, /* 0: unused */
1, /* 1: CHAR */
2, /* 2: SHORT */
2, /* 3: INT */
4, /* 4: LONG */
8, /* 5: LLONG */
4, /* 6: FLOAT */
8, /* 7: DOUBLE */
8, /* 8: LDOUBLE */
0, /* 9: VOID */
2, /* 10: POINTER */
0, /* 11: ARRAY */
0, /* 12: STRUCT */
0, /* 13: UNION */
2, /* 14: ENUM */
0, /* 15: FUNKT */
};
/* 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];
/* Names of all the registers.
* We can have 16 local variables per routine. Var 0 is always the C stack
* pointer, xp. All the others can be used by the compiler. xp doesn't actually
* appear in the register map, so we get 15 main registers.
*/
char* regnames[] = {
"sp", /* vbcc doesn't use this, but we do */
"xp", "r0", "r1", "r2", "r3", "r4", "r5", "r6",
"r7", "r8", "r9", "r10", "r11", "r12"};
#define XP 1
#define USERREG 2
/* The size of each register, in byes. */
zmax regsize[] = {
0,
2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2};
/* Type needed to store each register. */
struct Typ ityp = {INT};
struct Typ* regtype[] = {
NULL,
&ityp, &ityp, &ityp, &ityp, &ityp, &ityp, &ityp, &ityp,
&ityp, &ityp, &ityp, &ityp, &ityp, &ityp};
/* These registers are those dedicated for use by the backend. These ones will
* not be used by the code generator. */
int regsa[] = {
0,
1, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0};
/* Specifies which registers may be destroyed by function calls. As we're
* storing our registers in local variables so they're being automatically
* saved for us, none of them.
*/
int regscratch[] = {
0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0};
/* Default state for register parameter passing. */
struct reg_handle empty_reg_handle =
{USERREG};
/* Prefix for labels. */
static char* labelprefix = "L";
/* Name of the current module; used for generating unique names for statics and
* the constant pool. */
static char* modulename;
/* Stack frame layout:
*
* --------------
* Arg 4 (Arguments being passed to this function)
* Arg 3
* Arg 2
* Arg 1
* -------------- xp + stackparamadjust + stackoffset
* Local 4 (This function's temp space)
* Local 3
* Local 2
* Local 1
* -------------- xp + stackparamadjust
* Arg 2 (Arguments this function has pushed to pass
* Arg 1 to a called function)
* -------------- xp
*
* Any area may be zero in size. (Although stackoffset is always at least 2 for
* some inadequately explained reason.)
*/
static int stackoffset;
static int stackparamadjust;
/* Represents something the Z-machine can use as an instruction operand. */
struct zop {
int type;
union {
int reg;
zmax constant;
char* identifier;
} val;
};
enum {
ZOP_STACK,
ZOP_REG,
ZOP_CONSTANT,
ZOP_EXTERN,
ZOP_STATIC,
ZOP_CONSTANTADDR
};
/* Some useful zops. */
struct zop zop_zero = {ZOP_CONSTANT, {constant: 0}};
struct zop zop_xp = {ZOP_REG, {reg: XP}};
struct zop zop_stack = {ZOP_STACK, 0};
/* Temporaries used to store comparison register numbers. */
static struct zop compare1;
static struct zop compare2;
/* Keeps track of whether we've emitted anything or not. Used to determine
* whether to emit the seperating ; or not. If it's 1, we haven't emitted
* anything. If it's -1, we're doing an array, so we need to emit a final (0)
* to finish it off if it's only one byte long. 0 for anything else. */
static int virgin = 1;
/* The current variable we're emitting data for. */
struct variable {
int type;
union {
char* identifier;
int number;
} val;
zint offset;
};
struct variable currentvar;
/* Inform can't emit variable references inside arrays. So when vbcc wants to
* put, say, the address of something in a global variable, we have to write it
* in later. A linked list of these structures keeps track of the items that
* need fixing up. */
struct fixup {
struct fixup* next;
struct variable identifier;
struct variable value;
zmax offset;
};
static struct fixup* fixuplist = NULL;
/* 32-bit values are stored in a constant pool, for simplicity. It's kept track
* of in this linked list. */
struct constant {
struct constant* next;
int id;
zmax value;
};
static struct constant* constantlist = NULL;
static int constantnum = 0;
/* The function we're currently compiling. */
static struct Var* function;
/* Function prototypes. */
static void emit_add(FILE* fp, struct zop* q1, struct zop* q2, struct zop* z);
static void read_reg(FILE* fp, struct obj* obj, int typf, int reg);
static int addconstant(zmax value);
/* Emit debugging info. */
static void debugemit(FILE* fp, char* fmt, ...)
{
va_list ap;
va_start(ap, fmt);
if (g_flags[5] & USEDFLAG)
vfprintf(fp, fmt, ap);
va_end(ap);
}
/* Do we need to emit a ; before the next thing? */
static void reflower(FILE* fp)
{
if (!virgin)
fprintf(fp, ";\n");
if (virgin == -1)
{
if (currentvar.offset == 1)
fprintf(fp, "(0)");
fprintf(fp, ";");
}
virgin = 0;
}
/* Extract the sign extended byte n of a value. */
static char xbyte(zmax val, int byte)
{
val <<= (sizeof(val)*8) - (byte*8) - 8;
val >>= (sizeof(val)*8) - 8;
return (unsigned char) val;
}
/* Extract the sign extended word n of a value. */
static zshort xword(zmax val, int word)
{
val <<= (sizeof(val)*8) - (word*16) - 16;
val >>= (sizeof(val)*8) - 16;
return (zshort) val;
}
/* Debug function: prints the text name of a type. */
static void dump_type(FILE* fp, int typf)
{
switch (typf)
{
case VOID: fprintf(fp, "VOID"); break;
case CHAR: fprintf(fp, "CHAR"); break;
case SHORT: fprintf(fp, "SHORT"); break;
case INT: fprintf(fp, "INT"); break;
case LONG: fprintf(fp, "LONG"); break;
case POINTER: fprintf(fp, "POINTER"); break;
case STRUCT: fprintf(fp, "STRUCT"); break;
case ARRAY: fprintf(fp, "ARRAY"); break;
case UNION: fprintf(fp, "UNION"); break;
case FUNKT: fprintf(fp, "FUNKT"); break;
default: fprintf(fp, "unknown %X", typf);
}
}
/* Debug function: outputs the obj. */
static void dump_obj(FILE* fp, struct obj* obj, int typf)
{
int f = obj->flags & (KONST|REG|VAR|DREFOBJ|VARADR);
if (f == 0)
{
fprintf(fp, "[]");
return;
}
if (f & DREFOBJ)
fprintf(fp, "*");
if (f & VARADR)
fprintf(fp, "&");
if (f == KONST)
{
switch (typf & NU)
{
case CHAR:
fprintf(fp, "[char #%d]", obj->val.vchar);
break;
case UNSIGNED|CHAR:
fprintf(fp, "[uchar #%u]", obj->val.vuchar);
break;
case SHORT:
fprintf(fp, "[short #%d]", obj->val.vshort);
break;
case UNSIGNED|SHORT:
fprintf(fp, "[ushort #%u]", obj->val.vushort);
break;
case INT:
fprintf(fp, "[int #%d]", obj->val.vint);
break;
case UNSIGNED|INT:
fprintf(fp, "[uint #%d]", obj->val.vuint);
break;
case LONG:
fprintf(fp, "[long #%d]", obj->val.vlong);
break;
case UNSIGNED|LONG:
fprintf(fp, "[ulong #%u]", obj->val.vulong);
break;
case FLOAT:
fprintf(fp, "[float #%04X]", obj->val.vfloat);
break;
case DOUBLE:
fprintf(fp, "[double #%08X]", obj->val.vdouble);
break;
#if 0
case POINTER:
fprintf(fp, "[pointer #%04X]", obj->val.vpointer);
break;
#endif
}
}
else if (f == REG)
fprintf(fp, "[reg %s]", regnames[obj->reg]);
else if (f == (REG|DREFOBJ))
fprintf(fp, "[deref reg %s]", regnames[obj->reg]);
//else if (f & VAR)
else
{
fprintf(fp, "[var ");
dump_type(fp, typf);
fprintf(fp, " %s", obj->v->identifier);
if ((obj->v->storage_class == AUTO) ||
(obj->v->storage_class == REGISTER))
{
zmax offset = obj->v->offset;
//if (offset < 0)
// offset = -(offset+maxalign);
fprintf(fp, " at fp%+d", offset);
}
fprintf(fp, "+%ld", obj->val.vlong);
if (f & REG)
fprintf(fp, " in %s", regnames[obj->reg]);
fprintf(fp, "]");
}
}
/* Debug function: outputs the ic, as a comment. */
static void dump_ic(FILE* fp, struct IC* ic)
{
char* p;
if (!ic)
return;
if (!(g_flags[4] & USEDFLAG))
return;
if (g_flags[2] & USEDFLAG)
fprintf(fp, "print \"");
else
fprintf(fp, "! ");
switch (ic->code)
{
case ASSIGN: p = "ASSIGN"; break;
case OR: p = "OR"; break;
case XOR: p = "XOR"; break;
case AND: p = "AND"; break;
case LSHIFT: p = "LSHIFT"; break;
case RSHIFT: p = "RSHIFT"; break;
case ADD: p = "ADD"; break;
case SUB: p = "SUB"; break;
case MULT: p = "MULT"; break;
case DIV: p = "DIV"; break;
case MOD: p = "MOD"; break;
case KOMPLEMENT: p = "KOMPLEMENT"; break;
case MINUS: p = "MINUS"; break;
case ADDRESS: p = "ADDRESS"; break;
case CALL: p = "CALL"; break;
#if 0
case CONVCHAR: p = "CONVCHAR"; break;
case CONVSHORT: p = "CONVSHORT"; break;
case CONVINT: p = "CONVINT"; break;
case CONVLONG: p = "CONVLONG"; break;
case CONVFLOAT: p = "CONVFLOAT"; break;
case CONVDOUBLE: p = "CONVDOUBLE"; break;
case CONVPOINTER: p = "CONVPOINTER"; break;
case CONVUCHAR: p = "CONVUCHAR"; break;
case CONVUSHORT: p = "CONVUSHORT"; break;
case CONVUINT: p = "CONVUINT"; break;
case CONVULONG: p = "CONVULONG"; break;
#endif
case ALLOCREG: p = "ALLOCREG"; break;
case FREEREG: p = "FREEREG"; break;
case COMPARE: p = "COMPARE"; break;
case TEST: p = "TEST"; break;
case LABEL: p = "LABEL"; break;
case BEQ: p = "BEQ"; break;
case BNE: p = "BNE"; break;
case BLT: p = "BLT"; break;
case BGT: p = "BGT"; break;
case BLE: p = "BLE"; break;
case BGE: p = "BGE"; break;
case BRA: p = "BRA"; break;
case PUSH: p = "PUSH"; break;
case ADDI2P: p = "ADDI2P"; break;
case SUBIFP: p = "SUBIFP"; break;
case SUBPFP: p = "SUBPFP"; break;
case GETRETURN: p = "GETRETURN"; break;
case SETRETURN: p = "SETRETURN"; break;
case MOVEFROMREG: p = "MOVEFROMREG"; break;
case MOVETOREG: p = "MOVETOREG"; break;
case NOP: p = "NOP"; break;
default: p = "???";
}
fprintf(fp, "%s ", p);
dump_type(fp, ic->typf);
fprintf(fp, " ");
switch (ic->code)
{
case LABEL:
case BEQ:
case BNE:
case BLT:
case BGT:
case BLE:
case BGE:
case BRA:
fprintf(fp, "%d", ic->typf);
goto epilogue;
}
dump_obj(fp, &ic->q1, ic->typf);
fprintf(fp, " ");
dump_obj(fp, &ic->q2, ic->typf);
fprintf(fp, " -> ");
dump_obj(fp, &ic->z, ic->typf);
epilogue:
if (g_flags[2] & USEDFLAG)
fprintf(fp, "^\";\n");
else
fprintf(fp, "\n");
}
/* Initialise the code generator. This is called once. Returns 0 if things go
* wrong. */
int init_cg(void)
{
modulename = g_flags_val[0].p;
if (!modulename)
modulename = "";
/* 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]=tu_max[SHORT];
tu_max[LONG]=ul2zum(4294967295UL);
tu_max[LLONG]=zumkompl(ul2zum(0UL));
tu_max[MAXINT]=t_max(UNSIGNED|LLONG);
return 1;
}
/* Returns the register in which variables of type typ are returned (or 0 if it
* can't be done). */
int freturn(struct Typ *typ)
{
int s = sizetab[typ->flags & NQ];
if ((typ->flags & NQ) == VOID)
return USERREG;
if ((s <= sizetab[INT]) && (s > 0))
return USERREG;
return 0;
}
/* Returns 1 if register reg can store variables of type typ. mode is set
* if the register is a pointer and the register is going to be dereferenced.
*/
int regok(int reg, int typf, int mode)
{
int s = sizetab[typf & NQ];
if ((typf & NQ) == VOID)
return 1;
if ((s <= sizetab[INT]) && (s > 0))
return 1;
return 0;
}
/* Returns zero if the IC ic can be safely executed without danger of
* exceptions or similar things; for example, divisions or pointer dereferences
* are dangerous. This is used by the optimiser for code reordering.
*/
int dangerous_IC(struct IC *ic)
{
/* Check for dereferences. */
if ((ic->q1.flags & DREFOBJ) ||
(ic->q2.flags & DREFOBJ) ||
(ic->z.flags & DREFOBJ))
return 0;
/* Division or modulo? */
if ((ic->code == DIV) ||
(ic->code == MOD))
return 0;
/* Safe, as far as we can tell. */
return 1;
}
/* Returns zero if the code for converting type p->ntyp to type typ is a noop.
*/
int must_convert(int otyp, int typ,int const_expr)
{
int oldtype = otyp & NQ;
int newtype = typ & NQ;
/* ints and shorts are equivalent. */
if (oldtype == SHORT)
oldtype = INT;
if (newtype == SHORT)
newtype = INT;
/* Both the same type? */
if (oldtype == newtype)
return 0;
#if 0
/* Converting two basic integers? */
if ((oldtype <= INT) && (newtype <= INT))
{
/* ... but char to short needs an AND. */
if ((oldtype == CHAR) && (newtype != CHAR))
return 1;
return 0;
}
#endif
/* Pointer to/from int? */
if (((oldtype == INT) || (oldtype == POINTER)) &&
((newtype == INT) || (newtype == POINTER)))
return 0;
/* Everything else needs code. */
return 1;
}
/* Ensure the output is aligned. A noop on the Z-machine. */
void gen_align(FILE* fp, zmax align)
{
}
/* Generate the label part of a variable definition. */
void gen_var_head(FILE* fp, struct Var* var)
{
if (var->storage_class == EXTERN)
debugemit(fp, "! Var %s %X\n", var->identifier, var->flags);
if (var->storage_class == STATIC)
debugemit(fp, "! Var static %ld %s %X\n", var->offset, var->identifier, var->flags);
/* We only want to emit records for genuinely defined variables. For
* some reason, TENTATIVE is defined for some of this. */
if ((var->storage_class == EXTERN) &&
!(var->flags & DEFINED) &&
!(var->flags & TENTATIVE))
return;
reflower(fp);
virgin = -1;
switch (var->storage_class)
{
case EXTERN:
/* This doesn't actually mean external linkage; it
* means a non-static global that may be referenced
* externally. */
fprintf(fp, "Array _%s ->\n",
var->identifier);
currentvar.type = EXTERN;
currentvar.val.identifier = strdup(var->identifier);
currentvar.offset = 0;
break;
case STATIC:
fprintf(fp, "Array STATIC_%s_%ld ->\n",
modulename, var->offset);
currentvar.type = STATIC;
currentvar.val.number = var->offset;
currentvar.offset = 0;
break;
}
}
/* Emit a certain number of bytes of bss data. No bss on the Z-machine,
* remember. */
void gen_ds(FILE *fp, zmax size, struct Typ *typ)
{
fprintf(fp, " %ld\n", size);
currentvar.offset += size;
}
/* Emit a certain number of bytes of initialised data. */
void gen_dc(FILE *fp, int typf, struct const_list *p)
{
switch (typf & NQ)
{
case CHAR:
fprintf(fp, " (%d)\n",
p->val.vuchar);
currentvar.offset += 1;
break;
case SHORT:
case INT:
reallyanint:
fprintf(fp, " (%d) (%d)\n",
xbyte(p->val.vint, 1),
xbyte(p->val.vint, 0));
currentvar.offset += 2;
break;
case LONG:
fprintf(fp, " (%d) (%d) (%d) (%d)\n",
xbyte(p->val.vlong, 3),
xbyte(p->val.vlong, 2),
xbyte(p->val.vlong, 1),
xbyte(p->val.vlong, 0));
currentvar.offset += 4;
break;
case POINTER:
if (!p->tree)
goto reallyanint;
{
struct fixup* fixup = malloc(sizeof(struct fixup));
struct obj* obj = &p->tree->o;
fixup->next = fixuplist;
fixuplist = fixup;
fixup->identifier = currentvar;
switch (obj->v->storage_class)
{
case EXTERN:
fixup->value.type = EXTERN;
fixup->value.val.identifier = strdup(obj->v->identifier);
break;
case STATIC:
fixup->value.type = STATIC;
fixup->value.val.number = obj->v->offset;
break;
default:
ierror(0);
}
fixup->value.offset = 0;
fixup->offset = obj->val.vlong;
fprintf(fp, " (0) (0)\n");
currentvar.offset += 2;
}
break;
default:
printf("type %d\n", typf);
ierror(0);
}
}
/* Returns the offset of the (STATIC or AUTO) given object. */
zmax voff(struct obj* obj)
{
zmax offset = obj->v->offset;
if (offset < 0)
offset = stackparamadjust + stackoffset - offset - maxalign;
else
offset += stackparamadjust;
offset += obj->val.vlong;
return offset;
}
/* When a varargs function is called, we need to find where the parameters are
* on the stack in order to make the __va_start magic variable work. This
* function does that. */
static int find_varargs(void)
{
int offset = 0;
struct reg_handle rh = empty_reg_handle;
struct struct_declaration* sd = function->vtyp->exact;
int stackalign;
int i;
for (i=0; i<sd->count; i++)
{
/* Ignore the parameter if it's been assigned a register. */
if ((*sd->sl)[i].reg != 0)
continue;
/* void shouldn't happen. */
if (((*sd->sl)[i].styp->flags & NQ) == VOID)
ierror(0);
/* Does the backend want to assign it to a register? */
if (reg_parm(&rh, (*sd->sl)[i].styp, 0, 0))
continue;
/* Add on the size of this parameter. */
offset += sizetab[(*sd->sl)[i].styp->flags & NQ];
/* Stack align. */
stackalign = align[(*sd->sl)[i].styp->flags & NQ];
offset = ((offset+1) / stackalign) * stackalign;
}
return (offset + stackoffset);
}
/* Output the name of a global. */
static void emit_identifier(FILE* fp, struct obj* obj)
{
switch (obj->v->storage_class)
{
case STATIC:
fprintf(fp, "STATIC_%s_%ld",
modulename, obj->v->offset);
break;
case EXTERN:
fprintf(fp, "_%s", obj->v->identifier);
break;
default:
ierror(0);
}
}
/* Save a register. */
static void write_reg(FILE* fp, struct obj* obj, int typf, int reg)
{
int flags = obj->flags &
(KONST|REG|VAR|DREFOBJ|VARADR);
/* Constant? */
if (flags == KONST)
ierror(0);
/* Dereference? */
if (flags & DREFOBJ)
goto dereference;
/* Register? */
if ((flags == REG) ||
((flags & VAR) && (flags & REG) && (obj->v->storage_class == AUTO)) ||
((flags & VAR) && (flags & REG) && (obj->v->storage_class == REGISTER)))
{
if (flags & DREFOBJ)
fprintf(fp, "\t@store%c %s 0 %s;\n",
((typf & NQ) == CHAR) ? 'b' : 'w',
regnames[obj->reg], regnames[reg]);
else
{
struct zop in;
struct zop out;
in.type = ZOP_REG;
in.val.reg = reg;
out.type = ZOP_REG;
out.val.reg = obj->reg;
emit_add(fp, &in, &zop_zero, &out);
}
#if 0
fprintf(fp, "\t@add %s 0 -> %s;\n",
regnames[reg], regnames[obj->reg]);
#endif
return;
}
/* It must be a variable. */
switch (obj->v->storage_class)
{
case AUTO:
case REGISTER: /* Local variable */
{
zmax offset = voff(obj);
if ((typf & NQ) == CHAR)
fprintf(fp, "\t@storeb xp 0%+ld %s;\n",
offset, regnames[reg]);
else
{
if (offset & 1)
{
struct zop c;
c.type = ZOP_CONSTANT;
c.val.constant = offset;
emit_add(fp, &zop_xp, &c, &zop_stack);
//fprintf(fp, "\t@add xp 0%+ld -> sp;\n", offset);
fprintf(fp, "\t@storew sp 0 %s;\n", regnames[reg]);
}
else
fprintf(fp, "\t@storew xp 0%+ld %s;\n",
offset >> 1, regnames[reg]);
}
return;
}
case EXTERN:
case STATIC:
/* Dereference object. */
if ((typf & NQ) == CHAR)
{
fprintf(fp, "\t@storeb ");
emit_identifier(fp, obj);
fprintf(fp, " 0%+ld %s;\n",
obj->val.vlong, regnames[reg]);
}
else
{
if (obj->val.vlong & 1)
{
fprintf(fp, "\t@add ");
emit_identifier(fp, obj);
fprintf(fp, " 0%+ld -> sp;\n",
obj->val.vlong);
fprintf(fp, "\t@storew sp 0 %s;\n",
regnames[reg]);
}
else
{
fprintf(fp, "\t@storew ");
emit_identifier(fp, obj);
fprintf(fp, " 0%+ld %s;\n",
obj->val.vlong >> 1, regnames[reg]);
}
}
return;
#if 0
case EXTERN: /* External linkage */
if ((typf & NQ) == CHAR)
fprintf(fp, "\t@storeb _%s 0%+ld %s;\n",
obj->v->identifier, offset, regnames[reg]);
else
{
fprintf(fp, "\t@storew _%s 0 %s;\n",
obj->v->identifier, regnames[reg]);
return;
case STATIC: /* Static global */
if ((typf & NQ) == CHAR)
fprintf(fp, "\t@storeb STATIC_%s_%ld 0%+ld %s;\n",
modulename, obj->v->offset, offset, regnames[reg]);
else
fprintf(fp, "\t@storew STATIC_%s_%ld 0 %s;\n",
modulename, obj->v->offset, regnames[reg]);
return;
#endif
default:
ierror(0);
}
ierror(0); // Not reached
dereference:
/* These are a *pain*.
*
* The first thing we need to do is to read the old contents of the
* memory cell, to work out the address we need to write to; and then
* do the write. Hurray for the Z-machine stack. */
obj->flags &= ~DREFOBJ;
read_reg(fp, obj, POINTER, 0);
fprintf(fp, "\t@store%c sp 0 %s;\n",
((typf & NQ) == CHAR) ? 'b' : 'w',
regnames[reg]);
}
/* Move one register to another register. */
static void move_reg(FILE* fp, int reg1, int reg2)
{
struct zop r1;
struct zop r2;
r1.type = ZOP_REG;
r1.val.reg = reg1;
r2.type = ZOP_REG;
r2.val.reg = reg2;
emit_add(fp, &r1, &zop_zero, &r2);
}
/* Load a value into a zop. */
static void read_reg(FILE* fp, struct obj* obj, int typf, int reg)
{
int flags = obj->flags &
(KONST|REG|VAR|DREFOBJ|VARADR);
/* The only thing you can do with a function is to take the address of
* it. */
if ((typf & NQ) == FUNKT)
flags &= ~DREFOBJ & ~VARADR;
/* Is this a memory dereference? */
if (flags & DREFOBJ)
goto dereference;
/* Constant? */
if (flags == KONST)
{
struct zop c;
struct zop r;
c.type = ZOP_CONSTANT;
//fprintf(fp, "\t@add ");
switch (typf & NQ)
{
case CHAR: c.val.constant = obj->val.vchar; break;
case UNSIGNED|CHAR: c.val.constant = obj->val.vuchar; break;
case SHORT: c.val.constant = obj->val.vshort; break;
case UNSIGNED|SHORT: c.val.constant = obj->val.vushort; break;
case POINTER: ierror(0);
case INT: c.val.constant = obj->val.vint; break;
case UNSIGNED|INT: c.val.constant = obj->val.vuint; break;
default:
ierror(typf);
}
r.type = ZOP_REG;
r.val.reg = reg;
emit_add(fp, &c, &zop_zero, &r);
//fprintf(fp, " 0 -> %s;\n", regnames[reg]);
}
else if (flags == REG) /* Register? */
{
move_reg(fp, obj->reg, reg);
//fprintf(fp, "\t@add %s 0 -> %s;\n", regnames[obj->reg], regnames[reg]);
}
else if ((flags & REG) && ((typf & NQ) == FUNKT)) /* Function pointer? */
{
move_reg(fp, obj->reg, reg);
//fprintf(fp, "\t@add %s 0 -> %s;\n", regnames[obj->reg], regnames[reg]);
}
else
{
/* It must be a variable. */
switch (obj->v->storage_class)
{
case AUTO:
case REGISTER: /* Local variable */
if (flags & VARADR)
{
fprintf(fp, "\t@add xp 0%+ld -> %s;\n",
voff(obj), regnames[reg]);
}
else if (flags & REG)
{
move_reg(fp, obj->reg, reg);
//fprintf(fp, "\t@add %s 0 -> %s;\n",
// regnames[obj->reg], regnames[reg]);
}
else
{
zmax offset = voff(obj);
if ((typf & NQ) == CHAR)
fprintf(fp, "\t@loadb xp 0%+ld -> %s;\n",
offset, regnames[reg]);
else
{
if (offset & 1)
{
fprintf(fp, "\t@add xp 0%+ld -> sp;\n", offset);
fprintf(fp, "\t@loadw sp 0 -> %s;\n", regnames[reg]);
}
else
fprintf(fp, "\t@loadw xp 0%+ld -> %s;\n",
offset >> 1, regnames[reg]);
}
}
break;
case STATIC:
case EXTERN: /* Global variable. Implicit dereference,
with the offset in obj->val.vlong. */
/* ...but functions are never dereferenced. */
if ((flags & VARADR) ||
((typf & NQ) == FUNKT))
{
/* Fetch address of object. */
fprintf(fp, "\t@add ");
emit_identifier(fp, obj);
fprintf(fp, " 0%+ld -> %s;\n",
obj->val.vlong, regnames[reg]);
}
else if (strcmp(obj->v->identifier, "__va_start") == 0)
{
fprintf(fp, "\t@add xp 0%+ld -> %s;\n",
find_varargs(), regnames[reg]);
}
else
{
/* Dereference object. */
if ((typf & NQ) == CHAR)
{
fprintf(fp, "\t@loadb ");
emit_identifier(fp, obj);
fprintf(fp, " 0%+ld -> %s;\n",
obj->val.vlong, regnames[reg]);
}
else
{
if (obj->val.vlong & 1)
{
fprintf(fp, "\t@add ");
emit_identifier(fp, obj);
fprintf(fp, " 0%+ld -> sp;\n",
obj->val.vlong);
fprintf(fp, "\t@loadw sp 0 -> %s;\n",
regnames[reg]);
}
else
{
fprintf(fp, "\t@loadw ");
emit_identifier(fp, obj);
fprintf(fp, " 0%+ld -> %s;\n",
obj->val.vlong >> 1, regnames[reg]);
}
}
}
break;
default:
ierror(obj->v->storage_class);
}
}
return;
dereference:
/* Do we need to dereference the thing we just fetched? */
/* Fetch the value to dereference. */
obj->flags &= ~DREFOBJ;
read_reg(fp, obj, POINTER, 0);
if (flags & DREFOBJ)
{
switch (typf & NQ)
{
case CHAR:
fprintf(fp, "\t@loadb sp 0 -> %s;\n",
regnames[reg], regnames[reg]);
break;
case SHORT:
case INT:
case POINTER:
case FUNKT:
fprintf(fp, "\t@loadw sp 0 -> %s;\n",
regnames[reg], regnames[reg]);
break;
default:
ierror(typf & NQ);
}
}
}
/* Returns the zop to use for an input parameter, pushing that parameter onto
* the stack if necessary. */
static void push_value(FILE* fp, struct obj* obj, int typf, struct zop* op)
{
int flags = obj->flags &
(KONST|REG|VAR|DREFOBJ|VARADR);
if (flags == KONST)
{
op->type = ZOP_CONSTANT;
switch (typf & NU)
{
case CHAR: op->val.constant = obj->val.vchar; break;
case UNSIGNED|CHAR: op->val.constant = obj->val.vuchar; break;
case SHORT: op->val.constant = obj->val.vshort; break;
case UNSIGNED|SHORT: op->val.constant = obj->val.vushort; break;
case INT: op->val.constant = obj->val.vint; break;
case UNSIGNED|INT: op->val.constant = obj->val.vuint; break;
case POINTER: ierror(0);
default:
fprintf(fp, "XXX !!! bad konst type %X\n", typf);
}
return;
}
/* The only thing you can do with a function is to take the address of it. */
if ((typf & NQ) == FUNKT)
flags &= ~DREFOBJ & ~VARADR;
/* This is used by the long code. The longop functions can only operate
* on pointers to longs; so if we need to pass in a constant, we have
* to stash it on the stack and return a pointer. */
if (flags == (KONST|VARADR))
{
op->type = ZOP_CONSTANTADDR;
op->val.constant = addconstant(obj->val.vlong);
return;
}
if (flags == REG)
{
debugemit(fp, "! zop reg %d\n", obj->reg);
op->type = ZOP_REG;
op->val.reg = obj->reg;
return;
}
if ((flags == (VAR|REG)) &&
((obj->v->storage_class == AUTO) ||
(obj->v->storage_class == REGISTER)))
{
debugemit(fp, "! zop var reg %d\n", obj->reg);
op->type = ZOP_REG;
op->val.reg = obj->reg;
return;
}
if ((flags == (VAR|VARADR)) &&
(obj->v->storage_class == EXTERN) &&
(obj->v->offset == 0))
{
debugemit(fp, "! zop varaddr extern %s\n", obj->v->identifier);
op->type = ZOP_EXTERN;
op->val.identifier = obj->v->identifier;
return;
}
if ((flags == (VAR|VARADR)) &&
(obj->v->storage_class == STATIC) &&
(obj->v->offset == 0))
{
debugemit(fp, "! zop varaddr static %ld\n", obj->v->offset);
op->type = ZOP_STATIC;
op->val.constant = obj->v->offset;
return;
}
if ((flags & VAR) &&
((obj->v->vtyp->flags & NQ) == FUNKT))
{
if (obj->v->storage_class == EXTERN)
{
op->type = ZOP_EXTERN;
op->val.identifier = obj->v->identifier;
}
else
{
op->type = ZOP_STATIC;
op->val.constant = obj->v->offset;
}
return;
}
read_reg(fp, obj, typf, 0);
op->type = ZOP_STACK;
}
/* Same as push_value(), but returns a zop for the *address* of the object, not
* the object itself. Used a lot by the long code. */
static void push_addrof(FILE* fp, struct obj* obj, int typf, struct zop* op)
{
if (obj->flags & DREFOBJ)
obj->flags &= ~DREFOBJ;
else
obj->flags |= VARADR;
push_value(fp, obj, POINTER, op);
}
/* Returns the zop to use for an output parameter. Unlike push_value, this does
* not emit a pop; that must be done later, if the return parameter is zero. */
static void pop_value(FILE* fp, struct obj* obj, int typf, struct zop* op)
{
int flags = obj->flags &
(KONST|REG|VAR|DREFOBJ|VARADR);
/* We don't even *try* to handle dereferences here. */
if (flags & DREFOBJ)
goto stack;
if (flags == REG)
goto reg;
if ((flags == (VAR|REG)) &&
((obj->v->storage_class == AUTO) ||
(obj->v->storage_class == REGISTER)))
goto reg;
stack:
op->type = ZOP_STACK;
return;
reg:
op->type = ZOP_REG;
op->val.reg = obj->reg;
}
/* Writes code for a zop. */
static void emit_zop(FILE* fp, struct zop* op)
{
switch (op->type)
{
case ZOP_STACK:
fprintf(fp, "sp");
return;
case ZOP_REG:
fprintf(fp, "%s", regnames[op->val.reg]);
return;
case ZOP_CONSTANT:
fprintf(fp, "0%+ld", (zshort)op->val.constant);
return;
case ZOP_EXTERN:
fprintf(fp, "_%s", op->val.identifier);
return;
case ZOP_STATIC:
fprintf(fp, "STATIC_%s_%ld",
modulename, op->val.constant);
return;
case ZOP_CONSTANTADDR:
fprintf(fp, "CONSTANT_%s_%ld",
modulename, op->val.constant);
return;
default:
ierror(op->type);
}
}
/* This is used in conjunction with pop_value(). pop_value() returns a zop that
* represents the return value for a function. If that return value is the
* stack, the value on the stack needs to be written back into memory. That's
* what this function does. */
static void fin_zop(FILE* fp, struct obj* obj, int typf, struct zop* op)
{
switch (op->type)
{
case ZOP_STACK:
write_reg(fp, obj, typf, 0);
return;
case ZOP_REG:
return;
default:
ierror(0);
}
}
/* Emit a basic ADD instruction.
* This routine tests for all the various special cases, of which there are
* many, and attempts to produce optimal code.
*/
static void emit_add(FILE* fp, struct zop* q1, struct zop* q2, struct zop* z)
{
/* Sometimes we get ZOP_REG with reg=0. This actually means the stack. */
if ((q1->type == ZOP_REG) && (q1->val.reg == 0))
q1 = &zop_stack;
if ((q2->type == ZOP_REG) && (q2->val.reg == 0))
q2 = &zop_stack;
if ((z->type == ZOP_REG) && (z->val.reg == 0))
z = &zop_stack;
/* If q2 is a constant and 0, then this might be a register move of
* some kind. */
if ((q2->type == ZOP_CONSTANT) && (q2->val.constant == 0))
{
/* Left is a register? */
if (q1->type == ZOP_REG)
{
/* Right is a register? */
if (z->type == ZOP_REG)
{
/* They're the *same* register? */
if (q1->val.reg == z->val.reg)
{
/* No code need be emitted. */
return;
}
/* Emit a @store instruction. Unfortunately, I
* can't work out the syntax for Inform's
* @store opcode, so we emit a high-level
* assignment instead and let Inform work it
* out. */
fprintf(fp, "\t");
emit_zop(fp, z);
fprintf(fp, " = ");
emit_zop(fp, q1);
fprintf(fp, ";\n");
return;
}
/* Right is the stack? */
if (z->type == ZOP_STACK)
{
/* We're pushing the single parameter onto the
* stack. */
fprintf(fp, "\t@push ");
emit_zop(fp, q1);
fprintf(fp, ";\n");
return;
}
}
/* Left is the stack? */
if (q1->type == ZOP_STACK)
{
/* Right is a register? */
if (z->type == ZOP_REG)
{
/* We're popping the single parameter off the
* stack. */
fprintf(fp, "\t@pull ");
emit_zop(fp, z);
fprintf(fp, ";\n");
return;
}
}
}
/* Fall back on an ordinary @add. */
fprintf(fp, "\t@add ");
emit_zop(fp, q1);
fprintf(fp, " ");
emit_zop(fp, q2);
fprintf(fp, " -> ");
emit_zop(fp, z);
fprintf(fp, ";\n");
}
/* Copy a value from one zop to another. This is not quite as simple as you
* might think, because there are a number of optimisation cases to take into
* account.
*
* NOTE: for simplicity, this function will never emit just a single
* instruction --- the assignment is always done via the stack. FIXME. */
static void move_value(FILE* fp, struct obj* q1o, struct obj* zo, int typf)
{
struct zop q1;
struct zop z;
pop_value(fp, zo, typf, &z);
push_value(fp, q1o, typf, &q1);
debugemit(fp, "! L=%d R=%d\n", q1.type, z.type);
/* In all cases except when push_value() and fin_zop() *both* emit
* code, we need to insert an assignment here. As they only emit code
* in the ZOP_STACK case... */
if ((q1.type != ZOP_STACK) || (z.type != ZOP_STACK))
{
emit_add(fp, &q1, &zop_zero, &z);
#if 0
fprintf(fp, "\t@add ");
emit_zop(fp, &q1);
fprintf(fp, " 0 -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
#endif
}
fin_zop(fp, zo, typf, &z);
}
/* Copy a 32-bit value from one obj to another. */
static void move_long_value(FILE* fp, struct obj* q1, struct obj* z, int typf)
{
int flags = q1->flags &
(KONST|REG|VAR|DREFOBJ|VARADR);
struct zop q1z;
struct zop zz;
if (flags == KONST)
{
int hi = xword(q1->val.vlong, 1);
int lo = xword(q1->val.vlong, 0);
push_addrof(fp, z, POINTER, &zz);
fprintf(fp, "\t@call_vn __long_loadconst ");
emit_zop(fp, &zz);
fprintf(fp, " 0%+ld 0%+ld;\n", (short)hi, (short)lo);
return;
}
push_addrof(fp, z, POINTER, &zz);
push_addrof(fp, q1, POINTER, &q1z);
fprintf(fp, "\t@copy_table ");
emit_zop(fp, &q1z);
fprintf(fp, " ");
emit_zop(fp, &zz);
fprintf(fp, " 4;\n");
}
/* The code generator itself.
* This big, complicated, hairy and scary function does the work to actually
* produce the code. fp is the output stream, ic the beginning of the ic
* chain, func is a pointer to the actual function and stackframe is the size
* of the function's stack frame.
*/
void gen_code(FILE* fp, struct IC *ic, struct Var* func, zmax stackframe)
{
int i;
struct zop q1;
struct zop q2;
struct zop z;
int code, typf; // ...of the IC under consideration
int c,t,lastcomp=0,reg;
function = func;
/* r0..r5 are always used for parameter passing. */
regused[2] = 1;
regused[3] = 1;
regused[4] = 1;
regused[5] = 1;
regused[6] = 1;
regused[7] = 1;
/* This is the offset of the stack frame, relative to the current stack
* pointer. */
stackoffset = stackframe;
/* No parameters pushed yet. */
stackparamadjust = 0;
reflower(fp);
if (func->storage_class == STATIC)
fprintf(fp, "[ STATIC_%s_%ld xp\n", modulename, func->offset);
else
fprintf(fp, "[ _%s xp\n", func->identifier);
/* Tell Inform what registers the function is using. */
for (i=1; i<=MAXR; i++)
{
//fprintf(fp, "! i=%d used %d scratch %d alloc %d\n",
// i, regused[i], regscratch[i], regsa[i]);
if (regused[i] && !regsa[i])
fprintf(fp, "\t%s\n", regnames[i]);
}
fprintf(fp, ";\n");
/* Trace the function name. */
if (g_flags[1] & USEDFLAG)
{
if (func->storage_class == STATIC)
fprintf(fp, "print \"STATIC_%s_%ld^\";\n", modulename, func->offset);
else
fprintf(fp, "print \"_%s^\";\n", func->identifier);
}
/* Adjust stack for locals. */
if (stackframe)
fprintf(fp, "\t@sub xp 0%+ld -> xp;\n", stackframe);
//if (stackoffset)
// fprintf(fp, "\txp = xp - %ld\n", stackframe);
/* Iterate through all ICs. */
for (; dump_ic(fp, ic), ic; ic=ic->next)
{
c=ic->code;t=ic->typf;
code = ic->code;
typf = ic->typf;
/* Do nothing for NOPs. */
if (code == NOP)
continue;
/* Has the stack been adjusted due to a call? */
#if 0
if (stackcalladjustment)
{
if ((code != GETRETURN) &&
(code != FREEREG) &&
(code != ALLOCREG))
{
debugemit(fp, "! stack reset %d %d\n",
stackparamadjust, stackcallparamsize);
fprintf(fp, "\t@add xp %d -> xp;\n",
stackparamadjust+stackcallparamsize);
stackparamadjust = 0;
stackcallparamsize = 0;
stackcalladjustment = 0;
}
}
#endif
#if 0
if(notpopped&&!dontpop){
int flag=0;
if(c==LABEL||c==COMPARE||c==TEST||c==BRA){
fprintf(fp,"\tadd\t%s,#%ld\n",regnames[sp],notpopped);
stackoffset+=notpopped;notpopped=0;
}
}
#endif
/* These opcodes turn into other opcodes. */
switch (code)
{
case SUBPFP:
case SUBIFP:
code = SUB;
break;
case ADDI2P:
code = ADD;
break;
}
/* And now the big opcode switch. */
switch (code)
{
case ALLOCREG: /* Mark register in use */
regs[ic->q1.reg] = 1;
continue;
case FREEREG: /* Mark register not in use */
regs[ic->q1.reg] = 0;
continue;
case LABEL: /* Emit jump target */
fprintf(fp, ".%s%d;\n",
labelprefix, typf);
continue;
case BRA: /* Unconditional jump */
fprintf(fp, "\tjump %s%d;\n",
labelprefix, typf);
continue;
case GETRETURN: /* Read the last function call's return parameter */
switch (typf & NQ)
{
case CHAR:
//if (ic->q2.val.vlong != 1)
// goto copy_struct;
/* fall through */
case SHORT:
case INT:
case POINTER:
write_reg(fp, &ic->z, typf, 2);
break;
/* Ignore the following; the
* front-end will automatically
* pass in an implicit
* parameter to the function
* containing the address of
* the return parameter, so
* GETRETURN ought to be a
* noop. */
case LONG:
case STRUCT:
case VOID:
case ARRAY:
break;
#if 0
copy_struct:
push_addrof(fp, &ic->z, typf, &z);
fprintf(fp, "\t@copy_table xp ");
emit_zop(fp, &z);
fprintf(fp, " %ld;\n", szof(ic->z.v->vtyp));
break;
#endif
default:
ierror(typf & NQ);
}
//fprintf(fp, "\tr0 = ");
//emit_object(fp, &ic->q1, typf);
//fprintf(fp, ";\n");
//write_reg(fp, &ic->z, typf, 2);
continue;
case SETRETURN: /* Set this function's return parameter */
switch (typf & NQ)
{
case CHAR:
//if (ic->q2.val.vlong != 1)
// goto setreturn_copy_struct;
/* fall through */
case SHORT:
case INT:
case POINTER:
read_reg(fp, &ic->q1, typf, 2);
break;
case LONG:
case STRUCT:
case VOID:
case ARRAY:
#if 0
setreturn_copy_struct:
fprintf(fp, "\t@add xp %ld -> sp;\n",
stackoffset);
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@copy_table ");
emit_zop(fp, &q1);
fprintf(fp, " sp %ld;\n", szof(ic->q1.v->vtyp));
break;
#endif
default:
ierror(typf & NQ);
}
//fprintf(fp, "\tr0 = ");
//emit_object(fp, &ic->q1, typf);
//fprintf(fp, ";\n");
continue;
case MINUS: /* Unary minus */
switch (typf & NQ)
{
case CHAR:
case SHORT:
case INT:
push_value(fp, &ic->q1, typf, &q1);
pop_value(fp, &ic->z, typf, &z);
fprintf(fp, "\t@sub 0 ");
emit_zop(fp, &q1);
fprintf(fp, " -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
fin_zop(fp, &ic->z, typf, &z);
break;
case LONG:
push_addrof(fp, &ic->z, typf, &z);
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@call_vn __long_neg ");
emit_zop(fp, &q1);
fprintf(fp, " ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
break;
default:
ierror(0);
}
continue;
case KOMPLEMENT: /* Unary komplement */
/* INFORM BUG! */
/* The @not opcode doesn't work. We have to use a
* wrapper function instead. */
push_value(fp, &ic->q1, typf, &q1);
pop_value(fp, &ic->z, typf, &z);
fprintf(fp, "\t@call_2s __not ");
emit_zop(fp, &q1);
fprintf(fp, " -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
fin_zop(fp, &ic->z, typf, &z);
continue;
case MOVEFROMREG: /* Write a register to memory */
write_reg(fp, &ic->z, typf, ic->q1.reg);
continue;
case MOVETOREG: /* Read a register from memory */
read_reg(fp, &ic->q1, typf, ic->z.reg);
continue;
case ASSIGN: /* Move something to somewhere else */
debugemit(fp, "! ASSIGN size %d typf %d\n", ic->q2.val.vlong, typf & NQ);
switch (typf & NQ)
{
case CHAR:
if (ic->q2.val.vlong != 1)
goto assign_copy_struct;
/* fall through */
case SHORT:
case INT:
case POINTER:
move_value(fp, &ic->q1, &ic->z, typf);
break;
case LONG:
move_long_value(fp, &ic->q1, &ic->z, typf);
break;
case STRUCT:
case VOID:
case ARRAY:
assign_copy_struct:
push_addrof(fp, &ic->z, typf, &z);
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@copy_table ");
emit_zop(fp, &q1);
fprintf(fp, " ");
emit_zop(fp, &z);
fprintf(fp, " 0%+ld;\n", ic->q2.val.vlong);
break;
default:
ierror(typf & NQ);
}
continue;
case ADDRESS: /* Fetch the address of something, always
AUTO or STATIC */
i = voff(&ic->q1);
pop_value(fp, &ic->z, typf, &z);
fprintf(fp, "\t@add xp 0%+ld -> ", i);
emit_zop(fp, &z);
fprintf(fp, ";\n");
fin_zop(fp, &ic->z, typf, &z);
continue;
case PUSH: /* Push a value onto the stack */
fprintf(fp, "\t@sub xp 0%+ld -> xp;\n",
ic->q2.val.vlong);
//stackoffset += ic->q2.val.vlong;
stackparamadjust += ic->q2.val.vlong;
switch (ic->q2.val.vlong)
{
case 1:
push_value(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@storeb xp 0 ");
emit_zop(fp, &q1);
fprintf(fp, ";\n");
break;
case 2:
push_value(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@storew xp 0 ");
emit_zop(fp, &q1);
fprintf(fp, ";\n");
break;
default:
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@copy_table ");
emit_zop(fp, &q1);
fprintf(fp, " xp 0%+ld;\n", ic->q2.val.vlong);
break;
}
continue;
case ADD: /* Add two numbers */
case SUB: /* Subtract two numbers */
case MULT: /* Multiply two numbers */
case DIV: /* Divide two numbers */
case MOD: /* Modulo two numbers */
case OR: /* Bitwise or */
case XOR: /* Bitwise xor */
case AND: /* Bitwise and */
case LSHIFT: /* Shift left */
case RSHIFT: /* Shift right */
switch (typf & NQ)
{
case CHAR:
case SHORT:
case INT:
case POINTER:
/* Second parameter first! */
push_value(fp, &ic->q2, typf, &q2);
if (code == RSHIFT)
{
fprintf(fp, "\t@sub 0 ");
emit_zop(fp, &q2);
fprintf(fp, " -> sp;\n");
q2.type = ZOP_STACK;
}
push_value(fp, &ic->q1, typf, &q1);
pop_value(fp, &ic->z, typf, &z);
//fprintf(fp, "\t");
//emit_object(fp, &ic->z, typf);
//fprintf(fp, " = ");
//emit_object(fp, &ic->q1, typf);
switch (code)
{
case ADD:
fprintf(fp, "\t@add ");
break;
case SUB:
fprintf(fp, "\t@sub ");
break;
case MULT:
fprintf(fp, "\t@mul ");
break;
case DIV:
if (typf & UNSIGNED)
fprintf(fp, "\t@call_vs __unsigned_div ");
else
fprintf(fp, "\t@div ");
break;
case MOD:
if (typf & UNSIGNED)
fprintf(fp, "\t@call_vs __unsigned_mod ");
else
fprintf(fp, "\t@mod ");
break;
case AND:
fprintf(fp, "\t@and ");
break;
case XOR:
fprintf(fp, "\t@call_vs __xor ");
break;
case OR:
fprintf(fp, "\t@or ");
break;
case LSHIFT:
case RSHIFT:
if (typf & UNSIGNED)
fprintf(fp, "\t@log_shift ");
else
fprintf(fp, "\t@art_shift ");
break;
default:
/* Should never get here! */
ierror(0);
}
emit_zop(fp, &q1);
fprintf(fp, " ");
emit_zop(fp, &q2);
fprintf(fp, " -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
fin_zop(fp, &ic->z, typf, &z);
//emit_object(fp, &ic->q2, typf);
break;
case LONG:
/* Destination parameter first! */
push_addrof(fp, &ic->z, typf, &z);
push_addrof(fp, &ic->q2, typf, &q2);
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@call_vn __long_");
switch (code)
{
case ADD:
fprintf(fp, "add");
break;
case SUB:
fprintf(fp, "sub");
break;
case MULT:
fprintf(fp, "mul");
break;
case DIV:
if (typf & UNSIGNED)
fprintf(fp, "unsigned_div");
else
fprintf(fp, "div");
break;
case MOD:
if (typf & UNSIGNED)
fprintf(fp, "unsigned_mod");
else
fprintf(fp, "mod");
break;
case AND:
fprintf(fp, "and");
break;
case XOR:
fprintf(fp, "xor");
break;
case OR:
fprintf(fp, "or");
break;
case LSHIFT:
fprintf(fp, "lsl");
break;
case RSHIFT:
if (typf & UNSIGNED)
fprintf(fp, "lsr");
else
fprintf(fp, "asr");
break;
default:
/* Should never get here! */
ierror(0);
}
fprintf(fp, " ");
emit_zop(fp, &q1);
fprintf(fp, " ");
emit_zop(fp, &q2);
fprintf(fp, " ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
break;
default:
ierror(0);
}
continue;
case CONVERT:
if((q1typ(ic)&NU)==CHAR){
switch (ztyp(ic) & NU)
{
case CHAR:
case UNSIGNED|CHAR:
case UNSIGNED|SHORT:
case UNSIGNED|INT:
case SHORT:
case INT:
push_value(fp, &ic->q1, CHAR, &q1);
pop_value(fp, &ic->z, typf, &z);
fprintf(fp, "\t@log_shift ");
emit_zop(fp, &q1);
fprintf(fp, " 8 -> sp;\n");
fprintf(fp, "\t@art_shift sp 0-8 -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
fin_zop(fp, &ic->z, typf, &z);
break;
case LONG:
push_value(fp, &ic->q1, INT, &q1);
push_addrof(fp, &ic->z, typf, &z);
fprintf(fp, "\t@call_vn __long_fromchar");
emit_zop(fp, &z);
fprintf(fp, " ");
emit_zop(fp, &q1);
fprintf(fp, ";\n");
break;
default:
ierror(0);
}
continue;
}
if((q1typ(ic)&NU)==(UNSIGNED|CHAR)){
switch (ztyp(ic) & NQ)
{
case CHAR:
case SHORT:
case INT:
push_value(fp, &ic->q1, UNSIGNED|CHAR, &q1);
pop_value(fp, &ic->z, typf, &z);
if ((z.type != ZOP_STACK) || (q1.type != ZOP_STACK))
{
emit_add(fp, &q1, &zop_zero, &z);
#if 0
fprintf(fp, "\t@add ");
emit_zop(fp, &q1);
fprintf(fp, " 0 -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
#endif
}
fin_zop(fp, &ic->z, typf, &z);
break;
case LONG:
push_value(fp, &ic->q1, UNSIGNED|CHAR, &q1);
push_addrof(fp, &ic->z, typf, &z);
fprintf(fp, "\t@call_vn __long_fromint");
emit_zop(fp, &z);
fprintf(fp, " 0 ");
emit_zop(fp, &q1);
fprintf(fp, ";\n");
break;
default:
ierror(0);
}
continue;
}
if((q1typ(ic)&NU)==SHORT||(q1typ(ic)&NU)==INT){
switch (ztyp(ic) & NU)
{
case CHAR:
case UNSIGNED|CHAR:
case UNSIGNED|SHORT:
case UNSIGNED|INT:
case SHORT:
case INT:
push_value(fp, &ic->q1, INT, &q1);
pop_value(fp, &ic->z, typf, &z);
if ((z.type != ZOP_STACK) || (q1.type != ZOP_STACK))
{
emit_add(fp, &q1, &zop_zero, &z);
#if 0
fprintf(fp, "\t@add ");
emit_zop(fp, &q1);
fprintf(fp, " 0 -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
#endif
}
fin_zop(fp, &ic->z, typf, &z);
break;
case LONG:
push_value(fp, &ic->q1, INT, &q1);
push_addrof(fp, &ic->z, typf, &z);
fprintf(fp, "\t@call_vn __long_fromint ");
emit_zop(fp, &z);
fprintf(fp, " ");
emit_zop(fp, &q1);
fprintf(fp, ";\n");
break;
case UNSIGNED|LONG:
push_value(fp, &ic->q1, INT, &q1);
push_addrof(fp, &ic->z, typf, &z);
fprintf(fp, "\t@call_vn __long_loadconst ");
emit_zop(fp, &z);
fprintf(fp, " 0 ");
emit_zop(fp, &q1);
fprintf(fp, ";\n");
break;
default:
ierror(typf);
}
continue;
}
if((q1typ(ic)&NU)==(UNSIGNED|SHORT)||(q1typ(ic)&NU)==(UNSIGNED|INT)||(q1typ(ic)&NU)==POINTER){
switch (ztyp(ic) & NQ)
{
case CHAR:
case SHORT:
case INT:
push_value(fp, &ic->q1, INT, &q1);
pop_value(fp, &ic->z, typf, &z);
if ((z.type != ZOP_STACK) || (q1.type != ZOP_STACK))
{
emit_add(fp, &q1, &zop_zero, &z);
#if 0
fprintf(fp, "\t@add ");
emit_zop(fp, &q1);
fprintf(fp, " 0 -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
#endif
}
fin_zop(fp, &ic->z, typf, &z);
break;
case LONG:
push_value(fp, &ic->q1, INT, &q1);
push_addrof(fp, &ic->z, typf, &z);
fprintf(fp, "\t@call_vn __long_loadconst ");
emit_zop(fp, &z);
fprintf(fp, " 0 ");
emit_zop(fp, &q1);
fprintf(fp, ";\n");
break;
#if 0
case SHORT:
case INT:
fprintf(fp, "\t");
emit_object(fp, &ic->z, typf);
fprintf(fp, " = (");
emit_object(fp, &ic->q1, CHAR);
fprintf(fp, ") << 8 >> 8;\n");
break;
#endif
default:
printf("%X\n", typf);
ierror(0);
}
continue;
}
if((q1typ(ic)&NU)==(UNSIGNED|LONG)||(q1typ(ic)&NU)==LONG){
switch (ztyp(ic) & NQ)
{
case CHAR:
push_addrof(fp, &ic->q1, LONG, &q1);
pop_value(fp, &ic->z, typf, &z);
fprintf(fp, "\t@loadb ");
emit_zop(fp, &q1);
fprintf(fp, " 3 -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
fin_zop(fp, &ic->z, typf, &z);
break;
case SHORT:
case INT:
push_addrof(fp, &ic->q1, LONG, &q1);
pop_value(fp, &ic->z, typf, &z);
fprintf(fp, "\t@loadw ");
emit_zop(fp, &q1);
fprintf(fp, " 1 -> ");
emit_zop(fp, &z);
fprintf(fp, ";\n");
fin_zop(fp, &ic->z, typf, &z);
break;
default:
ierror(typf & NQ);
}
continue;
}
case COMPARE:
/* COMPARE is special. The next instruction is
* always a branch. The Z-machine does
* branches in the form:
*
* @j{e,g,l} <var1> <var2> [~]@<label>
*
* However, we don't know what short of branch
* to emit until the next instruction (which is
* the IC for a branch). So we have to stash
* the zops that we're using for the
* compare here, for use later. This is done
* using the globals compare1 and compare2.
*/
switch (typf & NU)
{
case CHAR:
case SHORT:
case INT:
case POINTER:
/* Second parameter first! */
push_value(fp, &ic->q2, typf, &compare2);
push_value(fp, &ic->q1, typf, &compare1);
break;
case UNSIGNED|CHAR:
case UNSIGNED|SHORT:
case UNSIGNED|INT:
/* Because the Z-machine only
* has signed comparisons, we
* need a dodgy algorithm to
* do this, which works as
* follows: in the signed
* domain, 0-7FFF compares
* greater than 8000-FFFF. In
* the unsigned domain, it's
* the other way around. So,
* by flipping the sign bits
* we do the logical
* equivalent of shifting the
* unsigned range up/down by
* 8000 which makes it fit
* the signed range. There.
* Did you understand that?
* Neither did I, the first
* few times it was explained
* to me. */
read_reg(fp, &ic->q2, typf, 0);
fprintf(fp, "\t@add sp $8000 -> sp;\n");
read_reg(fp, &ic->q1, typf, 0);
fprintf(fp, "\t@add sp $8000 -> sp;\n");
compare1.type = ZOP_STACK;
compare2.type = ZOP_STACK;
break;
case LONG:
push_addrof(fp, &ic->q2, typf, &q2);
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@call_vs __long_compare ");
emit_zop(fp, &q1);
fprintf(fp, " ");
emit_zop(fp, &q2);
fprintf(fp, " -> sp;\n");
compare1.type = ZOP_STACK;
compare2.type = ZOP_CONSTANT;
compare2.val.constant = 0;
break;
case UNSIGNED|LONG:
push_addrof(fp, &ic->q2, typf, &q2);
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@call_vs __long_unsigned_compare ");
emit_zop(fp, &q1);
fprintf(fp, " ");
emit_zop(fp, &q2);
fprintf(fp, " -> sp;\n");
compare1.type = ZOP_STACK;
compare2.type = ZOP_CONSTANT;
compare2.val.constant = 0;
break;
default:
ierror(typf & NQ);
}
continue;
case TEST:
/* TEST is a special COMPARE. It takes one
* parameter and always tests it against 0; it
* is guaranteed to be followed by BNE or BEQ.
* */
switch (typf & NQ)
{
case CHAR:
case SHORT:
case INT:
case POINTER:
push_value(fp, &ic->q1, typf, &compare1);
compare2.type = ZOP_CONSTANT;
compare2.val.constant = 0;
break;
case LONG:
push_addrof(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@call_vs __long_compare ");
emit_zop(fp, &q1);
fprintf(fp, " ");
q2.type = ZOP_CONSTANTADDR;
q2.val.constant = addconstant(0);
emit_zop(fp, &q2);
fprintf(fp, " -> sp;\n", i);
compare1.type = ZOP_STACK;
compare2.type = ZOP_CONSTANT;
compare2.val.constant = 0;
break;
default:
ierror(typf & NQ);
}
continue;
case BEQ:
case BNE:
case BLT:
case BGE:
case BLE:
case BGT:
{
static int branchlabel = 0;
fprintf(fp, "\t@j");
switch (code)
{
case BNE:
case BEQ: fprintf(fp, "e "); break;
case BLT:
case BGE: fprintf(fp, "l "); break;
case BGT:
case BLE: fprintf(fp, "g "); break;
}
emit_zop(fp, &compare1);
fprintf(fp, " ");
emit_zop(fp, &compare2);
fprintf(fp, " ?");
if (g_flags[3] & USEDFLAG)
{
if (!((code == BNE) || (code == BGE) || (code == BLE)))
fprintf(fp, "~");
fprintf(fp, "LL%d;\n", branchlabel);
fprintf(fp, "\tjump %s%d;\n", labelprefix, typf);
fprintf(fp, ".LL%d;\n", branchlabel++);
}
else
{
if ((code == BNE) || (code == BGE) || (code == BLE))
fprintf(fp, "~");
fprintf(fp, "%s%d;\n", labelprefix, typf);
}
continue;
}
case CALL:
{
#if 0
/* Calculate the amount of stack to reserve for
* the return parameter. ints and smaller go in
* the return register. */
stackcallparamsize = szof(ic->q1.v->vtyp->next);
if (stackcallparamsize <= sizetab[INT])
stackcallparamsize = 0;
if (stackcallparamsize)
fprintf(fp, "\t@sub xp %d -> xp;\n",
stackcallparamsize);
#endif
/* Is this actually an inline assembly function? */
if ((ic->q1.flags & VAR) &&
ic->q1.v->fi &&
ic->q1.v->fi->inline_asm)
{
/* Yes. Emit the assembly code. */
fprintf(fp, "%s", ic->q1.v->fi->inline_asm);
}
else
{
/* No; so emit a call. */
push_value(fp, &ic->q1, typf, &q1);
fprintf(fp, "\t@call_vs2 ");
emit_zop(fp, &q1);
fprintf(fp, " xp r0 r1 r2 r3 r4 r5 -> r0;\n");
}
//stackcalladjustment = 1;
/* If any parameters have been pushed, adjust
* the stack to pop them. */
if (stackparamadjust)
{
fprintf(fp, "\t@add xp 0%+ld -> xp;\n",
stackparamadjust);
//stackoffset -= stackparamadjust;
stackparamadjust = 0;
}
continue;
}
default:
ierror(code);
}
}
/* We really ought to tidy the stack up; but there's no need, because
* the old value of xp will be restored when the function exits. */
//if (stackframe)
// fprintf(fp, "\t@add xp %ld -> xp;\n", stackframe);
fprintf(fp, "\t@ret r0;\n");
fprintf(fp, "]\n");
// function_bottom(fp, func, loff);
}
int shortcut(int code, int typ)
{
return(0);
}
// Add a constant to the constant pool.
static int addconstant(zmax value)
{
struct constant* c;
/* Check to see if the constant's already in the pool. */
c = constantlist;
while (c)
{
if (c->value == value)
return c->id;
c = c->next;
}
/* It's not; add it. */
c = malloc(sizeof(struct constant));
c->next = constantlist;
c->id = constantnum++;
c->value = value;
constantlist = c;
return c->id;
}
void cleanup_cg(FILE *fp)
{
struct fixup* fixup = fixuplist;
/* Have we actually emitted anything? */
if (!fp)
return;
reflower(fp);
/* Emit the constant pool. */
{
struct constant* constant = constantlist;
while (constant)
{
fprintf(fp, "Array CONSTANT_%s_%ld -->\n",
modulename, constant->id);
fprintf(fp, " 0%+ld 0%+ld;\n",
xword(constant->value, 1),
xword(constant->value, 0));
constant = constant->next;
}
}
/* Emit the code to initialise the data area. */
{
struct fixup* fixup = fixuplist;
fprintf(fp, "[ __init_vars_%s;\n", modulename);
while (fixup)
{
fprintf(fp, "\t@add 0%+ld ", fixup->offset);
switch (fixup->value.type)
{
case STATIC:
fprintf(fp, "STATIC_%s_%ld -> sp;\n",
modulename, fixup->value.val.number);
break;
case EXTERN:
fprintf(fp, "_%s -> sp;\n",
fixup->value.val.identifier);
break;
default:
ierror(0);
}
switch (fixup->identifier.type)
{
case STATIC:
fprintf(fp, "\t@storew STATIC_%s_%ld 0%+ld sp;\n",
modulename, fixup->identifier.val.number,
fixup->identifier.offset);
break;
case EXTERN:
fprintf(fp, "\t@storew _%s 0%+ld sp;\n",
fixup->identifier.val.identifier,
fixup->identifier.offset);
break;
default:
ierror(0);
}
fixup = fixup->next;
}
fprintf(fp, "];\n");
}
}
/* The code generator's asking us to pass a parameter in a register. */
int reg_parm(struct reg_handle *rh, struct Typ *typ, int vararg, struct Typ *ft)
{
/* Vararg parameters never go in registers. */
if (vararg)
return 0;
/* Will the parameter fit? */
if (sizetab[typ->flags & NQ] > 2)
return 0;
/* Still enough registers? */
if (rh->reg >= NUM_REGPARMS+USERREG)
return 0;
return (rh->reg++);
}
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;
}
void init_db(FILE *f)
{
}
void cleanup_db(FILE *f)
{
}