439 lines
13 KiB
C
439 lines
13 KiB
C
/*
|
|
* Support for parse trees for the compiler.
|
|
*
|
|
* Added by Beek (Tim Hollebeek) 9/29/94. Only converting expression parsing
|
|
* to parse trees at this point; the rest of code generation will likely
|
|
* follow later.
|
|
*
|
|
* Note: it did. See ChangeLogs.
|
|
*
|
|
*/
|
|
|
|
#define SUPPRESS_COMPILER_INLINES
|
|
#include "std.h"
|
|
#include "lpc_incl.h"
|
|
#include "compiler.h"
|
|
#include "opcodes.h"
|
|
|
|
/* our globals */
|
|
static parse_node_block_t *parse_block_list = 0;
|
|
static parse_node_block_t *free_block_list = 0;
|
|
|
|
static parse_node_t *next_node = 0;
|
|
static parse_node_t *last_node = 0;
|
|
|
|
static int last_prog_size = 1;
|
|
|
|
/* called by code generation when it is done with the tree */
|
|
void
|
|
free_tree() {
|
|
parse_node_block_t *cur_block;
|
|
|
|
if (!(cur_block = parse_block_list))
|
|
return;
|
|
|
|
while (cur_block->next) cur_block = cur_block->next;
|
|
|
|
/* put all the blocks in the free list */
|
|
cur_block->next = free_block_list;
|
|
free_block_list = parse_block_list;
|
|
parse_block_list = 0;
|
|
next_node = 0;
|
|
last_node = 0;
|
|
}
|
|
|
|
/* called when the parser cleans up */
|
|
void
|
|
release_tree() {
|
|
parse_node_block_t *cur_block;
|
|
parse_node_block_t *next_block;
|
|
|
|
free_tree();
|
|
next_block = free_block_list;
|
|
while ((cur_block = next_block)) {
|
|
next_block = cur_block->next;
|
|
FREE(cur_block);
|
|
}
|
|
free_block_list = 0;
|
|
last_prog_size = 1;
|
|
}
|
|
|
|
/* get a new node to add to the tree */
|
|
parse_node_t *
|
|
new_node() {
|
|
parse_node_block_t *cur_block;
|
|
|
|
/* fast case */
|
|
if (next_node < last_node) {
|
|
next_node->line = current_line_base + current_line;
|
|
return next_node++;
|
|
}
|
|
|
|
/* no more nodes in the current block; do we have a free one? */
|
|
if ((cur_block = free_block_list)) {
|
|
free_block_list = cur_block->next;
|
|
} else {
|
|
cur_block = ALLOCATE(parse_node_block_t, TAG_COMPILER, "new_node");
|
|
}
|
|
/* add to block list */
|
|
cur_block->next = parse_block_list;
|
|
parse_block_list = cur_block;
|
|
/* point the nodes correctly */
|
|
next_node = &cur_block->nodes[0];
|
|
last_node = &cur_block->nodes[NODES_PER_BLOCK];
|
|
next_node->line = current_line_base + current_line;
|
|
return next_node++;
|
|
}
|
|
|
|
/* get a new node to add to the tree, but don't count it for line # purposes
|
|
* This should be used for nodes that hold expressions together but don't
|
|
* generate any code themselves (NODE_IF, etc)
|
|
*/
|
|
parse_node_t *
|
|
new_node_no_line() {
|
|
parse_node_block_t *cur_block;
|
|
|
|
/* fast case */
|
|
if (next_node < last_node) {
|
|
next_node->line = 0;
|
|
return next_node++;
|
|
}
|
|
/* no more nodes in the current block; do we have a free one? */
|
|
if ((cur_block = free_block_list)) {
|
|
free_block_list = cur_block->next;
|
|
} else {
|
|
cur_block = ALLOCATE(parse_node_block_t, TAG_COMPILER, "new_node");
|
|
}
|
|
/* add to block list */
|
|
cur_block->next = parse_block_list;
|
|
parse_block_list = cur_block;
|
|
/* point the nodes correctly */
|
|
next_node = &cur_block->nodes[0];
|
|
last_node = &cur_block->nodes[NODES_PER_BLOCK];
|
|
next_node->line = 0;
|
|
return next_node++;
|
|
}
|
|
|
|
/* quick routine to make a generic branched node */
|
|
parse_node_t *
|
|
make_branched_node (short kind, char type,
|
|
parse_node_t * l, parse_node_t * r) {
|
|
parse_node_t *ret;
|
|
|
|
ret = new_node();
|
|
ret->kind = kind;
|
|
ret->type = type;
|
|
ret->l.expr = l;
|
|
ret->r.expr = r;
|
|
return ret;
|
|
}
|
|
|
|
/* create an optimized typical binary integer operator */
|
|
parse_node_t *
|
|
binary_int_op (parse_node_t * l, parse_node_t * r,
|
|
char op, const char * name) {
|
|
parse_node_t *ret;
|
|
|
|
if (exact_types) {
|
|
if (!IS_TYPE(l->type, TYPE_NUMBER)) {
|
|
char buf[256];
|
|
char *end = EndOf(buf);
|
|
char *p;
|
|
|
|
p = strput(buf, end, "Bad left argument to '");
|
|
p = strput(p, end, name);
|
|
p = strput(p, end, "' : \"");
|
|
p = get_type_name(p, end, l->type);
|
|
p = strput(p, end, "\"");
|
|
yyerror(buf);
|
|
}
|
|
if (!IS_TYPE(r->type,TYPE_NUMBER)) {
|
|
char buf[256];
|
|
char *end = EndOf(buf);
|
|
char *p;
|
|
|
|
p = strput(buf, end, "Bad right argument to '");
|
|
p = strput(p, end, name);
|
|
p = strput(p, end, "' : \"");
|
|
p = get_type_name(p, end, r->type);
|
|
p = strput(p, end, "\"");
|
|
yyerror(buf);
|
|
}
|
|
}
|
|
if (l->kind == NODE_NUMBER) {
|
|
if (r->kind == NODE_NUMBER) {
|
|
switch (op) {
|
|
case F_OR: l->v.number |= r->v.number; break;
|
|
case F_XOR: l->v.number ^= r->v.number; break;
|
|
case F_AND: l->v.number &= r->v.number; break;
|
|
case F_LSH: l->v.number <<= r->v.number; break;
|
|
case F_RSH: l->v.number >>= r->v.number; break;
|
|
case F_MOD:
|
|
if (r->v.number == 0) {
|
|
yyerror("Modulo by zero constant");
|
|
break;
|
|
}
|
|
l->v.number %= r->v.number; break;
|
|
default: fatal("Unknown opcode in binary_int_op()\n");
|
|
}
|
|
return l;
|
|
}
|
|
switch (op) {
|
|
case F_OR:
|
|
case F_XOR:
|
|
case F_AND:
|
|
CREATE_BINARY_OP(ret, op, TYPE_NUMBER, r, l);
|
|
return ret;
|
|
}
|
|
}
|
|
CREATE_BINARY_OP(ret, op, TYPE_NUMBER, l, r);
|
|
return ret;
|
|
}
|
|
|
|
parse_node_t *make_range_node (int code, parse_node_t * expr,
|
|
parse_node_t * l,
|
|
parse_node_t * r) {
|
|
parse_node_t *newnode;
|
|
|
|
if (r) {
|
|
CREATE_TERNARY_OP(newnode, code, 0, l, r, expr);
|
|
} else {
|
|
CREATE_BINARY_OP(newnode, code, 0, l, expr);
|
|
}
|
|
|
|
if (exact_types) {
|
|
switch(expr->type) {
|
|
case TYPE_ANY:
|
|
case TYPE_STRING:
|
|
case TYPE_BUFFER:
|
|
newnode->type = expr->type;
|
|
break;
|
|
|
|
default:
|
|
if (expr->type & TYPE_MOD_ARRAY) newnode->type = expr->type;
|
|
else{
|
|
type_error("Bad type of argument used for range: ", expr->type);
|
|
newnode->type = TYPE_ANY;
|
|
}
|
|
}
|
|
|
|
if (!IS_TYPE(l->type, TYPE_NUMBER))
|
|
type_error("Bad type of left index to range operator", l->type);
|
|
if (r && !IS_TYPE(r->type, TYPE_NUMBER))
|
|
type_error("Bad type of right index to range operator", r->type);
|
|
} else newnode->type = TYPE_ANY;
|
|
return newnode;
|
|
}
|
|
|
|
parse_node_t *insert_pop_value (parse_node_t * expr) {
|
|
parse_node_t *replacement;
|
|
|
|
if (!expr)
|
|
return 0;
|
|
if (expr->type == TYPE_NOVALUE) {
|
|
expr->type = TYPE_VOID;
|
|
return expr;
|
|
}
|
|
switch (expr->kind) {
|
|
case NODE_EFUN:
|
|
if (expr->v.number & NOVALUE_USED_FLAG) {
|
|
expr->v.number &= ~NOVALUE_USED_FLAG;
|
|
return expr;
|
|
}
|
|
break;
|
|
case NODE_TWO_VALUES:
|
|
/* (two-values expr1 expr2) where expr1 is already popped.
|
|
*
|
|
* instead of: (pop (two-values expr1 expr2))
|
|
* generated: (two-values expr (pop expr2))
|
|
*
|
|
* both of which generate the same code, but the second optimizes
|
|
* better in cases like: i++, j++
|
|
*
|
|
* we get: (two-values (inc i) (post-inc j))
|
|
* first: (pop (two-values (inc i) (post-inc j)))
|
|
* -> INC i; POST_INC j; POP
|
|
* second: (two-values (inc i) (inc j))
|
|
* -> INC i; INC j
|
|
*/
|
|
if ((expr->r.expr = insert_pop_value(expr->r.expr)))
|
|
return expr;
|
|
return expr->l.expr;
|
|
case NODE_IF:
|
|
/* a NODE_IF that gets popped is a (x ? y : z);
|
|
* propagate the pop in order to produce the same code as
|
|
* if (x) y; else z;
|
|
*/
|
|
expr->l.expr = insert_pop_value(expr->l.expr);
|
|
expr->r.expr = insert_pop_value(expr->r.expr);
|
|
|
|
if (!expr->l.expr && !expr->r.expr) {
|
|
/* if both branches do nothing, don't bother with the test ... */
|
|
return insert_pop_value(expr->v.expr);
|
|
}
|
|
return expr;
|
|
case NODE_TERNARY_OP:
|
|
switch (expr->r.expr->v.number) {
|
|
case F_NN_RANGE: case F_RN_RANGE: case F_RR_RANGE: case F_NR_RANGE:
|
|
expr->kind = NODE_TWO_VALUES;
|
|
expr->l.expr = insert_pop_value(expr->l.expr);
|
|
expr->r.expr->kind = NODE_TWO_VALUES;
|
|
expr->r.expr->l.expr = insert_pop_value(expr->r.expr->l.expr);
|
|
expr->r.expr->r.expr = insert_pop_value(expr->r.expr->r.expr);
|
|
|
|
if (!expr->l.expr) {
|
|
expr = expr->r.expr;
|
|
if (!expr->l.expr)
|
|
return expr->r.expr;
|
|
if (!expr->r.expr)
|
|
return expr->l.expr;
|
|
} else {
|
|
if (!expr->r.expr->l.expr) {
|
|
expr->r.expr = expr->r.expr->r.expr;
|
|
if (!expr->r.expr)
|
|
return expr->l.expr;
|
|
} else {
|
|
if (!expr->r.expr->r.expr)
|
|
expr->r.expr = expr->r.expr->l.expr;
|
|
}
|
|
}
|
|
return expr;
|
|
}
|
|
break;
|
|
/* take advantage of the fact that opcodes don't clash */
|
|
case NODE_CALL:
|
|
case NODE_BINARY_OP:
|
|
case NODE_UNARY_OP_1:
|
|
case NODE_UNARY_OP:
|
|
case NODE_OPCODE_1:
|
|
switch (expr->v.number) {
|
|
case F_AGGREGATE_ASSOC:
|
|
/* This has to be done specially b/c of the way mapping constants
|
|
are stored */
|
|
return throw_away_mapping(expr);
|
|
case F_AGGREGATE:
|
|
return throw_away_call(expr);
|
|
case F_PRE_INC: case F_POST_INC:
|
|
expr->v.number = F_INC;
|
|
return expr;
|
|
case F_PRE_DEC: case F_POST_DEC:
|
|
expr->v.number = F_DEC;
|
|
return expr;
|
|
case F_NOT: case F_COMPL: case F_NEGATE:
|
|
expr = insert_pop_value(expr->r.expr);
|
|
return expr;
|
|
case F_MEMBER:
|
|
expr = insert_pop_value(expr->r.expr);
|
|
return expr;
|
|
case F_LOCAL: case F_GLOBAL: case F_REF:
|
|
return 0;
|
|
case F_EQ: case F_NE: case F_GT: case F_GE: case F_LT: case F_LE:
|
|
case F_OR: case F_XOR: case F_AND: case F_LSH: case F_RSH:
|
|
case F_ADD: case F_SUBTRACT: case F_MULTIPLY: case F_DIVIDE:
|
|
case F_MOD: case F_RE_RANGE: case F_NE_RANGE: case F_RINDEX:
|
|
case F_INDEX:
|
|
if ((expr->l.expr = insert_pop_value(expr->l.expr))) {
|
|
if ((expr->r.expr = insert_pop_value(expr->r.expr))) {
|
|
expr->kind = NODE_TWO_VALUES;
|
|
return expr;
|
|
} else
|
|
return expr->l.expr;
|
|
} else
|
|
return insert_pop_value(expr->r.expr);
|
|
break;
|
|
case F_ASSIGN:
|
|
if (IS_NODE(expr->r.expr, NODE_OPCODE_1, F_LOCAL_LVALUE)) {
|
|
long tmp = expr->r.expr->l.number;
|
|
expr->kind = NODE_UNARY_OP_1;
|
|
expr->r.expr = expr->l.expr;
|
|
expr->v.number = F_VOID_ASSIGN_LOCAL;
|
|
expr->l.number = tmp;
|
|
} else expr->v.number = F_VOID_ASSIGN;
|
|
return expr;
|
|
case F_ADD_EQ:
|
|
expr->v.number = F_VOID_ADD_EQ;
|
|
return expr;
|
|
}
|
|
break;
|
|
case NODE_PARAMETER:
|
|
case NODE_ANON_FUNC: /* some dweeb threw away one? */
|
|
case NODE_FUNCTION_CONSTRUCTOR:
|
|
return 0;
|
|
case NODE_NUMBER:
|
|
case NODE_STRING:
|
|
case NODE_REAL:
|
|
return 0;
|
|
}
|
|
CREATE_UNARY_OP(replacement, F_POP_VALUE, 0, expr);
|
|
return replacement;
|
|
}
|
|
|
|
parse_node_t *pop_value (parse_node_t * pn) {
|
|
if (pn) {
|
|
parse_node_t *ret = insert_pop_value(pn);
|
|
|
|
if (!ret) {
|
|
if (pn->kind == NODE_BINARY_OP && pn->v.number >= F_EQ &&
|
|
pn->v.number <= F_GT)
|
|
yywarn("Value of conditional expression is unused");
|
|
else
|
|
yywarn("Expression has no side effects, and the value is unused");
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int is_boolean (parse_node_t * pn) {
|
|
switch (pn->kind) {
|
|
case NODE_UNARY_OP:
|
|
if (pn->v.number == F_NOT)
|
|
return 1;
|
|
return 0;
|
|
case NODE_BINARY_OP:
|
|
if (pn->v.number >= F_EQ && pn->v.number <= F_GT)
|
|
return 1;
|
|
return 0;
|
|
case NODE_LAND_LOR:
|
|
case NODE_BRANCH_LINK:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
parse_node_t *optimize_loop_test (parse_node_t * pn) {
|
|
parse_node_t *ret;
|
|
|
|
if (!pn) return 0;
|
|
|
|
if (IS_NODE(pn, NODE_BINARY_OP, F_LT) &&
|
|
IS_NODE(pn->l.expr, NODE_OPCODE_1, F_LOCAL)) {
|
|
if (IS_NODE(pn->r.expr, NODE_OPCODE_1, F_LOCAL)) {
|
|
CREATE_OPCODE_2(ret, F_LOOP_COND_LOCAL, 0,
|
|
pn->l.expr->l.number,
|
|
pn->r.expr->l.number);
|
|
} else if (pn->r.expr->kind == NODE_NUMBER) {
|
|
CREATE_OPCODE_2(ret, F_LOOP_COND_NUMBER, 0,
|
|
pn->l.expr->l.number,
|
|
pn->r.expr->v.number);
|
|
} else
|
|
ret = pn;
|
|
} else if (IS_NODE(pn, NODE_UNARY_OP, F_POST_DEC) &&
|
|
IS_NODE(pn->r.expr, NODE_OPCODE_1, F_LOCAL_LVALUE)) {
|
|
long lvar = pn->r.expr->l.number;
|
|
CREATE_OPCODE_1(ret, F_WHILE_DEC, 0, lvar);
|
|
} else
|
|
ret = pn;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|