Jit/codegen/autogen.cpp (818 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "Jit/codegen/autogen.h"
#include "Jit/codegen/gen_asm_utils.h"
#include "Jit/codegen/x86_64.h"
#include "Jit/deopt_patcher.h"
#include "Jit/frame.h"
#include "Jit/lir/instruction.h"
#include "Jit/util.h"
#include <asmjit/x86/x86operand.h>
#include <type_traits>
#include <vector>
using namespace asmjit;
using namespace jit::lir;
using namespace jit::codegen;
namespace jit {
namespace codegen {
namespace autogen {
#define ANY "*"
namespace {
// Add a pattern to an existing trie tree. If the trie tree is nullptr, create a
// new one.
std::unique_ptr<PatternNode> addPattern(
std::unique_ptr<PatternNode> patterns,
const std::string& s,
PatternNode::func_t func) {
JIT_DCHECK(!s.empty(), "pattern string should not be empty.");
if (patterns == nullptr) {
patterns = std::make_unique<PatternNode>();
}
PatternNode* cur = patterns.get();
for (auto& c : s) {
auto iter = cur->next.find(c);
if (iter == cur->next.end()) {
cur = cur->next.emplace(c, std::make_unique<PatternNode>())
.first->second.get();
continue;
}
cur = iter->second.get();
}
JIT_DCHECK(cur->func == nullptr, "Found duplicated pattern.");
cur->func = func;
return patterns;
}
// Find the function associated to the pattern given in s.
PatternNode::func_t findByPattern(
const PatternNode* patterns,
const std::string& s) {
auto cur = patterns;
for (auto& c : s) {
auto iter = cur->next.find(c);
if (iter != cur->next.end()) {
cur = iter->second.get();
continue;
}
iter = cur->next.find('?');
if (iter != cur->next.end()) {
cur = iter->second.get();
continue;
}
iter = cur->next.find('*');
if (iter != cur->next.end()) {
cur = iter->second.get();
break;
}
return nullptr;
}
return cur->func;
}
} // namespace
// this function generates operand patterns from the inputs and outputs
// of a given instruction instr and calls the correspoinding code generation
// functions.
void AutoTranslator::translateInstr(Environ* env, const Instruction* instr)
const {
auto opcode = instr->opcode();
if (opcode == Instruction::kBind) {
return;
}
auto& instr_map = map_get(instr_rule_map_, opcode);
std::string pattern;
pattern.reserve(instr->getNumInputs() + instr->getNumOutputs());
if (instr->getNumOutputs()) {
auto operand = instr->output();
switch (operand->type()) {
case OperandBase::kReg:
pattern += (operand->isXmm() ? "X" : "R");
break;
case OperandBase::kStack:
case OperandBase::kMem:
case OperandBase::kInd:
pattern += "M";
break;
default:
JIT_CHECK(false, "Output operand has to be of type register or memory");
break;
}
}
instr->foreachInputOperand([&](const OperandBase* operand) {
switch (operand->type()) {
case OperandBase::kReg:
pattern += (operand->isXmm() ? "x" : "r");
break;
case OperandBase::kStack:
case OperandBase::kMem:
case OperandBase::kInd:
pattern += "m";
break;
case OperandBase::kImm:
pattern += "i";
break;
case OperandBase::kLabel:
pattern += "b";
break;
default:
JIT_CHECK(false, "Illegal input type.");
break;
}
});
auto func = findByPattern(instr_map.get(), pattern);
JIT_CHECK(
func != nullptr,
"No pattern found for opcode %s: %s",
InstrProperty::getProperties(instr).name,
pattern);
func(env, instr);
}
namespace {
void fillLiveValueLocations(
DeoptMetadata& deopt_meta,
const Instruction* instr,
size_t start_input) {
for (size_t i = start_input; i < instr->getNumInputs(); i++) {
auto loc = instr->getInput(i)->getPhyRegOrStackSlot();
deopt_meta.live_values[i - start_input].location = loc;
}
}
// Translate GUARD instruction
void TranslateGuard(Environ* env, const Instruction* instr) {
auto as = env->as;
// the first four operands of the guard instruction are:
// * kind
// * deopt meta id
// * guard var (physical register) (0 for AlwaysFail)
// * target (for GuardIs, and 0 for all others)
auto deopt_label = as->newLabel();
auto kind = instr->getInput(0)->getConstant();
auto reg = x86::rax;
bool is_double = false;
if (kind != kAlwaysFail) {
if (instr->getInput(2)->dataType() == jit::lir::OperandBase::kDouble) {
assert(kind == kNotZero);
auto xmm_reg = AutoTranslator::getXmm(instr->getInput(2));
as->ptest(xmm_reg, xmm_reg);
as->jz(deopt_label);
is_double = true;
} else {
reg = AutoTranslator::getGp(instr->getInput(2));
}
}
auto emit_cmp = [&](auto reg_arg) {
constexpr size_t kTargetIndex = 3;
auto target_opnd = instr->getInput(kTargetIndex);
if (target_opnd->isImm()) {
auto target = target_opnd->getConstant();
JIT_DCHECK(
fitsInt32(target),
"The constant operand should fit in a 32-bit register.");
as->cmp(reg_arg, target);
} else {
auto target_reg = AutoTranslator::getGp(instr->getInput(kTargetIndex));
as->cmp(reg_arg, target_reg);
}
};
if (!is_double) {
switch (kind) {
case kNotZero: {
as->test(reg, reg);
as->jz(deopt_label);
break;
}
case kNotNegative: {
as->test(reg, reg);
as->js(deopt_label);
break;
}
case kAlwaysFail:
as->jmp(deopt_label);
break;
case kIs:
emit_cmp(reg);
as->jne(deopt_label);
break;
case kHasType: {
emit_cmp(x86::qword_ptr(reg, offsetof(PyObject, ob_type)));
as->jne(deopt_label);
break;
}
}
}
auto index = instr->getInput(1)->getConstant();
auto& deopt_meta = env->rt->getDeoptMetadata(index);
// skip the first four inputs in Guard, which are
// kind, deopt_meta id, guard var, and target.
fillLiveValueLocations(deopt_meta, instr, 4);
env->deopt_exits.emplace_back(index, deopt_label);
}
void TranslateDeoptPatchpoint(Environ* env, const Instruction* instr) {
auto as = env->as;
// Generate patchpoint
auto patchpoint_label = as->newLabel();
as->bind(patchpoint_label);
DeoptPatcher::emitPatchpoint(*as);
// Fill in deopt metadata
auto index = instr->getInput(1)->getConstant();
auto& deopt_meta = env->rt->getDeoptMetadata(index);
// skip the first two inputs which are the patcher and deopt metadata id
fillLiveValueLocations(deopt_meta, instr, 2);
auto deopt_label = as->newLabel();
env->deopt_exits.emplace_back(index, deopt_label);
// The runtime will link the patcher to the appropriate point in the code
// once code generation has completed.
auto patcher =
reinterpret_cast<DeoptPatcher*>(instr->getInput(0)->getConstant());
env->pending_deopt_patchers.emplace_back(
patcher, patchpoint_label, deopt_label);
}
void TranslateCompare(Environ* env, const Instruction* instr) {
auto as = env->as;
const OperandBase* inp0 = instr->getInput(0);
const OperandBase* inp1 = instr->getInput(1);
if (inp1->type() == OperandBase::kImm) {
as->cmp(AutoTranslator::getGp(inp0), inp1->getConstant());
} else if (!inp1->isXmm()) {
as->cmp(AutoTranslator::getGp(inp0), AutoTranslator::getGp(inp1));
} else {
as->comisd(AutoTranslator::getXmm(inp0), AutoTranslator::getXmm(inp1));
}
auto output = AutoTranslator::getGp(instr->output());
switch (instr->opcode()) {
case Instruction::kEqual:
as->sete(output);
break;
case Instruction::kNotEqual:
as->setne(output);
break;
case Instruction::kGreaterThanSigned:
as->setg(output);
break;
case Instruction::kGreaterThanEqualSigned:
as->setge(output);
break;
case Instruction::kLessThanSigned:
as->setl(output);
break;
case Instruction::kLessThanEqualSigned:
as->setle(output);
break;
case Instruction::kGreaterThanUnsigned:
as->seta(output);
break;
case Instruction::kGreaterThanEqualUnsigned:
as->setae(output);
break;
case Instruction::kLessThanUnsigned:
as->setb(output);
break;
case Instruction::kLessThanEqualUnsigned:
as->setbe(output);
break;
default:
JIT_CHECK(false, "bad instruction for TranslateCompare");
break;
}
if (instr->output()->dataType() != OperandBase::k8bit) {
as->movzx(
AutoTranslator::getGp(instr->output()),
asmjit::x86::gpb(instr->output()->getPhyRegister()));
}
}
// Store meta-data about this yield in a generator suspend data pointed to by
// suspend_data_r. Data includes things like the address to resume execution at,
// and owned entries in the suspended spill data needed for GC operations etc.
void emitStoreGenYieldPoint(
x86::Builder* as,
Environ* env,
const Instruction* yield,
asmjit::Label resume_label,
x86::Gp suspend_data_r,
x86::Gp scratch_r) {
bool is_yield_from =
yield->isYieldFrom() || yield->isYieldFromSkipInitialSend();
auto calc_spill_offset = [&](size_t live_input_n) {
int mem_loc = yield->getInput(live_input_n)->getPhyRegOrStackSlot();
JIT_CHECK(mem_loc < 0, "Expected variable to have memory location");
return mem_loc / kPointerSize;
};
std::vector<ptrdiff_t> pyobj_offs;
size_t input_n = yield->getNumInputs() - 1;
uint64_t owned_lived_inputs_n = yield->getInput(input_n)->getConstant();
while (owned_lived_inputs_n) {
input_n--;
owned_lived_inputs_n--;
pyobj_offs.push_back(calc_spill_offset(input_n));
}
auto yield_from_offset = is_yield_from ? calc_spill_offset(2) : 0;
GenYieldPoint* gen_yield_point = env->code_rt->addGenYieldPoint(
GenYieldPoint{std::move(pyobj_offs), is_yield_from, yield_from_offset});
env->unresolved_gen_entry_labels.emplace(gen_yield_point, resume_label);
env->addIPToBCMapping(resume_label, yield);
as->mov(scratch_r, reinterpret_cast<uint64_t>(gen_yield_point));
auto yieldPointOffset = offsetof(GenDataFooter, yieldPoint);
as->mov(x86::qword_ptr(suspend_data_r, yieldPointOffset), scratch_r);
}
// On resuming the sent in value may source depends on the type of yield, and
// and new tstate is always in RDX from resume entry-point args.
void emitLoadResumedYieldInputs(
asmjit::x86::Builder* as,
const Instruction* instr,
PhyLocation sent_in_source_loc,
x86::Gp tstate_reg) {
int tstate_loc = instr->getInput(0)->getPhyRegOrStackSlot();
JIT_CHECK(tstate_loc < 0, "__asm_tstate should be spilled");
as->mov(x86::ptr(x86::rbp, tstate_loc), tstate_reg);
const lir::Operand* target = instr->output();
if (target->type() != OperandBase::kNone) {
PhyLocation target_loc = target->getPhyRegOrStackSlot();
if (target_loc.is_register()) {
if (target_loc != sent_in_source_loc) {
as->mov(x86::gpq(target_loc), x86::gpq(sent_in_source_loc));
}
} else {
as->mov(x86::ptr(x86::rbp, target_loc), x86::gpq(sent_in_source_loc));
}
}
}
void translateYieldInitial(Environ* env, const Instruction* instr) {
asmjit::x86::Builder* as = env->as;
// Load tstate into RSI for call to JITRT_MakeGenObject*.
// TODO(jbower) Avoid reloading tstate in from memory if it was already in a
// register before spilling. Still needs to be in memory though so it can be
// recovered after calling JITRT_MakeGenObject* which will trash it.
int tstate_loc = instr->getInput(0)->getPhyRegOrStackSlot();
JIT_CHECK(tstate_loc < 0, "__asm_tstate should be spilled");
as->mov(x86::rsi, x86::ptr(x86::rbp, tstate_loc));
// Make a generator object to be returned by the epilogue.
as->lea(x86::rdi, x86::ptr(env->gen_resume_entry_label));
JIT_CHECK(env->spill_size % kPointerSize == 0, "Bad spill alignment");
as->mov(x86::rdx, (env->spill_size / kPointerSize) + 1);
as->mov(x86::rcx, reinterpret_cast<uint64_t>(env->code_rt));
JIT_CHECK(instr->origin()->IsInitialYield(), "expected InitialYield");
PyCodeObject* code = static_cast<const hir::InitialYield*>(instr->origin())
->frameState()
->code;
as->mov(x86::r8, reinterpret_cast<uint64_t>(code));
if (code->co_flags & CO_COROUTINE) {
emitCall(*env, reinterpret_cast<uint64_t>(JITRT_MakeGenObjectCoro), instr);
} else if (code->co_flags & CO_ASYNC_GENERATOR) {
emitCall(
*env, reinterpret_cast<uint64_t>(JITRT_MakeGenObjectAsyncGen), instr);
} else {
emitCall(*env, reinterpret_cast<uint64_t>(JITRT_MakeGenObject), instr);
}
// Resulting generator is now in RAX for filling in below and epilogue return.
const auto gen_reg = x86::rax;
// Exit early if return from JITRT_MakeGenObject was NULL.
as->test(gen_reg, gen_reg);
as->jz(env->hard_exit_label);
// Set RDI to gen->gi_jit_data for use in emitStoreGenYieldPoint() and data
// copy using 'movsq' below.
auto gi_jit_data_offset = offsetof(PyGenObject, gi_jit_data);
as->mov(x86::rdi, x86::ptr(gen_reg, gi_jit_data_offset));
// Arbitrary scratch register for use in emitStoreGenYieldPoint().
auto scratch_r = x86::r9;
asmjit::Label resume_label = as->newLabel();
emitStoreGenYieldPoint(as, env, instr, resume_label, x86::rdi, scratch_r);
// Store variables spilled by this point to generator.
int frame_size = sizeof(FrameHeader);
as->lea(x86::rsi, x86::ptr(x86::rbp, -frame_size));
as->sub(x86::rdi, frame_size);
int current_spill_bytes = env->initial_yield_spill_size_ - frame_size;
JIT_CHECK(current_spill_bytes % kPointerSize == 0, "Bad spill alignment");
as->mov(x86::rcx, (current_spill_bytes / kPointerSize) + 1);
as->std();
as->rep().movsq();
as->cld();
// Jump to bottom half of epilogue
as->jmp(env->hard_exit_label);
// Resumed execution in this generator begins here
as->bind(resume_label);
// Sent in value is in RSI, and tstate is in RDX from resume entry-point args
emitLoadResumedYieldInputs(as, instr, PhyLocation::RSI, x86::rdx);
}
void translateYieldValue(Environ* env, const Instruction* instr) {
asmjit::x86::Builder* as = env->as;
// Make sure tstate is in RDI for use in epilogue.
int tstate_loc = instr->getInput(0)->getPhyRegOrStackSlot();
JIT_CHECK(tstate_loc < 0, "__asm_tstate should be spilled");
as->mov(x86::rdi, x86::ptr(x86::rbp, tstate_loc));
// Value to send goes to RAX so it can be yielded (returned) by epilogue.
int value_out_loc = instr->getInput(1)->getPhyRegOrStackSlot();
JIT_CHECK(value_out_loc < 0, "value to send out should be spilled");
as->mov(x86::rax, x86::ptr(x86::rbp, value_out_loc));
// Arbitrary scratch register for use in emitStoreGenYieldPoint()
auto scratch_r = x86::r9;
auto resume_label = as->newLabel();
emitStoreGenYieldPoint(as, env, instr, resume_label, x86::rbp, scratch_r);
// Jump to epilogue
as->jmp(env->exit_for_yield_label);
// Resumed execution in this generator begins here
as->bind(resume_label);
// Sent in value is in RSI, and tstate is in RDX from resume entry-point args
emitLoadResumedYieldInputs(as, instr, PhyLocation::RSI, x86::rdx);
}
void translateYieldFrom(Environ* env, const Instruction* instr) {
asmjit::x86::Builder* as = env->as;
bool skip_initial_send = instr->isYieldFromSkipInitialSend();
// Make sure tstate is in RDI for use in epilogue and here.
int tstate_loc = instr->getInput(0)->getPhyRegOrStackSlot();
JIT_CHECK(tstate_loc < 0, "__asm_tstate should be spilled");
auto tstate_phys_reg = x86::rdi;
as->mov(tstate_phys_reg, x86::ptr(x86::rbp, tstate_loc));
// If we're skipping the initial send the send value is actually the first
// value to yield and so needs to go into RAX to be returned. Otherwise,
// put initial send value in RSI, the same location future send values will
// be on resume.
int send_value_loc = instr->getInput(1)->getPhyRegOrStackSlot();
JIT_CHECK(send_value_loc < 0, "value to send out should be spilled");
const auto send_value_phys_reg =
skip_initial_send ? PhyLocation::RAX : PhyLocation::RSI;
as->mov(x86::gpq(send_value_phys_reg), x86::ptr(x86::rbp, send_value_loc));
asmjit::Label yield_label = as->newLabel();
if (skip_initial_send) {
as->jmp(yield_label);
} else {
// Setup call to JITRT_YieldFrom
// Put tstate and the current generator into RDX and RDI respectively, and
// set finish_yield_from (RCX) to 0. This register setup matches that when
// `resume_label` is reached from the resume entry.
as->mov(x86::rdx, tstate_phys_reg);
auto gen_offs = offsetof(GenDataFooter, gen);
as->mov(x86::rdi, x86::ptr(x86::rbp, gen_offs));
as->xor_(x86::rcx, x86::rcx);
}
// Resumed execution begins here
auto resume_label = as->newLabel();
as->bind(resume_label);
// Save tstate from resume to callee-saved reigster.
as->mov(x86::rbx, x86::rdx);
// 'send_value', and 'finish_yield_from' will already be in RSI and RCX
// respectively, either from code above on initial start or from resume entry
// point args.
// Load sub-iterator into RDI
int iter_loc = instr->getInput(2)->getPhyRegOrStackSlot();
JIT_CHECK(iter_loc < 0, "Iter should be spilled");
as->mov(x86::rdi, x86::ptr(x86::rbp, iter_loc));
emitCall(*env, reinterpret_cast<uint64_t>(JITRT_YieldFrom), instr);
// Yielded or final result value now in RAX. If the result was NULL then
// done will be set so we'll correctly jump to the following CheckExc.
const auto yf_result_phys_reg = PhyLocation::RAX;
const auto done_r = x86::rdx;
// Restore tstate from callee-saved register.
as->mov(tstate_phys_reg, x86::rbx);
// If not done, jump to epilogue which will yield/return the value from
// JITRT_YieldFrom in RAX.
as->test(done_r, done_r);
asmjit::Label done_label = as->newLabel();
as->jnz(done_label);
as->bind(yield_label);
// Arbitrary scratch register for use in emitStoreGenYieldPoint()
auto scratch_r = x86::r9;
emitStoreGenYieldPoint(as, env, instr, resume_label, x86::rbp, scratch_r);
as->jmp(env->exit_for_yield_label);
as->bind(done_label);
emitLoadResumedYieldInputs(as, instr, yf_result_phys_reg, tstate_phys_reg);
}
// ***********************************************************************
// The following templates and macros implement the auto generation table.
// The generator table defines a hash table, whose key is instruction type,
// and value is another hash table mapping instruction operand pattern and
// a function carrying out certain Actions for the instruction with the
// operand pattern.
// The list of Actions are encoded in the template class RuleActions as its
// template arguments. Currently, there are two types of Actions:
// * AsmAction - generate an asm instruction
// * CallAction - call a user defined instruction
// The Action classes are also templates, whose argument lists encode the
// parameters for the Action. For example, an AsmAction's argument list has
// the assembly instruction mnemonic and its operands.
// ***********************************************************************
template <int N>
const OperandBase* LIROperandMapper(const Instruction* instr) {
auto num_outputs = instr->getNumOutputs();
if (N < num_outputs) {
return instr->output();
} else {
return instr->getInput(N - num_outputs);
}
}
template <int N>
int LIROperandSizeMapper(const Instruction* instr) {
auto size_type = InstrProperty::getProperties(instr).opnd_size_type;
switch (size_type) {
case kDefault:
return LIROperandMapper<N>(instr)->sizeInBits();
case kAlways64:
return 64;
case kOut:
return LIROperandMapper<0>(instr)->sizeInBits();
}
JIT_CHECK(false, "Unknown size type");
}
template <int N>
struct ImmOperand {
using asmjit_type = const asmjit::Imm&;
static asmjit::Imm GetAsmOperand(Environ*, const Instruction* instr) {
return asmjit::Imm(LIROperandMapper<N>(instr)->getConstant());
}
};
template <typename T>
struct ImmOperandNegate {
using asmjit_type = const asmjit::Imm&;
static asmjit::Imm GetAsmOperand(Environ* env, const Instruction* instr) {
return asmjit::Imm(-T::GetAsmOperand(env, instr).i64());
}
};
template <typename T>
struct ImmOperandInvert {
using asmjit_type = const asmjit::Imm&;
static asmjit::Imm GetAsmOperand(Environ* env, const Instruction* instr) {
return asmjit::Imm(~T::GetAsmOperand(env, instr).u64());
}
};
template <int N>
struct RegOperand {
using asmjit_type = const asmjit::x86::Gp&;
static asmjit::x86::Gp GetAsmOperand(Environ*, const Instruction* instr) {
auto size = LIROperandSizeMapper<N>(instr);
switch (size) {
case 8:
return asmjit::x86::gpb(LIROperandMapper<N>(instr)->getPhyRegister());
case 16:
return asmjit::x86::gpw(LIROperandMapper<N>(instr)->getPhyRegister());
case 32:
return asmjit::x86::gpd(LIROperandMapper<N>(instr)->getPhyRegister());
case 64:
return asmjit::x86::gpq(LIROperandMapper<N>(instr)->getPhyRegister());
}
JIT_CHECK(false, "Incorrect operand size.");
}
};
template <int N>
struct XmmOperand {
using asmjit_type = const asmjit::x86::Xmm&;
static asmjit::x86::Xmm GetAsmOperand(Environ*, const Instruction* instr) {
return asmjit::x86::xmm(
LIROperandMapper<N>(instr)->getPhyRegister() -
PhyLocation::XMM_REG_BASE);
}
};
#define OP(v) \
typename std::conditional_t< \
pattern[v] == 'i', \
ImmOperand<v>, \
std::conditional_t< \
(pattern[v] == 'x' || pattern[v] == 'X'), \
XmmOperand<v>, \
RegOperand<v>>>
asmjit::x86::Mem AsmIndirectOperandBuilder(const OperandBase* operand) {
JIT_DCHECK(operand->isInd(), "operand should be an indirect reference");
auto indirect = operand->getMemoryIndirect();
OperandBase* base = indirect->getBaseRegOperand();
OperandBase* index = indirect->getIndexRegOperand();
if (index == nullptr) {
return asmjit::x86::ptr(
x86::gpq(base->getPhyRegister()), indirect->getOffset());
} else {
return asmjit::x86::ptr(
x86::gpq(base->getPhyRegister()),
x86::gpq(index->getPhyRegister()),
indirect->getMultipiler(),
indirect->getOffset());
}
}
template <int N>
struct MemOperand {
using asmjit_type = const asmjit::x86::Mem&;
static asmjit::x86::Mem GetAsmOperand(Environ*, const Instruction* instr) {
const OperandBase* operand = LIROperandMapper<N>(instr);
auto size = LIROperandSizeMapper<N>(instr) / 8;
asmjit::x86::Mem memptr;
if (operand->isStack()) {
memptr = asmjit::x86::ptr(asmjit::x86::rbp, operand->getStackSlot());
} else if (operand->isMem()) {
memptr = asmjit::x86::ptr(
reinterpret_cast<uint64_t>(operand->getMemoryAddress()));
} else if (operand->isInd()) {
memptr = AsmIndirectOperandBuilder(operand);
} else {
JIT_CHECK(false, "Unsupported operand type.");
}
memptr.setSize(size);
return memptr;
}
};
#define MEM(m) MemOperand<m>
#define STK(v) MemOperand<v>
template <int N>
struct LabelOperand {
using asmjit_type = const asmjit::Label&;
static asmjit::Label GetAsmOperand(Environ* env, const Instruction* instr) {
auto block = LIROperandMapper<N>(instr)->getBasicBlock();
return map_get(env->block_label_map, block);
}
};
#define LBL(v) LabelOperand<v>
template <typename... Args>
struct OperandList;
template <typename FuncType, FuncType func, typename OpndList>
struct AsmAction;
template <typename FuncType, FuncType func, typename... OpndTypes>
struct AsmAction<FuncType, func, OperandList<OpndTypes...>> {
static void eval(Environ* env, const Instruction* instr) {
(env->as->*func)(OpndTypes::GetAsmOperand(env, instr)...);
}
};
template <typename... Args>
struct AsminstructionType {
using type =
asmjit::Error (asmjit::x86::EmitterExplicitT<asmjit::x86::Builder>::*)(
typename Args::asmjit_type...);
};
template <void (*func)(Environ*, const Instruction*)>
struct CallAction {
static void eval(Environ* env, const Instruction* instr) {
func(env, instr);
}
};
template <typename... Actions>
struct RuleActions;
template <typename AAction, typename... Actions>
struct RuleActions<AAction, Actions...> {
static void eval(Environ* env, const Instruction* instr) {
AAction::eval(env, instr);
RuleActions<Actions...>::eval(env, instr);
}
};
template <>
struct RuleActions<> {
static void eval(Environ*, const Instruction*) {}
};
struct AddIPToBCMappingAction {
static void eval(Environ* env, const Instruction* instr) {
asmjit::Label label = env->as->newLabel();
env->as->bind(label);
env->addIPToBCMapping(label, instr);
}
};
} // namespace
#define ASM(instr, args...) \
AsmAction< \
typename AsminstructionType<args>::type, \
&asmjit::x86::Builder::instr, \
OperandList<args>>
#define CALL(func) CallAction<func>
#define ADDIPMAPPING() AddIPToBCMappingAction
#define BEGIN_RULE_TABLE void AutoTranslator::initTable() {
#define END_RULE_TABLE }
#define BEGIN_RULES(__t) \
{ \
auto& __rules = instr_rule_map_ \
.emplace( \
std::piecewise_construct, \
std::forward_as_tuple(__t), \
std::forward_as_tuple()) \
.first->second;
#define END_RULES }
#define GEN(s, actions...) \
{ \
UNUSED constexpr char pattern[] = s; \
using rule_actions = RuleActions<actions>; \
auto gen = [](Environ* env, const Instruction* instr) { \
rule_actions::eval(env, instr); \
}; \
__rules = addPattern(std::move(__rules), s, gen); \
}
// ***********************************************************************
// Definition of Auto Generation Table
// The table consisting of multiple rules, and the rules for the same LIR
// instruction are grouped by BEGIN_RULES(LIR instruction type) and
// END_RULES.
// GEN defines a rule for a certain operand pattern of the LIR instruction,
// and maps it to a list of actions:
// GEN(<operand pattern>, action1, action2, ...)
//
// TODO(tiansi): define macros for the operand pattern to make it more readable.
// The operand pattern is defined by a string, and each character in the string
// correpsonds to an operand of the instruction. The character can be one
// of the following:
// * 'R' - general purpose register operand output
// * 'r' - general purpose register operand input
// * 'X' - XMM floating-point register operand output
// * 'x' - XMM floating-point register operand input
// * 'i' - immediate operand input
// * 'M' - memory stack operand output
// * 'm' - memory stack operand input
// Wildcards "?" and "*" can also be used in patterns, where "?" represents any
// one of the types listed above and "*" represents one or more above types.
// Please note that while "?" can appear anywhere in a pattern, "*" can only be
// used at the end of a pattern.
// The actions can be ASM and CALL, meaning generating an assembly instruction
// and call a user-defined function, respectively. The first argument of ASM
// action is the mnemonic of the instruction to be generated, and the following
// arguments are the operands to the instruction. Currently, we have four types
// of assembly instruction operands:
// * OP - either an immediate operand or register oeprand
// * STK - a memory stack location [RBP - ?]
// * LBL - a label to a basic block
// * MEM - a memory operand. The size of the memory operand will be set to the
// size of the LIR instruction operand specified by the first argument
// of MEM.
// The assembly instruction operands are constructed from one or more LIR
// instruction operands. To specify the LIR operands, we use indices
// of the pattern string. For example:
// GEN("Rri", ASM(mov, OP(0), MEM(0, 1, 2)))
// means generating a mov instruction, whose first operand is a
// register/immediate operand, constructed from the only output of the LIR
// instruction, and the second operand is memory operand, constructed from the
// register input and the immediate input of the LIR instruction. The size of
// the memory operand is set to the size of the output of the LIR instruction.
// ***********************************************************************
// clang-format off
BEGIN_RULE_TABLE
BEGIN_RULES(Instruction::kLea)
GEN("Rm", ASM(lea, OP(0), MEM(1)))
END_RULES
BEGIN_RULES(Instruction::kCall)
GEN("Ri", ASM(call, OP(1)), ADDIPMAPPING())
GEN("Rr", ASM(call, OP(1)), ADDIPMAPPING())
GEN("i", ASM(call, OP(0)), ADDIPMAPPING())
GEN("r", ASM(call, OP(0)), ADDIPMAPPING())
GEN("m", ASM(call, STK(0)), ADDIPMAPPING())
END_RULES
BEGIN_RULES(Instruction::kMove)
GEN("Rr", ASM(mov, OP(0), OP(1)))
GEN("Ri", ASM(mov, OP(0), OP(1)))
GEN("Rm", ASM(mov, OP(0), MEM(1)))
GEN("Mr", ASM(mov, MEM(0), OP(1)))
GEN("Mi", ASM(mov, MEM(0), OP(1)))
GEN("Xx", ASM(movsd, OP(0), OP(1)))
GEN("Xm", ASM(movsd, OP(0), MEM(1)))
GEN("Mx", ASM(movsd, MEM(0), OP(1)))
GEN("Xr", ASM(movq, OP(0), OP(1)))
GEN("Rx", ASM(movq, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kGuard)
GEN(ANY, CALL(TranslateGuard));
END_RULES
BEGIN_RULES(Instruction::kDeoptPatchpoint)
GEN(ANY, CALL(TranslateDeoptPatchpoint));
END_RULES
BEGIN_RULES(Instruction::kNegate)
GEN("r", ASM(neg, OP(0)))
GEN("Ri", ASM(mov, OP(0), ImmOperandNegate<OP(1)>))
GEN("Rr", ASM(mov, OP(0), OP(1)), ASM(neg, OP(0)))
GEN("Rm", ASM(mov, OP(0), STK(1)), ASM(neg, OP(0)))
END_RULES
BEGIN_RULES(Instruction::kInvert)
GEN("Ri", ASM(mov, OP(0), ImmOperandInvert<OP(1)>))
GEN("Rr", ASM(mov, OP(0), OP(1)), ASM(not_, OP(0)))
GEN("Rm", ASM(mov, OP(0), STK(1)), ASM(not_, OP(0)))
END_RULES
BEGIN_RULES(Instruction::kMovZX)
GEN("Rr", ASM(movzx, OP(0), OP(1)))
GEN("Rm", ASM(movzx, OP(0), STK(1)))
END_RULES
BEGIN_RULES(Instruction::kMovSX)
GEN("Rr", ASM(movsx, OP(0), OP(1)))
GEN("Rm", ASM(movsx, OP(0), STK(1)))
END_RULES
BEGIN_RULES(Instruction::kMovSXD)
GEN("Rr", ASM(movsxd, OP(0), OP(1)))
GEN("Rm", ASM(movsxd, OP(0), STK(1)))
END_RULES
#define DEF_BINARY_OP_RULES(name, instr) \
BEGIN_RULES(Instruction::name) \
GEN("ri", ASM(instr, OP(0), OP(1))) \
GEN("rr", ASM(instr, OP(0), OP(1))) \
GEN("rm", ASM(instr, OP(0), STK(1))) \
GEN("Rri", ASM(mov, OP(0), OP(1)), ASM(instr, OP(0), OP(2))) \
GEN("Rrr", ASM(mov, OP(0), OP(1)), ASM(instr, OP(0), OP(2))) \
GEN("Rrm", ASM(mov, OP(0), OP(1)), ASM(instr, OP(0), STK(2))) \
END_RULES
DEF_BINARY_OP_RULES(kAdd, add)
DEF_BINARY_OP_RULES(kSub, sub)
DEF_BINARY_OP_RULES(kAnd, and_)
DEF_BINARY_OP_RULES(kOr, or_)
DEF_BINARY_OP_RULES(kXor, xor_)
DEF_BINARY_OP_RULES(kMul, imul)
BEGIN_RULES(Instruction::kDiv)
GEN("rrr", ASM(idiv, OP(0), OP(1), OP(2)) )
GEN("rrm", ASM(idiv, OP(0), OP(1), STK(2)) )
GEN("rr", ASM(idiv, OP(0), OP(1)) )
GEN("rm", ASM(idiv, OP(0), STK(1)) )
END_RULES
BEGIN_RULES(Instruction::kDivUn)
GEN("rrr", ASM(div, OP(0), OP(1), OP(2)) )
GEN("rrm", ASM(div, OP(0), OP(1), STK(2)) )
GEN("rr", ASM(div, OP(0), OP(1)) )
GEN("rm", ASM(div, OP(0), STK(1)) )
END_RULES
#undef DEF_BINARY_OP_RULES
BEGIN_RULES(Instruction::kFadd)
GEN("Xxx", ASM(movsd, OP(0), OP(1)), ASM(addsd, OP(0), OP(2)))
GEN("xx", ASM(addsd, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kFsub)
GEN("Xxx", ASM(movsd, OP(0), OP(1)), ASM(subsd, OP(0), OP(2)))
GEN("xx", ASM(subsd, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kFmul)
GEN("Xxx", ASM(movsd, OP(0), OP(1)), ASM(mulsd, OP(0), OP(2)))
GEN("xx", ASM(mulsd, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kFdiv)
GEN("Xxx", ASM(movsd, OP(0), OP(1)), ASM(divsd , OP(0), OP(2)))
GEN("xx", ASM(divsd, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kPush)
GEN("r", ASM(push, OP(0)))
GEN("m", ASM(push, STK(0)))
GEN("i", ASM(push, OP(0)))
END_RULES
BEGIN_RULES(Instruction::kPop)
GEN("R", ASM(pop, OP(0)))
GEN("M", ASM(pop, STK(0)))
END_RULES
BEGIN_RULES(Instruction::kCdq)
GEN("Rr", ASM(cdq, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kCwd)
GEN("Rr", ASM(cwd, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kCqo)
GEN("Rr", ASM(cqo, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kExchange)
GEN("Rr", ASM(xchg, OP(0), OP(1)))
GEN("Xx", ASM(pxor, OP(0), OP(1)),
ASM(pxor, OP(1), OP(0)),
ASM(pxor, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kCmp)
GEN("rr", ASM(cmp, OP(0), OP(1)))
GEN("ri", ASM(cmp, OP(0), OP(1)))
GEN("xx", ASM(comisd, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kTest)
GEN("rr", ASM(test, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kBranch)
GEN("b", ASM(jmp, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchZ)
GEN("b", ASM(jz, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchNZ)
GEN("b", ASM(jnz, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchA)
GEN("b", ASM(ja, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchB)
GEN("b", ASM(jb, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchAE)
GEN("b", ASM(jae, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchBE)
GEN("b", ASM(jbe, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchG)
GEN("b", ASM(jg, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchL)
GEN("b", ASM(jl, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchGE)
GEN("b", ASM(jge, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchLE)
GEN("b", ASM(jle, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchC)
GEN("b", ASM(jc, LBL(0)))
END_RULES
BEGIN_RULES(Instruction::kBranchNC)
GEN("b", ASM(jnc, LBL(0)))
END_RULES
#define DEF_COMPARE_OP_RULES(name, fpcomp) \
BEGIN_RULES(Instruction::name) \
GEN("Rrr", CALL(TranslateCompare)) \
GEN("Rri", CALL(TranslateCompare)) \
if (fpcomp) { \
GEN("Rxx", CALL(TranslateCompare)) \
} \
END_RULES
DEF_COMPARE_OP_RULES(kEqual, true)
DEF_COMPARE_OP_RULES(kNotEqual, true)
DEF_COMPARE_OP_RULES(kGreaterThanUnsigned, true)
DEF_COMPARE_OP_RULES(kGreaterThanEqualUnsigned, true)
DEF_COMPARE_OP_RULES(kLessThanUnsigned, true)
DEF_COMPARE_OP_RULES(kLessThanEqualUnsigned, true)
DEF_COMPARE_OP_RULES(kGreaterThanSigned, false)
DEF_COMPARE_OP_RULES(kGreaterThanEqualSigned, false)
DEF_COMPARE_OP_RULES(kLessThanSigned, false)
DEF_COMPARE_OP_RULES(kLessThanEqualSigned, false)
#undef DEF_COMPARE_OP_RULES
BEGIN_RULES(Instruction::kInc)
GEN("r", ASM(inc, OP(0)))
GEN("m", ASM(inc, STK(0)))
END_RULES
BEGIN_RULES(Instruction::kDec)
GEN("r", ASM(dec, OP(0)))
GEN("m", ASM(dec, STK(0)))
END_RULES
BEGIN_RULES(Instruction::kBitTest)
GEN("ri", ASM(bt, OP(0), OP(1)))
END_RULES
BEGIN_RULES(Instruction::kYieldInitial)
GEN(ANY, CALL(translateYieldInitial))
END_RULES
BEGIN_RULES(Instruction::kYieldFrom)
GEN(ANY, CALL(translateYieldFrom))
END_RULES
BEGIN_RULES(Instruction::kYieldFromSkipInitialSend)
GEN(ANY, CALL(translateYieldFrom))
END_RULES
BEGIN_RULES(Instruction::kYieldValue)
GEN(ANY, CALL(translateYieldValue))
END_RULES
END_RULE_TABLE
// clang-format on
} // namespace autogen
} // namespace codegen
} // namespace jit