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

372 lines
9.3 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/constpool.h>
#include <asmjit/support/support.h>
ASMJIT_BEGIN_NAMESPACE
// ConstPool - Construction & Destruction
// ======================================
ConstPool::ConstPool(Arena& arena) noexcept : _arena(arena) {
reset();
}
ConstPool::~ConstPool() noexcept {}
// ConstPool - Reset
// =================
void ConstPool::reset() noexcept {
size_t data_size = 1;
for (size_t i = 0; i < ASMJIT_ARRAY_SIZE(_tree); i++) {
_tree[i].reset();
_tree[i].set_data_size(data_size);
_gaps[i] = nullptr;
data_size <<= 1;
}
_gap_pool = nullptr;
_size = 0;
_alignment = 0;
_min_item_size = 0;
}
// ConstPool - Operations
// ======================
static inline ConstPool::Gap* ConstPool_allocGap(ConstPool* self) noexcept {
ConstPool::Gap* gap = self->_gap_pool;
if (!gap) {
return self->_arena.alloc_oneshot<ConstPool::Gap>();
}
self->_gap_pool = gap->_next;
return gap;
}
static inline void ConstPool_freeGap(ConstPool* self, ConstPool::Gap* gap) noexcept {
gap->_next = self->_gap_pool;
self->_gap_pool = gap;
}
static void ConstPool_addGap(ConstPool* self, size_t offset, size_t size) noexcept {
ASMJIT_ASSERT(size > 0);
while (size > 0) {
size_t gap_index;
size_t gap_size;
if (size >= 32 && Support::is_aligned<size_t>(offset, 32)) {
gap_index = ConstPool::kIndex32;
gap_size = 32;
}
else if (size >= 16 && Support::is_aligned<size_t>(offset, 16)) {
gap_index = ConstPool::kIndex16;
gap_size = 16;
}
else if (size >= 8 && Support::is_aligned<size_t>(offset, 8)) {
gap_index = ConstPool::kIndex8;
gap_size = 8;
}
else if (size >= 4 && Support::is_aligned<size_t>(offset, 4)) {
gap_index = ConstPool::kIndex4;
gap_size = 4;
}
else if (size >= 2 && Support::is_aligned<size_t>(offset, 2)) {
gap_index = ConstPool::kIndex2;
gap_size = 2;
}
else {
gap_index = ConstPool::kIndex1;
gap_size = 1;
}
// We don't have to check for errors here, if this failed nothing really happened (just the gap won't be
// visible) and it will fail again at place where the same check would generate \ref Error::kOutOfMemory error.
ConstPool::Gap* gap = ConstPool_allocGap(self);
if (!gap) {
return;
}
gap->_next = self->_gaps[gap_index];
self->_gaps[gap_index] = gap;
gap->_offset = offset;
gap->_size = gap_size;
offset += gap_size;
size -= gap_size;
}
}
Error ConstPool::add(const void* data, size_t size, Out<size_t> offset_out) noexcept {
constexpr size_t kMaxSize = size_t(1) << (kIndexCount - 1);
// Avoid sizes outside of the supported range.
if (ASMJIT_UNLIKELY(size == 0 || size > kMaxSize)) {
return make_error(Error::kInvalidArgument);
}
size_t tree_index = Support::ctz(size);
// Avoid sizes, which are not aligned to power of 2.
if (ASMJIT_UNLIKELY((size_t(1) << tree_index) != size)) {
return make_error(Error::kInvalidArgument);
}
ConstPool::Node* node = _tree[tree_index].get(data);
if (node) {
offset_out = node->_offset;
return Error::kOk;
}
// Before incrementing the current offset try if there is a gap that can be used for the requested data.
size_t offset = ~size_t(0);
size_t gap_index = tree_index;
while (gap_index != kIndexCount - 1) {
ConstPool::Gap* gap = _gaps[tree_index];
// Check if there is a gap.
if (gap) {
size_t gap_offset = gap->_offset;
size_t gap_size = gap->_size;
// Destroy the gap for now.
_gaps[tree_index] = gap->_next;
ConstPool_freeGap(this, gap);
offset = gap_offset;
ASMJIT_ASSERT(Support::is_aligned<size_t>(offset, size));
gap_size -= size;
if (gap_size > 0) {
ConstPool_addGap(this, gap_offset, gap_size);
}
}
gap_index++;
}
if (offset == ~size_t(0)) {
// Get how many bytes have to be skipped so the address is aligned accordingly to the 'size'.
size_t diff = Support::align_up_diff<size_t>(_size, size);
if (diff != 0) {
ConstPool_addGap(this, _size, diff);
_size += diff;
}
offset = _size;
_size += size;
}
// Add the initial node to the right index.
node = ConstPool::Tree::new_node_t(_arena, data, size, offset, false);
if (ASMJIT_UNLIKELY(!node)) {
return make_error(Error::kOutOfMemory);
}
_tree[tree_index].insert(node);
_alignment = Support::max<size_t>(_alignment, size);
offset_out = offset;
// Now create a bunch of shared constants that are based on the data pattern. We stop at size 4,
// it probably doesn't make sense to split constants down to 1 byte.
size_t p_count = 1;
size_t smaller_size = size;
while (smaller_size > 4) {
p_count <<= 1;
smaller_size >>= 1;
ASMJIT_ASSERT(tree_index != 0);
tree_index--;
const uint8_t* data_ptr = static_cast<const uint8_t*>(data);
for (size_t i = 0; i < p_count; i++, data_ptr += smaller_size) {
node = _tree[tree_index].get(data_ptr);
if (node) {
continue;
}
node = ConstPool::Tree::new_node_t(_arena, data_ptr, smaller_size, offset + (i * smaller_size), true);
_tree[tree_index].insert(node);
}
}
_min_item_size = !_min_item_size ? size : Support::min(_min_item_size, size);
return Error::kOk;
}
// ConstPool - Reset
// =================
struct ConstPoolFill {
inline ConstPoolFill(uint8_t* dst, size_t data_size) noexcept :
_dst(dst),
_data_size(data_size) {}
inline void operator()(const ConstPool::Node* node) noexcept {
if (!node->_shared) {
memcpy(_dst + node->_offset, node->data(), _data_size);
}
}
uint8_t* _dst;
size_t _data_size;
};
void ConstPool::fill(void* dst) const noexcept {
// Clears possible gaps, asmjit should never emit garbage to the output.
memset(dst, 0, _size);
ConstPoolFill filler(static_cast<uint8_t*>(dst), 1);
for (size_t i = 0; i < ASMJIT_ARRAY_SIZE(_tree); i++) {
_tree[i].for_each(filler);
filler._data_size <<= 1;
}
}
// ConstPool - Tests
// =================
#if defined(ASMJIT_TEST)
UNIT(const_pool) {
Arena arena(32u * 1024u);
ConstPool pool(arena);
uint32_t i;
uint32_t kCount = BrokenAPI::has_arg("--quick") ? 1000 : 1000000;
INFO("Adding %u constants to the pool", kCount);
{
size_t prev_offset;
size_t cur_offset;
uint64_t c = 0x0101010101010101u;
EXPECT_EQ(pool.add(&c, 8, Out(prev_offset)), Error::kOk);
EXPECT_EQ(prev_offset, 0u);
for (i = 1; i < kCount; i++) {
c++;
EXPECT_EQ(pool.add(&c, 8, Out(cur_offset)), Error::kOk);
EXPECT_EQ(prev_offset + 8, cur_offset);
EXPECT_EQ(pool.size(), (i + 1) * 8);
prev_offset = cur_offset;
}
EXPECT_EQ(pool.alignment(), 8u);
}
INFO("Retrieving %u constants from the pool", kCount);
{
uint64_t c = 0x0101010101010101u;
for (i = 0; i < kCount; i++) {
size_t offset;
EXPECT_EQ(pool.add(&c, 8, Out(offset)), Error::kOk);
EXPECT_EQ(offset, i * 8);
c++;
}
}
INFO("Checking if the constants were split into 4-byte patterns");
{
uint32_t c = 0x01010101u;
size_t offset;
EXPECT_EQ(pool.add(&c, 4, Out(offset)), Error::kOk);
EXPECT_EQ(offset, 0u);
// NOTE: We have to adjust the offset to successfully test this on big endian architectures.
size_t base_offset = size_t(Support::ByteOrder::kNative == Support::ByteOrder::kBE ? 4 : 0);
for (i = 1; i < kCount; i++) {
c++;
EXPECT_EQ(pool.add(&c, 4, Out(offset)), Error::kOk);
EXPECT_EQ(offset, base_offset + i * 8);
}
}
INFO("Adding 2 byte constant to misalign the current offset");
{
uint16_t c = 0xFFFF;
size_t offset;
EXPECT_EQ(pool.add(&c, 2, Out(offset)), Error::kOk);
EXPECT_EQ(offset, kCount * 8);
EXPECT_EQ(pool.alignment(), 8u);
}
INFO("Adding 8 byte constant to check if pool gets aligned again");
{
uint64_t c = 0xFFFFFFFFFFFFFFFFu;
size_t offset;
EXPECT_EQ(pool.add(&c, 8, Out(offset)), Error::kOk);
EXPECT_EQ(offset, kCount * 8 + 8u);
}
INFO("Adding 2 byte constant to verify the gap is filled");
{
uint16_t c = 0xFFFE;
size_t offset;
EXPECT_EQ(pool.add(&c, 2, Out(offset)), Error::kOk);
EXPECT_EQ(offset, kCount * 8 + 2);
EXPECT_EQ(pool.alignment(), 8u);
}
INFO("Checking reset functionality");
{
pool.reset();
arena.reset();
EXPECT_EQ(pool.size(), 0u);
EXPECT_EQ(pool.alignment(), 0u);
}
INFO("Checking pool alignment when combined constants are added");
{
uint8_t bytes[32] = { 0 };
size_t offset;
pool.add(bytes, 1, Out(offset));
EXPECT_EQ(pool.size(), 1u);
EXPECT_EQ(pool.alignment(), 1u);
EXPECT_EQ(offset, 0u);
pool.add(bytes, 2, Out(offset));
EXPECT_EQ(pool.size(), 4u);
EXPECT_EQ(pool.alignment(), 2u);
EXPECT_EQ(offset, 2u);
pool.add(bytes, 4, Out(offset));
EXPECT_EQ(pool.size(), 8u);
EXPECT_EQ(pool.alignment(), 4u);
EXPECT_EQ(offset, 4u);
pool.add(bytes, 4, Out(offset));
EXPECT_EQ(pool.size(), 8u);
EXPECT_EQ(pool.alignment(), 4u);
EXPECT_EQ(offset, 4u);
pool.add(bytes, 32, Out(offset));
EXPECT_EQ(pool.size(), 64u);
EXPECT_EQ(pool.alignment(), 32u);
EXPECT_EQ(offset, 32u);
}
}
#endif
ASMJIT_END_NAMESPACE