Files
asmjit-asmjit/asmjit/core/string.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

667 lines
16 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/string.h>
#include <asmjit/support/support.h>
ASMJIT_BEGIN_NAMESPACE
// String - Globals
// ================
static const char String_base_n[] = "0123456789ABCDEF";
constexpr size_t kMinAllocSize = 128;
constexpr size_t kMaxAllocSize = SIZE_MAX - Globals::kGrowThreshold;
// Based on ArenaVector_growCapacity().
//
// NOTE: The sizes here include null terminators - that way we can have aligned allocations that are power of 2s
// initially.
static ASMJIT_INLINE size_t String_grow_capacity(size_t byte_size, size_t min_byte_size) noexcept {
static constexpr size_t kGrowThreshold = Globals::kGrowThreshold;
ASMJIT_ASSERT(min_byte_size < kMaxAllocSize);
// This is more than exponential growth at the beginning.
if (byte_size < kMinAllocSize) {
byte_size = kMinAllocSize;
}
else if (byte_size < 512) {
byte_size = 512;
}
if (byte_size < min_byte_size) {
// Exponential growth before we reach `kGrowThreshold`.
byte_size = Support::align_up_power_of_2(min_byte_size);
// Bail to `min_byte_size` in case of overflow - most likely whatever that is happening afterwards would just fail.
if (byte_size < min_byte_size) {
return min_byte_size;
}
// Pretty much chunked growth advancing by `kGrowThreshold` after we exceed it.
if (byte_size > kGrowThreshold) {
// Align to kGrowThreshold.
size_t remainder = min_byte_size % kGrowThreshold;
byte_size = min_byte_size + remainder;
// Bail to `min_byte_size` in case of overflow.
if (byte_size < min_byte_size) {
return min_byte_size;
}
}
}
return Support::min<size_t>(byte_size, kMaxAllocSize);
}
// String - Clear & Reset
// ======================
Error String::reset() noexcept {
if (_type == kTypeLarge) {
::free(_large.data);
}
_reset_internal();
return Error::kOk;
}
Error String::clear() noexcept {
if (is_large_or_external()) {
_large.size = 0;
_large.data[0] = '\0';
}
else {
_raw.uptr[0] = 0;
}
return Error::kOk;
}
// String - Prepare
// ================
char* String::prepare(ModifyOp op, size_t size) noexcept {
uint8_t type = _type;
char* cur_data;
size_t cur_size;
size_t cur_capacity;
if (is_large_or_external(type)) {
cur_data = _large.data;
cur_size = _large.size;
cur_capacity = _large.capacity;
}
else {
// For some reason clang's static analysis flags this function having "use-after-free". The step
// to get to that is to execute this branch (`is_large_or_external()` returning false) and then
// assuming `type == kTypeLarge` in another condition, which contradicts the first condition.
ASMJIT_ASSERT(type < kTypeLarge);
cur_data = _small.data;
cur_size = _small.type;
cur_capacity = kSSOCapacity;
}
if (op == ModifyOp::kAssign) {
if (size > cur_capacity) {
// Prevent arithmetic overflow.
if (ASMJIT_UNLIKELY(size >= kMaxAllocSize)) {
return nullptr;
}
size_t new_capacity = Support::align_up<size_t>(size + 1, kMinAllocSize);
char* new_data = static_cast<char*>(::malloc(new_capacity));
if (ASMJIT_UNLIKELY(!new_data)) {
return nullptr;
}
if (type == kTypeLarge) {
::free(cur_data);
}
_large.type = kTypeLarge;
_large.size = size;
_large.capacity = new_capacity - 1;
_large.data = new_data;
new_data[size] = '\0';
return new_data;
}
else {
_set_size(size);
cur_data[size] = '\0';
return cur_data;
}
}
else {
// Prevent arithmetic overflow.
if (ASMJIT_UNLIKELY(size >= kMaxAllocSize - cur_size - 1)) {
return nullptr;
}
size_t new_size = size + cur_size;
size_t new_size_plus_one = new_size + 1;
if (new_size > cur_capacity) {
size_t new_capacity_plus_one = String_grow_capacity(size + 1u, new_size_plus_one);
ASMJIT_ASSERT(new_capacity_plus_one >= new_size_plus_one);
if (ASMJIT_UNLIKELY(new_capacity_plus_one < new_size_plus_one)) {
return nullptr;
}
char* new_data = static_cast<char*>(::malloc(new_capacity_plus_one));
if (ASMJIT_UNLIKELY(!new_data)) {
return nullptr;
}
memcpy(new_data, cur_data, cur_size);
if (type == kTypeLarge) {
::free(cur_data);
}
_large.type = kTypeLarge;
_large.size = new_size;
_large.capacity = new_capacity_plus_one - 1;
_large.data = new_data;
new_data[new_size] = '\0';
return new_data + cur_size;
}
else {
_set_size(new_size);
cur_data[new_size] = '\0';
return cur_data + cur_size;
}
}
}
// String - Assign
// ===============
Error String::assign(const char* data, size_t size) noexcept {
uint8_t type = _type;
char* dst = nullptr;
// Null terminated string without `size` specified.
if (size == SIZE_MAX) {
size = data ? strlen(data) : size_t(0);
}
if (is_large_or_external(type)) {
if (size <= _large.capacity) {
dst = _large.data;
_large.size = size;
}
else {
size_t capacity_plus_one = Support::align_up(size + 1, 32);
if (ASMJIT_UNLIKELY(capacity_plus_one < size)) {
return make_error(Error::kOutOfMemory);
}
dst = static_cast<char*>(::malloc(capacity_plus_one));
if (ASMJIT_UNLIKELY(!dst)) {
return make_error(Error::kOutOfMemory);
}
if (type == kTypeLarge) {
::free(_large.data);
}
_large.type = kTypeLarge;
_large.data = dst;
_large.size = size;
_large.capacity = capacity_plus_one - 1;
}
}
else {
if (size <= kSSOCapacity) {
ASMJIT_ASSERT(size < 0xFFu);
dst = _small.data;
_small.type = uint8_t(size);
}
else {
dst = static_cast<char*>(::malloc(size + 1));
if (ASMJIT_UNLIKELY(!dst)) {
return make_error(Error::kOutOfMemory);
}
_large.type = kTypeLarge;
_large.data = dst;
_large.size = size;
_large.capacity = size;
}
}
// Optionally copy data from `data` and null-terminate.
if (data && size) {
// NOTE: It's better to use `memmove()`. If, for any reason, somebody uses
// this function to substring the same string it would work as expected.
::memmove(dst, data, size);
}
dst[size] = '\0';
return Error::kOk;
}
// String - Operations
// ===================
Error String::_op_string(ModifyOp op, const char* str, size_t size) noexcept {
if (size == SIZE_MAX) {
size = str ? strlen(str) : size_t(0);
}
if (!size) {
return Error::kOk;
}
char* p = prepare(op, size);
if (!p) {
return make_error(Error::kOutOfMemory);
}
memcpy(p, str, size);
return Error::kOk;
}
Error String::_op_char(ModifyOp op, char c) noexcept {
char* p = prepare(op, 1);
if (!p) {
return make_error(Error::kOutOfMemory);
}
*p = c;
return Error::kOk;
}
Error String::_op_chars(ModifyOp op, char c, size_t n) noexcept {
if (!n) {
return Error::kOk;
}
char* p = prepare(op, n);
if (!p) {
return make_error(Error::kOutOfMemory);
}
memset(p, c, n);
return Error::kOk;
}
Error String::pad_end(size_t n, char c) noexcept {
size_t size = this->size();
return n > size ? append_chars(c, n - size) : Error::kOk;
}
Error String::_op_number(ModifyOp op, uint64_t i, uint32_t base, size_t width, StringFormatFlags flags) noexcept {
if (base == 0) {
base = 10;
}
char buf[128];
char* p = buf + ASMJIT_ARRAY_SIZE(buf);
uint64_t orig = i;
char sign = '\0';
// Format Sign
// -----------
if (Support::test(flags, StringFormatFlags::kSigned) && int64_t(i) < 0) {
i = Support::neg(i);
sign = '-';
}
else if (Support::test(flags, StringFormatFlags::kShowSign)) {
sign = '+';
}
else if (Support::test(flags, StringFormatFlags::kShowSpace)) {
sign = ' ';
}
// Format Number
// -------------
switch (base) {
case 2:
case 8:
case 16: {
uint32_t shift = Support::ctz(base);
uint32_t mask = base - 1;
do {
uint64_t d = i >> shift;
size_t r = size_t(i & mask);
*--p = String_base_n[r];
i = d;
} while (i);
break;
}
case 10: {
do {
uint64_t d = i / 10;
uint64_t r = i % 10;
*--p = char(uint32_t('0') + uint32_t(r));
i = d;
} while (i);
break;
}
default:
return make_error(Error::kInvalidArgument);
}
size_t number_size = (size_t)(buf + ASMJIT_ARRAY_SIZE(buf) - p);
// Alternate Form
// --------------
if (Support::test(flags, StringFormatFlags::kAlternate)) {
if (base == 8) {
if (orig != 0) {
*--p = '0';
}
}
if (base == 16) {
*--p = 'x';
*--p = '0';
}
}
// String Width
// ------------
if (sign != 0) {
*--p = sign;
}
if (width > 256) {
width = 256;
}
if (width <= number_size) {
width = 0;
}
else {
width -= number_size;
}
// Finalize
// --------
size_t prefix_size = (size_t)(buf + ASMJIT_ARRAY_SIZE(buf) - p) - number_size;
char* data = prepare(op, prefix_size + width + number_size);
if (!data) {
return make_error(Error::kOutOfMemory);
}
memcpy(data, p, prefix_size);
data += prefix_size;
memset(data, '0', width);
data += width;
memcpy(data, p + prefix_size, number_size);
return Error::kOk;
}
Error String::_op_hex(ModifyOp op, const void* data, size_t size, char separator) noexcept {
char* dst;
const uint8_t* src = static_cast<const uint8_t*>(data);
if (!size) {
return Error::kOk;
}
if (separator) {
if (ASMJIT_UNLIKELY(size >= SIZE_MAX / 3)) {
return make_error(Error::kOutOfMemory);
}
dst = prepare(op, size * 3 - 1);
if (ASMJIT_UNLIKELY(!dst)) {
return make_error(Error::kOutOfMemory);
}
size_t i = 0;
for (;;) {
dst[0] = String_base_n[(src[0] >> 4) & 0xF];
dst[1] = String_base_n[(src[0] ) & 0xF];
if (++i == size) {
break;
}
// This makes sure that the separator is only put between two hexadecimal bytes.
dst[2] = separator;
dst += 3;
src++;
}
}
else {
if (ASMJIT_UNLIKELY(size >= SIZE_MAX / 2)) {
return make_error(Error::kOutOfMemory);
}
dst = prepare(op, size * 2);
if (ASMJIT_UNLIKELY(!dst)) {
return make_error(Error::kOutOfMemory);
}
for (size_t i = 0; i < size; i++, dst += 2, src++) {
dst[0] = String_base_n[(src[0] >> 4) & 0xF];
dst[1] = String_base_n[(src[0] ) & 0xF];
}
}
return Error::kOk;
}
Error String::_op_format(ModifyOp op, const char* fmt, ...) noexcept {
Error err;
va_list ap;
va_start(ap, fmt);
err = _op_vformat(op, fmt, ap);
va_end(ap);
return err;
}
Error String::_op_vformat(ModifyOp op, const char* fmt, va_list ap) noexcept {
size_t start_at = (op == ModifyOp::kAssign) ? size_t(0) : size();
size_t remaining_capacity = capacity() - start_at;
char buf[1024];
int fmt_result;
size_t output_size;
va_list ap_copy;
va_copy(ap_copy, ap);
if (remaining_capacity >= 128) {
fmt_result = vsnprintf(data() + start_at, remaining_capacity, fmt, ap);
output_size = size_t(fmt_result);
if (ASMJIT_LIKELY(output_size <= remaining_capacity)) {
_set_size(start_at + output_size);
return Error::kOk;
}
}
else {
fmt_result = vsnprintf(buf, ASMJIT_ARRAY_SIZE(buf), fmt, ap);
output_size = size_t(fmt_result);
if (ASMJIT_LIKELY(output_size < ASMJIT_ARRAY_SIZE(buf))) {
return _op_string(op, buf, output_size);
}
}
if (ASMJIT_UNLIKELY(fmt_result < 0)) {
return make_error(Error::kInvalidState);
}
char* p = prepare(op, output_size);
if (ASMJIT_UNLIKELY(!p)) {
return make_error(Error::kOutOfMemory);
}
fmt_result = vsnprintf(p, output_size + 1, fmt, ap_copy);
ASMJIT_ASSERT(size_t(fmt_result) == output_size);
return Error::kOk;
}
Error String::truncate(size_t new_size) noexcept {
if (is_large_or_external()) {
if (new_size < _large.size) {
_large.data[new_size] = '\0';
_large.size = new_size;
}
}
else {
if (new_size < _type) {
_small.data[new_size] = '\0';
_small.type = uint8_t(new_size);
}
}
return Error::kOk;
}
bool String::equals(const char* other, size_t size) const noexcept {
const char* a_data = data();
const char* b_data = other;
size_t a_size = this->size();
size_t b_size = size;
if (b_size == SIZE_MAX) {
size_t i;
for (i = 0; i < a_size; i++) {
if (a_data[i] != b_data[i] || b_data[i] == 0) {
return false;
}
}
return b_data[i] == 0;
}
else {
if (a_size != b_size) {
return false;
}
return ::memcmp(a_data, b_data, a_size) == 0;
}
}
// String - Tests
// ==============
#if defined(ASMJIT_TEST)
static void test_string_grow() noexcept {
String s;
size_t c = s.capacity();
INFO("Testing string grow strategy (SSO capacity: %zu)", c);
for (size_t i = 0; i < 1000000; i++) {
s.append('x');
if (s.capacity() != c) {
c = s.capacity();
INFO(" String reallocated to new capacity: %zu", c);
}
}
// We don't expect a 1 million character string to occupy 4MiB, for example. So verify that!
EXPECT_LT(c, size_t(4 * 1024 * 1024));
}
UNIT(core_string) {
String s;
INFO("Testing string functionality");
EXPECT_FALSE(s.is_large_or_external());
EXPECT_FALSE(s.is_external());
EXPECT_EQ(s.assign('a'), Error::kOk);
EXPECT_EQ(s.size(), 1u);
EXPECT_EQ(s.capacity(), String::kSSOCapacity);
EXPECT_EQ(s.data()[0], 'a');
EXPECT_EQ(s.data()[1], '\0');
EXPECT_TRUE(s.equals("a"));
EXPECT_TRUE(s.equals("a", 1));
EXPECT_EQ(s.assign_chars('b', 4), Error::kOk);
EXPECT_EQ(s.size(), 4u);
EXPECT_EQ(s.capacity(), String::kSSOCapacity);
EXPECT_EQ(s.data()[0], 'b');
EXPECT_EQ(s.data()[1], 'b');
EXPECT_EQ(s.data()[2], 'b');
EXPECT_EQ(s.data()[3], 'b');
EXPECT_EQ(s.data()[4], '\0');
EXPECT_TRUE(s.equals("bbbb"));
EXPECT_TRUE(s.equals("bbbb", 4));
EXPECT_EQ(s.assign("abc"), Error::kOk);
EXPECT_EQ(s.size(), 3u);
EXPECT_EQ(s.capacity(), String::kSSOCapacity);
EXPECT_EQ(s.data()[0], 'a');
EXPECT_EQ(s.data()[1], 'b');
EXPECT_EQ(s.data()[2], 'c');
EXPECT_EQ(s.data()[3], '\0');
EXPECT_TRUE(s.equals("abc"));
EXPECT_TRUE(s.equals("abc", 3));
const char* large = "Large string that will not fit into SSO buffer";
EXPECT_EQ(s.assign(large), Error::kOk);
EXPECT_TRUE(s.is_large_or_external());
EXPECT_EQ(s.size(), strlen(large));
EXPECT_GT(s.capacity(), String::kSSOCapacity);
EXPECT_TRUE(s.equals(large));
EXPECT_TRUE(s.equals(large, strlen(large)));
const char* additional = " (additional content)";
EXPECT_TRUE(s.is_large_or_external());
EXPECT_EQ(s.append(additional), Error::kOk);
EXPECT_EQ(s.size(), strlen(large) + strlen(additional));
EXPECT_EQ(s.clear(), Error::kOk);
EXPECT_EQ(s.size(), 0u);
EXPECT_TRUE(s.is_empty());
EXPECT_EQ(s.data()[0], '\0');
EXPECT_TRUE(s.is_large_or_external()); // Clear should never release the memory.
EXPECT_EQ(s.append_uint(1234), Error::kOk);
EXPECT_TRUE(s.equals("1234"));
EXPECT_EQ(s.assign_uint(0xFFFF, 16, 0, StringFormatFlags::kAlternate), Error::kOk);
EXPECT_TRUE(s.equals("0xFFFF"));
StringTmp<64> s_tmp;
EXPECT_TRUE(s_tmp.is_large_or_external());
EXPECT_TRUE(s_tmp.is_external());
EXPECT_EQ(s_tmp.append_chars(' ', 1000), Error::kOk);
EXPECT_FALSE(s_tmp.is_external());
test_string_grow();
}
#endif
ASMJIT_END_NAMESPACE