mirror of https://github.com/inolen/redream.git
added initial expression simplification pass
This commit is contained in:
parent
9b9cde2b6d
commit
2b28a457da
|
@ -210,6 +210,7 @@ set(REDREAM_SOURCES
|
|||
#src/jit/ir/passes/constant_propagation_pass.c
|
||||
src/jit/ir/passes/conversion_elimination_pass.c
|
||||
src/jit/ir/passes/dead_code_elimination_pass.c
|
||||
src/jit/ir/passes/expression_simplification_pass.c
|
||||
src/jit/ir/passes/load_store_elimination_pass.c
|
||||
src/jit/ir/passes/pass_stat.c
|
||||
src/jit/ir/passes/register_allocation_pass.c
|
||||
|
|
|
@ -72,6 +72,30 @@ void ir_remove_instr(struct ir *ir, struct ir_instr *instr) {
|
|||
list_remove(&ir->instrs, &instr->it);
|
||||
}
|
||||
|
||||
struct ir_value *ir_alloc_int(struct ir *ir, int64_t c, enum ir_type type) {
|
||||
struct ir_value *v = ir_calloc(ir, sizeof(struct ir_value));
|
||||
v->type = type;
|
||||
switch (type) {
|
||||
case VALUE_I8:
|
||||
v->i8 = (int8_t)c;
|
||||
break;
|
||||
case VALUE_I16:
|
||||
v->i16 = (int16_t)c;
|
||||
break;
|
||||
case VALUE_I32:
|
||||
v->i32 = (int32_t)c;
|
||||
break;
|
||||
case VALUE_I64:
|
||||
v->i64 = c;
|
||||
break;
|
||||
default:
|
||||
LOG_FATAL("Unexpected value type");
|
||||
break;
|
||||
}
|
||||
v->reg = NO_REGISTER;
|
||||
return v;
|
||||
}
|
||||
|
||||
struct ir_value *ir_alloc_i8(struct ir *ir, int8_t c) {
|
||||
struct ir_value *v = ir_calloc(ir, sizeof(struct ir_value));
|
||||
v->type = VALUE_I8;
|
||||
|
|
|
@ -181,6 +181,7 @@ struct ir_instr *ir_append_instr(struct ir *ir, enum ir_op op,
|
|||
enum ir_type result_type);
|
||||
void ir_remove_instr(struct ir *ir, struct ir_instr *instr);
|
||||
|
||||
struct ir_value *ir_alloc_int(struct ir *ir, int64_t c, enum ir_type type);
|
||||
struct ir_value *ir_alloc_i8(struct ir *ir, int8_t c);
|
||||
struct ir_value *ir_alloc_i16(struct ir *ir, int16_t c);
|
||||
struct ir_value *ir_alloc_i32(struct ir *ir, int32_t c);
|
||||
|
|
|
@ -2,21 +2,19 @@
|
|||
#include "jit/ir/ir.h"
|
||||
#include "jit/ir/passes/pass_stat.h"
|
||||
|
||||
DEFINE_STAT(num_sext_removed, "Number of sext eliminated");
|
||||
DEFINE_STAT(num_zext_removed, "Number of zext eliminated");
|
||||
DEFINE_STAT(num_trunc_removed, "Number of trunc eliminated");
|
||||
|
||||
const char *cve_name = "cve";
|
||||
DEFINE_STAT(sext_removed, "Sign extends eliminated");
|
||||
DEFINE_STAT(zext_removed, "Zero extends eliminated");
|
||||
DEFINE_STAT(trunc_removed, "Truncations eliminated");
|
||||
|
||||
void cve_run(struct ir *ir) {
|
||||
list_for_each_entry_safe(instr, &ir->instrs, struct ir_instr, it) {
|
||||
// eliminate unnecessary sext / zext operations
|
||||
/* eliminate unnecessary sext / zext operations */
|
||||
if (instr->op == OP_LOAD || instr->op == OP_LOAD_FAST ||
|
||||
instr->op == OP_LOAD_SLOW || instr->op == OP_LOAD_CONTEXT) {
|
||||
enum ir_type memory_type = VALUE_V;
|
||||
bool same_type = true;
|
||||
bool all_sext = true;
|
||||
bool all_zext = true;
|
||||
int same_type = 1;
|
||||
int all_sext = 1;
|
||||
int all_zext = 1;
|
||||
|
||||
list_for_each_entry(use, &instr->result->uses, struct ir_use, it) {
|
||||
struct ir_instr *use_instr = use->instr;
|
||||
|
@ -28,38 +26,38 @@ void cve_run(struct ir *ir) {
|
|||
}
|
||||
|
||||
if (memory_type != use_result->type) {
|
||||
same_type = false;
|
||||
same_type = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (use_instr->op != OP_SEXT) {
|
||||
all_sext = false;
|
||||
all_sext = 0;
|
||||
}
|
||||
|
||||
if (use_instr->op != OP_ZEXT) {
|
||||
all_zext = false;
|
||||
all_zext = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (same_type && all_sext) {
|
||||
// TODO implement
|
||||
/* TODO implement */
|
||||
|
||||
STAT_num_sext_removed++;
|
||||
STAT_sext_removed++;
|
||||
} else if (same_type && all_zext) {
|
||||
// TODO implement
|
||||
/* TODO implement */
|
||||
|
||||
STAT_num_zext_removed++;
|
||||
STAT_zext_removed++;
|
||||
}
|
||||
} else if (instr->op == OP_STORE || instr->op == OP_STORE_FAST ||
|
||||
instr->op == OP_STORE_SLOW || instr->op == OP_STORE_CONTEXT) {
|
||||
struct ir_value *store_value = instr->arg[1];
|
||||
|
||||
if (store_value->def && store_value->def->op == OP_TRUNC) {
|
||||
// TODO implement
|
||||
/* TODO implement */
|
||||
|
||||
// note, don't actually remove the truncation as other values may
|
||||
// reference it. let DCE clean it up
|
||||
STAT_num_trunc_removed++;
|
||||
/* note, don't actually remove the truncation as other values may
|
||||
reference it. let DCE clean it up */
|
||||
STAT_trunc_removed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
#include "jit/ir/ir.h"
|
||||
#include "jit/ir/passes/pass_stat.h"
|
||||
|
||||
DEFINE_STAT(num_dead_removed, "Number of dead instructions eliminated");
|
||||
DEFINE_STAT(dead_removed, "Dead instructions eliminated");
|
||||
|
||||
void dce_run(struct ir *ir) {
|
||||
// iterate in reverse in order to remove groups of dead instructions that
|
||||
// only use eachother
|
||||
/* iterate in reverse in order to remove groups of dead instructions that
|
||||
only use eachother */
|
||||
list_for_each_entry_safe_reverse(instr, &ir->instrs, struct ir_instr, it) {
|
||||
struct ir_value *result = instr->result;
|
||||
|
||||
|
@ -17,7 +17,7 @@ void dce_run(struct ir *ir) {
|
|||
if (list_empty(&result->uses)) {
|
||||
ir_remove_instr(ir, instr);
|
||||
|
||||
STAT_num_dead_removed++;
|
||||
STAT_dead_removed++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
#include "jit/ir/passes/expression_simplification_pass.h"
|
||||
#include "jit/ir/ir.h"
|
||||
#include "jit/ir/passes/pass_stat.h"
|
||||
|
||||
DEFINE_STAT(zero_properties_removed, "Zero properties removed");
|
||||
DEFINE_STAT(zero_identities_removed, "Zero identities removed");
|
||||
DEFINE_STAT(one_identities_removed, "One identities removed");
|
||||
|
||||
void esimp_run(struct ir *ir) {
|
||||
list_for_each_entry(instr, &ir->instrs, struct ir_instr, it) {
|
||||
/* TODO simplify bitwise identities */
|
||||
/* x & x = x */
|
||||
/* x | x = x */
|
||||
/* x ^ x = 0 */
|
||||
|
||||
/* binary ops involving constants normally have the constant
|
||||
argument as the second argument */
|
||||
if (instr->arg[1] && ir_is_constant(instr->arg[1]) &&
|
||||
ir_is_int(instr->arg[1]->type)) {
|
||||
uint64_t rhs = ir_zext_constant(instr->arg[1]);
|
||||
|
||||
/* simplify binary ops where an argument of 0 always results in 0 */
|
||||
if ((instr->op == OP_AND || instr->op == OP_SMUL ||
|
||||
instr->op == OP_UMUL) &&
|
||||
rhs == 0) {
|
||||
struct ir_value *zero = ir_alloc_int(ir, 0, instr->result->type);
|
||||
ir_replace_uses(instr->result, zero);
|
||||
STAT_zero_properties_removed++;
|
||||
}
|
||||
|
||||
/* TODO simplify binary ops where 0 is the identity element */
|
||||
/* x + 0 = x */
|
||||
/* x - 0 = x */
|
||||
/* x | 0 = x */
|
||||
/* x ^ 0 = x */
|
||||
/* x << 0 = x */
|
||||
/* x >> 0 = x */
|
||||
|
||||
/* TODO simplify binary ops where 1 is the identity element */
|
||||
/* x * 1 = x */
|
||||
/* x / 1 = x */
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef EXPRESSION_SIMPLIFICATION_PASS_H
|
||||
#define EXPRESSION_SIMPLIFICATION_PASS_H
|
||||
|
||||
struct ir;
|
||||
|
||||
void esimp_run(struct ir *ir);
|
||||
|
||||
#endif
|
|
@ -2,10 +2,8 @@
|
|||
#include "jit/ir/ir.h"
|
||||
#include "jit/ir/passes/pass_stat.h"
|
||||
|
||||
DEFINE_STAT(num_loads_removed, "Number of loads eliminated");
|
||||
DEFINE_STAT(num_stores_removed, "Number of stores eliminated");
|
||||
|
||||
const char *lse_name = "lse";
|
||||
DEFINE_STAT(loads_removed, "Context loads eliminated");
|
||||
DEFINE_STAT(stores_removed, "Context stores eliminated");
|
||||
|
||||
#define MAX_OFFSET 16384
|
||||
|
||||
|
@ -19,7 +17,7 @@ struct lse {
|
|||
};
|
||||
|
||||
static void lse_clear_available(struct lse *lse) {
|
||||
// TODO use auto-incremented token instead of memset'ing this each time
|
||||
/* TODO use auto-incremented token instead of memset'ing this each time */
|
||||
memset(lse->available, 0, sizeof(lse->available));
|
||||
}
|
||||
|
||||
|
@ -28,9 +26,9 @@ static struct ir_value *lse_get_available(struct lse *lse, int offset) {
|
|||
|
||||
struct available *entry = &lse->available[offset];
|
||||
|
||||
// entries are added for the entire range of an available value to help with
|
||||
// invalidation. if this entry doesn't start at the requested offset, it's
|
||||
// not actually valid for reuse
|
||||
/* entries are added for the entire range of an available value to help with
|
||||
invalidation. if this entry doesn't start at the requested offset, it's
|
||||
not actually valid for reuse */
|
||||
if (entry->offset != offset) {
|
||||
return NULL;
|
||||
}
|
||||
|
@ -44,8 +42,8 @@ static void lse_erase_available(struct lse *lse, int offset, int size) {
|
|||
int begin = offset;
|
||||
int end = offset + size - 1;
|
||||
|
||||
// if the invalidation range intersects with an entry, merge that entry into
|
||||
// the invalidation range
|
||||
/* if the invalidation range intersects with an entry, merge that entry into
|
||||
the invalidation range */
|
||||
struct available *begin_entry = &lse->available[begin];
|
||||
struct available *end_entry = &lse->available[end];
|
||||
|
||||
|
@ -73,8 +71,8 @@ static void lse_set_available(struct lse *lse, int offset, struct ir_value *v) {
|
|||
|
||||
lse_erase_available(lse, offset, size);
|
||||
|
||||
// add entries for the entire range to aid in invalidation. only the initial
|
||||
// entry where offset == entry.offset is valid for reuse
|
||||
/* add entries for the entire range to aid in invalidation. only the initial
|
||||
entry where offset == entry.offset is valid for reuse */
|
||||
for (; begin <= end; begin++) {
|
||||
struct available *entry = &lse->available[begin];
|
||||
entry->offset = offset;
|
||||
|
@ -85,7 +83,7 @@ static void lse_set_available(struct lse *lse, int offset, struct ir_value *v) {
|
|||
void lse_run(struct ir *ir) {
|
||||
struct lse lse;
|
||||
|
||||
// eliminate redundant loads
|
||||
/* eliminate redundant loads */
|
||||
{
|
||||
lse_clear_available(&lse);
|
||||
|
||||
|
@ -93,8 +91,8 @@ void lse_run(struct ir *ir) {
|
|||
if (instr->op == OP_LABEL) {
|
||||
lse_clear_available(&lse);
|
||||
} else if (instr->op == OP_LOAD_CONTEXT) {
|
||||
// if there is already a value available for this offset, reuse it and
|
||||
// remove this redundant load
|
||||
/* if there is already a value available for this offset, reuse it and
|
||||
remove this redundant load */
|
||||
int offset = instr->arg[0]->i32;
|
||||
struct ir_value *available = lse_get_available(&lse, offset);
|
||||
|
||||
|
@ -102,7 +100,7 @@ void lse_run(struct ir *ir) {
|
|||
ir_replace_uses(instr->result, available);
|
||||
ir_remove_instr(ir, instr);
|
||||
|
||||
STAT_num_loads_removed++;
|
||||
STAT_loads_removed++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
@ -111,15 +109,14 @@ void lse_run(struct ir *ir) {
|
|||
} else if (instr->op == OP_STORE_CONTEXT) {
|
||||
int offset = instr->arg[0]->i32;
|
||||
|
||||
// mark the value being stored as available
|
||||
/* mark the value being stored as available */
|
||||
lse_set_available(&lse, offset, instr->arg[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eliminate dead stores
|
||||
/* eliminate dead stores */
|
||||
{
|
||||
// iterate in reverse so the current instruction is the one being removed
|
||||
lse_clear_available(&lse);
|
||||
|
||||
list_for_each_entry_safe_reverse(instr, &ir->instrs, struct ir_instr, it) {
|
||||
|
@ -131,8 +128,8 @@ void lse_run(struct ir *ir) {
|
|||
|
||||
lse_erase_available(&lse, offset, size);
|
||||
} else if (instr->op == OP_STORE_CONTEXT) {
|
||||
// if subsequent stores have been made for this offset that would
|
||||
// overwrite it completely, mark instruction as dead
|
||||
/* if subsequent stores have been made for this offset that would
|
||||
overwrite it completely, mark instruction as dead */
|
||||
int offset = instr->arg[0]->i32;
|
||||
struct ir_value *available = lse_get_available(&lse, offset);
|
||||
int available_size = available ? ir_type_size(available->type) : 0;
|
||||
|
@ -141,7 +138,7 @@ void lse_run(struct ir *ir) {
|
|||
if (available_size >= store_size) {
|
||||
ir_remove_instr(ir, instr);
|
||||
|
||||
STAT_num_stores_removed++;
|
||||
STAT_stores_removed++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
#include "jit/ir/ir.h"
|
||||
#include "jit/ir/passes/pass_stat.h"
|
||||
|
||||
DEFINE_STAT(num_spills, "Number of registers spilled");
|
||||
DEFINE_STAT(gprs_spilled, "GPRs spilled");
|
||||
DEFINE_STAT(fprs_spilled, "FPRs spilled");
|
||||
|
||||
#define MAX_REGISTERS 32
|
||||
|
||||
|
@ -212,7 +213,11 @@ static int ra_alloc_blocked_register(struct ra *ra, struct ir *ir,
|
|||
/* reset insert point */
|
||||
ir->current_instr = insert_point;
|
||||
|
||||
STAT_num_spills++;
|
||||
if (ir_is_int(instr->result->type)) {
|
||||
STAT_gprs_spilled++;
|
||||
} else {
|
||||
STAT_fprs_spilled++;
|
||||
}
|
||||
|
||||
return interval->reg;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "jit/frontend/jit_frontend.h"
|
||||
#include "jit/ir/ir.h"
|
||||
#include "jit/ir/passes/dead_code_elimination_pass.h"
|
||||
#include "jit/ir/passes/expression_simplification_pass.h"
|
||||
#include "jit/ir/passes/load_store_elimination_pass.h"
|
||||
#include "jit/ir/passes/register_allocation_pass.h"
|
||||
#include "sys/exception_handler.h"
|
||||
|
@ -325,6 +326,7 @@ void jit_compile_block(struct jit *jit, uint32_t guest_addr) {
|
|||
|
||||
/* run optimization passes */
|
||||
lse_run(&ir);
|
||||
esimp_run(&ir);
|
||||
dce_run(&ir);
|
||||
ra_run(&ir, jit->backend->registers, jit->backend->num_registers);
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
#include "jit/ir/ir.h"
|
||||
#include "jit/ir/passes/conversion_elimination_pass.h"
|
||||
#include "jit/ir/passes/dead_code_elimination_pass.h"
|
||||
#include "jit/ir/passes/expression_simplification_pass.h"
|
||||
#include "jit/ir/passes/load_store_elimination_pass.h"
|
||||
#include "jit/ir/passes/pass_stat.h"
|
||||
#include "jit/ir/passes/register_allocation_pass.h"
|
||||
|
@ -12,14 +13,14 @@
|
|||
#include "sys/filesystem.h"
|
||||
|
||||
DEFINE_OPTION_BOOL(help, false, "Show help");
|
||||
DEFINE_OPTION_STRING(pass, "lse,dce,ra",
|
||||
DEFINE_OPTION_STRING(pass, "lse,cve,esimp,dce,ra",
|
||||
"Comma-separated list of passes to run");
|
||||
DEFINE_OPTION_BOOL(stats, true, "Print pass stats");
|
||||
DEFINE_OPTION_BOOL(print_after_all, true, "Print IR after each pass");
|
||||
|
||||
DEFINE_STAT(backend_size, "Backend code size");
|
||||
DEFINE_STAT(num_instrs, "Total IR instructions");
|
||||
DEFINE_STAT(num_instrs_removed, "Total IR instructions removed");
|
||||
DEFINE_STAT(num_instrs, "Total instructions");
|
||||
DEFINE_STAT(num_instrs_removed, "Total instructions removed");
|
||||
|
||||
static uint8_t ir_buffer[1024 * 1024];
|
||||
static uint8_t code[1024 * 1024];
|
||||
|
@ -64,6 +65,8 @@ static void process_file(struct jit *jit, const char *filename,
|
|||
cve_run(&ir);
|
||||
} else if (!strcmp(name, "dce")) {
|
||||
dce_run(&ir);
|
||||
} else if (!strcmp(name, "esimp")) {
|
||||
esimp_run(&ir);
|
||||
} else if (!strcmp(name, "ra")) {
|
||||
ra_run(&ir, x64_registers, x64_num_registers);
|
||||
} else {
|
||||
|
|
Loading…
Reference in New Issue