Skip to content

Commit

Permalink
Introduce 'immortally' created values
Browse files Browse the repository at this point in the history
From the perspective of the register allocator, values residing
in nonvolatile registers are 'immortal'. These registers should
not be freed over overwritten.

This does not fix all the bugs, but some of them.
Also implement the handlers for pre-and-post branch logic,
but we do not yet call them.
  • Loading branch information
bdw committed Sep 3, 2015
1 parent 3571e61 commit 2bfd5dd
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 60 deletions.
4 changes: 4 additions & 0 deletions src/core/dynar.h
Expand Up @@ -31,6 +31,10 @@
x[x ## _num++] = (value); \
} while(0)

#define MVM_DYNAR_POP(x) \
(x)[--(x ## _num)]


#define MVM_DYNAR_APPEND(x, ar, len) do { \
MVM_DYNAR_ENSURE_SPACE(x, len); \
memcpy(x + x ## _num, ar, (len) * sizeof(x[0])); \
Expand Down
66 changes: 19 additions & 47 deletions src/jit/compile.c
Expand Up @@ -178,9 +178,17 @@ static void enter_conditional(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitEx
* to spill inside the conditional, unless on some future date
* we're going to implement local spill-and-restore. But that's
* relatively more complicated, so we don't. */
NYI(enter_conditional);
MVM_jit_spill_before_conditional(tc, cl, tree, node);
}

static void enter_branch(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitExprTree *tree, MVMint32 node) {
MVM_jit_enter_branch(tc, cl);
}


static void leave_branch(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitExprTree *tree, MVMint32 node) {
MVM_jit_leave_branch(tc, cl);
}

static void leave_conditional(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitExprTree *tree, MVMint32 node) {
/* After leaving a conditional, if we ever choose to implement
Expand Down Expand Up @@ -503,7 +511,6 @@ static void compile_labels(MVMThreadContext *tc, MVMJitTreeTraverser *traverser,
default:
break;
}

}


Expand Down Expand Up @@ -553,53 +560,15 @@ static void compile_arglist(MVMThreadContext *tc, MVMJitCompiler *compiler,

/* Now emit the gpr values */
for (i = 0; i < num_gpr; i++) {
MVMJitExprValue *argval = gpr_values[i];
MVMint8 reg_num = x64_gpr_args[i];
if (argval->state == MVM_JIT_VALUE_SPILLED) {
/* Take the register, load, and assign */
MVM_jit_register_take(tc, compiler, MVM_JIT_REGCLS_GPR, reg_num);
MVM_jit_register_load(tc, compiler, argval->spill_location,
MVM_JIT_REGCLS_GPR, reg_num, argval->size);
} else if (argval->state == MVM_JIT_VALUE_ALLOCATED) {
if (argval->reg_cls == MVM_JIT_REGCLS_GPR && argval->reg_num == reg_num) {
/* happy days, nothing to do for us */
} else {
/* Aqcuire the register and copy the value */
MVM_jit_register_take(tc, compiler, MVM_JIT_REGCLS_GPR, reg_num);
MVM_jit_emit_copy(tc, compiler, MVM_JIT_REGCLS_GPR, reg_num,
argval->reg_cls, argval->reg_num);
/* Reassign to the new register. */
MVM_jit_register_expire(tc, compiler, argval);
MVM_jit_register_assign(tc, compiler, argval, MVM_JIT_REGCLS_GPR, reg_num);
}
} else {
MVM_oops(tc, "ARGLIST: Live Value Inaccessible");
}
MVM_jit_register_put(tc, compiler, gpr_values[i], MVM_JIT_REGCLS_GPR, x64_gpr_args[i]);
/* Mark GPR as used - nb, that means we need some cleanup logic afterwards */
MVM_jit_register_use(tc, compiler, MVM_JIT_REGCLS_GPR, reg_num);
MVM_jit_register_use(tc, compiler, MVM_JIT_REGCLS_GPR, x64_gpr_args[i]);
}

/* SSE logic is pretty much the same as GPR logic, just with SSE rather than GPR args */
for (i = 0; i < num_sse; i++) {
MVMJitExprValue *argval = sse_values[i];
MVMint8 reg_num = x64_sse_args[i];
if (argval->state == MVM_JIT_VALUE_SPILLED) {
MVM_jit_register_take(tc, compiler, MVM_JIT_REGCLS_NUM, reg_num);
MVM_jit_register_load(tc, compiler, argval->spill_location,
MVM_JIT_REGCLS_NUM, reg_num, argval->size);
} else if (argval->state == MVM_JIT_VALUE_ALLOCATED) {
if (argval->reg_cls == MVM_JIT_REGCLS_NUM && argval->reg_num == reg_num) {
} else {
MVM_jit_register_take(tc, compiler, MVM_JIT_REGCLS_NUM, reg_num);
MVM_jit_emit_copy(tc, compiler, MVM_JIT_REGCLS_NUM, reg_num,
argval->reg_cls, argval->reg_num);
MVM_jit_register_expire(tc, compiler, argval);
MVM_jit_register_assign(tc, compiler, argval, MVM_JIT_REGCLS_NUM, reg_num);
}
} else {
MVM_oops(tc, "ARGLIST: Live Value Inaccessible");
}
MVM_jit_register_use(tc, compiler, MVM_JIT_REGCLS_NUM, reg_num);
MVM_jit_register_put(tc, compiler, gpr_values[i], MVM_JIT_REGCLS_NUM, x64_sse_args[i]);
MVM_jit_register_use(tc, compiler, MVM_JIT_REGCLS_NUM, x64_sse_args[i]);
}

/* Stack arguments are simpler than register arguments */
Expand All @@ -616,6 +585,7 @@ static void compile_arglist(MVMThreadContext *tc, MVMJitCompiler *compiler,
MVM_jit_emit_stack_arg(tc, compiler, i * 8, MVM_JIT_REGCLS_GPR, reg_num, argval->size);
MVM_jit_register_free(tc, compiler, MVM_JIT_REGCLS_GPR, reg_num);
} else if (argval->state == MVM_JIT_VALUE_ALLOCATED) {
/* Emitting a stack argument is not a free */
MVM_jit_emit_stack_arg(tc, compiler, i * 8, argval->reg_cls,
argval->reg_num, argval->size);
} else {
Expand Down Expand Up @@ -687,6 +657,7 @@ static void compile_tile(MVMThreadContext *tc, MVMJitTreeTraverser *traverser, M
if (tile == NULL || tile->rule == NULL) {
MVM_oops(tc, "Tile without a rule!");
}
/* pre call we should store all values */
pre_call(tc, cl, tree, node);
/* Emit call */
MVM_jit_tile_get_values(tc, tree, node, tile->path, values+1);
Expand Down Expand Up @@ -750,7 +721,6 @@ static void compile_tile(MVMThreadContext *tc, MVMJitTreeTraverser *traverser, M
case MVM_JIT_TC:
case MVM_JIT_CU:
case MVM_JIT_STACK:

if (tile->rule == NULL)
return;
tile->rule(tc, cl, tree, node, values, args);
Expand All @@ -762,12 +732,14 @@ static void compile_tile(MVMThreadContext *tc, MVMJitTreeTraverser *traverser, M
return;
/* Extract value pointers from the tree */
MVM_jit_tile_get_values(tc, tree, node, tile->path, values+1);

ensure_values(tc, cl, values+1, tile->num_values);

if (tile->vtype == MVM_JIT_REG) {
/* allocate a register for the result */
if (tile->num_values > 0 && values[1]->type == MVM_JIT_REG && values[1]->last_use == cl->order_nr) {
if (tile->num_values > 0 &&
values[1]->type == MVM_JIT_REG &&
values[1]->state == MVM_JIT_VALUE_ALLOCATED &&
values[1]->last_use == cl->order_nr) {
/* First register expires immediately, therefore we can safely cross-assign */
MVM_jit_register_assign(tc, cl, values[0], values[1]->reg_cls, values[1]->reg_num);
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/jit/expr.h
Expand Up @@ -108,7 +108,8 @@ struct MVMJitExprValue {
MVM_JIT_VALUE_EMPTY,
MVM_JIT_VALUE_ALLOCATED,
MVM_JIT_VALUE_SPILLED,
MVM_JIT_VALUE_DEAD
MVM_JIT_VALUE_DEAD,
MVM_JIT_VALUE_IMMORTAL
} state;

/* register allocated to this value */
Expand Down
116 changes: 111 additions & 5 deletions src/jit/register.c
Expand Up @@ -54,6 +54,8 @@ void MVM_jit_register_allocator_init(MVMThreadContext *tc, MVMJitCompiler *compi
MVMJitRegisterAllocator *alc) {
/* Store live ranges */
MVM_DYNAR_INIT(alc->active, NUM_GPR);
/* And branches */
MVM_DYNAR_INIT(alc->branches, 4);
/* Initialize free register buffer */
alc->free_reg = MVM_malloc(sizeof(free_gpr));
memcpy(alc->free_reg, free_gpr, NUM_GPR);
Expand Down Expand Up @@ -105,6 +107,13 @@ void MVM_jit_register_free(MVMThreadContext *tc, MVMJitCompiler *compiler, MVMin
if (reg_cls == MVM_JIT_REGCLS_NUM) {
NYI(numeric_regs);
} else {
MVMint32 i;
for (i = 0; i < sizeof(free_gpr); i++) {
if (free_gpr[i] == reg_num)
goto ok;
}
MVM_oops(tc, "Trying to free register %d\n", reg_num);
ok:
if (alc->reg_give == alc->reg_take) {
MVM_oops(tc, "Trying to free too many registers");
}
Expand Down Expand Up @@ -194,10 +203,12 @@ void MVM_jit_register_spill(MVMThreadContext *tc, MVMJitCompiler *compiler, MVMi
/* Bitmap was full */
spill_location = alc->spill_top++;
}
MVM_jit_log(tc, "Emit spill of register %d to location %d\n", reg_num, spill_location);
MVM_jit_emit_spill(tc, compiler, spill_location, reg_cls, reg_num, MVM_JIT_REG_SZ);
} /* if it was spilled before, it's immutable now */
MVM_free(spill_bmp);
/* Mark nodes as spilled on the location */
MVM_jit_log(tc, "Going to mark %d values as spilled\n", alc->reg_use[reg_num]);
for (i = 0; i < alc->active_num; i++) {
MVMJitExprValue *value = compiler->allocator->active[i];
if (VALUE_IS_ASSIGNED(value, reg_cls, reg_num)) {
Expand All @@ -206,10 +217,11 @@ void MVM_jit_register_spill(MVMThreadContext *tc, MVMJitCompiler *compiler, MVMi
alc->reg_use[reg_num]--;
}
}

/* register ought to be free now! */
if (alc->reg_use[reg_num] != 0) {
MVM_oops(tc, "After spill no users of the registers should remain");
} else {
MVM_jit_log(tc, "All values were spilled\n");
}
/* make it available */
MVM_jit_register_free(tc, compiler, reg_cls, reg_num);
Expand Down Expand Up @@ -250,6 +262,7 @@ void MVM_jit_register_assign(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitExp
alc->reg_use[reg_num]++;
}


/* Expiring a value marks it dead and possibly releases its register */
void MVM_jit_register_expire(MVMThreadContext *tc, MVMJitCompiler *compiler, MVMJitExprValue *value) {
MVMJitRegisterAllocator *alc = compiler->allocator;
Expand All @@ -269,12 +282,65 @@ void MVM_jit_register_expire(MVMThreadContext *tc, MVMJitCompiler *compiler, MVM
i++;
}
}

if (value->state == MVM_JIT_VALUE_ALLOCATED) {
/* Decrease register number count and free if possible */
alc->reg_use[reg_num]--;
if (alc->reg_use[reg_num] == 0) {
MVM_jit_register_free(tc, compiler, value->reg_cls, reg_num);
}
}
/* Mark value as dead */
value->state = MVM_JIT_VALUE_DEAD;
/* Decrease register number count and free if possible */
alc->reg_use[reg_num]--;
if (alc->reg_use[reg_num] == 0) {
MVM_jit_register_free(tc, compiler, value->reg_cls, reg_num);
}

/* Put a value into a specific register */
void MVM_jit_register_put(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitExprValue *value, MVMint32 reg_cls, MVMint8 reg_num) {
MVMJitRegisterAllocator *alc = cl->allocator;
MVMint32 i;
if (VALUE_IS_ASSIGNED(value, reg_cls, reg_num)) {
/* happy case */
return;
}
/* Take the register */
if (alc->reg_use[reg_num] > 0) {
MVM_jit_register_take(tc, cl, reg_cls, reg_num);
}
if (value->state == MVM_JIT_VALUE_ALLOCATED) {
MVMint32 cur_reg_cls = value->reg_cls, cur_reg_num = value->reg_num;
MVM_jit_emit_copy(tc, cl, reg_cls, reg_num, value->reg_cls, value->reg_num);
for (i = 0; i < alc->active_num; i++) {
/* update active values to new register */
MVMJitExprValue *active = alc->active[i];
if (VALUE_IS_ASSIGNED(active, cur_reg_cls, cur_reg_num)) {
/* Assign to new register */
alc->reg_use[cur_reg_num]--;
active->reg_cls = reg_cls;
active->reg_num = reg_num;
alc->reg_use[reg_num]++;
}
}
if (alc->reg_use[cur_reg_num] == 0) {
MVM_jit_register_free(tc, cl, cur_reg_cls, cur_reg_num);
} else {
MVM_oops(tc, "After copy, register is still not free");
}
/* An allocated value should have been live, so should have been updated... */
if (value->reg_cls != reg_cls || value->reg_num != reg_num) {
MVM_jit_log(tc, "Allocated value %x at register %d wasn't actually in active (active_num=%d)\n", value, value->reg_num, alc->active_num);
MVM_jit_register_assign(tc, cl, value, reg_cls, reg_num);
}
}
else if (value->state == MVM_JIT_VALUE_SPILLED) {
/* Spilled values ought to be life values, which means load should */
MVM_jit_register_load(tc, cl, value->spill_location, reg_cls, reg_num, MVM_JIT_REG_SZ);
} else if (value->state == MVM_JIT_VALUE_IMMORTAL) {
/* Immortal values are always present */
MVM_jit_emit_copy(tc, cl, reg_cls, reg_num, value->reg_cls, value->reg_num);
/* However, they are not assigned to a register, so we assign it */
MVM_jit_register_assign(tc, cl, value, reg_cls, reg_num);
} else {
MVM_oops(tc, "Tried to put a non-live value into a register");
}
}

Expand All @@ -292,6 +358,45 @@ void MVM_jit_spill_before_call(MVMThreadContext *tc, MVMJitCompiler *cl) {
}
}

void MVM_jit_spill_before_conditional(MVMThreadContext *tc, MVMJitCompiler *cl, MVMJitExprTree *tree, MVMint32 node) {
MVMJitRegisterAllocator *alc = cl->allocator;
MVMint32 exit_order_nr = tree->info[node].value.first_created;
MVMint32 i;
for (i = 0; i < alc->active_num; i++) {
MVMJitExprValue *value = alc->active[i];
if (value->last_use > exit_order_nr) {
/* Emit a spill - this inadvertently frees the register */
MVM_jit_register_spill(tc, cl, value->reg_cls, value->reg_num);
/* So we have to take it */
MVM_jit_register_take(tc, cl, value->reg_cls, value->reg_num);
/* Because the value has been assigned before, we should not reassign it,
* but update it's state directly */
value->state = MVM_JIT_VALUE_ALLOCATED;
alc->reg_use[value->reg_num]++;
}
}
}

void MVM_jit_enter_branch(MVMThreadContext *tc, MVMJitCompiler *cl) {
MVM_DYNAR_PUSH(cl->allocator->branches, cl->order_nr);
}

void MVM_jit_leave_branch(MVMThreadContext *tc, MVMJitCompiler *cl) {
MVMJitRegisterAllocator *alc = cl->allocator;
MVMint32 entry_order_nr = MVM_DYNAR_POP(alc->branches);
MVMint32 i = 0;
while (i < alc->active_num) {
MVMJitExprValue *value = alc->active[i];
if (value->first_created >= entry_order_nr) {
if (value->last_use > cl->order_nr) {
MVM_oops(tc, "Last use of conditional value beyond conditional block");
}
MVM_jit_register_expire(tc, cl, value);
} else {
i++;
}
}
}

/* Expire values that are no longer useful */
void MVM_jit_expire_values(MVMThreadContext *tc, MVMJitCompiler *compiler) {
Expand All @@ -304,6 +409,7 @@ void MVM_jit_expire_values(MVMThreadContext *tc, MVMJitCompiler *compiler) {
/* can't expire values held in locked registers */
!(value->state == MVM_JIT_VALUE_ALLOCATED &&
REGISTER_IS_LOCKED(alc, value->reg_num))) {
MVM_jit_log(tc, "Expiring value %x first created at %d at order nr %d \n", value, value->first_created, compiler->order_nr);
MVM_jit_register_expire(tc, compiler, value);
} else {
i++;
Expand Down
14 changes: 12 additions & 2 deletions src/jit/register.h
Expand Up @@ -3,8 +3,10 @@
* descending by end point. I want to use this algorithm online, so it
* seems logical to use a heap */
struct MVMJitRegisterAllocator {
/* Heap of active 'live' ranges */
/* List of active 'live' ranges */
MVM_DYNAR_DECL(MVMJitExprValue*, active);
MVM_DYNAR_DECL(MVMint32, branches);

/* stacks of free registers */
MVMint8 *free_reg;
MVMuint8 *reg_use;
Expand Down Expand Up @@ -41,6 +43,14 @@ void MVM_jit_register_assign(MVMThreadContext *tc, MVMJitCompiler *compiler,
MVMJitExprValue *value, MVMint32 reg_cls, MVMint8 reg_num);
void MVM_jit_register_expire(MVMThreadContext *tc, MVMJitCompiler *compiler,
MVMJitExprValue *value);
void MVM_jit_register_put(MVMThreadContext *tc, MVMJitCompiler *compiler,
MVMJitExprValue *value, MVMint32 reg_cls, MVMint8 reg_num);

void MVM_jit_expire_values(MVMThreadContext *tc, MVMJitCompiler *compiler);
void MVM_jit_spill_before_call(MVMThreadContext *tc, MVMJitCompiler *compiler);
void MVM_jit_spill_before_conditional(MVMThreadContext *tc, MVMJitCompiler *compiler,
MVMJitExprTree *tree, MVMint32 node);
void MVM_jit_expire_values(MVMThreadContext *tc, MVMJitCompiler *compiler);

void MVM_jit_enter_branch(MVMThreadContext *tc, MVMJitCompiler *compiler);
void MVM_jit_leave_branch(MVMThreadContext *tc, MVMJitCompiler *compiler);

10 changes: 5 additions & 5 deletions src/jit/x64/tiles.dasc
Expand Up @@ -3,35 +3,35 @@

MVM_JIT_TILE_DECL(load_stack) {
values[0]->type = MVM_JIT_REG;
values[0]->state = MVM_JIT_VALUE_ALLOCATED;
values[0]->state = MVM_JIT_VALUE_IMMORTAL;
values[0]->reg_cls = MVM_JIT_X64_GPR;
values[0]->reg_num = MVM_JIT_X64_RSP;
}

MVM_JIT_TILE_DECL(load_local) {
values[0]->type = MVM_JIT_REG;
values[0]->state = MVM_JIT_VALUE_ALLOCATED;
values[0]->state = MVM_JIT_VALUE_IMMORTAL;
values[0]->reg_cls = MVM_JIT_X64_GPR;
values[0]->reg_num = MVM_JIT_X64_RBX;
}

MVM_JIT_TILE_DECL(load_frame) {
values[0]->type = MVM_JIT_REG;
values[0]->state = MVM_JIT_VALUE_ALLOCATED;
values[0]->state = MVM_JIT_VALUE_IMMORTAL;
values[0]->reg_cls = MVM_JIT_X64_GPR;
values[0]->reg_num = MVM_JIT_X64_R12;
}

MVM_JIT_TILE_DECL(load_cu) {
values[0]->type = MVM_JIT_REG;
values[0]->state = MVM_JIT_VALUE_ALLOCATED;
values[0]->state = MVM_JIT_VALUE_IMMORTAL;
values[0]->reg_cls = MVM_JIT_X64_GPR;
values[0]->reg_num = MVM_JIT_X64_R13;
}

MVM_JIT_TILE_DECL(load_tc) {
values[0]->type = MVM_JIT_REG;
values[0]->state = MVM_JIT_VALUE_ALLOCATED;
values[0]->state = MVM_JIT_VALUE_IMMORTAL;
values[0]->reg_cls = MVM_JIT_X64_GPR;
values[0]->reg_num = MVM_JIT_X64_R14;
}
Expand Down

0 comments on commit 2bfd5dd

Please sign in to comment.