You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1331 lines
28 KiB
1331 lines
28 KiB
/* code.c */ |
|
|
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <assert.h> |
|
#include <err.h> |
|
#include <limits.h> |
|
#include "types.h" |
|
#include "ast.h" |
|
#include "codegen.h" |
|
#include "code.h" |
|
#include "mem.h" |
|
#include "hash.h" |
|
|
|
|
|
struct var_desc { |
|
int type; |
|
int addr; |
|
int flags; |
|
char *name; |
|
}; |
|
|
|
struct fn_desc { |
|
int type; |
|
int num; |
|
char *name; |
|
}; |
|
|
|
struct label { |
|
int value; |
|
char *name; |
|
}; |
|
|
|
#define DEBUG 1 |
|
#define MOREDEBUG 0 |
|
|
|
#define MAX_LABELS 4096 |
|
#define MAX_CONSTS 4096 |
|
|
|
struct label *labels = NULL; |
|
int nlabels = 0; |
|
int maxlabels = 0; |
|
|
|
int *consts = NULL; |
|
int nconsts = 0; |
|
int maxconsts = 0; |
|
|
|
FILE *binout = NULL; |
|
|
|
int sp = 0; |
|
int consume = 1; |
|
int real = 0; |
|
int switchmin; |
|
int *switchlabels = NULL; |
|
int switchdefault; |
|
int breaklabel; |
|
|
|
int output_asm = 1; |
|
|
|
void output_int(int); |
|
void output_byte(int); |
|
void output_string(char *); |
|
int get_label(int); |
|
|
|
#define IF_ISINSTR 0x01 |
|
#define IF_ISLABEL 0x02 |
|
#define IF_ADDLABEL 0x04 |
|
#define IF_ADDCONST 0x08 |
|
#define IF_HASOPERAND 0x10 |
|
#define IF_HASSTRING 0x20 |
|
#define IF_SUBPC 0x40 |
|
#define IF_FIXEDSIZE 0x80 |
|
|
|
struct instr { |
|
struct instr *next; |
|
int opcode; |
|
int operand; |
|
char *string; |
|
int label; |
|
int constant; |
|
int bytes; |
|
int flags; |
|
}; |
|
|
|
struct instr *instr_head = NULL; |
|
struct instr *instr_tail = NULL; |
|
|
|
#define VAR_LOCAL 1 |
|
#define VAR_GLOBAL 2 |
|
#define VAR_ARRAY 4 |
|
|
|
#define VAR_ARG 1 |
|
#define VAR_REAL 2 |
|
|
|
#define FN_INT 1 |
|
#define FN_STR 2 |
|
#define FN_LOCAL 3 |
|
|
|
int fnconst; |
|
int nargs; |
|
int local_variable_count = -1; |
|
int local_arg_count = -1; |
|
int function_count = -1; |
|
int constant_count = -1; |
|
|
|
#define HASHSIZE 512 |
|
#define HASHMASK (HASHSIZE - 1) |
|
|
|
struct hashentry *varhash[HASHSIZE]; |
|
struct hashentry *fnhash[HASHSIZE]; |
|
struct hashentry *constanthash[HASHSIZE]; |
|
|
|
void compiler_error(char *str) |
|
{ |
|
errx(1, str); |
|
} |
|
|
|
int count_local_variables(void) |
|
{ |
|
return local_variable_count; |
|
} |
|
|
|
void dump_local_variables_action(struct hashentry *ptr) |
|
{ |
|
printf("%%var %s %d %d\n", ptr->name, ptr->value, ptr->flags); |
|
} |
|
|
|
void dump_local_variables(void) |
|
{ |
|
hash_iterate(varhash, dump_local_variables_action); |
|
} |
|
|
|
void output_functions_action(struct hashentry *ptr) |
|
{ |
|
int len, pad; |
|
|
|
if (ptr->flags == FN_LOCAL) { |
|
len = strlen(ptr->name) + 1 + 16; |
|
pad = (4 - (len % 4)) % 4; |
|
output_int(len+pad); |
|
output_int(0); /* type */ |
|
output_int(get_label(ptr->value) + 28); |
|
output_int(0); /* nargs */ |
|
output_string(ptr->name); |
|
output_byte(0); /* terminator */ |
|
while (pad--) |
|
output_byte(0); |
|
} |
|
} |
|
|
|
void output_functions(void) |
|
{ |
|
hash_iterate(fnhash, output_functions_action); |
|
|
|
output_int(0); /* terminator */ |
|
} |
|
|
|
void reset_local_variables(void) |
|
{ |
|
if (local_variable_count >= 0) |
|
hash_clear(varhash); |
|
|
|
hash_init(varhash); |
|
|
|
local_variable_count = 0; |
|
local_arg_count = 0; |
|
} |
|
|
|
void reset_functions(void) |
|
{ |
|
if (function_count >= 0) |
|
hash_clear(fnhash); |
|
|
|
hash_init(fnhash); |
|
|
|
function_count = 0; |
|
} |
|
|
|
void reset_constants(void) |
|
{ |
|
if (constant_count >= 0) |
|
hash_clear(constanthash); |
|
|
|
hash_init(constanthash); |
|
|
|
constant_count = 0; |
|
} |
|
|
|
int lookup_variable(ast *node, struct var_desc *var, int create, int flags) |
|
{ |
|
char *varname; |
|
struct hashentry *hashptr; |
|
|
|
assert((node->tag == var_ast) || ((node->tag == array_ast) && |
|
(node->info.node.tag = kind_array))); |
|
|
|
var->type = 0; |
|
|
|
if (node->tag == var_ast) |
|
varname = node->info.variable; |
|
else { |
|
varname = node->info.node.head->elem->info.variable; |
|
var->type |= VAR_ARRAY; |
|
} |
|
|
|
assert(varname != NULL); |
|
assert(varname[0] != '\0'); |
|
|
|
var->name = varname; |
|
|
|
if (local_variable_count < 0) |
|
reset_local_variables(); |
|
|
|
|
|
switch (varname[0]) { |
|
case '$': |
|
var->type |= VAR_GLOBAL; |
|
var->flags = 0; |
|
break; |
|
case '%': |
|
flags |= VAR_REAL; |
|
default: |
|
var->type |= VAR_LOCAL; |
|
hashptr = hash_lookup(varhash, varname, create); |
|
|
|
if (!hashptr) |
|
return 0; |
|
|
|
if (hashptr->name == NULL) { |
|
hashptr->name = varname; |
|
hashptr->flags = flags; |
|
if (flags & VAR_ARG) |
|
hashptr->value = local_arg_count++; |
|
else |
|
hashptr->value = local_variable_count++; |
|
} |
|
|
|
var->addr = hashptr->value; |
|
var->flags = hashptr->flags; |
|
|
|
printf("var %s, flags %d\n", varname, var->flags); |
|
fflush(stdout); |
|
break; |
|
} |
|
return 1; |
|
} |
|
|
|
int lookup_function(ast *node, struct fn_desc *fn) |
|
{ |
|
char *fnname = node->info.string; |
|
struct hashentry *hashptr; |
|
|
|
fn->name = fnname; |
|
|
|
if (function_count < 0) |
|
reset_functions(); |
|
|
|
hashptr = hash_lookup(fnhash, fnname, 0); |
|
|
|
if (!hashptr) |
|
return 0; |
|
|
|
fn->type = hashptr->flags; |
|
fn->num = hashptr->value; |
|
|
|
return 1; |
|
} |
|
|
|
#define lookup_constant(node, constant) \ |
|
lookup_constant_string(node->info.string, constant) |
|
|
|
int lookup_constant_string(char *constantname, int *constant) |
|
{ |
|
struct hashentry *hashptr; |
|
|
|
if (constant_count < 0) |
|
reset_constants(); |
|
|
|
hashptr = hash_lookup(constanthash, constantname, 0); |
|
|
|
if (!hashptr) |
|
return 0; |
|
|
|
*constant = hashptr->value; |
|
|
|
return 1; |
|
} |
|
|
|
void create_function(char *fnname, int type, int num) |
|
{ |
|
struct hashentry *hashptr; |
|
|
|
if (function_count < 0) |
|
reset_functions(); |
|
|
|
hashptr = hash_lookup(fnhash, fnname, 1); |
|
|
|
if (hashptr->name) |
|
compiler_error("Function already exists"); |
|
|
|
if (output_asm) { |
|
switch (type) { |
|
case FN_INT: |
|
printf("%%fnint %s %d\n", fnname, num); |
|
break; |
|
case FN_STR: |
|
printf("%%fnext %s\n", fnname); |
|
break; |
|
case FN_LOCAL: |
|
printf("%%fnlocal %s lab_%d\n", fnname, num); |
|
break; |
|
} |
|
} |
|
|
|
hashptr->name = fnname; |
|
hashptr->flags = type; |
|
hashptr->value = num; |
|
} |
|
|
|
void create_constant(char *constantname, int value) |
|
{ |
|
struct hashentry *hashptr; |
|
|
|
if (constant_count < 0) |
|
reset_constants(); |
|
|
|
hashptr = hash_lookup(constanthash, constantname, 1); |
|
|
|
if (hashptr->name) |
|
compiler_error("Constant already defined"); |
|
|
|
hashptr->name = constantname; |
|
hashptr->value = value; |
|
} |
|
|
|
int newlabel(char *name) |
|
{ |
|
if (nlabels >= maxlabels) { |
|
/* We need to allocate more label space */ |
|
if (nlabels != 0) { |
|
/* Error for now */ |
|
compiler_error("Too many labels"); |
|
} |
|
maxlabels = MAX_LABELS; |
|
labels = safe_malloc(maxlabels * sizeof(struct label)); |
|
} |
|
labels[nlabels].value = -1; |
|
labels[nlabels].name = name; |
|
|
|
return nlabels++; |
|
} |
|
|
|
int newconst(void) |
|
{ |
|
if (nconsts >= maxconsts) { |
|
/* We need to allocate more const space */ |
|
if (nconsts != 0) { |
|
/* Error for now */ |
|
compiler_error("Too many consts"); |
|
} |
|
maxconsts = MAX_CONSTS; |
|
consts = safe_malloc(maxconsts * sizeof(int)); |
|
} |
|
consts[nconsts] = 0; |
|
|
|
return nconsts++; |
|
} |
|
|
|
void set_label(int label, int value) |
|
{ |
|
labels[label].value = value; |
|
} |
|
|
|
void set_const(int constant, int value) |
|
{ |
|
if (output_asm) |
|
printf("%%const const_%d %d\n", constant, value); |
|
consts[constant] = value; |
|
} |
|
|
|
int get_label(int label) |
|
{ |
|
return labels[label].value; |
|
} |
|
|
|
int get_const(int constant) |
|
{ |
|
return consts[constant]; |
|
} |
|
|
|
#define INSTR_ADD(i) i->next = NULL; \ |
|
if (instr_tail) \ |
|
instr_tail->next = i; \ |
|
else \ |
|
instr_head = i; \ |
|
instr_tail = i |
|
|
|
|
|
void emit_simple_instr(int opcode) |
|
{ |
|
struct instr *i; |
|
|
|
i = safe_malloc(sizeof(struct instr)); |
|
i->opcode = opcode; |
|
i->bytes = 1; |
|
i->flags = IF_ISINSTR; |
|
|
|
if (output_asm) |
|
printf("\t%s\n", INSTR_NAME(opcode)); |
|
|
|
INSTR_ADD(i); |
|
} |
|
|
|
void emit_instr_immediate(int opcode, int operand) |
|
{ |
|
struct instr *i; |
|
|
|
i = safe_malloc(sizeof(struct instr)); |
|
i->opcode = opcode; |
|
i->operand = operand; |
|
i->bytes = 2; /* Start at 2 */ |
|
i->flags = IF_ISINSTR | IF_HASOPERAND; |
|
|
|
if (output_asm) |
|
printf("\t%s %d\n", INSTR_NAME(opcode), operand); |
|
|
|
INSTR_ADD(i); |
|
} |
|
|
|
void emit_instr_string(int opcode, char *string) |
|
{ |
|
struct instr *i; |
|
|
|
i = safe_malloc(sizeof(struct instr)); |
|
i->opcode = opcode; |
|
i->string = string; |
|
i->bytes = strlen(string) + 2; |
|
i->flags = IF_ISINSTR | IF_HASSTRING; |
|
|
|
if (output_asm) |
|
printf("\t%s \"%s\"\n", INSTR_NAME(opcode), string); |
|
|
|
INSTR_ADD(i); |
|
} |
|
|
|
void emit_instr_imm_const(int opcode, int operand, int constant) |
|
{ |
|
struct instr *i; |
|
|
|
i = safe_malloc(sizeof(struct instr)); |
|
i->opcode = opcode; |
|
i->operand = operand; |
|
i->constant = constant; |
|
i->bytes = 2; /* Start at 2 */ |
|
i->flags = IF_ISINSTR | IF_HASOPERAND | IF_ADDCONST; |
|
|
|
if (output_asm) |
|
printf("\t%s %d + const_%d\n", INSTR_NAME(opcode), operand, |
|
constant); |
|
|
|
INSTR_ADD(i); |
|
} |
|
|
|
void emit_instr_label(int opcode, int label) |
|
{ |
|
struct instr *i; |
|
|
|
i = safe_malloc(sizeof(struct instr)); |
|
i->opcode = opcode; |
|
i->operand = 0; |
|
i->label = label; |
|
i->bytes = 2; /* Start at 2 */ |
|
i->flags = IF_ISINSTR | IF_HASOPERAND | IF_ADDLABEL | IF_SUBPC; |
|
|
|
if (output_asm) |
|
printf("\t%s lab_%d\n", INSTR_NAME(opcode), label); |
|
|
|
INSTR_ADD(i); |
|
} |
|
|
|
void emit_instr_label_sized(int opcode, int label, int size) |
|
{ |
|
struct instr *i; |
|
|
|
i = safe_malloc(sizeof(struct instr)); |
|
i->opcode = opcode; |
|
i->operand = 0; |
|
i->label = label; |
|
i->bytes = size; |
|
i->flags = IF_ISINSTR | IF_HASOPERAND | IF_ADDLABEL | IF_SUBPC |
|
| IF_FIXEDSIZE; |
|
|
|
if (output_asm) |
|
printf("\t%s lab_%d\n", INSTR_NAME(opcode), label); |
|
|
|
INSTR_ADD(i); |
|
} |
|
|
|
void emit_label(int label) |
|
{ |
|
struct instr *i; |
|
|
|
i = safe_malloc(sizeof(struct instr)); |
|
i->flags = IF_ISLABEL; |
|
i->label = label; |
|
i->bytes = 0; |
|
|
|
if (output_asm) |
|
printf("lab_%d:\n", label); |
|
|
|
INSTR_ADD(i); |
|
} |
|
|
|
void codegen(ast *node) |
|
{ |
|
struct var_desc var; |
|
struct fn_desc fn; |
|
int len; |
|
ast_list *ptr; |
|
int savedsp; |
|
int savedreal; |
|
int label, label2; |
|
int localconsume; |
|
int num; |
|
int max; |
|
int range; |
|
int savedswitchmin; |
|
int *savedswitchlabels; |
|
int savedswitchdefault; |
|
int savedbreaklabel; |
|
int switchend; |
|
int stackfixlabel; |
|
int hasdefault; |
|
int i; |
|
|
|
union { |
|
int i; |
|
float f; |
|
} conv; |
|
|
|
#if DEBUG |
|
printf("entering codegen with node %p, sp = %d, tag = %d\n", node, sp, node->tag); |
|
if (node->tag == node_ast) |
|
printf("kind == %d\n", node->info.node.tag); |
|
fflush(stdout); |
|
#endif |
|
|
|
switch (node->tag) { |
|
case int_ast: |
|
emit_instr_immediate(OP_PUSH, node->info.integer); |
|
real = 0; |
|
sp++; |
|
break; |
|
case real_ast: |
|
conv.f = node->info.real; /* XXX Not exactly MI */ |
|
emit_instr_immediate(OP_PUSH, conv.i); |
|
real = 1; |
|
sp++; |
|
break; |
|
case casenum_ast: |
|
num = node->info.integer; |
|
num = num - switchmin; |
|
emit_label(switchlabels[num]); |
|
break; |
|
case casevar_ast: |
|
lookup_constant(node, &num); |
|
num = num - switchmin; |
|
emit_label(switchlabels[num]); |
|
break; |
|
case var_ast: |
|
case array_ast: |
|
if (!lookup_variable(node, &var, 0, 0)) { |
|
/* |
|
* If the var doesn't exist, check for a constant |
|
* If we find one, great. Deal with it, and get |
|
* on with the rest of the tree. If not, bail. |
|
*/ |
|
if (lookup_constant(node, &num)) { |
|
emit_instr_immediate(OP_PUSH, num); |
|
sp++; |
|
real = (*node->info.string == '%'); |
|
break; |
|
} |
|
printf("error: variable '%s' used before assignment\n", |
|
var.name); |
|
exit(EXIT_FAILURE); |
|
} |
|
// printf("sp = %d\n", sp); |
|
if (var.flags & VAR_REAL) |
|
real = 1; |
|
else |
|
real = 0; |
|
switch (var.type) { |
|
case VAR_LOCAL: |
|
if (var.flags & VAR_ARG) { |
|
/* |
|
* Arguments on the stack directly above |
|
* local variables, and then return address |
|
*/ |
|
emit_instr_imm_const(OP_LOAD, |
|
sp + 1 + nargs - var.addr, fnconst); |
|
} else { |
|
emit_instr_imm_const(OP_LOAD, sp - var.addr - 1, fnconst); |
|
} |
|
sp++; |
|
break; |
|
case VAR_LOCAL | VAR_ARRAY: |
|
compiler_error("Local arrays not supported"); |
|
break; |
|
case VAR_GLOBAL: |
|
emit_instr_string(OP_PUSHSTR, var.name); |
|
emit_instr_immediate(OP_CALLNUM, INTFN_GLOBAL_LOAD); |
|
sp++; |
|
break; |
|
case VAR_GLOBAL | VAR_ARRAY: |
|
codegen(node->info.node.head->next->elem); |
|
emit_instr_string(OP_PUSHSTR, var.name); |
|
emit_instr_immediate(OP_CALLNUM, |
|
INTFN_GLOBAL_ARRAY_LOAD); |
|
/* No sp++ because codegen has already done it */ |
|
break; |
|
} |
|
break; |
|
case str_ast: |
|
len = strlen(node->info.string); |
|
emit_instr_string(OP_PUSHSTR, node->info.string); |
|
sp+=(len+sizeof(stkentry)-1) / sizeof(stkentry) + 1; |
|
break; |
|
case node_ast: |
|
switch (node->info.node.tag) { |
|
case kind_fndefint: |
|
assert(sp == 0); |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
|
|
create_function(node->info.node.head->elem->info.string, |
|
FN_INT, |
|
node->info.node.head->next->elem->info.integer); |
|
break; |
|
case kind_constant: |
|
assert(sp == 0); |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
|
|
create_constant(node->info.node.head->elem->info.string, |
|
node->info.node.head->next->elem->info.integer); |
|
break; |
|
case kind_fndefext: |
|
assert(sp == 0); |
|
assert(node->info.node.head != NULL); |
|
|
|
create_function(node->info.node.head->elem->info.string, |
|
FN_STR, 0); |
|
break; |
|
case kind_fndef: |
|
assert(sp == 0); |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
assert(node->info.node.head->next->next != NULL); |
|
|
|
label = |
|
newlabel(node->info.node.head->elem->info.string); |
|
emit_label(label); |
|
/* Set up the arguments in the symbol table */ |
|
nargs = 0; |
|
for (ptr = |
|
node->info.node.head->next->elem->info.node.head; |
|
ptr; ptr = ptr->next) { |
|
lookup_variable(ptr->elem, &var, 1, VAR_ARG); |
|
nargs++; |
|
} |
|
|
|
/* Should store the number of args somewhere */ |
|
create_function(node->info.node.head->elem->info.string, |
|
FN_LOCAL, label); |
|
|
|
/* Allocate space on stack for local variables */ |
|
fnconst = newconst(); |
|
emit_instr_imm_const(OP_ALLOC, 0, fnconst); |
|
/* Evaluate the contents */ |
|
codegen(node->info.node.head->next->next->elem); |
|
/* Restore the stack and return */ |
|
emit_instr_imm_const(OP_POP, 0, fnconst); |
|
emit_simple_instr(OP_RET); |
|
/* And set the constant */ |
|
set_const(fnconst, count_local_variables()); |
|
if (output_asm) |
|
dump_local_variables(); |
|
reset_local_variables(); |
|
break; |
|
case kind_assign: |
|
/* We have a list with an lvalue and an expression */ |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
assert(node->info.node.head->next->next == NULL); |
|
|
|
ptr = node->info.node.head->elem->info.node.head; |
|
|
|
consume = 0; |
|
while (ptr) { |
|
consume++; |
|
ptr = ptr->next; |
|
} |
|
if (consume < 1) |
|
consume = 1; |
|
|
|
/* Evaluate the expression first */ |
|
codegen(node->info.node.head->next->elem); |
|
|
|
ptr = node->info.node.head->elem->info.node.head; |
|
if (ptr == NULL) { |
|
/* Consume expression */ |
|
emit_instr_immediate(OP_POP, 1); |
|
sp--; |
|
} |
|
while (ptr) { |
|
lookup_variable(ptr->elem, &var, 1, 0); |
|
switch (var.type) { |
|
case VAR_LOCAL: |
|
emit_instr_imm_const(OP_STORE, |
|
sp - var.addr - 2, fnconst); |
|
sp--; |
|
break; |
|
case VAR_LOCAL | VAR_ARRAY: |
|
compiler_error("Local arrays not supported"); |
|
break; |
|
case VAR_GLOBAL: |
|
emit_instr_string(OP_PUSHSTR, var.name); |
|
emit_instr_immediate(OP_CALLNUM, |
|
INTFN_GLOBAL_STORE); |
|
sp--; |
|
break; |
|
case VAR_GLOBAL | VAR_ARRAY: |
|
codegen(ptr->elem->info.node.head->next->elem); |
|
emit_instr_string(OP_PUSHSTR, var.name); |
|
emit_instr_immediate(OP_CALLNUM, |
|
INTFN_GLOBAL_ARRAY_STORE); |
|
sp--; |
|
sp--; |
|
break; |
|
} |
|
ptr = ptr->next; |
|
} |
|
break; |
|
case kind_list: |
|
for (ptr = node->info.node.head; ptr; ptr = ptr->next) |
|
codegen(ptr->elem); |
|
break; |
|
case kind_call: |
|
/* We have a function name and a list of arguments */ |
|
assert(node->info.node.head != NULL); |
|
|
|
savedsp = sp; |
|
localconsume = consume; |
|
consume = 1; |
|
|
|
// printf("savedsp = %d\n", savedsp); |
|
|
|
/* Evaluate the arguments first */ |
|
for (ptr = |
|
node->info.node.head->next->elem->info.node.head; |
|
ptr; ptr = ptr->next) { |
|
codegen(ptr->elem); |
|
} |
|
|
|
// printf("sp = %d\n", sp); |
|
|
|
emit_instr_immediate(OP_ALLOC, 1); |
|
sp++; |
|
|
|
if (!lookup_function(node->info.node.head->elem, &fn)) |
|
compiler_error("Function not found"); |
|
switch (fn.type) { |
|
case FN_INT: |
|
emit_instr_immediate(OP_CALLNUM, fn.num); |
|
break; |
|
case FN_STR: |
|
emit_instr_string(OP_CALLSTR, fn.name); |
|
break; |
|
case FN_LOCAL: |
|
emit_instr_label(OP_BL, fn.num); |
|
break; |
|
} |
|
// printf("sp = %d, savedsp = %d\n", sp, savedsp); |
|
if (sp > (savedsp + localconsume)) { |
|
emit_instr_immediate(OP_STORE, sp - savedsp |
|
- localconsume - 1); |
|
if (sp > (savedsp + localconsume + 1)) { |
|
emit_instr_immediate(OP_POP, |
|
sp-savedsp - localconsume - 1); |
|
} |
|
} |
|
// printf("localconsume = %d\n", localconsume); |
|
sp = savedsp + localconsume; |
|
break; |
|
|
|
#define BINOP assert(node->info.node.head != NULL); \ |
|
assert(node->info.node.head->next != NULL); \ |
|
assert(node->info.node.head->next->next == NULL); \ |
|
codegen(node->info.node.head->elem); \ |
|
savedreal = real; \ |
|
codegen(node->info.node.head->next->elem); \ |
|
if (savedreal != real) \ |
|
compiler_error("Type mismatch"); |
|
|
|
case op_plus: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_ADD); |
|
else |
|
emit_simple_instr(OP_ADD); |
|
sp--; |
|
break; |
|
case op_minus: |
|
assert(node->info.node.head != NULL); |
|
if (node->info.node.head->next == NULL) { |
|
emit_instr_immediate(OP_PUSH, 0); |
|
sp++; |
|
} |
|
codegen(node->info.node.head->elem); |
|
savedreal = real; |
|
if (node->info.node.head->next != NULL) |
|
codegen(node->info.node.head->next->elem); |
|
if (savedreal != real) |
|
compiler_error("Type mismatch"); |
|
|
|
if (real) |
|
emit_simple_instr(FLOP_SUB); |
|
else |
|
emit_simple_instr(OP_SUB); |
|
sp--; |
|
break; |
|
case op_times: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_MUL); |
|
else |
|
emit_simple_instr(OP_MUL); |
|
sp--; |
|
break; |
|
case op_divide: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_DIV); |
|
else |
|
emit_simple_instr(OP_DIV); |
|
sp--; |
|
break; |
|
case op_gt: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_GT); |
|
else |
|
emit_simple_instr(OP_GT); |
|
real = 0; |
|
sp--; |
|
break; |
|
case op_lt: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_LT); |
|
else |
|
emit_simple_instr(OP_LT); |
|
real = 0; |
|
sp--; |
|
break; |
|
case op_ge: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_GE); |
|
else |
|
emit_simple_instr(OP_GE); |
|
real = 0; |
|
sp--; |
|
break; |
|
case op_le: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_LE); |
|
else |
|
emit_simple_instr(OP_LE); |
|
real = 0; |
|
sp--; |
|
break; |
|
case op_eq: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_EQ); |
|
else |
|
emit_simple_instr(OP_EQ); |
|
real = 0; |
|
sp--; |
|
break; |
|
case op_ne: |
|
BINOP |
|
if (real) |
|
emit_simple_instr(FLOP_NE); |
|
else |
|
emit_simple_instr(OP_NE); |
|
real = 0; |
|
sp--; |
|
break; |
|
case op_and: |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
assert(node->info.node.head->next->next == NULL); |
|
label = newlabel(NULL); |
|
label2 = newlabel(NULL); |
|
codegen(node->info.node.head->elem); |
|
if (real) |
|
compiler_error("Type mismatch"); |
|
emit_instr_label(OP_BZ, label); |
|
sp--; |
|
codegen(node->info.node.head->next->elem); |
|
if (real) |
|
compiler_error("Type mismatch"); |
|
emit_instr_label(OP_BZ, label); |
|
emit_instr_immediate(OP_PUSH, 1); |
|
emit_instr_label(OP_B, label2); |
|
emit_label(label); |
|
emit_instr_immediate(OP_PUSH, 0); |
|
emit_label(label2); |
|
break; |
|
case op_or: |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
assert(node->info.node.head->next->next == NULL); |
|
label = newlabel(NULL); |
|
label2 = newlabel(NULL); |
|
codegen(node->info.node.head->elem); |
|
if (real) |
|
compiler_error("Type mismatch"); |
|
emit_instr_label(OP_BNZ, label); |
|
sp--; |
|
codegen(node->info.node.head->next->elem); |
|
if (real) |
|
compiler_error("Type mismatch"); |
|
emit_instr_label(OP_BNZ, label); |
|
emit_instr_immediate(OP_PUSH, 0); |
|
emit_instr_label(OP_B, label2); |
|
emit_label(label); |
|
emit_instr_immediate(OP_PUSH, 1); |
|
emit_label(label2); |
|
break; |
|
case stmt_if: |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
if (node->info.node.head->next->next) |
|
assert(node->info.node.head->next->next->next |
|
== NULL); |
|
|
|
/* Evaluate condition first */ |
|
codegen(node->info.node.head->elem); |
|
assert(real == 0); |
|
label = newlabel(NULL); /* else */ |
|
label2 = newlabel(NULL); /* end of else */ |
|
emit_instr_label(OP_BZ, label); |
|
sp--; |
|
savedsp = sp; |
|
/* Then evaluate the "if" code */ |
|
codegen(node->info.node.head->next->elem); |
|
// printf("sp = %d, savedsp = %d\n", sp, savedsp); |
|
assert(sp == savedsp); |
|
if (node->info.node.head->next->next) { |
|
emit_instr_label(OP_B, label2); |
|
emit_label(label); |
|
codegen(node->info.node.head->next->next->elem); |
|
} else { |
|
emit_label(label); |
|
} |
|
emit_label(label2); |
|
/* Then, if we had an else, evaluate it here */ |
|
break; |
|
case stmt_while: |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
assert(node->info.node.head->next->next == NULL); |
|
|
|
/* Label at the start of the loop */ |
|
label = newlabel(NULL); |
|
emit_label(label); |
|
/* Evaluate the condition */ |
|
codegen(node->info.node.head->elem); |
|
assert(real == 0); |
|
/* Exit loop if condition evaluates to false */ |
|
label2 = newlabel(NULL); |
|
savedbreaklabel = breaklabel; |
|
breaklabel = label2; |
|
emit_instr_label(OP_BZ, label2); |
|
sp--; |
|
/* Evaluate the loop interior */ |
|
codegen(node->info.node.head->next->elem); |
|
/* Branch to the beginning of the loop */ |
|
emit_instr_label(OP_B, label); |
|
/* Exit point */ |
|
emit_label(label2); |
|
breaklabel = savedbreaklabel; |
|
break; |
|
case stmt_switch: |
|
assert(node->info.node.head != NULL); |
|
assert(node->info.node.head->next != NULL); |
|
assert(node->info.node.head->next->next == NULL); |
|
/* Verify constants, and calculate min and max */ |
|
savedswitchmin = switchmin; |
|
savedswitchlabels = switchlabels; |
|
savedswitchdefault = switchdefault; |
|
savedbreaklabel = breaklabel; |
|
switchmin = INT_MAX; |
|
max = 0; |
|
for (ptr = |
|
node->info.node.head->next->elem->info.node.head; |
|
ptr; ptr = ptr->next) { |
|
num = 0; |
|
switch (ptr->elem->tag) { |
|
case casenum_ast: |
|
num = ptr->elem->info.integer; |
|
if (num > max) |
|
max = num; |
|
if (num < switchmin) |
|
switchmin = num; |
|
break; |
|
case casevar_ast: |
|
if (!lookup_constant(ptr->elem, &num)) |
|
compiler_error("Not a constant"); |
|
if (num > max) |
|
max = num; |
|
if (num < switchmin) |
|
switchmin = num; |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
if (switchmin > max) |
|
switchmin = max; |
|
range = max - switchmin; |
|
if (range > 1024) |
|
compiler_error("Excessive range in switch statement"); |
|
printf("Switch min %d max %d range %d\n", switchmin, max, range); |
|
switchlabels = safe_malloc(sizeof(int)*(range+1)); |
|
switchdefault = newlabel(NULL); |
|
for (i = 0; i <= range; i++) |
|
switchlabels[i] = switchdefault; |
|
/* |
|
* Allocate labels for all used cases, and set |
|
* the rest to default |
|
*/ |
|
hasdefault = 0; |
|
for (ptr = |
|
node->info.node.head->next->elem->info.node.head; |
|
ptr; ptr = ptr->next) { |
|
switch (ptr->elem->tag) { |
|
case casenum_ast: |
|
num = ptr->elem->info.integer; |
|
switchlabels[num-switchmin] = |
|
newlabel(NULL); |
|
break; |
|
case casevar_ast: |
|
lookup_constant(ptr->elem, &num); |
|
switchlabels[num-switchmin] = |
|
newlabel(NULL); |
|
break; |
|
case node_ast: |
|
if (ptr->elem->info.node.tag == |
|
stmt_default) |
|
hasdefault = 1; |
|
default: |
|
break; |
|
} |
|
} |
|
switchend = newlabel(NULL); |
|
stackfixlabel = newlabel(NULL); |
|
breaklabel = switchend; |
|
|
|
/* Evaluate condition */ |
|
codegen(node->info.node.head->elem); |
|
if (switchmin < 0) { |
|
emit_instr_immediate(OP_PUSH, -switchmin); |
|
emit_simple_instr(OP_ADD); |
|
} |
|
if (switchmin > 0) { |
|
emit_instr_immediate(OP_PUSH, switchmin); |
|
emit_simple_instr(OP_SUB); |
|
} |
|
/* Range check */ |
|
emit_instr_immediate(OP_LOAD, 0); |
|
emit_instr_immediate(OP_PUSH, 0); |
|
emit_simple_instr(OP_LT); |
|
emit_instr_label(OP_BNZ, stackfixlabel); |
|
emit_instr_immediate(OP_LOAD, 0); |
|
emit_instr_immediate(OP_PUSH, range); |
|
emit_simple_instr(OP_GT); |
|
emit_instr_label(OP_BNZ, stackfixlabel); |
|
|
|
/* Emit branch table */ |
|
emit_instr_immediate(OP_PUSH, 3); |
|
emit_simple_instr(OP_MUL); |
|
emit_simple_instr(OP_BI); |
|
for (i = 0; i <= range; i++) |
|
/* |
|
* We must constrain branch table entries to |
|
* a fixed size |
|
*/ |
|
emit_instr_label_sized(OP_B, switchlabels[i], |
|
3); |
|
|
|
emit_label(stackfixlabel); |
|
emit_instr_immediate(OP_POP, 1); |
|
emit_instr_label(OP_B, switchdefault); |
|
|
|
sp--; |
|
printf("sp = %d\n", sp); |
|
|
|
/* Evaluate all cases */ |
|
codegen(node->info.node.head->next->elem); |
|
|
|
printf("sp = %d\n", sp); |
|
|
|
if (!hasdefault) |
|
emit_label(switchdefault); |
|
emit_label(switchend); |
|
free(switchlabels); |
|
switchmin = savedswitchmin; |
|
switchlabels = savedswitchlabels; |
|
switchdefault = savedswitchdefault; |
|
breaklabel = savedbreaklabel; |
|
break; |
|
case stmt_default: |
|
emit_label(switchdefault); |
|
break; |
|
case stmt_break: |
|
emit_instr_label(OP_B, breaklabel); |
|
break; |
|
case stmt_return: |
|
assert(node->info.node.head == NULL); |
|
|
|
emit_instr_imm_const(OP_POP, sp, fnconst); |
|
emit_simple_instr(OP_RET); |
|
break; |
|
default: |
|
printf("unknown list type:\n"); |
|
break; |
|
} |
|
|
|
break; |
|
default: |
|
printf("unknown node type\n"); |
|
break; |
|
} |
|
} |
|
|
|
void output_byte(int byte) |
|
{ |
|
#if MOREDEBUG |
|
printf("Output byte: %x\n", byte); |
|
#endif |
|
fputc(byte, binout); |
|
} |
|
|
|
void output_int(int num) |
|
{ |
|
#if MOREDEBUG |
|
printf("Output int: %x\n", num); |
|
#endif |
|
output_byte((num >> 24) & 0xff); |
|
output_byte((num >> 16) & 0xff); |
|
output_byte((num >> 8) & 0xff); |
|
output_byte(num & 0xff); |
|
} |
|
|
|
void output_string(char *string) |
|
{ |
|
#if MOREDEBUG |
|
printf("Output string: \"%s\"\n", string); |
|
#endif |
|
fputs(string, binout); |
|
} |
|
|
|
int bytesmask[4] = { 0xff, 0xffff, 0xffffff, 0xffffffff }; |
|
|
|
#define BYTESMASK(bytes) (bytesmask[bytes]) |
|
|
|
void output_code(void) |
|
{ |
|
struct instr *ptr; |
|
int morepasses = 1; |
|
int passno = 0; |
|
int pc; |
|
int abiver; |
|
|
|
while (morepasses) { |
|
ptr = instr_head; |
|
pc = 0; |
|
morepasses = 0; |
|
#if DEBUG |
|
printf("Starting pass %d\n", passno); |
|
#endif |
|
while (ptr) { |
|
if (ptr->flags & IF_ISLABEL) |
|
if (get_label(ptr->label) != pc) { |
|
set_label(ptr->label, pc); |
|
#if DEBUG |
|
printf("New pass (label) at PC = %d\n", pc); |
|
#endif |
|
morepasses = 1; |
|
} |
|
if (ptr->flags & IF_ISINSTR) |
|
pc += ptr->bytes; |
|
if ((passno > 0) && (ptr->flags & IF_HASOPERAND)) { |
|
int operand; |
|
operand = ptr->operand; |
|
if (ptr->flags & IF_ADDCONST) |
|
operand += get_const(ptr->constant); |
|
if (ptr->flags & IF_ADDLABEL) |
|
operand += get_label(ptr->label); |
|
if (ptr->flags & IF_SUBPC) |
|
operand -= pc; |
|
assert(ptr->bytes <= 5); |
|
#if MOREDEBUG |
|
printf("operand = %x, bytes = %d, mask = %x\n", operand, ptr->bytes-2, BYTESMASK(ptr->bytes-2)); |
|
#endif |
|
while ((operand & BYTESMASK(ptr->bytes-2)) != |
|
operand) { |
|
if ((!morepasses) && |
|
(ptr->flags & IF_FIXEDSIZE)) { |
|
fprintf(stderr, "Label out of range: lab_%d: %d\n", ptr->label, operand); |
|
compiler_error("Label out of range"); |
|
} |
|
if (!(ptr->flags & IF_FIXEDSIZE)) { |
|
ptr->bytes++; |
|
pc++; |
|
morepasses = 1; |
|
#if DEBUG |
|
printf("New pass at PC = %d\n", pc); |
|
#endif |
|
} else { |
|
break; |
|
} |
|
assert(ptr->bytes <= 5); |
|
} |
|
} |
|
ptr = ptr->next; |
|
} |
|
passno++; |
|
#if DEBUG |
|
printf("End of pass pc = %d\n", pc); |
|
#endif |
|
} |
|
|
|
#if DEBUG |
|
printf("Finished passes.. outputting code\n"); |
|
#endif |
|
|
|
/* |
|
* Code is relocatable. Output function table, and fix up |
|
* pointers by the size of the header. |
|
*/ |
|
|
|
output_byte(MAGIC1); |
|
output_byte(MAGIC2); |
|
output_byte(VERSION1); |
|
output_byte(VERSION2); |
|
|
|
#define ABIVERSION(x) do { \ |
|
abiver = 0; \ |
|
if (!lookup_constant_string(x, &abiver)) \ |
|
printf("%s not defined\n", x); \ |
|
output_int(abiver); \ |
|
printf("ABIVERSION(%s) = %x\n", x, abiver); \ |
|
} while (0) |
|
|
|
pc = (pc + 3) & ~3; /* Align table */ |
|
|
|
output_int(pc+28); /* Pointer to function table */ |
|
|
|
ABIVERSION("__abiversion1"); |
|
ABIVERSION("__abiversion2"); |
|
ABIVERSION("__abiversion3"); |
|
ABIVERSION("__abiversion4"); |
|
ABIVERSION("__abiversion5"); |
|
|
|
ptr = instr_head; |
|
pc = 0; |
|
while (ptr) { |
|
if (ptr->flags & IF_ISINSTR) { |
|
int bytes; |
|
bytes = ptr->bytes; |
|
pc+=ptr->bytes; |
|
bytes--; |
|
if (ptr->flags & IF_HASOPERAND) { |
|
int operand; |
|
#if MOREDEBUG |
|
printf("bytes = %d\n", bytes); |
|
#endif |
|
output_byte(ptr->opcode | SET_BYTECOUNT(bytes)); |
|
operand = ptr->operand; |
|
if (ptr->flags & IF_ADDCONST) |
|
operand += get_const(ptr->constant); |
|
if (ptr->flags & IF_ADDLABEL) |
|
operand += get_label(ptr->label); |
|
if (ptr->flags & IF_SUBPC) |
|
operand -= pc; |
|
while (bytes) { |
|
output_byte((operand >> (8*(bytes-1))) |
|
& 0xff); |
|
bytes--; |
|
} |
|
} else if (ptr->flags & IF_HASSTRING) { |
|
int len; |
|
len = strlen(ptr->string); |
|
bytes -= len; |
|
output_byte(ptr->opcode | SET_BYTECOUNT(bytes)); |
|
while (bytes) { |
|
output_byte((len >> (8*(bytes-1))) |
|
& 0xff); |
|
bytes--; |
|
} |
|
output_string(ptr->string); |
|
} else { |
|
output_byte(ptr->opcode); |
|
} |
|
} |
|
ptr = ptr->next; |
|
} |
|
while (pc % 4) { |
|
output_byte(0); |
|
pc++; |
|
} |
|
output_functions(); |
|
}
|
|
|