blob: 701dd1c3ce97d4724459ea993ee59243cecc046c [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 "compiler.h"
#include "arith_rt.h"
#include "trace.h"
#include "stats.h"
#ifdef WIN32
#include <malloc.h>
#else
#include <stdlib.h>
#endif
#include <stdarg.h>
#include <algorithm>
using std::min;
#include <open/vm.h>
#include <open/vm_ee.h>
#include <open/vm_class_info.h>
#include <open/vm_class_loading.h>
#include <jit_runtime_support.h>
#include <jit_intf.h>
#include <jni_types.h>
#include "port_threadunsafe.h"
/**
* @file
* @brief Common CodeGen's routines and datas.
*/
namespace Jitrino {
namespace Jet {
const CallSig ci_helper_o(CCONV_HELPERS, jvoid, jobj);
const CallSig ci_helper_v(CCONV_HELPERS, jvoid);
const CallSig ci_helper_oi(CCONV_HELPERS, jobj, jobj, i32);
const CallSig ci_helper_lazy(CCONV_MANAGED, jvoid, jobj, jobj);
const CallSig ci_helper_linkerr(CCONV_HELPERS, jvoid, jobj, i32, i32);
void CodeGen::do_mov(const Val& dst_s, const Val& src_s, bool skipTypeCheck)
{
assert(!dst_s.is_imm());
jtype sjt = jtmov(src_s.jt());
jtype djt = jtmov(dst_s.jt());
assert(skipTypeCheck || sjt == djt);
if (dst_s.is_mem() && src_s.is_mem()) {
// Need to allocate a reg
Opnd src(sjt, src_s.base(), src_s.disp(), src_s.index(), src_s.scale());
Opnd dst(djt, dst_s.base(), dst_s.disp(), dst_s.index(), dst_s.scale());
rlock(dst_s);
rlock(src_s);
AR ar = valloc(src.jt());
Opnd reg(src.jt(), ar);
mov(reg, src);
mov(dst, reg);
runlock(dst_s);
runlock(src_s);
return;
}
if (src_s.is_imm() && (is_f(src_s.jt()))) {
// store to memory [first, then upload to register]
rlock(dst_s);
rlock(src_s);
Opnd dst;
if (dst_s.is_mem()) {
dst = dst_s.as_opnd(i32);
}
else {
// 'mov (flt32/dbl64) fr, imm' - store to scratch area first,
// then upload to register
assert(dst_s.is_reg());
dst = Opnd(i32, m_base, voff(m_stack.scratch()));
}
if (src_s.jt() == flt32) {
float f = src_s.fval();
Opnd val(*(int*)&f);
mov(dst, val);
}
else {
double d = src_s.dval();
Opnd val_lo(*(int*)&d);
mov(dst, val_lo);
Opnd dst_hi(i32, dst.base(), dst.disp()+4, dst.index(),
dst.scale());
Opnd val_hi(*(1+(int*)&d));
mov(dst_hi, val_hi);
}
// Succesfully stored immediate to memory. Now, if dst_s is regiter
// upload the value to it
if (dst_s.is_reg()) {
Opnd scratch = Opnd(sjt, m_base, voff(m_stack.scratch()));
mov(dst_s.as_opnd(), scratch);
}
runlock(dst_s);
runlock(src_s);
return;
}
mov(dst_s.as_opnd(djt), src_s.as_opnd(sjt));
}
void CodeGen::gen_check_null(unsigned depth)
{
Val& obj = vstack(depth);
gen_check_null(obj, true);
}
void CodeGen::gen_check_null(Val& obj, bool hw_ok)
{
assert(obj.jt() == jobj);
if (obj.has(VA_NZ)) {
UNSAFE_REGION_START
STATS_INC(Stats::npesEliminated,1);
UNSAFE_REGION_END
if (is_set(DBG_TRACE_CG)) {
dbg(";;>check.npe for %s - skipped\n",
to_str(obj.as_opnd()).c_str());
}
return;
}
if (obj.is_imm()) {
STATS_INC(Stats::npesEliminated,1);
if (obj.pval() == NULL_REF) {
//gen_args(const CallSig& cs, unsigned idx, const Val * parg0 = NULL,
// const Val * parg1 = NULL, const Val * parg2 = NULL);
Class_Handle npeClass = vm_lookup_class_with_bootstrap(NULL_POINTER_EXCEPTION);
gen_throw(npeClass, false);
}
return;
}
// Try to guess whether we can use hardware NPE @ this point.
// First, check if the method may use it at all:
//
// No catch handler in the method - any exception exits the method
// !synchronized => to avoid the following:
// synchronized method throws HW NPE
// GC starts during handling in VM
// rt_enum() detects that the exception cames from HW and reports
// nothing => 'this' gets no references and killed by GC
// after the GC, VM gets that exception comes from inside the synch
// method. VM tries to release monitor => asks for
// jit_get_address_of_this, but this address already collected by
// GC as garbage - everyone's dead.
//
// Also, we can not use HW checks for compressed references - the
// uncompressed null reference in not zero.
//
bool useHW = hw_ok && get_bool_arg("hwnpe", true) && !g_refs_squeeze;
if (meth_is_sync_inst() || meth_num_handlers() != 0) {
useHW = false;
}
// Now, all variables must be as if we were entering catch handler -
// the locals on callee-save registers must be on that registers and
// other locals must be in the memory
if (useHW) {
unsigned vars=m_infoBlock.get_num_locals();
for (unsigned i=0; i<vars; i++){
const Val& var = m_jframe->var(i);
AR ar = vreg(var.jt(), i);
if (ar == ar_x) continue; // no global allocation - skip it
if (is_callee_save(ar)) {
if (!var.is_reg()) {
// spilled callee-save globally allocated register.
// hmmm... how comes ? anyway, can't use HW check
useHW = false;
break;
}
else {
assert(var.reg() == ar);
}
}
else {
// Scratch register holds a variable - can't use HW check
if (!var.is_mem()) {
useHW = false;
break;
}
}
}
}
if (useHW) {
//TODO:
// Current check is based on the presumption that the fisrt 64K of
// address space raises hardware NPE. However, an object may occupy
// more than 64K - e.g. for array of longs, the (8192+1) items
// reside in more that 64K. Need to check whether 64k+ also raises
// the NPE, or change the NPE checks for field and array accesses.
if (is_set(DBG_TRACE_CG)) {
dbg(";;>check.npe.HW for %s\n", to_str(obj.as_opnd()).c_str());
}
}
else {
if (is_set(DBG_TRACE_CG)) {
dbg(";;>check.npe for %s\n", to_str(obj.as_opnd()).c_str());
}
STATS_INC(Stats::npesPerformed,1);
rlock(obj);
Opnd opnd = obj.as_opnd();
if (g_refs_squeeze) {
//AR gr_null = rfind(Val(jobj, NULL_REF));
//if (gr_null == ar_x) {
// gr_null = valloc(jobj);
// movp(gr_null, NULL_REF);
// rset(gr_null, Val(jobj, NULL_REF));
//}
AR gr_null = valloc(jobj);
movp(gr_null, NULL_REF);
alu(alu_cmp, opnd, gr_null);
}
else {
assert(NULL_REF == 0);
if (opnd.is_reg()) {
alu(alu_test, opnd, opnd);
} else {
// There is no imm64 for cmp. But it is not a problem as we compare with zero.
alu(alu_cmp, opnd, Opnd((int)(int_ptr)NULL_REF));
}
}
runlock(obj);
unsigned br_off = br(ne, 0, 0, taken);
Class_Handle npeClass = vm_lookup_class_with_bootstrap(NULL_POINTER_EXCEPTION);
gen_throw(npeClass, true);
patch(br_off, ip());
} // if !useHW
// Mark the object 'non-null, guaranteed'
obj.set(VA_NZ);
// Propagate 'non-null' attribute to the same items in the frame
for (unsigned i=1; i<m_jframe->size(); i++) {
Val& that = m_jframe->dip(i);
if (that == obj) {
that.set(VA_NZ);
}
}
for (unsigned i=0; i<m_infoBlock.get_num_locals(); i++) {
Val& var = m_jframe->var(i);
if (var == obj) {
var.set(VA_NZ);
}
}
if (is_set(DBG_TRACE_CG) && !useHW) { dbg(";;>~check.npe\n"); }
}
void CodeGen::gen_check_bounds(unsigned ref_depth, unsigned index_depth)
{
if (is_set(DBG_TRACE_CG)) {dbg(";;>check.bounds\n");}
const Opnd arr = vstack(ref_depth, true).as_opnd();
const Opnd idx = vstack(index_depth, vis_mem(index_depth)).as_opnd();
Opnd len(i32, arr.reg(), rt_array_length_offset);
if (idx.is_reg()) {
alu(alu_cmp, len, Opnd(i32, idx.reg()));
}
else {
alu(alu_cmp, len, Opnd(idx.ival()));
}
// Unsigned condition here - aka 'len > (unsigned)index' - this also
// covers 'index < 0' - in a single comparation.
unsigned br_off = br(above, 0, 0, taken);
//gen_call_vm_restore(true, ci_helper_v, rt_helper_throw_out_of_bounds, 0);
Class_Handle ioobClass = vm_lookup_class_with_bootstrap(INDEX_OUT_OF_BOUNDS);
gen_throw(ioobClass, true);
patch(br_off, ip());
if (is_set(DBG_TRACE_CG)) {dbg(";;>~check.bounds\n");}
}
void CodeGen::gen_check_div_by_zero(jtype jt, unsigned divizor_depth)
{
const Val& s = vstack(divizor_depth);
//
if ((s.is_imm() && s.ival() != 0) || s.has(VA_NZ)) {
// not zero - guaranteed
return;
}
if (is_set(DBG_TRACE_CG)) {dbg(";;>check.div_by_zero\n");}
// The first Val is immediate and zero
if (s.is_imm() && s.ival() == 0) {
// if it's i32, then nothing to do more - throw exception ...
if (jt == i32) {
// IS zero. Why do people want to divide on zero explicitly?..
//gen_call_throw(ci_helper_v, rt_helper_throw_div_by_zero_exc, 0);
Class_Handle aeClass = vm_lookup_class_with_bootstrap(DIVIDE_BY_ZERO_EXCEPTION);
gen_throw(aeClass, false);
if (is_set(DBG_TRACE_CG)) {dbg(";;>~check.div_by_zero\n");}
return;
}
else if (is_big(jt)) {
// ... otherwise check high part of long constant ...
const Val& shi = m_jframe->dip(divizor_depth+1);
if (shi.is_imm() && shi.ival() == 0) {
// ... yes, it's zero too - throw ...
// Why do people want to divide on zero explicitly?..
//gen_call_throw(ci_helper_v, rt_helper_throw_div_by_zero_exc, 0);
Class_Handle aeClass = vm_lookup_class_with_bootstrap(DIVIDE_BY_ZERO_EXCEPTION);
gen_throw(aeClass, false);
if (is_set(DBG_TRACE_CG)) {dbg(";;>~check.div_by_zero\n");}
return;
}
else if (shi.is_imm() && shi.ival() != 0) {
// ... no, the high part is not zero - may return.
if (is_set(DBG_TRACE_CG)) {dbg(";;>~check.div_by_zero\n");}
return;
}
}
// fall through to the next checks
}
// Long value on the stack and we are on 64bit platform - check
// a single constant at once
if (s.is_imm() && jt == i64 && !is_big(i64)) {
if (s.lval() == 0) {
// IS zero. Why do people want to divide on zero explicitly?..
//gen_call_throw(ci_helper_v, rt_helper_throw_div_by_zero_exc, 0);
Class_Handle aeClass = vm_lookup_class_with_bootstrap(DIVIDE_BY_ZERO_EXCEPTION);
gen_throw(aeClass, false);
}
if (is_set(DBG_TRACE_CG)) {dbg(";;>~check.div_by_zero\n");}
return;
}
// at this point:
assert(!s.is_imm() || (s.is_imm() && s.ival()==0 && jt == i64));
// .. and 'shi' is not immediate
assert(jt != i64 || !vis_imm(divizor_depth+1));
if (s.is_reg()) {
Opnd reg = s.as_opnd(jtmov(jt));
alu(alu_test, reg, reg);
}
else {
Opnd mem = s.as_opnd(jtmov(jt));
alu(alu_cmp, mem, Opnd(0));
}
if (jt == i32 || !is_big(jt)) {
unsigned br_off = br(nz, 0, 0, taken);
//gen_call_vm_restore(true, ci_helper_v, rt_helper_throw_div_by_zero_exc, 0);
Class_Handle aeClass = vm_lookup_class_with_bootstrap(DIVIDE_BY_ZERO_EXCEPTION);
gen_throw(aeClass, true);
patch(br_off, ip());
if (is_set(DBG_TRACE_CG)) {dbg(";;>~check.div_by_zero\n");}
return;
}
unsigned br_off = NOTHING;
const Val& shi = m_jframe->dip(divizor_depth+1);
if (!s.is_imm()) {
// jump around the further check --> [1]
br_off = br(nz, 0, 0, hint_none);
}
//
// NB: the code generated below (till if(is_set)) may be jumped over
// so no methods should be invoked that may change location of an item
// - like vstack(), vswap() etc. If it's necessary to invoke such
// methods, this should be done before the br() above.
//
if (shi.is_reg()) {
Opnd reg = shi.as_opnd(i32);
alu(alu_test, reg, reg);
}
else {
Opnd mem = shi.as_opnd(i32);
alu(alu_cmp, mem, Opnd(0));
}
unsigned br_hi = br(nz, 0, 0, taken);
//gen_call_vm_restore(true, ci_helper_v, rt_helper_throw_div_by_zero_exc, 0);
Class_Handle aeClass = vm_lookup_class_with_bootstrap(DIVIDE_BY_ZERO_EXCEPTION);
gen_throw(aeClass, true);
patch(br_hi, ip());
if (!s.is_imm()) {
// [1] --> connect to here
patch(br_off, ip());
}
if (is_set(DBG_TRACE_CG)) {dbg(";;>~check.div_by_zero\n");}
}
void CodeGen::gen_brk(void)
{
trap();
}
void CodeGen::gen_gc_stack(int depth /*=-1*/, bool trackIt /*=false*/)
{
if (depth == -1) {
depth = m_jframe->size();
}
// prepare GC info for stack
// Store the current depth
if (m_bbstate->stack_depth == (unsigned)depth) {
if (is_set(DBG_TRACE_CG)) {
dbg(";;>GC.stack.depth - skipped (%d)\n", depth);
}
}
else {
Opnd op_depth(i32, m_base, voff(m_stack.info_gc_stack_depth()));
mov(op_depth, depth);
if (trackIt) {
m_bbstate->stack_depth = depth;
}
};
if (depth == 0) {
return;
}
unsigned n_words = words(depth);
if (n_words != 0) {
unsigned gc_word = 0;
unsigned size = min(m_jframe->size(), WORD_SIZE);
for (unsigned i=0; i<size; i++) {
const Val& s = m_jframe->at(i);
if (s.jt() != jobj) continue;
if (vvar_idx(s) != -1) continue;
if (s.is_reg() && is_callee_save(s.reg())) continue;
if (s.survive_calls()) continue;
gc_word |= 1<<i;
}
// check whether we do need to store first word
if (m_bbstate->stack_mask_valid &&
gc_word == m_bbstate->stack_mask) {
// do not need to update the GC mask, it's the same
if (is_set(DBG_TRACE_CG)) {
dbg(";;>GC.stack.mask - skipped(0x%X)\n", gc_word);
}
}
else {
Opnd op_mask(i32, m_base, 0*sizeof(int)+ voff(m_stack.info_gc_stack()));
mov(op_mask, gc_word);
if (trackIt) {
m_bbstate->stack_mask = gc_word;
m_bbstate->stack_mask_valid = true;
}
}
}
// store the bit masks
unsigned size = m_jframe->size();
for (unsigned i = 1; i < n_words; i++) {
unsigned pos = i*WORD_SIZE; // where to start
unsigned end_pos = min(pos + WORD_SIZE, size);
unsigned gc_word = 0;
for ( ; pos < end_pos; pos++) {
const Val& s = m_jframe->at(pos);
if (s.jt() != jobj) continue;
if (vvar_idx(s) != -1) continue;
if (s.is_reg() && is_callee_save(s.reg())) continue;
if (s.survive_calls()) continue;
gc_word |= 1<<pos;
}
Opnd op_mask(i32, m_base, i*sizeof(int)+ voff(m_stack.info_gc_stack()));
mov(op_mask, gc_word);
}
}
void CodeGen::gen_gc_mark_local(jtype jt, unsigned idx)
{
jtype jtvar = vtype(idx);
if (jtvar != jvoid && jtvar != jobj) {
// the variable is known as never contains an object.
assert(jt != jobj);
if (m_infoBlock.get_flags() & DBG_TRACE_CG) {
dbg(";;> skipping GC mark - the item is known to be non-object\n");
}
return;
}
bool mark = jt == jobj;
Val& v = vlocal(jt, idx);
// If an item was already marked and its type is still the same, then
// skip the mark
if ((v.has(VA_MARKED)) &&
((v.type() == jobj && mark) || (v.type() != jobj && !mark))) {
if (is_set(DBG_TRACE_CG)) {
dbg(";;>GC mark skipped - object type is known\n");
}
return;
}
// prepare GC info for variable
unsigned offset;
unsigned bitno;
AR ar = vreg(jt, idx);
if (ar != ar_x && is_callee_save(ar)) {
// mark the callee-save register
offset = m_stack.info_gc_regs();
bitno = bit_no(ar_idx(ar));
}
else if (vis_arg(idx)) {
// mark input argument
unsigned i = vget_arg(idx);
assert(m_ci.reg(i) == ar_x);
assert(0 == m_ci.off(i)%STACK_SLOT_SIZE);
int inVal = m_ci.off(i)/STACK_SLOT_SIZE;
unsigned word = word_no(inVal);
offset = word*sizeof(int) + m_stack.info_gc_args();
bitno = bit_no(inVal);
}
else {
// mark the Val in the stack frame
unsigned word = word_no(idx);
offset = word*sizeof(int) + m_stack.info_gc_locals();
bitno = bit_no(idx);
}
unsigned mask = 1<<bitno;
if (is_wide(jt)) {
// We must also clear bit for the next Val
assert(!mark);
if (bitno < 31) {
// good. can do in one touch
mask |= 1 << (bitno+1);
}
else {
// Bad, the next Val crosses the word's boundary
unsigned offset2 = offset + sizeof(int);
unsigned mask2 = 1;
const Opnd opnd2(i32, m_base, voff(offset2));
alu(alu_and, opnd2, Opnd(~mask2));
}
}
const Opnd opnd(i32, m_base, voff(offset));
if (mark) {
alu(alu_or, opnd, Opnd(mask));
}
else {
alu(alu_and, opnd, Opnd(~mask));
}
v.set(VA_MARKED);
}
unsigned CodeGen::gen_stack_to_args(bool pop, const CallSig& cs,
unsigned idx, int cnt)
{
assert(idx <= cs.count());
unsigned num = cs.count() - idx;
if (cnt == -1) {
cnt = num;
}
// A special case on IA32 - our frame layout fits best for the managed
// calling convention, and the args can be prepared in a few
// instructions. - later
// TODO: it's not only for 'MANAGED_IA32' but for 'l2r && callee pops
// && !align stack'.
if (false && pop && idx == 0 && cnt == (int)cs.count() &&
(cs.cc() == CCONV_MANAGED_IA32)) {
int fix = 0;
if (cnt != 0) {
// find the difference between last used slot and the end of
// stack frame
int s = m_stack.stack_slot(m_jframe->depth2slot(0));
fix = m_stack.size() + s; // s is < 0
}
for (unsigned i=0; i<(unsigned)cnt; i++) {
jtype jt = m_jframe->top();
vswap(0);
if (is_big(jt)) {
vswap(1);
}
vpop();
}
if (fix != 0) {
alu(alu_add, sp, fix);
}
return fix;
}
if (idx == 0 && cs.size() != 0) {
alu(alu_sub, sp, cs.size());
}
int depth = 0;
// 1st pass - free all register that are used for args passing
for (int i=0; !(cs.cc() & CCONV_MEM) && i<cnt; i++) {
unsigned arg_id = idx+cnt-i-1;
AR ar = cs.reg(arg_id);
if (ar == ar_x) continue;
vpark(ar);
}
rlock(cs);
for (int i=0; i<cnt; i++) {
unsigned arg_id = idx+cnt-i-1;
jtype jt = cs.jt(arg_id);
if (jt<i32) {
jt = i32;
}
const Val& s = m_jframe->dip(depth);
if (cs.reg(arg_id) != ar_x) {
Opnd rarg(jt, cs.reg(arg_id));
do_mov(rarg, s);
}
else {
jtype jtm = jtmov(jt);
Opnd arg(jtm, sp, cs.off(arg_id));
do_mov(arg, s);
if (is_big(jt)) {
Opnd arg_hi(jtm, sp, cs.off(arg_id)+4);
const Val& s_hi = m_jframe->dip(depth+1);
do_mov(arg_hi, s_hi);
}
}
if (pop) {
vpop();
}
else {
depth += is_wide(jt) ? 2 : 1;
}
}
return 0;
}
void CodeGen::gen_call_throw(const CallSig& cs, void * target,
unsigned idx, ...)
{
// say 'stack is empty'
gen_gc_stack(0, false);
vpark(false);
va_list valist;
va_start(valist, idx);
rlock(cs);
AR gr = valloc(jobj);
call_va(is_set(DBG_CHECK_STACK), gr, target, cs, idx, valist);
runlock(cs);
#ifdef _DEBUG
// just to make sure we do not return from there
gen_brk();
#endif
}
void CodeGen::gen_call_vm(const CallSig& cs, void * target,
unsigned idx, ...)
{
vpark();
gen_gc_stack(-1, true);
va_list valist;
va_start(valist, idx);
rlock(cs);
AR gr = valloc(jobj);
call_va(is_set(DBG_CHECK_STACK), gr, target, cs, idx, valist);
runlock(cs);
}
void CodeGen::gen_call_novm(const CallSig& cs, void * target,
unsigned idx, ...)
{
vpark();
va_list valist;
va_start(valist, idx);
rlock(cs);
AR gr = valloc(jobj);
call_va(is_set(DBG_CHECK_STACK), gr, target, cs, idx, valist);
runlock(cs);
}
void CodeGen::gen_call_vm_restore(bool exc, const CallSig& cs,
void * target, unsigned idx, ...)
{
BBState saveBB = *m_bbstate;
// 1. store scratch registers in a secret place
// 2. park everything
// 3. call whatever
// 4. restore scratch regs from the secret place
// 5. restore the state for callee-save registers
//-----------------------------------------------
// 1.
bool saveScratch = !exc;
for (unsigned i=0; i<ar_num; i++) {
AR ar = _ar(i);
if (is_callee_save(ar)) continue;
if (saveScratch && rrefs(ar) != 0) {
jtype jt = is_f(ar) ? dbl64 : jobj;
Opnd mem(jt, m_base, voff(m_stack.spill(ar)));
Opnd reg(jt, ar);
mov(mem, reg);
}
if (rlocks(ar) != 0) {
runlock(ar, true);
}
}
// 2.
vpark();
gen_gc_stack(-1, true);
// 3.
va_list valist;
va_start(valist, idx);
rlock(cs);
AR gr = valloc(jobj);
call_va(is_set(DBG_CHECK_STACK), gr, target, cs, idx, valist);
runlock(cs);
// 4.
// Restore BBState first, so ref_counts for registers become valid
*m_bbstate = saveBB;
// restore the registers state
for (unsigned i=0; saveScratch && i<ar_num; i++) {
AR ar = _ar(i);
if (is_callee_save(ar)) continue;
if (rrefs(ar) != 0) {
jtype jt = is_f(ar) ? dbl64 : jobj;
Opnd mem(jt, m_base, voff(m_stack.spill(ar)));
Opnd reg(jt, ar);
mov(reg, mem);
}
}
// 5.
// Actually nothing to do here.
// If we had a local var on register before, then it's still on the reg
// If we had the var with static assignment which was in memory, before,
// then the memory was not corrupted.
// So, just nothing to do with callee-save regs
//
}
void CodeGen::gen_throw(Class_Handle exnClass, bool restore)
{
#ifdef _EM64T_
bool lazy = false;
#else
bool lazy = true;
#endif
BBState saveBB;
//TODO: Workaround for x86-64 stack should be aligned to half of 16
#ifdef _EM64T_
alu(alu_sub, sp, (unsigned)STACK_SLOT_SIZE);
alu(alu_and, sp, ~((unsigned)STACK_SLOT_SIZE));
alu(alu_add, sp, (unsigned)STACK_SLOT_SIZE);
#endif
if (restore){
saveBB = *m_bbstate;
for (unsigned i=0; i<ar_num; i++) {
AR ar = _ar(i);
if (rlocks(ar) != 0) {
runlock(ar, true);
}
}
}
if (lazy) {
gen_call_throw(ci_helper_lazy, rt_helper_throw_lazy, 0, exnClass, NULL);
} else {
static const CallSig ci_new(CCONV_HELPERS, jobj, i32, jobj);
unsigned size = (unsigned)class_get_object_size(exnClass);
unsigned stackFix;
Allocation_Handle ah = class_get_allocation_handle(exnClass);
gen_call_vm(ci_new, rt_helper_new, 0, size, ah);
gen_save_ret(ci_new);
static const CallSig cs_constructor(CCONV_MANAGED, jvoid, jobj);
static Method_Handle constructorMethDesc =
class_lookup_method_recursively(exnClass, DEFAUlT_COSTRUCTOR_NAME,
DEFAUlT_COSTRUCTOR_DESCRIPTOR);
static char* constructorMethAddr =
*(char**)method_get_indirect_address(constructorMethDesc);
stackFix = gen_stack_to_args(false, cs_constructor, 0);
gen_call_vm(cs_constructor, constructorMethAddr, 1);
runlock(cs_constructor);
if (stackFix != 0) {
alu(alu_sub, sp, stackFix);
}
static const CallSig cs_throw(CCONV_HELPERS, jvoid, jobj);
stackFix = gen_stack_to_args(true, cs_throw, 0);
gen_call_vm(cs_throw, rt_helper_throw, 1);
runlock(cs_throw);
if (stackFix != 0) {
alu(alu_sub, sp, stackFix);
}
}
// Restore BBState first, so ref_counts for registers become valid
if (restore){
*m_bbstate = saveBB;
}
}
}}; // ~namespace Jitrino::Jet