blob: a7da127b0f2d2929546d506cd6cf537eaa8b3f3c [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
*/
/**
* @file
* @brief Platform-independent stuff of Encoder class.
*/
#include "enc.h"
#include <stdio.h>
#include "trace.h"
#ifdef PLATFORM_POSIX
#include <signal.h>
#else
#endif
#include "compiler.h"
namespace Jitrino {
namespace Jet {
bitset<ar_num> Encoder::isCalleeSave;
void Encoder::init(void)
{
for (unsigned i=0; i<ar_num; i++) {
AR ar = _ar(i);
isCalleeSave.set(i, is_callee_save_impl(ar));
}
}
void Encoder::debug(void)
{
#ifdef PLATFORM_POSIX
raise(SIGTRAP);
#else
DebugBreak();
#endif
}
unsigned gen_num_calle_save(void)
{
static int num = -1;
if (num == -1) {
num = 0;
for (int i=0; i<ar_num; i++) {
AR ar = (AR)(gr0+i);
if (is_callee_save(ar)) {
++num;
}
}
}
return num;
}
void Encoder::lea(const Opnd& reg, const Opnd& mem)
{
assert(reg.is_reg());
assert(mem.is_mem());
if (is_trace_on()) {
trace("lea", to_str(reg), to_str(mem));
}
lea_impl(reg, mem);
}
unsigned Encoder::movp(AR op0, unsigned udata, unsigned ubase)
{
assert(op0 != ar_x);
if (is_trace_on()) {
trace("movp.patch", to_str(op0), to_str(udata));
}
unsigned pid = reg_patch(true, udata, ubase);
movp_impl(op0, NULL);
reg_patch_end(pid);
return pid;
}
void Encoder::sx1(const Opnd& op0, const Opnd& op1)
{
assert(op0 != ar_x && op1 != ar_x);
if (is_trace_on()) {
trace("sx1", to_str(op0), to_str(op1));
}
sx1_impl(op0, op1);
}
void Encoder::sx2(const Opnd& op0, const Opnd& op1)
{
assert(op0 != ar_x && op1 != ar_x);
if (is_trace_on()) {
trace("sx2", to_str(op0), to_str(op1));
}
sx2_impl(op0, op1);
}
void Encoder::sx(const Opnd& op0, const Opnd& op1)
{
assert(op0 != ar_x && op1 != ar_x);
if (is_trace_on()) {
trace("sx", to_str(op0), to_str(op1));
}
sx_impl(op0, op1);
}
void Encoder::zx2(const Opnd& op0, const Opnd& op1)
{
assert(op0 != ar_x && op1 != ar_x);
if (is_trace_on()) {
trace("zx2", to_str(op0), to_str(op1));
}
zx2_impl(op0, op1);
}
void Encoder::fld(jtype jt, AR op0, AR base, int disp, AR index, unsigned scale)
{
assert(op0 != ar_x);
assert(is_f(jt));
if (is_trace_on()) {
trace("fld", to_str(op0), to_str(base, disp, index, scale));
}
fld_impl(jt, op0, base, disp, index, scale);
}
void Encoder::fst(jtype jt, AR op0, AR base, int disp, AR index, unsigned scale)
{
assert(op0 != ar_x);
assert(is_f(jt));
if (is_trace_on()) {
trace("fst", to_str(base, disp, index, scale), to_str(op0));
}
fst_impl(jt, op0, base, disp, index, scale);
}
void Encoder::trap(void)
{
if (is_trace_on()) {
trace("trap", "", "");
}
trap_impl();
}
void Encoder::trace(const string& func, const string& op0, const string& op1)
{
if (!m_trace) {
return;
}
dbg("\t; %s%s", m_prefix.c_str(), func.c_str());
if (!op0.empty()) {
dbg(" %s", op0.c_str());
if (!op1.empty()) {
dbg(", %s", op1.c_str());
}
}
else {
assert(op1.empty());
}
dbg("\n");
}
string Encoder::to_str(AR ar, bool platf)
{
if (ar == ar_x) {
return "";
}
if (ar == bp) {
return "bp";
}
if (ar == sp) {
return "sp";
}
if (ar == fp0) {
return "fp0";
}
if (platf) {
return to_str_impl(ar);
}
char buf[20];
snprintf(buf, sizeof(buf)-1, "%s%d",
is_f(ar) ? (is_callee_save(ar) ? "FR" : "fr") :
(is_callee_save(ar) ? "GR" : "gr"),
type_idx(ar));
return string(buf);
}
string Encoder::to_str(const Opnd& op)
{
if (op.is_reg()) return to_str(op.reg());
if (op.is_mem()) return to_str(op.base(), op.disp(), op.index(), op.scale());
assert(op.is_imm());
return to_str(op.ival());
}
string Encoder::to_str(jtype jt)
{
return jt == jvoid ? "" : jtypes[jt].name;
}
string Encoder::to_str(AR base, int disp, AR index, unsigned scale)
{
if (base == (AR)NOTHING) {
base = ar_x;
}
assert(base == gr_x || is_gr(base));
assert(index == gr_x || is_gr(index));
char buf[100] = "[";
if (base != gr_x) {
strcat(buf, to_str(base).c_str());
}
if (index != gr_x) {
if (base != gr_x) {
strcat(buf, "+");
}
strcat(buf, to_str(index).c_str());
strcat(buf, "*");
char tmp[20];
snprintf(tmp, sizeof(tmp)-1, "%d", scale);
strcat(buf, tmp);
}
if (disp != 0) {
char tmp[20];
if (base == ar_x && index==ar_x) {
snprintf(tmp, sizeof(tmp)-1, "0x%X", disp);
}
else {
bool _plus = (index != gr_x || base != gr_x) && disp>0;
if (_plus) {
strcat(buf, "+");
}
if (abs(disp)<256*1024) {
snprintf(tmp, sizeof(tmp)-1, "%d", disp);
}
else {
if (_plus) {
snprintf(tmp, sizeof(tmp)-1, "0x%X", disp);
}
else {
snprintf(tmp, sizeof(tmp)-1, "+0x%X", disp);
}
}
}
strcat(buf, tmp);
}
strcat(buf, "]");
return buf;
}
string Encoder::to_str(const void * addr)
{
char buf[50];
snprintf(buf, sizeof(buf)-1, "%p", addr);
return buf;
}
string Encoder::to_str(int i)
{
char buf[50];
snprintf(buf, sizeof(buf)-1, "0x%X/%d", i, i);
return buf;
}
string Encoder::to_str(ALU op)
{
if (op==alu_add) return "add";
if (op==alu_sub) return "sub";
if (op==alu_mul) return "mul";
if (op==alu_div) return "div";
if (op==alu_rem) return "rem";
if (op==alu_or) return "or";
if (op==alu_xor) return "xor";
if (op==alu_and) return "and";
if (op==alu_cmp) return "cmp";
if (op==alu_test) return "test";
if (op==alu_shl) return "shl";
if (op==alu_shr) return "shr";
if (op==alu_sar) return "sar";
assert(false);
return "???";
}
string Encoder::to_str(COND cond)
{
if (cond==cond_none) return "";
if (cond==ge) return ":ge";
if (cond==le) return ":le";
if (cond==gt) return ":gt";
if (cond==lt) return ":lt";
if (cond==eq) return ":eq";
if (cond==ne) return ":ne";
if (cond==ae) return ":ae";
if (cond==be) return ":be";
if (cond==above) return ":above";
if (cond==below) return ":below";
assert(false);
return "???";
}
string Encoder::to_str(HINT hint)
{
if (hint==hint_none) return "";
if (hint==taken) return "[hint:taken]";
if (hint==not_taken) return "[hint:not taken]";
assert(false);
return "???";
}
void Encoder::call(bool check_stack, AR gr, const void * target,
const CallSig& cs, unsigned idx, ...)
{
//TODO: Add IA-64 check
#ifdef HYX86_64
if (check_stack) {
alu(alu_test, sp, 0x0F);
unsigned br_off = br(z, 0, 0, hint_none);
trap();
patch(br_off, ip(br_off), ip());
}
#endif
va_list valist;
va_start(valist, idx);
call_va(check_stack, gr, target, cs, idx, valist);
}
void Encoder::call_va(bool check_stack, AR ar, const void *target,
const CallSig& cs, unsigned idx, va_list& valist)
{
if (is_trace_on()) {
trace("[+]call:", to_str(ar), to_str(target));
m_prefix = " |";
}
if (idx == 0 && cs.size()) {
alu(alu_sub, sp, cs.size());
}
for (unsigned i=idx; i<cs.count(); i++) {
jtype jt = cs.jt(i);
assert(!is_f(jt)); // not implemented
AR gr = cs.reg(i); ;
if (jt == jobj) {
void * addr = va_arg(valist, void*);
if (gr != ar_x) {
movp(gr, addr);
}
else if (is_ia32()) {
int val = (int)(int_ptr)addr;
mov(Opnd(i32, sp, cs.off(i)), val);
}
else {
#ifdef HYX86_64
int_ptr val = va_arg(valist, int_ptr);
mov(Opnd(i64, sp, cs.off(i)), val);
#else
int val = lo32((jlong)(int_ptr)addr);
mov(Opnd(i32, sp, cs.off(i)), val);
val = hi32((jlong)(int_ptr)addr);
mov(Opnd(i32, sp, cs.off(i)+4), val);
#endif
}
}
else if (jt==i64) {
#ifdef HYX86_64
int_ptr val = va_arg(valist, int_ptr);
mov(gr == gr_x ? Opnd(i64, sp, cs.off(i)) : Opnd(i64, gr), val);
#else
assert(false);
#endif
}
else {
int val = va_arg(valist, int);
mov(gr == gr_x ? Opnd(i32, sp, cs.off(i)) : Opnd(i32, gr), val);
}
}
movp(ar, target);
call(Opnd(jobj, ar), cs, check_stack);
if (is_trace_on()) {
m_prefix.clear();
trace("[^]call:", to_str(ar), to_str(target));
}
}
void Encoder::gen_args(const CallSig& cs, AR grtmp, unsigned idx,
unsigned count, ...)
{
if (idx == 0 && cs.size() != 0) {
alu(alu_sub, sp, cs.size());
}
va_list valist;
va_start(valist, count);
for (int i=idx; i!=(int)(idx+count); i++) {
jtype jt = cs.jt(i);
assert(!is_f(jt)); // not implemented
AR gr = cs.reg(i); ;
if (gr == gr_x) {
assert(grtmp != gr_x); // NYI
gr = grtmp;
}
else {
//assert(gr_idx(cs.reg(i)) != gr_idx(gr_call));
}
if (jt == jobj) {
void * addr = va_arg(valist, void*);
movp(gr, addr);
}
else if (jt==i64) {
//jlong jl = va_arg(valist, jlong);
//Opnd op()
//movl(gr, jl);
assert(false); // not expected - there were no usage.
}
else {
int i = va_arg(valist, int);
mov(gr, i);
}
if (cs.reg(i) == gr_x) {
st(jt, gr, sp, cs.off(i));
}
}
}
int Encoder::push(const Opnd& op0)
{
if (is_trace_on()) {
trace("push", to_str(op0), "");
}
return push_impl(op0);
}
int Encoder::pop(const Opnd& op0)
{
if (is_trace_on()) {
trace("pop", to_str(op0), "");
}
return pop_impl(op0);
}
int Encoder::push_all(bool includeCalleeSave)
{
if (is_trace_on()) {
trace("[+]push_all", "", "");
m_prefix = " |";
}
int size = get_all_regs_size();
size = (size+15) & (~0xF);
alu(alu_sub, sp, size);
for (unsigned i=0; i<gr_num; i++) {
AR gr = _gr(i);
if (is_callee_save(gr) && !includeCalleeSave) continue;
Opnd reg(jobj, gr);
Opnd mem(jobj, sp, i*STACK_SLOT_SIZE);
mov(mem, reg);
}
for (unsigned i=0; i<fr_num; i++) {
AR fr = _fr(i);
if (is_callee_save(fr) && !includeCalleeSave) continue;
Opnd reg(dbl64, fr);
Opnd mem(dbl64, sp, gr_num*STACK_SLOT_SIZE+i*8);
mov(mem, reg);
}
if (is_trace_on()) {
m_prefix.clear();
trace("[^]push_all", "", "");
}
return -size;
}
int Encoder::pop_all(bool includeCalleeSave)
{
if (is_trace_on()) {
trace("[+]pop_all", "", "");
m_prefix = " |";
}
int size = get_all_regs_size();
size = (size+15) & (~0xF);
for (unsigned i=0; i<gr_num; i++) {
AR gr = _gr(i);
if (is_callee_save(gr) && !includeCalleeSave) continue;
Opnd reg(jobj, gr);
Opnd mem(jobj, sp, i*STACK_SLOT_SIZE);
mov(reg, mem);
}
for (unsigned i=0; i<fr_num; i++) {
AR fr = _fr(i);
if (is_callee_save(fr) && !includeCalleeSave) continue;
Opnd reg(dbl64, fr);
Opnd mem(dbl64, sp, gr_num*STACK_SLOT_SIZE+i*8);
mov(reg, mem);
}
alu(alu_add, sp, size);
if (is_trace_on()) {
m_prefix.clear();
trace("[^]pop_all", "", "");
}
return size;
}
void Encoder::call(const Opnd& target, const CallSig& ci, bool check_stack)
{
assert(!target.is_reg() || !is_f(target.reg()));
if (is_trace_on()) {
trace("call", to_str(target), "");
}
unsigned alignment = (ci.cc() & CCONV_STACK_ALIGN_HALF16) ? CCONV_STACK_ALIGN16
: ci.cc() & CCONV_STACK_ALIGN_MASK;
if (check_stack && alignment != 0) {
alu(alu_sub, sp, (unsigned)STACK_SLOT_SIZE);
if (ci.cc() & CCONV_STACK_ALIGN_HALF16) {
alu(alu_sub, sp, (unsigned)STACK_SLOT_SIZE);
}
alu(alu_test, sp, (alignment - 1));
unsigned br_off = br(z, 0, 0, taken);
trap();
patch(br_off, ip());
if (ci.cc() & CCONV_STACK_ALIGN_HALF16) {
alu(alu_add, sp, (unsigned)STACK_SLOT_SIZE);
}
alu(alu_add, sp, (unsigned)STACK_SLOT_SIZE);
}
call_impl(target);
if (ci.caller_pops() && ci.size() != 0) {
alu(alu_add, sp, ci.size());
}
}
void Encoder::ret(unsigned pop)
{
if (is_trace_on()) {
trace("ret", to_str(pop), "");
}
ret_impl(pop);
}
unsigned Encoder::reg_patch(bool data, unsigned udata, unsigned ubase)
{
CodePatchItem cpi;
cpi.data = data;
cpi.len = (unsigned)-1;
cpi.done = false;
cpi.udata = udata;
cpi.ubase = ubase;
unsigned pid = ipoff();
assert(m_patches.count(pid) == 0);
m_patches[pid] = cpi;
return pid;
}
void Encoder::reg_patch_end(unsigned pid)
{
assert(m_patches.count(pid) != 0);
CodePatchItem& cpi = m_patches[pid];
assert(cpi.len == (unsigned)-1);
cpi.len = m_codeStream.ipoff() - pid; // pid is ipoff
assert(cpi.len != 0);
}
unsigned Encoder::br(COND cond, unsigned udata, unsigned base, HINT hint)
{
if (is_trace_on()) {
trace(to_str(hint) + "br"+to_str(cond), to_str(udata), to_str(base));
}
unsigned pid = reg_patch(false, udata, base);
br_impl(cond, hint);
reg_patch_end(pid);
return pid;
}
void Encoder::br(const Opnd& op, COND cond, HINT hint)
{
if (is_trace_on()) {
trace(to_str(hint)+"br"+to_str(cond), to_str(op), "");
}
br_impl(op, cond, hint);
}
void Encoder::patch(unsigned pid, void * inst_addr, void * target_addr)
{
assert(m_patches.count(pid) != 0);
CodePatchItem& cpi = m_patches[pid];
if (cpi.done) {
return;
}
if (cpi.data) {
assert(cpi.len>sizeof(void*)); // We're writing address there
// Address is normally placed at the end of instruction
char* addr_addr = (char*)inst_addr+cpi.len-sizeof(void*);
*(void**)addr_addr = target_addr;
}
else {
// only long JMPs currently, short JMPs to be done
assert(cpi.len>4);
int offset = (int)((char*)target_addr-((char*)inst_addr+cpi.len));
// Offset is normally placed at the end of instruction
char* offset_addr = (char*)inst_addr+cpi.len-4;
*(int*)offset_addr = offset;
}
cpi.done = true;
}
}
}; // ~namespace Jitrino::Jet