blob: 3b4909434607277e0e64f393cbdb53f2fecce764 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Alexander Astapchuk
*/
#include "open/vm_method_access.h"
#include "compiler.h"
#include "trace.h"
/**
* @file
* @brief Registers manipulation routines and operand stack mimics.
*/
namespace Jitrino {
namespace Jet {
AR CodeGen::valloc(jtype jt)
{
unsigned start = is_f(jt) ? ar_idx(fr0) : ar_idx(gr0);
unsigned count = is_f(jt) ? fr_num : gr_num;
unsigned highest_index = start+count-1;
//
// We are going to allocate a temporary register - we do several tries
// to reach the following goals:
// - minimize memory accesses
// - minimize callee-saved registers usages
// Try to find the first SCRATCH (non callee-save) register
AR last_used = rlast(jt);
for (unsigned i=ar_idx(last_used)+1; i<highest_index; i++) {
AR ar = _ar(i);
if (is_callee_save(ar) || ar == sp || ar == m_base) continue;
if (rrefs(ar) == 0 && rlocks(ar) == 0 && !m_global_rusage.test(i)) {
// good, found available scratch register
rlast(ar);
return ar;
}
}
// No free scratch registers above the 'last used'. Try any scratch reg.
for (unsigned i=start; i<highest_index; i++) {
AR ar = _ar(i);
if (is_callee_save(ar) || ar == sp || ar == m_base) continue;
if (rrefs(ar) == 0 && rlocks(ar) == 0 && !m_global_rusage.test(i)) {
// good, found available scratch register
rlast(ar);
return ar;
}
}
#if 0
//
// No free scratch registers. How about free callee-save ?
//
// not now. currently, only scratch regs are used as temporaries,
// and callee-save are used as global regs. may revisit it.
for (unsigned i=start; i<highest_index; i++) {
AR ar = _ar(i);
if (ar == sp || ar == m_base) continue;
if (m_bbstate->regs[i].refs == 0 && m_bbstate->regs[i].locks==0 &&
!m_static_rs.test(i)) {
last_used = ar;
rrefs(ar);
return ar;
}
}
#endif
//
// Ugh, no free registers of the needed kind available, need to spill
// someone out
// try to locate first non locked scratch register with min number of
// refs
unsigned min_ref = NOTHING, min_idx = NOTHING;
for (unsigned i=start; i<highest_index; i++) {
AR ar = _ar(i);
if (is_callee_save(ar) || ar == sp || ar == m_base) continue;
if (min_ref > rrefs(ar) && rlocks(ar)==0 && !m_global_rusage.test(i)) {
min_ref = rrefs(ar);
min_idx = i;
}
}
// this means that all scratch registers are locked. Cant happen.
assert(min_idx != NOTHING);
AR ar = _ar(min_idx);
#if 0 // TODO: checkit
// How about scratch registers of other kind ?
// Storing to a register allows us avoid memory access. This might seem
// questionable, as on IA32 'MOVD xmm, r32' has the latency of incredible
// 10 cycles.
// However, the throughput is just 1 cycle, so the port is free again
// very soon. Also, as we're allocating, say, GP register, then we'll
// operate on it in next few instructions, and will not use FPU during
// this, so the latency will [hopefully] be masked.
//
//Note: kinda prototype, not even tested.
start = !is_f(jt) ? ar_idx(fr0) : ar_idx(gr0);
count = !is_f(jt) ? fr_num : gr_num;
AR otherKind = ar_x;
for (unsigned i=start; i<highest_index; i++) {
AR ar = _ar(i);
if (is_callee_save(ar) || ar == sp || ar == m_base) continue;
if (m_bbstate->regs[i].refs == 0 && !m_bbstate->regs[i].locked) {
otherKind = ar;
break;
}
}
if (otherKind != ar_x) {
// Cool - do have a scratch register of other kind, let's unload there
mov(otherKind, ar);
m_bbstate->regs[min_idx].temp = otherKind;
}
#endif // if 0
// Ugh... No way out, have to spill to the memory...
if (is_set(DBG_TRACE_CG)) { dbg(";;>spill %s\n", to_str(ar).c_str()); }
// First, free out the stack items, which are the register
for (unsigned i=0; i<m_jframe->size(); i++) {
Val& s = m_jframe->dip(i);
if (!s.is_reg() || s.reg() != ar) { continue; };
jtype mov_jt = jtmov(s.jt());
st(mov_jt, ar, m_base, vstack_off(i));
rfree(s);
s.to_mem(m_base, vstack_off(i));
rref(s);
}
// Next, free out the locals, which are the register
for (unsigned i=0; i<m_jframe->num_vars(); i++) {
Val& s = m_jframe->var(i);
if (!s.is_reg() || s.reg() != ar) { continue; };
jtype mov_jt = jtmov(s.jt());
st(mov_jt, ar, m_base, vlocal_off(i));
rfree(s);
s.to_mem(m_base, vlocal_off(i));
rref(s);
}
//
// Now, free up the stack items that are the memory
// addressed via register (e.g. an instance field value)
//
for (unsigned i=0; is_gr(ar) && i<m_jframe->size(); i++) {
Val& s = m_jframe->dip(i);
if (!s.is_mem() || !s.uses(ar)) { continue; };
//WARN: both slots for i64 type have register assigned, but for dbl64 - only first slot is marked..
bool need_double_slot = is_ia32() && s.jt()==dbl64;
push(s.as_opnd(iplatf));
if (need_double_slot) {
Opnd hi_mem(iplatf, s.base(), s.disp() + STACK_SLOT_SIZE, s.index(), s.scale());
push(hi_mem);
}
rfree(s);
int stack_off = vstack_off(i);
s.to_mem(m_base, stack_off);
rref(s);
if (need_double_slot) {
Opnd hi_stk(iplatf, m_base, stack_off + STACK_SLOT_SIZE);
pop(hi_stk);
}
Opnd stk(iplatf, m_base, stack_off);
pop(stk);
}
if (is_set(DBG_TRACE_CG)) { dbg(";;>~spill\n"); }
return ar;
}
Val& CodeGen::vstack(unsigned depth, bool toReg)
{
Val& s = m_jframe->dip(depth);
if (s.is_reg()) {
return s;
}
jtype jtm = jtmov(m_jframe->top(depth));
if (s.is_mem()) {
if (toReg) {
// TODO: if the item represents the local variable then it may
// worth to upload the local first, then return the register
// reference
AR ar = valloc(jtm);
// 's' must still reside in memory
assert(s.is_mem());
// but it's location may change.
// Eg if we had arraylength ([gr0+offset]) on the stack,
// after allocation and possible spilling may have
// [m_base+stack_offset].
Opnd reg(jtm, ar);
do_mov(reg, s.as_opnd(jtm));
rfree(s);
s.to_reg(ar);
rref(s);
}
return s;
}
assert(s.is_imm());
if (is_f(s.jt()) && s.caddr() != NULL) {
AR fr = valloc(s.jt());
Opnd reg(s.jt(), fr);
Opnd mem = vaddr(s.jt(), s.caddr());
do_mov(reg, mem);
rfree(s);
s.to_reg(fr);
rref(s);
return s;
}
if (is_f(s.jt())) {
if (toReg) {
// swap to memory first
vswap(depth);
// allocate register and move to it from memory
AR ar = valloc(jtm);
Val reg(jtm, ar);
do_mov(reg, s);
rfree(s);
s.to_reg(ar);
rref(s);
}
return s;
}
//if (s.jt() == i32 || (s.jt() == i64 && is_big(i64))) {
if (s.jt() == i32 || s.jt() == i64) {
if (toReg) {
AR ar = valloc(jtm);
Val reg(jtm, ar);
do_mov(reg, s);
rfree(s);
s.to_reg(ar);
rref(s);
}
return s;
}
//if (s.jt() == i64) {
// assert(!is_big(i64));
// return s;
//}
if (s.jt() == jobj) {
#ifdef HYX86_64
//TODO: it may be not always necessary to upload to register.
toReg = true;
#endif
if (toReg) {
AR ar = valloc(s.jt());
movp(ar, s.pval());
rfree(s);
s.to_reg(ar);
rref(s);
}
return s;
}
//assert(false);
return s;
}
AR CodeGen::vreg(jtype jt, unsigned idx)
{
return m_ra[idx];
}
jtype CodeGen::vtype(unsigned idx)
{
return m_staticTypes[idx];
}
void CodeGen::vassign(jtype jt, unsigned idx, const Val& s)
{
Val& v = m_jframe->var(idx);
assert(!v.is_reg() && s.is_reg());
rfree(v);
v = s.as_opnd(jt);
v.attrs(s.attrs());
rref(v);
}
bool CodeGen::vis_stack(const Val& s) const
{
if (!s.is_mem()) return false;
if (s.base() != m_base) return false;
// Presumption about the operand stack layout in the native frame
assert(m_stack.stack_bot() >= m_stack.unused());
if (m_stack.stack_bot()<s.disp()) return false;
if (s.disp()<m_stack.unused()) return false;
return true;
}
int CodeGen::vvar_idx(const Val& s) const
{
if (!s.is_mem()) return -1;
if (s.base() != ar_x && s.base() != m_base) return -1;
for (unsigned i=0; i<m_stack.get_num_locals(); i++) {
int off = vlocal_off(i);
if (s.disp() == off) return i;
}
return -1;
}
void CodeGen::vunref(jtype jt)
{
// TODO(?): may optimize a bit. Eg. when we do PUTFIELD, there is no
// need to un-shadow say array items on operand stack and vise versa.
// this will require adding a trait to Val - field, static, array items
bool do_print = true;
for (unsigned i=0; i<m_jframe->size(); i++) {
Val& s = m_jframe->dip(i);
if (s.jt() != jt) continue;
// The stack item itself - dont care
if (vis_stack(s)) continue;
// Not a memory ref or a register/immediate - don't care
if (!s.is_mem() || s.is_dummy()) continue;
// Refers to a local var - will be unreferenced explicitly
// when needed (via vunref(Val&) call)
if (vvar_idx(s) != -1) continue;
if (do_print && is_set(DBG_TRACE_CG)) {
dbg(";;>vunref(%s)\n", jtypes[jt].name);
do_print = false;
}
Val save = s;
Val op = vstack(i, true);
for (unsigned j=i+1; j<m_jframe->size(); j++) {
Val& s1 = m_jframe->dip(j);
if (s1 == save) {
rfree(s1);
s1.to_reg(op.reg());
rref(s1);
}
}
}
if (!do_print && is_set(DBG_TRACE_CG)) {
dbg(";;>~vunref(%s)\n", jtypes[jt].name);
}
}
void CodeGen::vunref(jtype jt, const Val& op, unsigned skipDepth)
{
if (op.is_imm()) {
// Nothing to do with immediates
return;
}
jtype jtm = jtmov(jt);
bool printDone = false;
for (unsigned i=0; i<m_jframe->size(); i++) {
if (i == skipDepth) {
continue;
}
Val& s = m_jframe->dip(i);
if (s != op && !(op.is_reg() && s.uses(op.reg()))) {
continue;
}
if (is_set(DBG_TRACE_CG) && !printDone) {
dbg(";;>vunref for %s\n", to_str(op.as_opnd()).c_str());
printDone = true;
}
Opnd stk(jtm, m_base, vstack_off(i));
do_mov(stk, s);
rfree(s);
s.to_mem(m_base, vstack_off(i));
rref(s);
}
if (is_set(DBG_TRACE_CG) && printDone) {
dbg(";;>~vunref for %s\n", to_str(op.as_opnd()).c_str());
}
}
void CodeGen::vvar_def(jtype jt, unsigned idx)
{
Val& v = m_jframe->var(idx);
if (v.jt() != jt) {
rfree(v);
v = Val(jt, m_base, m_stack.local(idx));
rref(v);
}
if (is_wide(jt)) {
Val& v1 = m_jframe->var(idx+1);
if (v1.jt() != jt) {
rfree(v1);
v1 = Val(jt, m_base, m_stack.local(idx+1));
rref(v1);
}
}
}
Val& CodeGen::vlocal(jtype jt, unsigned idx, bool forDef)
{
Val& v = m_jframe->var(idx);
if (v.is_dummy()) {
// Not initialized - set up default values
v.to_mem(m_base, vlocal_off(idx));
rref(v);
}
// jretAddr - special case for ASTORE_ in JSR block.
assert(v.jt() == jvoid || v.jt() == jt ||
(v.jt() == jretAddr && jt == jobj) ||
(v.jt() == jobj && jt == jretAddr));
// The following combinations are possible:
// has global assignment ? | where's currently ? | what to do ?
// yes | reg | if (forDef) {unref} else {noop}
// yes | mem | if (forDef) {no op} else {load into reg}
//
// no | reg | if (forDef) {unref} else {noop}
// no | mem |
if (v.jt() == jvoid) {
rfree(v);
assert(v.is_mem());
v = Val(jt, v.base(), v.disp());
rref(v);
}
if (forDef) {
vunref(jt, v);
}
AR ar = vreg(jt, idx);
if (ar != ar_x && v.is_reg()) {
// Must be exactly the same as statically allocated
assert(v.reg() == ar);
}
else if (ar != ar_x) {
assert(v.is_mem());
// unref() - actually, no need to unref() -
// as the var has global assignment, we just could not
// load onto the operant stack the memory reference
for (unsigned i=0; i<m_jframe->size(); i++) {
assert(vvar_idx(m_jframe->dip(i)) != (int)idx);
}
rfree(v);
v.to_reg(ar);
rref(v);
if (!forDef) {
ld(jtmov(jt), ar, m_base, vlocal_off(idx));
}
}
return v;
}
void CodeGen::vpop(void)
{
Val& s = m_jframe->dip(0);
rfree(s);
if (is_wide(s.jt())) {
Val& s1 = m_jframe->dip(1);
if (!s1.is_dummy()) rfree(s1);
}
m_jframe->pop(m_jframe->top());
}
void CodeGen::vpush(jtype jt)
{
m_jframe->push(jt);
Val& s = m_jframe->dip(0);
s.to_mem(m_base, vstack_off(0));
rref(s);
}
void CodeGen::vpush(const Val& s)
{
assert(s.jt() != jvoid);
// use vpush2() for big types
assert(!is_big(s.jt()));
m_jframe->push(s.jt());
m_jframe->dip(0) = s;
rref(s);
}
void CodeGen::vpush2(const Val& op_lo, const Val& op_hi)
{
assert(op_lo.jt() != jvoid && is_big(op_lo.jt()));
assert(op_hi.jt() != jvoid && is_big(op_hi.jt()));
assert(op_hi.jt() == op_lo.jt());
// not a big problem, just a self-check - this is expected usage scheme
//assert(op_hi.kind() == op_lo.kind());
m_jframe->push(op_lo.jt());
m_jframe->dip(1) = op_hi;
m_jframe->dip(0) = op_lo;
rref(op_hi);
rref(op_lo);
}
void CodeGen::vpop2(Val* pop_lo, Val* pop_hi)
{
assert(is_big(m_jframe->top()));
*pop_hi = vstack(1);
*pop_lo = vstack(0);
rfree(*pop_hi);
rfree(*pop_lo);
m_jframe->pop2();
}
bool CodeGen::vis_arg(unsigned local_idx) const
{
if (local_idx >= m_argSlots)return false;
int argid = m_argids[local_idx];
if(argid == -1) return false;
if (m_ci.reg(argid) != ar_x) return false;
return true;
}
unsigned CodeGen::vget_arg(unsigned local_idx) const
{
assert(vis_arg(local_idx));
int argid = m_argids[local_idx];
return argid;
}
int CodeGen::vlocal_off(unsigned idx) const
{
if (!vis_arg(idx)) {
return voff(m_stack.local(idx));
}
int argid = m_argids[idx];
assert(argid != -1);
return voff(m_ci.off(argid) + STACK_SLOT_SIZE);//SSS <=retAddr
}
int CodeGen::vstack_off(unsigned depth) const
{
unsigned slot = m_jframe->depth2slot(depth);
int off = m_stack.stack_slot(slot);
return voff(off);
}
int CodeGen::voff(int off) const
{
if (m_base != sp) {
return off;
}
return off + m_stack.size() + m_depth;
}
void CodeGen::vswap(unsigned depth)
{
Val& v = m_jframe->dip(depth);
if (v.is_dummy() || vis_stack(v)) {
return;
}
Val mem(v.jt(), m_base, vstack_off(depth));
rlock(v);
do_mov(mem, v);
runlock(v);
rfree(v);
v.to_mem(m_base, vstack_off(depth));
rref(v);
assert(vis_stack(v));
}
void Compiler::gen_bb_leave(unsigned to)
{
if (is_set(DBG_TRACE_CG)) { dbg(";;>bb_leave to %d\n", to); }
bool targetIsMultiRef = false;
bool to_eh;
if (to == NOTHING || m_pc == 0 || g_jvmtiMode) {
// leaving JSR block - act as if were leaving to multiref block
// Also, special processing for 0th BB - see also gen_bb_enter()
// and gen_prolog().
// The same for JVMTI mode - do not allow a thing to be transferred
// on a temporary register between basic blocks
targetIsMultiRef = true;
to_eh = false;
}
else {
// Must be BB
assert(m_bbs.find(to) != m_bbs.end());
const BBInfo& bbto = m_bbs[to];
// Jumps to ehandler ?
to_eh = bbto.ehandler;
// Now, check where the control flow may be transferred from the
// current instruction. If *any* of the targets is multi-ref, then
// perform a full sync.
const JInst& jinst = *m_curr_inst;
for (unsigned i=0; i<jinst.get_num_targets(); i++) {
unsigned targetPC = jinst.get_target(i);
targetIsMultiRef = targetIsMultiRef || (m_insts[targetPC].ref_count > 1);
}
if (jinst.is_switch()) {
unsigned targetPC = jinst.get_def_target();
targetIsMultiRef = targetIsMultiRef || (m_insts[targetPC].ref_count > 1);
}
if (!jinst.is_set(OPF_DEAD_END)) {
unsigned targetPC = jinst.next;
targetIsMultiRef = targetIsMultiRef || (m_insts[targetPC].ref_count > 1);
}
}
if (!targetIsMultiRef && !to_eh) {
// nothing to do anymore
if (is_set(DBG_TRACE_CG)) { dbg(";;>~bb_leave\n"); }
return;
}
for (unsigned i=0; !to_eh && i<m_jframe->size(); i++) {
Val& v = m_jframe->dip(i);
// already in the memory - nothing to do
if (vis_stack(v)) continue;
vswap(i);
//assert(vis_stack(v));
//if (is_wide(v.jt()) && !is_big(v.jt())) {
// ++i;
//}
}
unsigned vars = m_infoBlock.get_num_locals();
// park all locals
for (unsigned i=0; i<vars; i++) {
Val& v = m_jframe->var(i);
AR ar = m_ra[i];
//
if (v.is_mem() && ar == ar_x) { continue; }
if (ar == ar_x) {
assert(v.is_reg());
// need to spill out
assert(v.jt() != jvoid);
jtype jtm = jtmov(v.jt());
st(jtm, v.reg(), m_base, vlocal_off(i));
rfree(v);
v = Val(v.jt(), m_base, vlocal_off(i));
rref(v);
continue;
}
// must have global assignment
jtype jt = m_staticTypes[i];
assert(jt != jvoid);
jtype jtm = jtmov(jt);
if (v.is_mem()) {
if (!is_callee_save(ar) && to_eh) {
// entering catch handler - must leave the var on memory
continue;
}
ld(jtm, ar, m_base, vlocal_off(i));
rfree(v);
v = Val(jt, ar);
rref(v);
continue;
}
assert(v.is_reg() && v.reg() == ar);
if (is_callee_save(ar) || !to_eh) {
continue;
}
// entering catch handler - must swap the var to memory
st(jtm, ar, m_base, vlocal_off(i));
rfree(v);
v = Val(jt, m_base, vlocal_off(i));
rref(v);
}
if (to_eh) {
// we're generating a 'goto/fall through/if_' to a basic block
// if the BB is also the catch handler, then it can't have a
// ref_count less than 2.
assert(m_insts[to].ref_count > 1);
// Must have only Exception on the top of stack
assert(m_jframe->size() == 1 && m_jframe->top(0) == jobj);
// if top of the stack is currently not on the gr_ret, then
// force it to be there
Val& ev = m_jframe->dip(0);
if (!ev.is_reg() || ev.reg() != gr0) {
// locals were just spilled and no other stack items left
// therefore gr0 must be unused, simply load the Exception
assert(rrefs(gr0) == 0);
Opnd reg(ev.jt(), gr0);
do_mov(reg, ev.as_opnd());
rfree(ev);
ev.to_reg(gr0);
rref(ev);
}
}
if (is_set(DBG_TRACE_CG)) { dbg(";;>~bb_leave\n"); }
}
void Compiler::gen_bb_enter(void)
{
assert(m_bbs.find(m_pc) != m_bbs.end());
const BBInfo& bbinfo = m_bbs[m_pc];
if (is_set(DBG_TRACE_CG)) { dbg(";;>bb_enter to %d\n", m_pc); }
if (bbinfo.ehandler) {
assert(m_jframe->size() == 1);
Val& s = m_jframe->dip(0);
if (!s.is_reg()) {
rref(gr0);
s = Val(jobj, gr0);
// We're entering exception handler - that do not have 'direct'
// (non exception) ways in it - the object on the top of the
// stack is exception and is guaranteed to be non-null.
if (m_insts[m_pc].ref_count == 1) {
s.set(VA_NZ);
}
}
else {
assert(s.reg() == gr0);
}
}
// We always process 0th BB as multiref BB - see also gen_prolog()
// when it calls gen_bb_leave(NOTHING).
if (m_insts[m_pc].ref_count == 1 && m_pc != 0 && !bbinfo.ehandler) {
if (is_set(DBG_TRACE_CG)) { dbg(";;>~bb_enter\n"); }
return;
}
rclear();
unsigned vars = m_infoBlock.get_num_locals();
for (unsigned i=0; i<vars; i++) {
AR ar = m_ra[i];
jtype jtglobal = m_staticTypes[i];
Val& v = m_jframe->var(i);
// If we enter a BB - then every reg-assigned local is on its reg,
// until we're entering exception handler...
if (ar != ar_x && (is_callee_save(ar) || !bbinfo.ehandler)) {
assert(jtglobal != jvoid);
// Overwrite attributes as well
v = Val(jtglobal, ar);
rref(v);
continue;
}
// ... in this case, scratch-regs assigned variables are in memory
assert(ar == ar_x || bbinfo.ehandler);
if (jtglobal != jvoid) {
v = Val(jtglobal, m_base, vlocal_off(i));
}
else {
v = Val(jvoid, m_base, vlocal_off(i));
}
assert(v.is_mem());
rref(v);
}
for (unsigned i=0; i<m_jframe->size(); i++) {
Val& s = m_jframe->dip(i);
if (i==0 && bbinfo.ehandler) {
assert(s.is_reg() && s.reg() == gr0);
}
else {
s = Val(s.jt(), m_base, vstack_off(i));
}
rref(s);
}
// For non-static methods, that also do not write into the 0th slot,
// we can guarantee that 0th slot (thiz) is always non-null.
if (!(meth_is_static()) && m_defs[0] == 0) {
Val& v = m_jframe->var(0);
v.set(VA_NZ);
}
if (is_set(DBG_TRACE_CG)) { dbg(";;>~bb_enter\n"); }
}
void CodeGen::vpark(bool doStack)
{
bool do_print = true;
for (unsigned i=0; i<m_infoBlock.get_num_locals(); i++) {
Val& v = m_jframe->var(i);
if (!v.is_reg() || is_callee_save(v.reg())) continue;
if (is_set(DBG_TRACE_CG) && do_print) {
dbg(";; >vpark(all)\n");
do_print = false;
}
vpark(v.reg(), doStack);
}
for (unsigned i=0; doStack && i<m_jframe->size(); i++) {
Val& s = m_jframe->dip(i);
unsigned saveI = i;
// refers to local variable - will be no changes as result of call
if (vvar_idx(s) != -1) continue;
// the stack item itself, will not change during the call
if (vis_stack(s)) continue;
// Something constant or a register
if (s.survive_calls()) continue;
if (s.is_reg() && is_callee_save(s.reg())) continue;
if (is_set(DBG_TRACE_CG) && do_print) {
dbg(";; >vpark(all)\n");
do_print = false;
}
vswap(saveI);
}
if (is_set(DBG_TRACE_CG) && !do_print) {
dbg(";; >~vpark(all)\n");
}
}
void CodeGen::vpark(AR ar, bool doStack)
{
bool do_print = true;
rlock(ar);
for (unsigned i=0; i<m_infoBlock.get_num_locals(); i++) {
Val& v = m_jframe->var(i);
if (!v.is_reg() || v.reg() != ar) continue;
if (do_print && is_set(DBG_TRACE_CG)) {
dbg(";;>vpark(%s)\n", to_str(ar).c_str());
do_print = false;
}
jtype jtm = jtmov(v.jt());
if (true || (m_defs[i] != 0)) {
// XXX actually, we need an attribute, whether the variable
// was changed.
// m_defs[i] != 0 works bad for example if float point
// parameter came on an register
st(jtm, v.reg(), m_base, vlocal_off(i));
}
rfree(v);
v.to_mem(m_base, vlocal_off(i));
rref(v);
}
for (unsigned k=0; doStack && k<m_jframe->size(); k++) {
Val& s = m_jframe->dip(k);
if (!s.uses(ar)) continue;
jtype jtm = jtmov(s.jt());
if (do_print && is_set(DBG_TRACE_CG)) {
dbg(";;>vpark(%s)\n", to_str(ar).c_str());
do_print = false;
}
rfree(s);
Opnd stk(jtm, m_base, vstack_off(k));
if (s.is_reg()) {
mov(stk, Opnd(jtm, ar));
}
else {
do_mov(stk, s);
}
s.to_mem(m_base, vstack_off(k));
rref(s);
}
runlock(ar);
if (!do_print && is_set(DBG_TRACE_CG)) {
dbg(";;>~vpark(%s)\n", to_str(ar).c_str());
}
}
void CodeGen::vcheck(void)
{
unsigned regs[ar_num];
for (unsigned i=0; i<ar_num; i++) {
regs[i] = 0;
}
const AR noar = (AR)NOTHING;
for (unsigned i=0; i<m_jframe->size(); i++) {
const Val& s = m_jframe->dip(i);
if (s.is_reg()) {
unsigned idx = ar_idx(s.reg());
regs[idx]++;
}
else if (s.is_mem()) {
if (s.base() != ar_x && s.base() != noar) { regs[ar_idx(s.base())]++; };
assert(s.index() != noar);
if (s.index() != ar_x) { regs[ar_idx(s.index())]++; };
}
}
for (unsigned i=0; i<m_jframe->num_vars(); i++) {
Val& s = m_jframe->var(i);
if (s.is_reg()) {
unsigned idx = ar_idx(s.reg());
regs[idx]++;
}
else if (s.is_mem()) {
if (s.base() != ar_x && s.base() != noar) { regs[ar_idx(s.base())]++; };
assert(s.index() != noar);
if (s.index() != ar_x) { regs[ar_idx(s.index())]++; };
}
}
for (unsigned i=0; i<ar_num; i++) {
AR ar = _ar(i);
//
if (rlocks(ar) != 0) {
dbg_dump_state("Register lock cant cross instruction boundaries",
m_bbstate);
assert(false);
}
if (rrefs(ar) != regs[i]) {
dbg("ERROR: leaked/lost register: %s. refs=%u, must be=%u",
Encoder::to_str(ar).c_str(), rrefs(ar), regs[i]);
dbg_dump_state("Problematic frame:", m_bbstate);
assert(false);
}
}
}
}}; // ~namespace Jitrino::Jet