Files
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

317 lines
12 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/func.h>
#include <asmjit/core/operand.h>
#include <asmjit/core/type.h>
#include <asmjit/core/funcargscontext_p.h>
#if !defined(ASMJIT_NO_X86)
#include <asmjit/x86/x86func_p.h>
#endif
#if !defined(ASMJIT_NO_AARCH64)
#include <asmjit/arm/a64func_p.h>
#endif
ASMJIT_BEGIN_NAMESPACE
// CallConv - Initialization & Reset
// =================================
ASMJIT_FAVOR_SIZE Error CallConv::init(CallConvId call_conv_id, const Environment& environment) noexcept {
reset();
#if !defined(ASMJIT_NO_X86)
if (environment.is_family_x86()) {
return x86::FuncInternal::init_call_conv(*this, call_conv_id, environment);
}
#endif
#if !defined(ASMJIT_NO_AARCH64)
if (environment.is_family_aarch64()) {
return a64::FuncInternal::init_call_conv(*this, call_conv_id, environment);
}
#endif
return make_error(Error::kInvalidArgument);
}
// FuncDetail - Init / Reset
// =========================
ASMJIT_FAVOR_SIZE Error FuncDetail::init(const FuncSignature& signature, const Environment& environment) noexcept {
CallConvId call_conv_id = signature.call_conv_id();
uint32_t arg_count = signature.arg_count();
if (ASMJIT_UNLIKELY(arg_count > Globals::kMaxFuncArgs)) {
return make_error(Error::kInvalidArgument);
}
CallConv& cc = _call_conv;
ASMJIT_PROPAGATE(cc.init(call_conv_id, environment));
uint32_t register_size = Environment::reg_size_of_arch(cc.arch());
uint32_t deabstract_delta = TypeUtils::deabstract_delta_of_size(register_size);
const TypeId* signature_args = signature.args();
for (uint32_t arg_index = 0; arg_index < arg_count; arg_index++) {
FuncValuePack& arg_pack = _args[arg_index];
arg_pack[0].init_type_id(TypeUtils::deabstract(signature_args[arg_index], deabstract_delta));
}
_arg_count = uint8_t(arg_count);
_va_index = uint8_t(signature.va_index());
TypeId ret = signature.ret();
if (ret != TypeId::kVoid) {
_rets[0].init_type_id(TypeUtils::deabstract(ret, deabstract_delta));
}
#if !defined(ASMJIT_NO_X86)
if (environment.is_family_x86()) {
return x86::FuncInternal::init_func_detail(*this, signature, register_size);
}
#endif
#if !defined(ASMJIT_NO_AARCH64)
if (environment.is_family_aarch64()) {
return a64::FuncInternal::init_func_detail(*this, signature);
}
#endif
// We should never bubble here as if `cc.init()` succeeded then there has to be an implementation for the current
// architecture. However, stay safe.
return make_error(Error::kInvalidArgument);
}
// FuncFrame - Init
// ================
ASMJIT_FAVOR_SIZE Error FuncFrame::init(const FuncDetail& func) noexcept {
Arch arch = func.call_conv().arch();
if (!Environment::is_valid_arch(arch)) {
return make_error(Error::kInvalidArch);
}
const ArchTraits& arch_traits = ArchTraits::by_arch(arch);
// Initializing FuncFrame means making a copy of some properties of `func`. Properties like `_local_stack_size` will
// be set by the user before the frame is finalized.
reset();
_arch = arch;
_sp_reg_id = uint8_t(arch_traits.sp_reg_id());
_sa_reg_id = uint8_t(Reg::kIdBad);
uint32_t natural_stack_alignment = func.call_conv().natural_stack_alignment();
uint32_t min_dynamic_alignment = Support::max<uint32_t>(natural_stack_alignment, 16);
if (min_dynamic_alignment == natural_stack_alignment) {
min_dynamic_alignment <<= 1;
}
_natural_stack_alignment = uint8_t(natural_stack_alignment);
_min_dynamic_alignment = uint8_t(min_dynamic_alignment);
_red_zone_size = uint8_t(func.red_zone_size());
_spill_zone_size = uint8_t(func.spill_zone_size());
_final_stack_alignment = uint8_t(_natural_stack_alignment);
if (func.has_flag(CallConvFlags::kCalleePopsStack)) {
_callee_stack_cleanup = uint16_t(func.arg_stack_size());
}
// Initial masks of dirty and preserved registers.
for (RegGroup group : Support::enumerate(RegGroup::kMaxVirt)) {
_dirty_regs[group] = func.used_regs(group);
_preserved_regs[group] = func.preserved_regs(group);
}
// Exclude stack pointer - this register is never included in saved GP regs.
_preserved_regs[RegGroup::kGp] &= ~Support::bit_mask<RegMask>(arch_traits.sp_reg_id());
// The size and alignment of save/restore area of registers for each virtual register group
_save_restore_reg_size = func.call_conv()._save_restore_reg_size;
_save_restore_alignment = func.call_conv()._save_restore_alignment;
return Error::kOk;
}
// FuncFrame - Finalize
// ====================
ASMJIT_FAVOR_SIZE Error FuncFrame::finalize() noexcept {
if (!Environment::is_valid_arch(arch())) {
return make_error(Error::kInvalidArch);
}
const ArchTraits& arch_traits = ArchTraits::by_arch(arch());
uint32_t register_size = _save_restore_reg_size[RegGroup::kGp];
uint32_t vector_size = _save_restore_reg_size[RegGroup::kVec];
uint32_t return_address_size = arch_traits.has_link_reg() ? 0u : register_size;
// The final stack alignment must be updated accordingly to call and local stack alignments.
uint32_t stack_alignment = _final_stack_alignment;
ASMJIT_ASSERT(stack_alignment == Support::max(_natural_stack_alignment, _call_stack_alignment, _local_stack_alignment));
bool has_fp = has_preserved_fp();
bool has_da = has_dynamic_alignment();
uint32_t kSp = arch_traits.sp_reg_id();
uint32_t kFp = arch_traits.fp_reg_id();
uint32_t kLr = arch_traits.link_reg_id();
// Make frame pointer dirty if the function uses it.
if (has_fp) {
_dirty_regs[RegGroup::kGp] |= Support::bit_mask<RegMask>(kFp);
// Currently required by ARM, if this works differently across architectures we would have to generalize most
// likely in CallConv.
if (kLr != Reg::kIdBad) {
_dirty_regs[RegGroup::kGp] |= Support::bit_mask<RegMask>(kLr);
}
}
// These two are identical if the function doesn't align its stack dynamically.
uint32_t sa_reg_id = _sa_reg_id;
if (sa_reg_id == Reg::kIdBad) {
sa_reg_id = kSp;
}
// Fix stack arguments base-register from SP to FP in case it was not picked before and the function performs
// dynamic stack alignment.
if (has_da && sa_reg_id == kSp) {
sa_reg_id = kFp;
}
// Mark as dirty any register but SP if used as SA pointer.
if (sa_reg_id != kSp) {
_dirty_regs[RegGroup::kGp] |= Support::bit_mask<RegMask>(sa_reg_id);
}
_sp_reg_id = uint8_t(kSp);
_sa_reg_id = uint8_t(sa_reg_id);
// Setup stack size used to save preserved registers.
uint32_t save_restore_sizes[2] {};
for (RegGroup group : Support::enumerate(RegGroup::kMaxVirt)) {
save_restore_sizes[size_t(!arch_traits.has_inst_push_pop(group))]
+= Support::align_up(Support::popcnt(saved_regs(group)) * save_restore_reg_size(group), save_restore_alignment(group));
}
_push_pop_save_size = uint16_t(save_restore_sizes[0]);
_extra_reg_save_size = uint16_t(save_restore_sizes[1]);
uint32_t v = 0; // The beginning of the stack frame relative to SP after prolog.
v += call_stack_size(); // Count 'call_stack_size' <- This is used to call functions.
v = Support::align_up(v, stack_alignment); // Align to function's stack alignment.
_local_stack_offset = v; // Store 'local_stack_offset' <- Function's local stack starts here.
v += local_stack_size(); // Count 'local_stack_size' <- Function's local stack ends here.
// If the function's stack must be aligned, calculate the alignment necessary to store vector registers, and set
// `FuncAttributes::kAlignedVecSR` to inform PEI that it can use instructions that perform aligned stores/loads.
if (stack_alignment >= vector_size && _extra_reg_save_size) {
add_attributes(FuncAttributes::kAlignedVecSR);
v = Support::align_up(v, vector_size); // Align 'extra_reg_save_offset'.
}
_extra_reg_save_offset = v; // Store 'extra_reg_save_offset' <- Non-GP save/restore starts here.
v += _extra_reg_save_size; // Count 'extra_reg_save_size' <- Non-GP save/restore ends here.
// Calculate if dynamic alignment (DA) slot (stored as offset relative to SP) is required and its offset.
if (has_da && !has_fp) {
_da_offset = v; // Store 'da_offset' <- DA pointer would be stored here.
v += register_size; // Count 'da_offset'.
}
else {
_da_offset = FuncFrame::kTagInvalidOffset;
}
// Link Register
// -------------
//
// The stack is aligned after the function call as the return address is stored in a link register. Some
// architectures may require to always have aligned stack after PUSH/POP operation, which is represented
// by ArchTraits::hw_stack_alignment().
//
// No Link Register (X86/X64)
// --------------------------
//
// The return address should be stored after GP save/restore regs. It has the same size as `register_size`
// (basically the native register/pointer size). We don't adjust it now as `v` now contains the exact size
// that the function requires to adjust (call frame + stack frame, vec stack size). The stack (if we consider
// this size) is misaligned now, as it's always aligned before the function call - when `call()` is executed
// it pushes the current EIP|RIP onto the stack, and unaligns it by 12 or 8 bytes (depending on the
// architecture). So count number of bytes needed to align it up to the function's CallFrame (the beginning).
if (v || has_func_calls() || !return_address_size) {
v += Support::align_up_diff(v + push_pop_save_size() + return_address_size, stack_alignment);
}
_push_pop_save_offset = v; // Store 'push_pop_save_offset' <- Function's push/pop save/restore starts here.
_stack_adjustment = v; // Store 'stack_adjustment' <- SA used by 'add SP, SA' and 'sub SP, SA'.
v += _push_pop_save_size; // Count 'push_pop_save_size' <- Function's push/pop save/restore ends here.
_final_stack_size = v; // Store 'final_stack_size' <- Final stack used by the function.
if (!arch_traits.has_link_reg()) {
v += register_size; // Count 'ReturnAddress' <- As CALL pushes onto stack.
}
// If the function performs dynamic stack alignment then the stack-adjustment must be aligned.
if (has_da) {
_stack_adjustment = Support::align_up(_stack_adjustment, stack_alignment);
}
// Calculate where the function arguments start relative to SP.
_sa_offset_from_sp = has_da ? FuncFrame::kTagInvalidOffset : v;
// Calculate where the function arguments start relative to FP or user-provided register.
_sa_offset_from_sa = has_fp ? return_address_size + register_size // Return address + frame pointer.
: return_address_size + _push_pop_save_size; // Return address + all push/pop regs.
return Error::kOk;
}
// FuncArgsAssignment - UpdateFuncFrame
// ====================================
ASMJIT_FAVOR_SIZE Error FuncArgsAssignment::update_func_frame(FuncFrame& frame) const noexcept {
Arch arch = frame.arch();
const FuncDetail* func = func_detail();
if (!func) {
return make_error(Error::kInvalidState);
}
RAConstraints constraints;
ASMJIT_PROPAGATE(constraints.init(arch));
FuncArgsContext ctx;
ASMJIT_PROPAGATE(ctx.init_work_data(frame, *this, &constraints));
ASMJIT_PROPAGATE(ctx.mark_dst_regs_dirty(frame));
ASMJIT_PROPAGATE(ctx.mark_scratch_regs(frame));
ASMJIT_PROPAGATE(ctx.mark_stack_args_reg(frame));
return Error::kOk;
}
// Func API - Tests
// ================
#if defined(ASMJIT_TEST)
UNIT(func_signature) {
FuncSignature signature;
signature.set_ret_t<int8_t>();
signature.add_arg_t<int16_t>();
signature.add_arg(TypeId::kInt32);
EXPECT_EQ(signature, FuncSignature::build<int8_t, int16_t, int32_t>());
}
#endif
ASMJIT_END_NAMESPACE