Files
asmjit-asmjit/asmjit/core/emithelper.cpp
T
kobalicek b56f4176cb Codebase update and improvements, instruction DB update
* Denested src folder to root, renamed testing to asmjit-testing

  * Refactored how headers are included into <asmjit/...> form. This
    is necessary as compilers would never simplify a path once a ..
    appears in include directory - then paths such as ../core/../core
    appeared in asserts, which was ugly

  * Moved support utilities into asmjit/support/... (still included
    by asmjit/core.h for convenience and compatibility)

  * Added CMakePresets.json for making it easy to develop AsmJit

  * Reworked CMakeLists to be shorter and use CMake option(),
    etc... This simplifies it and makes it using more standard
    features

  * ASMJIT_EMBED now creates asmjit_embed INTERFACE library,
    which is accessible via asmjit::asmjit target - this simplifies
    embedding and makes it the same as library targets from a CMake
    perspective

  * Removed ASMJIT_DEPS - this is now provided by cmake target
    aliases - 'asmjit::asmjit' so users should not need this variable

  * Changed meaning of ASMJIT_LIBS - this now contains only AsmJit
    dependencies without asmjit::asmjit target alias. Don't rely on
    ASMJIT_LIBS anymore as it's only used internally

  * Removed ASMJIT_NO_DEPRECATED option - AsmJit is not going
    to provide controllable deprecations in the future

  * Removed ASMJIT_NO_VALIDATION in favor of ASMJIT_NO_INTROSPECTION,
    which now controls query, features, and validation API presence

  * Removed ASMJIT_DIR option - it was never really needed

  * Removed AMX_TRANSPOSE feature from instruction database (X86).
    Intel has removed it as well, so it's a feature that won't
    be siliconized
2025-11-02 22:31:46 +01:00

366 lines
11 KiB
C++

