mirror of
https://github.com/sfall-team/sslc.git
synced 2026-07-27 16:52:49 -07:00
1331 lines
47 KiB
C
1331 lines
47 KiB
C
#include "parse.h"
|
|
#include "parselib.h"
|
|
#include "extra.h"
|
|
|
|
#include "opcodes.h"
|
|
#include "oplib.h"
|
|
#include "opextra.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define Protect(a) if (_stricmp(c, a) == 0) return 1;
|
|
|
|
#define F_OP(token,op) case token: out->floatData = fd1 op fd2; break;
|
|
#define F_IOP(token,op) case token: out->type = V_INT; out->intData = fd1 op fd2; break;
|
|
#define I_OP(token,op) case token: out->intData = in1->intData op in2->intData; break;
|
|
#define I_OPU(token,op) case token: out->intData = (unsigned int)in1->intData op (unsigned int)in2->intData; break;
|
|
|
|
#define VU_FIRST_ASSIGN_IS_PURE 0x01
|
|
#define VU_FIRST_ASSIGN_IN_WHILE 0x02
|
|
#define VU_LAST_ASSIGN_IN_WHILE 0x04
|
|
#define VU_FIRST_ASSIGN_IS_DEAD 0x08
|
|
#define VU_LAST_ASSIGN_IS_PURE 0x10
|
|
#define VU_FIRST_READ_IN_WHILE 0x20
|
|
#define VU_LAST_READ_IN_WHILE 0x40
|
|
#define VU_FIRST_ASSIGN_TRANSFERABLE 0x80
|
|
|
|
extern int optimize;
|
|
static Program* currprogram;
|
|
|
|
//Sanity checks. Should never be hit
|
|
static void assert(int i) {
|
|
if (!i)
|
|
parseError("ICE in optimizer.");
|
|
}
|
|
|
|
//Strip out some nods from a nodelist
|
|
static void RemoveNodes(NodeList* nodes, int start, int count) {
|
|
memmove(&nodes->nodes[start], &nodes->nodes[start + count], (nodes->numNodes - (start + count)) * sizeof(Node));
|
|
nodes->numNodes -= count;
|
|
//not much point reallocing
|
|
}
|
|
|
|
//Check if a node is a local variable, and return its id if so or -1 otherwise
|
|
static int LookupVariable(const Node* node) {
|
|
assert(node->token == T_SYMBOL);
|
|
if (node->value.type != P_LOCAL) return -1;
|
|
return node->value.intData;
|
|
}
|
|
|
|
//Returns true if a token is known to have no side effects
|
|
static int IsTokenPure(int token) {
|
|
//TODO: Go through the whole list
|
|
switch (token) {
|
|
case T_TS_LEN_ARRAY:
|
|
case T_TS_ATOI:
|
|
case T_TS_ATOF:
|
|
case T_TS_TILE_PID:
|
|
case T_TS_MODIFIED_INI:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//Returns true if we can prove an expression has no side effects
|
|
static int IsExpressionPure(const Node* node) {
|
|
assert(node->token == T_START_EXPRESSION);
|
|
while ((++node)->token != T_END_EXPRESSION) {
|
|
//if (node->token == T_START_EXPRESSION) return 0;
|
|
if (node->token >= T_END_CORE && !IsTokenPure(node->token)) return 0;
|
|
if (node->token == T_SYMBOL && node->value.type & P_PROCEDURE) {
|
|
if (node->value.type != (P_LOCAL | P_PROCEDURE) || !(currprogram->procedures.procedures[node->value.intData].type & P_PURE)) return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
typedef struct {
|
|
int firstassign;
|
|
int lastassign;
|
|
int firstuse;
|
|
int lastuse;
|
|
int flags;
|
|
} VarUsage;
|
|
|
|
static void MarkVariableRead(VarUsage* usage, int var, int currstatement, int whiledepth) {
|
|
usage[var].lastuse = currstatement;
|
|
if (whiledepth) usage[var].flags |= VU_LAST_READ_IN_WHILE;
|
|
else usage[var].flags &= ~VU_LAST_READ_IN_WHILE;
|
|
if (!usage[var].firstuse) {
|
|
usage[var].firstuse = currstatement;
|
|
if (whiledepth) usage[var].flags |= VU_FIRST_READ_IN_WHILE;
|
|
}
|
|
}
|
|
|
|
static void FindVarUsage(const Node* node, VarUsage* usage, int varCount) {
|
|
int depth = 1, currstatement = 0, i = 0, whiledepth = 0, ifdepth = 0, var, expressiondepth;
|
|
memset(usage, 0, varCount * sizeof(VarUsage));
|
|
assert(node->token == T_BEGIN);
|
|
while (depth) {
|
|
i++;
|
|
switch ((++node)->token) {
|
|
case T_BEGIN: depth++; break;
|
|
case T_END: depth--; break;
|
|
case T_START_STATEMENT:
|
|
if (ifdepth) ifdepth++;
|
|
if (!whiledepth) {
|
|
currstatement = i;
|
|
if (node[1].token == T_WHILE) {
|
|
whiledepth = 1;
|
|
} else if (node[1].token == T_IF && !ifdepth) {
|
|
ifdepth = 1;
|
|
}
|
|
} else whiledepth++;
|
|
break;
|
|
case T_END_STATEMENT:
|
|
if (ifdepth) ifdepth--;
|
|
if (whiledepth) whiledepth--;
|
|
break;
|
|
case T_ASSIGN: case T_ASSIGN_ADD: case T_ASSIGN_SUB: case T_ASSIGN_MUL: case T_ASSIGN_DIV:
|
|
if ((var = LookupVariable(&node[-1])) != -1) {
|
|
int pure = IsExpressionPure(&node[1]);
|
|
usage[var].lastassign = currstatement;
|
|
if (whiledepth) usage[var].flags |= VU_LAST_ASSIGN_IN_WHILE;
|
|
else usage[var].flags &= ~VU_LAST_ASSIGN_IN_WHILE;
|
|
if (pure) usage[var].flags |= VU_LAST_ASSIGN_IS_PURE;
|
|
if (!usage[var].firstuse) {
|
|
if (!usage[var].firstassign) {
|
|
usage[var].firstassign = currstatement;
|
|
if (whiledepth) usage[var].flags |= VU_FIRST_ASSIGN_IN_WHILE;
|
|
if (pure) {
|
|
usage[var].flags |= VU_FIRST_ASSIGN_IS_PURE;
|
|
if (!whiledepth && !ifdepth && node[2].token == T_CONSTANT && node[3].token == T_END_EXPRESSION && node[2].value.type != V_STRING) {
|
|
usage[var].flags |= VU_FIRST_ASSIGN_TRANSFERABLE | VU_FIRST_ASSIGN_IS_DEAD;
|
|
}
|
|
}
|
|
} else if (!whiledepth && !ifdepth) {
|
|
if (usage[var].flags & VU_FIRST_ASSIGN_IS_PURE && node->token == T_ASSIGN) usage[var].flags |= VU_FIRST_ASSIGN_IS_DEAD;
|
|
} else if (!pure && currstatement == usage[var].firstassign) usage[var].flags &= ~VU_FIRST_ASSIGN_IS_PURE;
|
|
}
|
|
if (node->token != T_ASSIGN) {
|
|
MarkVariableRead(usage, var, currstatement, whiledepth);
|
|
}
|
|
}
|
|
break;
|
|
case T_START_EXPRESSION:
|
|
expressiondepth = 1;
|
|
while (expressiondepth) {
|
|
i++;
|
|
switch ((++node)->token) {
|
|
case T_START_EXPRESSION: expressiondepth++; break;
|
|
case T_END_EXPRESSION: expressiondepth--; break;
|
|
case T_SYMBOL:
|
|
if ((var = LookupVariable(node)) != -1) {
|
|
MarkVariableRead(usage, var, currstatement, whiledepth);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case T_CALL:
|
|
if ((node + 1)->token == T_SYMBOL && ((var = LookupVariable(node + 1)) != -1)) {
|
|
MarkVariableRead(usage, var, currstatement, whiledepth);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
assert(node->token == T_END);
|
|
}
|
|
|
|
static int isValidBinaryOp(int op) {
|
|
return op == '+' || op == '-' || op == '*' || op == '/' || op == T_DIV2 ||
|
|
op == T_AND || op == T_OR || op == T_BWAND || op == T_BWOR || op == T_BWXOR ||
|
|
op == '>' || op == '<' || op == T_EQUAL || op == T_NOT_EQUAL || op == T_LESS_EQUAL || op == T_GREATER_EQUAL;
|
|
}
|
|
|
|
//Calculate the result of a constant operation (doesn't handle strings, which would require namespace modifications)
|
|
static void PerformConstOp(const Value* in1, const Value* in2, Value* out, int op, const Node* node) {
|
|
int isfloat;
|
|
assert(isValidBinaryOp(op));
|
|
assert(in1->type == V_INT || in1->type == V_FLOAT);
|
|
assert(in2->type == V_INT || in2->type == V_FLOAT);
|
|
isfloat = in1->type == V_FLOAT || in2->type == V_FLOAT;
|
|
if (isfloat) {
|
|
float fd1 = in1->type == V_FLOAT ? in1->floatData : (float)in1->intData;
|
|
float fd2 = in2->type == V_FLOAT ? in2->floatData : (float)in2->intData;
|
|
out->type = V_FLOAT;
|
|
switch (op) {
|
|
F_OP('+', +)
|
|
F_OP('-', -)
|
|
F_OP('*', *)
|
|
F_OP('/', /)
|
|
F_OP(T_DIV2, /)
|
|
F_IOP(T_EQUAL, ==)
|
|
F_IOP(T_NOT_EQUAL, !=)
|
|
F_IOP('>', >)
|
|
F_IOP('<', <)
|
|
F_IOP(T_LESS_EQUAL, <=)
|
|
F_IOP(T_GREATER_EQUAL, >=)
|
|
case T_AND:
|
|
out->type = V_INT;
|
|
out->intData = (fd1 != 0.0) && (fd2 != 0.0);
|
|
break;
|
|
case T_OR:
|
|
out->type = V_INT;
|
|
out->intData = (fd1 != 0.0) || (fd2 != 0.0);
|
|
break;
|
|
case T_BWAND:
|
|
out->type = V_INT;
|
|
parseWarningAtNode(node, "Optimizer encountered a bitwise operation on a float constant");
|
|
out->intData = in1->intData & in2->intData;
|
|
break;
|
|
case T_BWOR:
|
|
out->type = V_INT;
|
|
parseWarningAtNode(node, "Optimizer encountered a bitwise operation on a float constant");
|
|
out->intData = in1->intData | in2->intData;
|
|
break;
|
|
case T_BWXOR:
|
|
out->type = V_INT;
|
|
parseWarningAtNode(node, "Optimizer encountered a bitwise operation on a float constant");
|
|
out->intData = in1->intData ^ in2->intData;
|
|
break;
|
|
}
|
|
} else {
|
|
out->type = V_INT;
|
|
switch (op) {
|
|
I_OP('+', +)
|
|
I_OP('-', -)
|
|
I_OP('*', *)
|
|
I_OP('/', /)
|
|
I_OPU(T_DIV2, /)
|
|
I_OP(T_AND, &&)
|
|
I_OP(T_OR, ||)
|
|
I_OP(T_BWAND, &)
|
|
I_OP(T_BWOR, |)
|
|
I_OP(T_BWXOR, ^)
|
|
I_OP(T_EQUAL, ==)
|
|
I_OP(T_NOT_EQUAL, !=)
|
|
I_OP('>', >)
|
|
I_OP('<', <)
|
|
I_OP(T_LESS_EQUAL, <=)
|
|
I_OP(T_GREATER_EQUAL, >=)
|
|
}
|
|
}
|
|
}
|
|
|
|
static int ConstantFolding(NodeList* _nodes) {
|
|
int i, token, matched=0;
|
|
Node* nodes = _nodes->nodes;
|
|
for (i = 2; i < _nodes->numNodes; i++) {
|
|
token = nodes[i].token;
|
|
if (isValidBinaryOp(token)) {
|
|
if (nodes[i - 1].token == T_CONSTANT && nodes[i - 2].token == T_CONSTANT && nodes[i - 1].value.type != V_STRING && nodes[i - 2].value.type != V_STRING) {
|
|
parseMessageAtNode(&nodes[i], "Folding constant binary expression");
|
|
matched = 1;
|
|
PerformConstOp(&nodes[i - 2].value, &nodes[i - 1].value, &nodes[i].value, token, &nodes[i]);
|
|
nodes[i].token = T_CONSTANT;
|
|
i -= 2;
|
|
RemoveNodes(_nodes, i, 2);
|
|
}/* AND/OR were changed in the tree
|
|
else if (token == T_AND || token == T_OR) {
|
|
if ((nodes[i - 1].token == T_CONSTANT && nodes[i - 1].value.type != V_STRING) || (nodes[i - 2].token == T_CONSTANT && nodes[i - 2].value.type != V_STRING)) {
|
|
if ((nodes[i - 1].token == T_SYMBOL && !(nodes[i - 1].value.type & P_PROCEDURE)) || (nodes[i - 2].token == T_SYMBOL && !(nodes[i - 1].value.type & P_PROCEDURE))) {
|
|
int value, found = 0;
|
|
if (nodes[i - 1].token == T_CONSTANT && nodes[i - 1].value.type != V_STRING) value = nodes[i - 1].value.intData;
|
|
else value = nodes[i - 2].value.intData;
|
|
if (!value && token == T_AND) {
|
|
found = 1;
|
|
} else if (value && token == T_OR) {
|
|
found = 2;
|
|
}
|
|
if (found) {
|
|
parseMessageAtNode(&nodes[i], "Folding semi-constant binary expression");
|
|
nodes[i].token = T_CONSTANT;
|
|
nodes[i].value.type = V_INT;
|
|
nodes[i].value.intData = found - 1;
|
|
RemoveNodes(_nodes, i - 2, 2);
|
|
i -= 2;
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
} else if (token == T_NOT || token == T_BWNOT || token == T_NEGATE) {
|
|
if (nodes[i - 1].token == T_CONSTANT && nodes[i - 1].value.type != V_STRING) {
|
|
parseMessageAtNode(&nodes[i], "Folding constant unary expression");
|
|
matched = 1;
|
|
if (token == T_NOT) {
|
|
nodes[i - 1].value.type = V_INT;
|
|
nodes[i - 1].value.intData = !nodes[i - 1].value.intData;
|
|
} else if (token == T_BWNOT) {
|
|
if (nodes[i - 1].value.type != V_INT) parseWarningAtNode(&nodes[i - 1], "Optimizer encountered a bitwise operation on a float constant");
|
|
nodes[i - 1].value.type = V_INT;
|
|
nodes[i - 1].value.intData = ~nodes[i - 1].value.intData;
|
|
} else {
|
|
if (nodes[i - 1].value.type == V_INT)
|
|
nodes[i - 1].value.intData = -nodes[i - 1].value.intData;
|
|
else
|
|
nodes[i - 1].value.floatData = -nodes[i - 1].value.floatData;
|
|
}
|
|
RemoveNodes(_nodes, i, 1);
|
|
i -= 1;
|
|
}
|
|
}
|
|
}
|
|
return matched;
|
|
}
|
|
|
|
static int isValidMathOp(int op) {
|
|
// 43 45 42 47
|
|
return op == '+' || op == '-' || op == '*' || op == '/'; // | op == T_DIV2
|
|
}
|
|
|
|
// optimizes remaining not optimized mathematical operations (except for logical operations) | added: Fakels
|
|
static int ConstantFoldingPassTwo(NodeList* _nodes) {
|
|
int i, token, _token, isStartExp = 0;
|
|
int nResult = 0, tokenOp = 0, isNotEquals = 0, matched = 0, nonConstant = 0;
|
|
|
|
Node* nodes = _nodes->nodes;
|
|
for (i = 2; i < _nodes->numNodes; i++)
|
|
{
|
|
token = nodes[i].token;
|
|
if (!isStartExp){
|
|
if (token == T_START_EXPRESSION) isStartExp = 1;
|
|
continue;
|
|
} else if (token == T_END_EXPRESSION) {
|
|
isStartExp = 0;
|
|
nonConstant = 0;
|
|
continue;
|
|
}
|
|
if (token == T_SYMBOL) {
|
|
tokenOp = 0;
|
|
// remember position for last non-const symbol
|
|
if (nodes[i + 2].token != '+') nonConstant = i + 1; // skip for 'T_SYMBOL + a'
|
|
continue;
|
|
}
|
|
if (token == T_CONSTANT && nodes[i].value.type != V_STRING && (nonConstant == 0 || nonConstant < i)) {
|
|
_token = nodes[i + 1].token;
|
|
if (!isValidMathOp(_token)) {
|
|
tokenOp = 0;
|
|
continue;
|
|
}
|
|
if (!tokenOp) {
|
|
tokenOp = _token;
|
|
nResult = i;
|
|
continue;
|
|
}
|
|
if (tokenOp != _token) {
|
|
isNotEquals = 1;
|
|
if (tokenOp == '+' && _token == '-') isNotEquals = 0; // exclusion for 'a + b - c'
|
|
}
|
|
if (!isNotEquals) {
|
|
parseMessageAtNode(&nodes[i + 1], "Pass two: Folding constant mathematics expression");
|
|
PerformConstOp(&nodes[nResult].value, &nodes[i].value, &nodes[nResult].value, _token, &nodes[i + 1]);
|
|
RemoveNodes(_nodes, i, 2);
|
|
i -= 2;
|
|
matched = 1;
|
|
} else {
|
|
isNotEquals = 0;
|
|
}
|
|
tokenOp = 0;
|
|
i--;
|
|
}
|
|
}
|
|
return matched;
|
|
}
|
|
|
|
static int ConstantPropagateExpression(Node* nodes, Variable* vars, Value* values, int varCount, int *_i) {
|
|
int expressiondepth = 1, matched = 0, token, var, i = *_i;
|
|
assert(nodes[i].token == T_START_EXPRESSION);
|
|
while (expressiondepth) {
|
|
token = nodes[++i].token;
|
|
if (token == T_START_EXPRESSION) expressiondepth++;
|
|
else if (token == T_END_EXPRESSION) expressiondepth--;
|
|
else if (token == T_SYMBOL) {
|
|
if ((var = LookupVariable(&nodes[i])) != -1) {
|
|
if (values[var].type != -1) {
|
|
nodes[i].token = T_CONSTANT;
|
|
nodes[i].value = values[var];
|
|
parseMessageAtNode(&nodes[i], "propagating constant %d", values[var].intData);
|
|
matched = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert(nodes[i].token == T_END_EXPRESSION);
|
|
*_i = i;
|
|
return matched;
|
|
}
|
|
|
|
static int* FindAssignmentsInBlock(const Node* nodes, const Variable* vars, int varCount) {
|
|
int* results = (int*)calloc(1, varCount * 4);
|
|
int statementdepth = 1, var;
|
|
assert(nodes->token == T_START_STATEMENT);
|
|
while (statementdepth) {
|
|
switch (nodes->token) {
|
|
case T_START_STATEMENT:
|
|
statementdepth++;
|
|
break;
|
|
case T_END_STATEMENT:
|
|
statementdepth--;
|
|
break;
|
|
case T_ASSIGN:
|
|
case T_ASSIGN_ADD:
|
|
case T_ASSIGN_SUB:
|
|
case T_ASSIGN_MUL:
|
|
case T_ASSIGN_DIV:
|
|
if (nodes[-1].token == T_SYMBOL && (var = LookupVariable(&nodes[-1])) != -1) {
|
|
results[var] = 1;
|
|
}
|
|
break;
|
|
}
|
|
nodes++;
|
|
}
|
|
return results;
|
|
}
|
|
|
|
static int ConstantPropagateBlock(Node* nodes, int *_i, Variable* vars, Value* values, int varCount) {
|
|
int i = _i ? *_i : 0,
|
|
matched = 0,
|
|
blockdepth = 1,
|
|
token, var, blockbegin, blockend, j;
|
|
|
|
assert(nodes->token == T_BEGIN || nodes->token == T_START_STATEMENT);
|
|
|
|
if (nodes[i].token == T_BEGIN) {
|
|
blockbegin = T_BEGIN;
|
|
blockend = T_END;
|
|
} else {
|
|
blockbegin = T_START_STATEMENT;
|
|
blockend = T_END_STATEMENT;
|
|
}
|
|
|
|
i++;
|
|
while (1) {
|
|
token = nodes[i].token;
|
|
if (token == blockbegin) blockdepth++;
|
|
else if (token == blockend) {
|
|
if (!--blockdepth) break;
|
|
} else if (token == T_ASSIGN || token == T_ASSIGN_ADD ||token == T_ASSIGN_SUB || token == T_ASSIGN_MUL || token == T_ASSIGN_DIV) {
|
|
if ((var = LookupVariable(&nodes[i - 1])) != -1) {
|
|
if (nodes[i + 1].token == T_START_EXPRESSION && nodes[i + 2].token == T_CONSTANT && nodes[i + 3].token == T_END_EXPRESSION) {
|
|
if (token == T_ASSIGN)
|
|
values[var] = nodes[i + 2].value;
|
|
else if (values[var].type != -1) {
|
|
switch (token) {
|
|
case T_ASSIGN_ADD: token = '+'; break;
|
|
case T_ASSIGN_SUB: token = '-'; break;
|
|
case T_ASSIGN_MUL: token = '*'; break;
|
|
case T_ASSIGN_DIV: token = '/'; break;
|
|
}
|
|
PerformConstOp(&values[var], &nodes[i + 2].value, &values[var], token, &nodes[i]);
|
|
}
|
|
} else {
|
|
values[var].type = -1;
|
|
}
|
|
}
|
|
} else if (token == T_IF) {
|
|
int *aif = 0, *aelse = 0;
|
|
Value* backup = (Value*)malloc(varCount * sizeof(Value));
|
|
memcpy(backup, values, varCount * sizeof(Value));
|
|
i++;
|
|
matched |= ConstantPropagateExpression(nodes, vars, values, varCount, &i);
|
|
i++;
|
|
assert(nodes[i++].token == T_THEN);
|
|
aif = FindAssignmentsInBlock(&nodes[i], vars, varCount);
|
|
ConstantPropagateBlock(nodes, &i, vars, values, varCount);
|
|
if (nodes[i + 1].token == T_ELSE) {
|
|
i += 2;
|
|
memcpy(values, backup, varCount * sizeof(Value));
|
|
aelse = FindAssignmentsInBlock(&nodes[i], vars, varCount);
|
|
for (j = 0; j < varCount; j++) if (aelse[j]) aif[j] = 1;
|
|
ConstantPropagateBlock(nodes, &i, vars, values, varCount);
|
|
}
|
|
for (j = 0; j < varCount; j++) {
|
|
if (aif[j]) values[j].type = -1;
|
|
}
|
|
free(aif);
|
|
if (aelse) free(aelse);
|
|
free(backup);
|
|
} else if (token == T_WHILE) {
|
|
//TODO: Check here if the while loop expression is a single symbol; if it's 0 at this point we can eat the loop, even if the loop modifies it
|
|
Node* nodes2 = &nodes[i];
|
|
int* invalidate;
|
|
while (nodes2->token != T_DO) nodes2++;
|
|
invalidate = FindAssignmentsInBlock(nodes2 + 1, vars, varCount);
|
|
for (j = 0; j < varCount; j++) if (invalidate[j]) values[j].type = -1;
|
|
} else if (token == T_START_EXPRESSION) {
|
|
matched |= ConstantPropagateExpression(nodes, vars, values, varCount, &i);
|
|
}
|
|
i++;
|
|
}
|
|
assert(nodes[i].token == blockend);
|
|
if (_i) *_i = i;
|
|
return matched;
|
|
}
|
|
|
|
static int ConstantPropagation(NodeList* _nodes, Variable* vars, Value* values, int varCount, int numArgs) {
|
|
int i;
|
|
for (i = 0; i < numArgs; i++) values[i].type = -1;
|
|
for (i = numArgs; i < varCount; i++) values[i] = vars[i].value;
|
|
return ConstantPropagateBlock(_nodes->nodes, 0, vars, values, varCount);
|
|
}
|
|
|
|
#define SkipStatement { statementdepth = 1; \
|
|
assert(nodes[i - 1].token == T_START_STATEMENT); \
|
|
while (statementdepth) { if (nodes[i].token == T_START_STATEMENT) statementdepth++; \
|
|
else if (nodes[i].token == T_END_STATEMENT) statementdepth--; \
|
|
i++; \
|
|
}}
|
|
|
|
static void EatStatement(NodeList* _nodes, int start) {
|
|
int statementdepth = 1, i = start + 1;
|
|
Node* nodes = _nodes->nodes;
|
|
assert(nodes[start].token == T_START_STATEMENT);
|
|
SkipStatement;
|
|
RemoveNodes(_nodes, start, i-start);
|
|
}
|
|
|
|
static void EatAssignsInWhile(NodeList* _nodes, int start, int varid) {
|
|
int statementdepth = 1;
|
|
Node* nodes = _nodes->nodes;
|
|
assert(nodes[start++].token == T_START_STATEMENT);
|
|
assert(nodes[start].token == T_WHILE);
|
|
while (statementdepth) {
|
|
switch (nodes[++start].token) {
|
|
case T_START_STATEMENT:
|
|
if (nodes[start + 1].token == T_SYMBOL && nodes[start + 2].token >= T_ASSIGN && nodes[start + 2].token <= T_ASSIGN_DIV && LookupVariable(&nodes[start + 1]) == varid) {
|
|
EatStatement(_nodes, start--);
|
|
} else statementdepth++;
|
|
break;
|
|
case T_END_STATEMENT: statementdepth--; break;
|
|
}
|
|
}
|
|
assert(nodes[start].token == T_END_STATEMENT);
|
|
}
|
|
|
|
static void EatDeadStore(NodeList* _nodes, int start, int var) {
|
|
Node* nodes = _nodes->nodes;
|
|
assert(nodes[start].token == T_START_STATEMENT);
|
|
if (nodes[start + 1].token == T_WHILE) {
|
|
parseMessageAtNode(&nodes[start + 1], "Eating dead stores to variable %d in while loop", var);
|
|
EatAssignsInWhile(_nodes, start, var);
|
|
} else {
|
|
parseMessageAtNode(&nodes[start + 1], "Eating dead store to variable %d", var);
|
|
EatStatement(_nodes, start);
|
|
}
|
|
}
|
|
|
|
static int DeadStoreRemoval(NodeList* _nodes, VariableList* vars, VarUsage* usage) {
|
|
int i, matched1, matched2 = 0;
|
|
Node* nodes = _nodes->nodes;
|
|
while (1) {
|
|
matched1 = 0;
|
|
FindVarUsage(nodes, usage, vars->numVariables);
|
|
for (i = 0; i < vars->numVariables; i++) {
|
|
if (usage[i].flags & VU_FIRST_ASSIGN_IS_DEAD) {
|
|
if (usage[i].flags & VU_FIRST_ASSIGN_TRANSFERABLE) {
|
|
parseMessageAtNode(&nodes[usage[i].firstassign], "Using constant variable store for initializer");
|
|
assert(nodes[usage[i].firstassign + 4].token == T_CONSTANT);
|
|
vars->variables[i].value = nodes[usage[i].firstassign + 4].value;
|
|
}
|
|
EatDeadStore(_nodes, usage[i].firstassign, i);
|
|
matched1 = 1;
|
|
break;
|
|
} else if (usage[i].lastassign >= usage[i].lastuse && usage[i].flags & VU_LAST_ASSIGN_IS_PURE) {
|
|
if (nodes[usage[i].lastassign + 1].token != T_WHILE || usage[i].lastassign > usage[i].lastuse) {
|
|
//Pretty sure I don't need the do check here
|
|
if (nodes[usage[i].lastassign - 1].token != T_THEN && nodes[usage[i].lastassign - 1].token != T_ELSE && nodes[usage[i].lastassign - 1].token != T_DO) {
|
|
EatDeadStore(_nodes, usage[i].lastassign, i);
|
|
matched1 = 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (matched1) {
|
|
matched2 = 1;
|
|
continue;
|
|
}
|
|
break;
|
|
};
|
|
return matched2;
|
|
}
|
|
|
|
static int DeadCodeRemoval(NodeList* _nodes) {
|
|
int i, statementdepth, takebranch, matched = 0;
|
|
Node* nodes = _nodes->nodes;
|
|
//First look for constant if branches
|
|
for (i = 0; i < _nodes->numNodes; i++) {
|
|
if (nodes[i].token == T_IF && nodes[i - 1].token == T_START_STATEMENT) {
|
|
if (nodes[i + 1].token == T_START_EXPRESSION && nodes[i + 2].token == T_CONSTANT && nodes[i + 3].token == T_END_EXPRESSION) {
|
|
int back=i - 1;
|
|
matched = 1;
|
|
//Need to check if fallout considers 'if ("bingle")' and 'if ("")' to be 1 or 0
|
|
takebranch = nodes[i + 2].value.intData;
|
|
RemoveNodes(_nodes, --i, 6); //start statement, if, start expression, const, end expression, then
|
|
assert(nodes[i].token == T_START_STATEMENT);
|
|
if (takebranch) {
|
|
int back2 = i++, tmp;
|
|
parseMessageAtNode(&nodes[i], "Eliminating constant if expression (true)");
|
|
assert(nodes[i].token == T_BEGIN);
|
|
SkipStatement;
|
|
assert(nodes[i - 2].token == T_END);
|
|
RemoveNodes(_nodes, i - 2, 2); //end end statement
|
|
tmp = i;
|
|
i = back2;
|
|
assert(nodes[i].token == T_START_STATEMENT);
|
|
assert(nodes[i + 1].token == T_BEGIN);
|
|
RemoveNodes(_nodes, i, 2); //begin begin statement
|
|
i = tmp - 4;
|
|
if (nodes[i].token == T_ELSE) {
|
|
RemoveNodes(_nodes, i, 1);
|
|
EatStatement(_nodes, i);
|
|
}
|
|
} else {
|
|
parseMessageAtNode(&nodes[i], "Eliminating constant if expression (false)");
|
|
EatStatement(_nodes, i);
|
|
if (nodes[i].token == T_ELSE) {
|
|
int back2 = i++, tmp;
|
|
RemoveNodes(_nodes, i - 1, 1); //else
|
|
assert(nodes[i].token == T_BEGIN);
|
|
SkipStatement;
|
|
assert(nodes[i - 2].token == T_END);
|
|
RemoveNodes(_nodes, i - 2, 2); //end end statement
|
|
tmp = i;
|
|
i = back2;
|
|
assert(nodes[i].token == T_START_STATEMENT);
|
|
assert(nodes[i + 1].token == T_BEGIN);
|
|
RemoveNodes(_nodes, i, 2); //else begin statement begin
|
|
i = tmp - 4;
|
|
}
|
|
}
|
|
assert(nodes[i].token == T_END_STATEMENT);
|
|
RemoveNodes(_nodes, i, 1); //The final end statement
|
|
i = back;
|
|
}
|
|
}
|
|
}
|
|
//Next strip out anything following returns, breaks and continue
|
|
for (i = 0; i < _nodes->numNodes; i++) {
|
|
if (nodes[i].token == T_RETURN||nodes[i].token == T_BREAK||nodes[i].token == T_CONTINUE) {
|
|
//if (nodes[i].token == T_RETURN)
|
|
SkipStatement;
|
|
while (nodes[i].token == T_START_STATEMENT) {
|
|
parseMessageAtNode(&nodes[i], "Eating statement hidden by return");
|
|
EatStatement(_nodes, i);
|
|
matched=1;
|
|
}
|
|
}
|
|
}
|
|
//Finally look for empty if blocks and remove them if the expression is pure
|
|
for (i = 0; i < _nodes->numNodes - 5; i++) {
|
|
if (nodes[i].token == T_THEN&&nodes[i+3].token == T_END&&nodes[i+4].token == T_END_STATEMENT&&nodes[i+5].token!=T_ELSE) {
|
|
int back = i, expressiondepth = 1;
|
|
assert(nodes[i + 1].token == T_START_STATEMENT);
|
|
assert(nodes[i + 2].token == T_BEGIN);
|
|
assert(nodes[i - 1].token == T_END_EXPRESSION);
|
|
i -= 2;
|
|
while (expressiondepth) {
|
|
if (nodes[i].token == T_START_EXPRESSION) expressiondepth--;
|
|
else if (nodes[i].token == T_END_EXPRESSION) expressiondepth++;
|
|
i--;
|
|
}
|
|
assert(nodes[i].token == T_IF);
|
|
assert(nodes[--i].token == T_START_STATEMENT);
|
|
if (IsExpressionPure(&nodes[i + 2])) {
|
|
parseMessageAtNode(&nodes[i], "Eating empty if block");
|
|
EatStatement(_nodes, i);
|
|
matched = 1;
|
|
} else {
|
|
parseWarningAtNode(&nodes[i], "Optimizer found an empty if block, but could not remove it because the expression could not be confirmed as pure");
|
|
}
|
|
i = back;
|
|
}
|
|
if (nodes[i].token == T_ELSE && nodes[i + 3].token == T_END && nodes[i + 4].token == T_END_STATEMENT) {
|
|
int back = i, expressiondepth = 1;
|
|
assert(nodes[i + 1].token == T_START_STATEMENT);
|
|
assert(nodes[i + 2].token == T_BEGIN);
|
|
assert(nodes[i - 1].token == T_END_STATEMENT);
|
|
parseMessageAtNode(&nodes[i], "Eating empty else block");
|
|
RemoveNodes(_nodes, i, 1);
|
|
EatStatement(_nodes, i);
|
|
matched = 1;
|
|
}
|
|
}
|
|
return matched;
|
|
}
|
|
|
|
static int CanCombine(VarUsage *u1, VarUsage *u2) {
|
|
int u1min = u1->firstassign < u1->firstuse ? u1->firstassign : u1->firstuse;
|
|
int u2max = u2->lastassign > u2->lastuse ? u2->lastassign : u2->lastuse;
|
|
if (u1min < u2max) return 0;
|
|
if (u1min > u2max) return 1;
|
|
//TODO: If they're equal, if we're in a while loop return 0, otherwise if one is an assign and the other is part of the expression we can probably combine
|
|
return 0;
|
|
}
|
|
|
|
static void VariableReuse(NodeList* _nodes, VariableList* vars, int numArgs, VarUsage* usage) {
|
|
int i, j, k;
|
|
Node* nodes=_nodes->nodes;
|
|
FindVarUsage(nodes, usage, vars->numVariables);
|
|
for (i=vars->numVariables-1;i>=numArgs;i--) {
|
|
for (j=0;j<i; j++) {
|
|
if (CanCombine(&usage[i], &usage[j]) || CanCombine(&usage[j], &usage[i])) {
|
|
parseMessageAtNode(&nodes[0], "Merging variables %d and %d", i, j);
|
|
for (k=0;k<_nodes->numNodes;k++) {
|
|
if (nodes[k].token == T_SYMBOL&&LookupVariable(&nodes[k]) == i) nodes[k].value.intData=j;
|
|
}
|
|
FindVarUsage(nodes, usage, vars->numVariables);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DeadVariableRemoval(NodeList* _nodes, VariableList* vars, int numArgs) {
|
|
int i, var, j;
|
|
Node* nodes = _nodes->nodes;
|
|
int *uses = (int*)calloc(1, vars->numVariables * 4);
|
|
for (i = 0; i < vars->numVariables; i++) uses[i] = 0;
|
|
for (i = 0; i < _nodes->numNodes; i++) {
|
|
if (nodes[i].token == T_SYMBOL && (var = LookupVariable(&nodes[i])) != -1) {
|
|
uses[var]++;
|
|
// If any proc argument is used, mark all previous as used too, to prevent argument values being swapped.
|
|
if (var < numArgs) {
|
|
for (j = 0; j < var; j++) {
|
|
if (uses[j] == 0) uses[j]++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (i = vars->numVariables - 1; i >= 0;i--) {
|
|
if (!uses[i]) {
|
|
memmove(&vars->variables[i], &vars->variables[i + 1], (vars->numVariables - (i + 1)) * sizeof(Variable));
|
|
vars->numVariables--;
|
|
parseMessageAtNode(&nodes[0], "Eliminating variable id %d", i);
|
|
for (j = 0; j < _nodes->numNodes; j++) {
|
|
if (nodes[j].token == T_SYMBOL && LookupVariable(&nodes[j]) > i) nodes[j].value.intData--;
|
|
}
|
|
}
|
|
}
|
|
free(uses);
|
|
}
|
|
|
|
static int Combine(NodeList* _nodes) {
|
|
int i, depth, j, k, matched = 0;
|
|
char op;
|
|
Node* nodes = _nodes->nodes;
|
|
for (i = 5; i < _nodes->numNodes - 4; i++) {
|
|
if (nodes[i].token < T_ASSIGN_ADD || nodes[i].token > T_ASSIGN_DIV) continue;
|
|
switch (nodes[i].token) {
|
|
case T_ASSIGN_ADD: op = '+'; break;
|
|
case T_ASSIGN_SUB: op = '-'; break;
|
|
case T_ASSIGN_MUL: op = '*'; break;
|
|
case T_ASSIGN_DIV: op = '/'; break;
|
|
}
|
|
assert(nodes[i + 1].token == T_START_EXPRESSION);
|
|
assert(nodes[i - 2].token == T_START_STATEMENT);
|
|
assert(nodes[i - 1].token == T_SYMBOL);
|
|
if (nodes[i - 3].token != T_END_STATEMENT) continue;
|
|
depth = 1;
|
|
j = i - 3;
|
|
while (depth) {
|
|
j--;
|
|
if (nodes[j].token == T_START_STATEMENT) depth--;
|
|
else if (nodes[j].token == T_END_STATEMENT) depth++;
|
|
}
|
|
assert(nodes[j].token == T_START_STATEMENT);
|
|
if (nodes[j + 1].token != T_SYMBOL || nodes[j + 2].token != T_ASSIGN || nodes[j + 1].value.intData != nodes[i - 1].value.intData || nodes[j + 1].value.type != nodes[i - 1].value.type) continue;
|
|
|
|
parseMessageAtNode(&nodes[i], "Combining assignments");
|
|
matched = 1;
|
|
|
|
assert(nodes[i - 4].token == T_END_EXPRESSION);
|
|
depth = 1;
|
|
k = i + 1;
|
|
while (depth) {
|
|
k++;
|
|
if (nodes[k].token == T_START_EXPRESSION) depth++;
|
|
else if (nodes[k].token == T_END_EXPRESSION) depth--;
|
|
}
|
|
assert(nodes[k].token == T_END_EXPRESSION);
|
|
assert(nodes[k + 1].token == T_END_STATEMENT);
|
|
memmove(&nodes[i - 4], &nodes[i + 2], (k - i - 1) * sizeof(Node));
|
|
assert(nodes[k - 6].token == T_END_EXPRESSION);
|
|
nodes[k - 6].token = op;
|
|
nodes[k - 5].token = T_END_EXPRESSION;
|
|
nodes[k - 4].token = T_END_STATEMENT;
|
|
memmove(&nodes[k - 3], &nodes[k + 2], (_nodes->numNodes - k - 2) * sizeof(Node));
|
|
_nodes->numNodes -= 6;
|
|
}
|
|
return matched;
|
|
}
|
|
|
|
static void OptimizeProcedure(Procedure* proc) {
|
|
int found;
|
|
int hasVars = proc->variables.variables != 0;
|
|
Value *values;
|
|
VarUsage *usage;
|
|
|
|
if (hasVars) {
|
|
values = (Value*)malloc(sizeof(Value) * proc->variables.numVariables);
|
|
usage = (VarUsage*)malloc(proc->variables.numVariables * sizeof(VarUsage));
|
|
}
|
|
do {
|
|
found = 0;
|
|
found = ConstantFolding(&proc->nodes);
|
|
if (hasVars) {
|
|
if (optimize >= 3) { // constant propagation is known to break code
|
|
found |= ConstantPropagation(&proc->nodes, proc->variables.variables, values, proc->variables.numVariables, proc->numArgs);
|
|
}
|
|
found |= DeadStoreRemoval(&proc->nodes, &proc->variables, usage);
|
|
}
|
|
found |= DeadCodeRemoval(&proc->nodes);
|
|
found |= Combine(&proc->nodes);
|
|
} while (found);
|
|
#ifdef _DEBUG
|
|
while (ConstantFoldingPassTwo(&proc->nodes)); // Additional optimization passes
|
|
#endif
|
|
if (hasVars) {
|
|
DeadVariableRemoval(&proc->nodes, &proc->variables, proc->numArgs); //use this twice so that VariableReuse has no completely dead variables to worry about
|
|
if (optimize >= 3) { // variable reuse is known to break code
|
|
VariableReuse(&proc->nodes, &proc->variables, proc->numArgs, usage);
|
|
DeadVariableRemoval(&proc->nodes, &proc->variables, proc->numArgs);
|
|
}
|
|
free(values);
|
|
free(usage);
|
|
}
|
|
}
|
|
|
|
int IsProtectedProc(const char* c) {
|
|
// this should be the full list of procedures (copy-pasted directly from EXE)
|
|
Protect("no_p_proc");
|
|
Protect("start");
|
|
Protect("spatial_p_proc");
|
|
Protect("description_p_proc");
|
|
Protect("desc_p_proc"); // Fallout 1
|
|
Protect("pickup_p_proc");
|
|
Protect("drop_p_proc");
|
|
Protect("use_p_proc");
|
|
Protect("use_obj_on_p_proc");
|
|
Protect("use_skill_on_p_proc");
|
|
Protect("talk_p_proc");
|
|
Protect("critter_p_proc");
|
|
Protect("combat_p_proc");
|
|
Protect("damage_p_proc");
|
|
Protect("map_enter_p_proc");
|
|
Protect("map_exit_p_proc");
|
|
Protect("create_p_proc");
|
|
Protect("destroy_p_proc");
|
|
Protect("look_at_p_proc");
|
|
Protect("timed_event_p_proc");
|
|
Protect("map_update_p_proc");
|
|
Protect("push_p_proc");
|
|
Protect("is_dropping_p_proc");
|
|
Protect("combat_is_starting_p_proc");
|
|
Protect("combat_is_over_p_proc");
|
|
|
|
//Protect("node998");
|
|
//Protect("node999");
|
|
return 0;
|
|
}
|
|
static int __once = 0;
|
|
static void EliminateProcedure(Program *prog, int id) {
|
|
int i, j;
|
|
parseMessageAtNode(&prog->procedures.procedures[id].nodes.nodes[0], "Eliminating procedure %s", prog->namelist + prog->procedures.procedures[id].name);
|
|
/*, remLen, srcOfs;
|
|
long *namesLen;
|
|
char *src, *dst;
|
|
namesLen = (long*)prog->namelist;
|
|
dst = prog->namelist + prog->procedures.procedures[id].name - 2;
|
|
src = dst + 2 + *(short*)dst;
|
|
srcOfs = src - prog->namelist + 2;
|
|
remLen = src - dst;
|
|
//parseMessageAtNode(&prog->procedures.procedures[id].nodes.nodes[0], "Eliminating procedure %s total=%d srcOfs=%d dstOfs=%d rem=%d move=%d", prog->namelist + prog->procedures.procedures[id].name, namesLen, srcOfs-prog->namelist, dstOfs-prog->namelist, remLen, prog->namelist+*namesLen-srcOfs);
|
|
// remove from name list
|
|
if (!__once) {
|
|
memmove(dst, src, *namesLen - (src - prog->namelist) + 4);
|
|
for (i = id; i < prog->procedures.numProcedures; i++) {
|
|
if (prog->procedures.procedures[i].name >= srcOfs)
|
|
prog->procedures.procedures[i].name -= remLen;
|
|
}
|
|
for (i = 0; i < prog->variables.numVariables; i++) {
|
|
if (prog->variables.variables[i].name >= srcOfs)
|
|
prog->variables.variables[i].name -= remLen;
|
|
}
|
|
for (i = 0; i < prog->externals.numVariables; i++) {
|
|
if (prog->externals.variables[i].name >= srcOfs)
|
|
prog->externals.variables[i].name -= remLen;
|
|
}
|
|
*namesLen -= remLen;
|
|
__once = 1;
|
|
}*/
|
|
memmove(&prog->procedures.procedures[id], &prog->procedures.procedures[id + 1], (prog->procedures.numProcedures - (id + 1)) * sizeof(Procedure));
|
|
prog->procedures.numProcedures--;
|
|
for (i = 1; i < prog->procedures.numProcedures; i++) {
|
|
NodeList* list = &prog->procedures.procedures[i].nodes;
|
|
for (j = 0; j < list->numNodes; j++) {
|
|
if (list->nodes[j].token == T_SYMBOL && (list->nodes[j].value.type & P_PROCEDURE) && list->nodes[j].value.intData >= id) {
|
|
//assert(list->nodes[j].value.intData != id); //The procedure elimination can now cope with circular references, which would trigger this assert
|
|
list->nodes[j].value.intData--;
|
|
}
|
|
}
|
|
if (prog->procedures.procedures[i].type & P_CONDITIONAL) {
|
|
list = &prog->procedures.procedures[i].condition;
|
|
for (j = 0; j < list->numNodes; j++) {
|
|
if (list->nodes[j].token == T_SYMBOL && (list->nodes[j].value.type & P_PROCEDURE) && list->nodes[j].value.intData >= id) {
|
|
//assert(list->nodes[j].value.intData != id);
|
|
list->nodes[j].value.intData--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//This is the old method of procedure elimination; it wouldn't correctly remove circular references
|
|
/*static void UpdateProcedureReferences(Procedure* procs, int count) {
|
|
int i, j;
|
|
Node* node;
|
|
for (i = 1; i < count; i++) procs[i].uses = 0;
|
|
for (i = 1; i < count; i++) {
|
|
for (j = 0; j < procs[i].nodes.numNodes; j++) {
|
|
node = &procs[i].nodes.nodes[j];
|
|
if (node->token == T_SYMBOL && node->value.type == (P_PROCEDURE | P_LOCAL)) procs[node->value.intData].uses++;
|
|
}
|
|
if (procs[i].type & P_CONDITIONAL) {
|
|
for (j = 0; j < procs[i].condition.numNodes; j++) {
|
|
node = &procs[i].condition.nodes[j];
|
|
if (node->token == T_SYMBOL && node->value.type == (P_PROCEDURE | P_LOCAL)) procs[node->value.intData].uses++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DecendUnusedProcedures(Program *prog) {
|
|
int i, matched = 1;
|
|
while (matched) {
|
|
matched = 0;
|
|
UpdateProcedureReferences(prog->procedures.procedures, prog->procedures.numProcedures);
|
|
for (i = 1; i < prog->procedures.numProcedures; i++) {
|
|
if (prog->procedures.procedures[i].type & (P_TIMED | P_IMPORT | P_EXPORT | P_CONDITIONAL | P_CRITICAL)) continue;
|
|
if (!prog->procedures.procedures[i].uses && !IsProtectedProc(prog->namelist + prog->procedures.procedures[i].name)) {
|
|
EliminateProcedure(prog, i--);
|
|
matched = 1;
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
static int IsProcedureReference(Node* node) {
|
|
return node->token == T_SYMBOL && ((node->value.type & (P_PROCEDURE | P_LOCAL)) == (P_PROCEDURE | P_LOCAL));
|
|
}
|
|
|
|
static void UpdateProcedureReferences(Procedure* procs, int count) {
|
|
int i, j, matched = 1;
|
|
Node* node;
|
|
for (i = 1; i < count; i++) {
|
|
if (IsProtectedProc(currprogram->namelist + procs[i].name) || procs[i].type & (P_TIMED | P_CONDITIONAL | P_EXPORT/* | P_CRITICAL*/)) procs[i].uses = 1;
|
|
//else if (procs[i].type & P_IMPORT) procs[i].uses = 2;
|
|
else procs[i].uses = 0;
|
|
}
|
|
while (matched) {
|
|
matched = 0;
|
|
for (i = 1; i < count; i++) {
|
|
if (procs[i].uses != 1 || procs[i].type & P_IMPORT) continue;
|
|
procs[i].uses = 2;
|
|
for (j = 0; j < procs[i].nodes.numNodes; j++) {
|
|
node = &procs[i].nodes.nodes[j];
|
|
if (IsProcedureReference(node)) {
|
|
if (!procs[node->value.intData].uses) {
|
|
matched = 1;
|
|
procs[node->value.intData].uses = 1;
|
|
}
|
|
}
|
|
}
|
|
if (procs[i].type & P_CONDITIONAL) {
|
|
for (j = 0; j < procs[i].condition.numNodes; j++) {
|
|
node = &procs[i].condition.nodes[j];
|
|
if (IsProcedureReference(node)) {
|
|
if (!procs[node->value.intData].uses) {
|
|
matched = 1;
|
|
procs[node->value.intData].uses = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DecendUnusedProcedures(Program *prog) {
|
|
int i;
|
|
UpdateProcedureReferences(prog->procedures.procedures, prog->procedures.numProcedures);
|
|
for (i = 1; i < prog->procedures.numProcedures; i++) {
|
|
if (!prog->procedures.procedures[i].uses) EliminateProcedure(prog, i--);
|
|
}
|
|
}
|
|
|
|
static int ConstantPropagateGlobals(Program *prog) {
|
|
Value* values = (Value*)malloc(prog->variables.numVariables * sizeof(Variable));
|
|
int i, j, matched = 0;
|
|
//Make initial list of variables
|
|
for (i = 0; i < prog->variables.numVariables; i++) {
|
|
values[i]=prog->variables.variables[i].value;
|
|
}
|
|
//Check for any that are assigned
|
|
for (i = 1; i < prog->procedures.numProcedures; i++) {
|
|
for (j = 1; j < prog->procedures.procedures[i].nodes.numNodes; j++) {
|
|
if (prog->procedures.procedures[i].nodes.nodes[j].token >= T_ASSIGN && prog->procedures.procedures[i].nodes.nodes[j].token <= T_ASSIGN_DIV) {
|
|
Node* node = &prog->procedures.procedures[i].nodes.nodes[j - 1];
|
|
assert(node->token == T_SYMBOL);
|
|
if (node->value.type == P_GLOBAL) values[node->value.intData].type = -1;
|
|
}
|
|
}
|
|
}
|
|
//Constant propagate any that are not
|
|
for (i = 1; i < prog->procedures.numProcedures; i++) {
|
|
for (j = 1; j < prog->procedures.procedures[i].nodes.numNodes; j++) {
|
|
Node* node = &prog->procedures.procedures[i].nodes.nodes[j];
|
|
if (node->token == T_SYMBOL && node->value.type == P_GLOBAL && values[node->value.intData].type != -1) {
|
|
if (node[1].token == T_START_EVENT) {
|
|
parseWarningAtNode(node, "Global variable '%s' is called as a procedure butnever recieves a value", prog->namelist + prog->variables.variables[node->value.intData].name);
|
|
} else {
|
|
parseMessageAtNode(node, "Constant propagating global variable %s", prog->namelist + prog->variables.variables[node->value.intData].name);
|
|
node->token = T_CONSTANT;
|
|
node->value = values[node->value.intData];
|
|
matched = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
free(values);
|
|
return matched;
|
|
}
|
|
|
|
static void ShiftSymbolIntValues(Program *prog, int var, int matchType) {
|
|
int i, j;
|
|
for (i = 1; i < prog->procedures.numProcedures; i++) {
|
|
NodeList* list = &prog->procedures.procedures[i].nodes;
|
|
for (j = 0; j < list->numNodes; j++) {
|
|
if (list->nodes[j].token == T_SYMBOL && list->nodes[j].value.type == matchType && list->nodes[j].value.intData >= var) {
|
|
assert(list->nodes[j].value.intData != var);
|
|
list->nodes[j].value.intData--;
|
|
}
|
|
}
|
|
if (prog->procedures.procedures[i].type & P_CONDITIONAL) {
|
|
list = &prog->procedures.procedures[i].condition;
|
|
for (j = 0; j < list->numNodes; j++) {
|
|
if (list->nodes[j].token == T_SYMBOL && list->nodes[j].value.type == matchType && list->nodes[j].value.intData >= var) {
|
|
assert(list->nodes[j].value.intData != var);
|
|
list->nodes[j].value.intData--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void EliminateUnreferencedGlobals(Program *prog) {
|
|
int i, j, var;
|
|
Node* node;
|
|
//count variable references
|
|
for (i = 0; i < prog->variables.numVariables; i++) prog->variables.variables[i].uses = 0;
|
|
for (i = 0; i < prog->externals.numVariables; i++)
|
|
prog->externals.variables[i].uses = (prog->externals.variables[i].type == V_EXPORT); // assume export variable is always used
|
|
for (i = 1; i < prog->procedures.numProcedures; i++) {
|
|
for (j = 0; j < prog->procedures.procedures[i].nodes.numNodes; j++) {
|
|
node = &prog->procedures.procedures[i].nodes.nodes[j];
|
|
if (node->token == T_SYMBOL) {
|
|
if (node->value.type == P_GLOBAL) prog->variables.variables[node->value.intData].uses++;
|
|
if (node->value.type == P_EXTERN) prog->externals.variables[node->value.intData].uses++;
|
|
}
|
|
}
|
|
if (prog->procedures.procedures[i].type & P_CONDITIONAL) {
|
|
for (j = 0; j < prog->procedures.procedures[i].condition.numNodes; j++) {
|
|
node = &prog->procedures.procedures[i].condition.nodes[j];
|
|
if (node->token == T_SYMBOL) {
|
|
if (node->value.type == P_GLOBAL) prog->variables.variables[node->value.intData].uses++;
|
|
if (node->value.type == P_EXTERN) prog->externals.variables[node->value.intData].uses++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//remove any variables that aren't referenced or exported
|
|
for (var = prog->variables.numVariables - 1; var >= 0; var--) {
|
|
if (!prog->variables.variables[var].uses) {
|
|
parseMessageAtNode(0, "Eliminating unused global variable %s", prog->namelist + prog->variables.variables[var].name);
|
|
|
|
memmove(&prog->variables.variables[var], &prog->variables.variables[var + 1], (prog->variables.numVariables - (var + 1)) * sizeof(Variable));
|
|
prog->variables.numVariables--;
|
|
ShiftSymbolIntValues(prog, var, P_GLOBAL);
|
|
}
|
|
}
|
|
// remove unreferenced import variables
|
|
for (var = prog->externals.numVariables - 1; var >= 0; var--) {
|
|
if (!prog->externals.variables[var].uses) {
|
|
parseMessageAtNode(0, "Eliminating unused import variable %s", prog->namelist + prog->externals.variables[var].name);
|
|
|
|
memmove(&prog->externals.variables[var], &prog->externals.variables[var + 1], (prog->externals.numVariables - (var + 1)) * sizeof(Variable));
|
|
prog->externals.numVariables--;
|
|
ShiftSymbolIntValues(prog, var, P_EXTERN);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void GetNamelistData(char* namelist, int* outEntries, char** outEndptr, int** outRefs, int** outOffsets, int** outTransforms) {
|
|
char* list = namelist + 4, *endptr;
|
|
int entries = 0, *refs, *offsets, *transforms, i;
|
|
|
|
while (*(unsigned short*)list != 0xffff) {
|
|
entries++;
|
|
list += *(unsigned short*)list + 2;
|
|
}
|
|
endptr = list + 2;
|
|
refs = (int*)calloc(1, entries * 4);
|
|
offsets = (int*)malloc(entries * 4);
|
|
transforms = (int*)malloc(entries * 4);
|
|
//first find the offsets
|
|
list = namelist + 4;
|
|
for (i = 0; i < entries; i++) {
|
|
offsets[i] = 2 + (unsigned int)list - (unsigned int)namelist;
|
|
list += *(unsigned short*)list + 2;
|
|
}
|
|
memcpy(transforms, offsets, entries * 4);
|
|
|
|
*outEntries = entries;
|
|
*outEndptr = endptr;
|
|
*outRefs = refs;
|
|
*outOffsets = offsets;
|
|
*outTransforms = transforms;
|
|
}
|
|
|
|
static void FreeNamelistData(int* refs, int* offsets, int* transforms) {
|
|
free(refs);
|
|
free(offsets);
|
|
free(transforms);
|
|
}
|
|
|
|
static void RemoveFromNamelist(char* namelist, int i, int entries, int* offsets, int* transforms, char** endptr, char* logFormat) {
|
|
char* namestart;
|
|
int j, len;
|
|
parseMessageAtNode(0, logFormat, namelist + offsets[i]);
|
|
namestart = namelist + offsets[i] - 2;
|
|
len = *(unsigned short*)namestart + 2;
|
|
memmove(namestart, len + namestart, *endptr - (len + namestart));
|
|
(*(unsigned int*)namelist) -= len;
|
|
*endptr -= len;
|
|
transforms[i] = 0x7fffffff;
|
|
for (j = i + 1; j < entries; j++) transforms[j] -= len;
|
|
}
|
|
|
|
static void SetNameReferenced(int name, int entries, int* refs, int* offsets, int refType) {
|
|
int j;
|
|
for (j = 0; j < entries; j++) {
|
|
if (name == offsets[j]) {
|
|
refs[j] |= refType;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void ShiftName(int* name, int entries, int* transforms, int* offsets) {
|
|
int j;
|
|
for (j = 0; j < entries; j++) {
|
|
if (*name == offsets[j]) {
|
|
assert(transforms[j] != 0x7fffffff);
|
|
*name = transforms[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
static void CompressNamelist(Program *prog) {
|
|
char* endptr, *namestart;
|
|
int entries = 0, *refs, *offsets, *transforms, i, j, len;
|
|
|
|
GetNamelistData(prog->namelist, &entries, &endptr, &refs, &offsets, &transforms);
|
|
|
|
//Then check all variables, imports and procedures to see where in the namelist they point
|
|
for (i = 0; i < prog->externals.numVariables; i++) {
|
|
SetNameReferenced(prog->externals.variables[i].name, entries, refs, offsets, 1);
|
|
}
|
|
for (i = 0; i < prog->variables.numVariables; i++) {
|
|
SetNameReferenced(prog->variables.variables[i].name, entries, refs, offsets, 2);
|
|
}
|
|
for (i = 0; i < prog->procedures.numProcedures; i++) {
|
|
SetNameReferenced(prog->procedures.procedures[i].name, entries, refs, offsets, 4);
|
|
}
|
|
//For each string that isn't referenced, remove it
|
|
for (i = entries - 1; i >= 0; i--) {
|
|
if (!refs[i]) {
|
|
RemoveFromNamelist(prog->namelist, i, entries, offsets, transforms, &endptr, "Removing unused string '%s' from program namespace");
|
|
} else if (refs[i] == 2 && optimize >= 3) {
|
|
namestart = prog->namelist + offsets[i] - 2;
|
|
len = *(unsigned short*)namestart + 2;
|
|
if (len > 4) {
|
|
parseMessageAtNode(0, "Shortening non-visible string '%s' in program namespace", prog->namelist + offsets[i]);
|
|
*(unsigned short*)(namestart) = 2;
|
|
*(char*)(prog->namelist + offsets[i] + 0) = 'a';
|
|
*(char*)(prog->namelist + offsets[i] + 1) = 0;
|
|
(*(unsigned int*)prog->namelist) -= len - 4;
|
|
memmove(prog->namelist + offsets[i] + 2, len + namestart, endptr - (len + namestart));
|
|
for (j = i + 1; j < entries; j++) transforms[j] -= len - 4;
|
|
}
|
|
}
|
|
}
|
|
//And finally, update the name pointers of everything else
|
|
for (i = 0; i < prog->externals.numVariables; i++) {
|
|
ShiftName(&prog->externals.variables[i].name, entries, transforms, offsets);
|
|
}
|
|
for (i = 0; i < prog->variables.numVariables; i++) {
|
|
ShiftName(&prog->variables.variables[i].name, entries, transforms, offsets);
|
|
}
|
|
for (i = 0; i < prog->procedures.numProcedures; i++) {
|
|
ShiftName(&prog->procedures.procedures[i].name, entries, transforms, offsets);
|
|
}
|
|
|
|
FreeNamelistData(refs, offsets, transforms);
|
|
}
|
|
|
|
static int* GetStringOffsetRef(Node *node, Program *prog) {
|
|
if (node->token == T_CONSTANT && node->value.type == V_STRING) {
|
|
return &node->value.stringData;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void SetVariableDefaultValueReferenced(Variable* var, int entries, int* refs, int* offsets, int refType) {
|
|
if (var->initialized && var->value.type == V_STRING) {
|
|
SetNameReferenced(var->value.stringData, entries, refs, offsets, refType);
|
|
}
|
|
}
|
|
|
|
static void ShiftVariableDefaultValue(Variable* var, int entries, int* transforms, int* offsets) {
|
|
if (var->initialized && var->value.type == V_STRING) {
|
|
ShiftName(&var->value.stringData, entries, transforms, offsets);
|
|
}
|
|
}
|
|
|
|
static void CompressStringspace(Program *prog) {
|
|
char* endptr;
|
|
int entries = 0, *refs, *offsets, *transforms, i, k, *stringData;
|
|
Procedure* proc;
|
|
Node* node;
|
|
|
|
if (!prog->stringspace) return;
|
|
|
|
GetNamelistData(prog->stringspace, &entries, &endptr, &refs, &offsets, &transforms);
|
|
|
|
//Then check all procedures nodes and variable default values to see where in the stringspace they point
|
|
for (i = 0; i < prog->procedures.numProcedures; i++) {
|
|
proc = &prog->procedures.procedures[i];
|
|
for (k = 0; k < proc->nodes.numNodes; k++) {
|
|
node = &proc->nodes.nodes[k];
|
|
if (stringData = GetStringOffsetRef(node, prog)) {
|
|
SetNameReferenced(*stringData, entries, refs, offsets, 1);
|
|
}
|
|
}
|
|
for (k = 0; k < proc->variables.numVariables; k++) {
|
|
SetVariableDefaultValueReferenced(&proc->variables.variables[k], entries, refs, offsets, 2);
|
|
}
|
|
}
|
|
for (i = 0; i < prog->externals.numVariables; i++) {
|
|
SetVariableDefaultValueReferenced(&prog->externals.variables[i], entries, refs, offsets, 4);
|
|
}
|
|
for (i = 0; i < prog->variables.numVariables; i++) {
|
|
SetVariableDefaultValueReferenced(&prog->variables.variables[i], entries, refs, offsets, 8);
|
|
}
|
|
//Handle stringified names
|
|
for (i = 0; i < prog->procedures.numProcedures; i++) {
|
|
SetNameReferenced(prog->procedures.procedures[i].stringifiedName, entries, refs, offsets, 16);
|
|
}
|
|
//For each string that isn't referenced, remove it
|
|
for (i = entries - 1; i >= 0; i--) {
|
|
if (!refs[i]) {
|
|
RemoveFromNamelist(prog->stringspace, i, entries, offsets, transforms, &endptr, "Removing unused string '%s' from program stringspace");
|
|
}
|
|
}
|
|
//And finally, update the string pointers of everything else
|
|
for (i = 0; i < prog->procedures.numProcedures; i++) {
|
|
proc = &prog->procedures.procedures[i];
|
|
for (k = 0; k < proc->nodes.numNodes; k++) {
|
|
node = &proc->nodes.nodes[k];
|
|
if (stringData = GetStringOffsetRef(node, prog)) {
|
|
ShiftName(stringData, entries, transforms, offsets);
|
|
}
|
|
}
|
|
for (k = 0; k < proc->variables.numVariables; k++) {
|
|
ShiftVariableDefaultValue(&proc->variables.variables[k], entries, transforms, offsets);
|
|
}
|
|
}
|
|
for (i = 0; i < prog->externals.numVariables; i++) {
|
|
ShiftVariableDefaultValue(&prog->externals.variables[i], entries, transforms, offsets);
|
|
}
|
|
for (i = 0; i < prog->variables.numVariables; i++) {
|
|
ShiftVariableDefaultValue(&prog->variables.variables[i], entries, transforms, offsets);
|
|
}
|
|
//Handle stringified names
|
|
for (i = 0; i < prog->procedures.numProcedures; i++) {
|
|
ShiftName(&prog->procedures.procedures[i].stringifiedName, entries, transforms, offsets);
|
|
}
|
|
|
|
FreeNamelistData(refs, offsets, transforms);
|
|
}
|
|
|
|
void optimizeTree(Program *prog) {
|
|
int i, matched = 1;
|
|
currprogram = prog;
|
|
if (optimize == 1)
|
|
{
|
|
DecendUnusedProcedures(prog);
|
|
EliminateUnreferencedGlobals(prog);
|
|
}
|
|
else
|
|
{
|
|
while (matched) {
|
|
matched = 0;
|
|
DecendUnusedProcedures(prog);
|
|
for (i = 1; i <prog->procedures.numProcedures; i++) {
|
|
Procedure* proc = &prog->procedures.procedures[i];
|
|
if ((proc->type & P_IMPORT) || proc->deftype != 2) continue;
|
|
OptimizeProcedure(proc);
|
|
}
|
|
DecendUnusedProcedures(prog);
|
|
matched |= ConstantPropagateGlobals(prog);
|
|
EliminateUnreferencedGlobals(prog);
|
|
}
|
|
}
|
|
CompressNamelist(prog);
|
|
CompressStringspace(prog);
|
|
}
|