// This file is part of AsmJit project <https://asmjit.com>
//
// See <asmjit/core.h> or LICENSE.md for license and copyright information
// SPDX-License-Identifier: Zlib
#include <asmjit/core/api-build_p.h>
#include <asmjit/core/archtraits.h>
#include <asmjit/core/emithelper_p.h>
#include <asmjit/core/formatter.h>
#include <asmjit/core/funcargscontext_p.h>
#include <asmjit/core/radefs_p.h>
// Can be used for debugging...
// #define ASMJIT_DUMP_ARGS_ASSIGNMENT
ASMJIT_BEGIN_NAMESPACE
// BaseEmitHelper - Formatting
// ===========================
#ifdef ASMJIT_DUMP_ARGS_ASSIGNMENT
static void dump_func_value(String& sb, Arch arch, const FuncValue& value) noexcept {
Formatter::format_type_id(sb, value.type_id());
sb.append('@');
if (value.is_indirect()) {
sb.append('[');
}
if (value.is_reg()) {
Formatter::format_register(sb, 0, nullptr, arch, value.reg_type(), value.reg_id());
}
else if (value.is_stack()) {
sb.append_format("[%d]", value.stack_offset());
}
else {
sb.append("<none>");
}
if (value.is_indirect()) {
sb.append(']');
}
}
static void dump_assignment(String& sb, const FuncArgsContext& ctx) noexcept {
using Var = FuncArgsContext::Var;
Arch arch = ctx.arch();
uint32_t var_count = ctx.var_count();
for (uint32_t i = 0; i < var_count; i++) {
const Var& var = ctx.var(i);
const FuncValue& dst = var.out;
const FuncValue& cur = var.cur;
sb.append_format("Var%u: ", i);
dump_func_value(sb, arch, dst);
sb.append(" <- ");
dump_func_value(sb, arch, cur);
if (var.is_done()) {
sb.append(" {Done}");
}
sb.append('\n');
}
}
#endif
// BaseEmitHelper - Abstract
// =========================
Error BaseEmitHelper::emit_reg_move(const Operand_& dst_, const Operand_& src_, TypeId type_id, const char* comment) {
Support::maybe_unused(dst_, src_, type_id, comment);
return make_error(Error::kInvalidState);
}
Error BaseEmitHelper::emit_reg_swap(const Reg& a, const Reg& b, const char* comment) {
Support::maybe_unused(a, b, comment);
return make_error(Error::kInvalidState);
}
Error BaseEmitHelper::emit_arg_move(const Reg& dst_, TypeId dst_type_id, const Operand_& src_, TypeId src_type_id, const char* comment) {
Support::maybe_unused(dst_, dst_type_id, src_, src_type_id, comment);
return make_error(Error::kInvalidState);
}
// BaseEmitHelper - EmitArgsAssignment
// ===================================
ASMJIT_FAVOR_SIZE Error BaseEmitHelper::emit_args_assignment(const FuncFrame& frame, const FuncArgsAssignment& args) {
using Var = FuncArgsContext::Var;
using WorkData = FuncArgsContext::WorkData;
enum WorkFlags : uint32_t {
kWorkNone = 0x00,
kWorkDidSome = 0x01,
kWorkPending = 0x02,
kWorkPostponed = 0x04
};
Arch arch = frame.arch();
const ArchTraits& arch_traits = ArchTraits::by_arch(arch);
RAConstraints constraints;
FuncArgsContext ctx;
ASMJIT_PROPAGATE(constraints.init(arch));
ASMJIT_PROPAGATE(ctx.init_work_data(frame, args, &constraints));
#ifdef ASMJIT_DUMP_ARGS_ASSIGNMENT
{
String sb;
dump_assignment(sb, ctx);
printf("%s\n", sb.data());
}
#endif
auto& work_data = ctx._work_data;
uint32_t var_count = ctx._var_count;
uint32_t sa_var_id = ctx._sa_var_id;
Reg sp = Reg(_emitter->_gp_signature, arch_traits.sp_reg_id());
Reg sa = sp;
if (frame.has_dynamic_alignment()) {
if (frame.has_preserved_fp()) {
sa.set_id(arch_traits.fp_reg_id());
}
else {
sa.set_id(sa_var_id < var_count ? ctx._vars[sa_var_id].cur.reg_id() : frame.sa_reg_id());
}
}
// Register to stack and stack to stack moves must be first as now we have
// the biggest chance of having as many as possible unassigned registers.
if (ctx._stack_dst_mask) {
// Base address of all arguments passed by stack.
BaseMem base_arg_ptr(sa, int32_t(frame.sa_offset(sa.id())));
BaseMem base_stack_ptr(sp, 0);
for (uint32_t var_id = 0; var_id < var_count; var_id++) {
Var& var = ctx._vars[var_id];
if (!var.out.is_stack()) {
continue;
}
FuncValue& cur = var.cur;
FuncValue& out = var.out;
ASMJIT_ASSERT(cur.is_reg() || cur.is_stack());
Reg reg;
BaseMem dst_stack_ptr = base_stack_ptr.clone_adjusted(out.stack_offset());
BaseMem src_stack_ptr = base_arg_ptr.clone_adjusted(cur.stack_offset());
if (cur.is_indirect()) {
if (cur.is_stack()) {
// TODO: Indirect stack.
return make_error(Error::kInvalidAssignment);
}
else {
src_stack_ptr.set_base_id(cur.reg_id());
}
}
if (cur.is_reg() && !cur.is_indirect()) {
WorkData& wd = work_data[RegUtils::group_of(cur.reg_type())];
uint32_t reg_id = cur.reg_id();
reg.set_signature_and_id(RegUtils::signature_of(cur.reg_type()), reg_id);
wd.unassign(var_id, reg_id);
}
else {
// Stack to reg move - tricky since we move stack to stack we can decide which register to use. In general
// we follow the rule that IntToInt moves will use GP regs with possibility to signature or zero extend,
// and all other moves will either use GP or VEC regs depending on the size of the move.
OperandSignature signature = get_suitable_reg_for_mem_to_mem_move(arch, out.type_id(), cur.type_id());
if (ASMJIT_UNLIKELY(!signature.is_valid())) {
return make_error(Error::kInvalidState);
}
WorkData& wd = work_data[signature.reg_group()];
RegMask available_regs = wd.available_regs();
if (ASMJIT_UNLIKELY(!available_regs)) {
return make_error(Error::kInvalidState);
}
uint32_t available_id = Support::ctz(available_regs);
reg.set_signature_and_id(signature, available_id);
ASMJIT_PROPAGATE(emit_arg_move(reg, out.type_id(), src_stack_ptr, cur.type_id()));
}
if (cur.is_indirect() && cur.is_reg()) {
work_data[RegGroup::kGp].unassign(var_id, cur.reg_id());
}
// Register to stack move.
ASMJIT_PROPAGATE(emit_reg_move(dst_stack_ptr, reg, cur.type_id()));
var.mark_done();
}
}
// Shuffle all registers that are currently assigned accordingly to target assignment.
uint32_t work_flags = kWorkNone;
for (;;) {
for (uint32_t var_id = 0; var_id < var_count; var_id++) {
Var& var = ctx._vars[var_id];
if (var.is_done() || !var.cur.is_reg()) {
continue;
}
FuncValue& cur = var.cur;
FuncValue& out = var.out;
RegGroup cur_group = RegUtils::group_of(cur.reg_type());
RegGroup out_group = RegUtils::group_of(out.reg_type());
uint32_t cur_id = cur.reg_id();
uint32_t out_id = out.reg_id();
if (cur_group != out_group) {
// TODO: Conversion is not supported.
return make_error(Error::kInvalidAssignment);
}
else {
WorkData& wd = work_data[out_group];
if (!wd.is_assigned(out_id) || cur_id == out_id) {
EmitMove:
ASMJIT_PROPAGATE(
emit_arg_move(
Reg(RegUtils::signature_of(out.reg_type()), out_id), out.type_id(),
Reg(RegUtils::signature_of(cur.reg_type()), cur_id), cur.type_id()));
// Only reassign if this is not a sign/zero extension that happens on the same in/out register.
if (cur_id != out_id) {
wd.reassign(var_id, out_id, cur_id);
}
cur.init_reg(out.reg_type(), out_id, out.type_id());
if (out_id == out.reg_id()) {
var.mark_done();
}
work_flags |= kWorkDidSome | kWorkPending;
}
else {
uint32_t alt_id = wd._phys_to_var_id[out_id];
Var& alt_var = ctx._vars[alt_id];
if (!alt_var.out.is_initialized() || (alt_var.out.is_reg() && alt_var.out.reg_id() == cur_id)) {
// Only few architectures provide swap operations, and only for few register groups.
if (arch_traits.has_inst_reg_swap(cur_group)) {
RegType highest_type = Support::max(cur.reg_type(), alt_var.cur.reg_type());
if (Support::is_between(highest_type, RegType::kGp8Lo, RegType::kGp16)) {
highest_type = RegType::kGp32;
}
OperandSignature signature = RegUtils::signature_of(highest_type);
ASMJIT_PROPAGATE(emit_reg_swap(Reg(signature, out_id), Reg(signature, cur_id)));
wd.swap(var_id, cur_id, alt_id, out_id);
cur.set_reg_id(out_id);
var.mark_done();
alt_var.cur.set_reg_id(cur_id);
if (alt_var.out.is_initialized()) {
alt_var.mark_done();
}
work_flags |= kWorkDidSome;
}
else {
// If there is a scratch register it can be used to perform the swap.
RegMask available_regs = wd.available_regs();
if (available_regs) {
RegMask in_out_regs = wd.dst_regs();
if (available_regs & ~in_out_regs) {
available_regs &= ~in_out_regs;
}
out_id = Support::ctz(available_regs);
goto EmitMove;
}
else {
work_flags |= kWorkPending;
}
}
}
else {
work_flags |= kWorkPending;
}
}
}
}
if (!(work_flags & kWorkPending)) {
break;
}
// If we did nothing twice it means that something is really broken.
if ((work_flags & (kWorkDidSome | kWorkPostponed)) == kWorkPostponed) {
return make_error(Error::kInvalidState);
}
work_flags = (work_flags & kWorkDidSome) ? kWorkNone : kWorkPostponed;
}
// Load arguments passed by stack into registers. This is pretty simple and
// it never requires multiple iterations like the previous phase.
if (ctx._has_stack_src) {
uint32_t iter_count = 1;
if (frame.has_dynamic_alignment() && !frame.has_preserved_fp()) {
sa.set_id(sa_var_id < var_count ? ctx._vars[sa_var_id].cur.reg_id() : frame.sa_reg_id());
}
// Base address of all arguments passed by stack.
BaseMem base_arg_ptr(sa, int32_t(frame.sa_offset(sa.id())));
for (uint32_t iter = 0; iter < iter_count; iter++) {
for (uint32_t var_id = 0; var_id < var_count; var_id++) {
Var& var = ctx._vars[var_id];
if (var.is_done()) {
continue;
}
if (var.cur.is_stack()) {
ASMJIT_ASSERT(var.out.is_reg());
uint32_t out_id = var.out.reg_id();
RegType out_type = var.out.reg_type();
RegGroup group = RegUtils::group_of(out_type);
WorkData& wd = work_data[group];
if (out_id == sa.id() && group == RegGroup::kGp) {
// This register will be processed last as we still need `sa_reg_id`.
if (iter_count == 1) {
iter_count++;
continue;
}
wd.unassign(wd._phys_to_var_id[out_id], out_id);
}
Reg dst_reg = Reg(RegUtils::signature_of(out_type), out_id);
BaseMem src_mem = base_arg_ptr.clone_adjusted(var.cur.stack_offset());
ASMJIT_PROPAGATE(emit_arg_move(
dst_reg, var.out.type_id(),
src_mem, var.cur.type_id()));
wd.assign(var_id, out_id);
var.cur.init_reg(out_type, out_id, var.cur.type_id(), FuncValue::kFlagIsDone);
}
}
}
}
return Error::kOk;
}
ASMJIT_END_NAMESPACE