New internal string format:
* maximum size of a string is 2^32-1 now by default on a 64 bits system (can be increased using M_USE_STRING_LARGE_INDEX if needed). * This reduce consumption of a string_t from 24 bytes to 16 bytes. * Change the stack representation of a string, so that it lets 15 allocated bytes for the user even in 64 bytes mode. For 32 bits system, it increases to 15 allocated byte for stack representation. * synthetic performance benchmark shows a little bit of decrease of performance. But performance in real application shows a significant increase (probably due to the reduced size of the type). *Augment test suite to be sure to test all corner cases of the new representaiton.
This commit is contained in:
1 parent
bf59c53921
commit
67e9c8ff06
3 files changed
+198
-96
No files matched your search
+10
-6
@@ -71,15 +71,17 @@ m_ser1al_json_write_float(m_serial_write_t serial, const long double data, const
|
|||||||
M_INLINE m_serial_return_code_t
|
M_INLINE m_serial_return_code_t
|
||||||
m_ser1al_json_write_string(m_serial_write_t serial, const char data[], size_t length)
|
m_ser1al_json_write_string(m_serial_write_t serial, const char data[], size_t length)
|
||||||
{
|
{
|
||||||
|
M_ASSERT(length == (m_str1ng_size_t) length);
|
||||||
M_ASSERT_SLOW(length == strlen(data) );
|
M_ASSERT_SLOW(length == strlen(data) );
|
||||||
FILE *f = (FILE *)serial->data[0].p;
|
FILE *f = (FILE *)serial->data[0].p;
|
||||||
M_ASSERT(f != NULL && data != NULL);
|
M_ASSERT(f != NULL && data != NULL);
|
||||||
/* HACK: Build dummy string to reuse m_string_out_str */
|
/* HACK: Build dummy string to reuse m_string_out_str */
|
||||||
m_string_t v2;
|
m_string_t v2;
|
||||||
uintptr_t ptr = (uintptr_t) data;
|
uintptr_t ptr = (uintptr_t) data;
|
||||||
v2->u.heap.size = length;
|
v2->u.heap.size = (m_str1ng_size_t) length;
|
||||||
v2->u.heap.alloc = length + 1;
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1;
|
||||||
v2->ptr = (char*)ptr;
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31;
|
||||||
|
v2->u.heap.ptr = (char*)ptr;
|
||||||
m_string_out_str(f, v2);
|
m_string_out_str(f, v2);
|
||||||
return M_UNLIKELY(ferror(f)) ? m_core_serial_fail() : M_SERIAL_OK_DONE;
|
return M_UNLIKELY(ferror(f)) ? m_core_serial_fail() : M_SERIAL_OK_DONE;
|
||||||
}
|
}
|
||||||
@@ -620,15 +622,17 @@ m_ser1al_str_json_write_float(m_serial_write_t serial, const long double data, c
|
|||||||
M_INLINE m_serial_return_code_t
|
M_INLINE m_serial_return_code_t
|
||||||
m_ser1al_str_json_write_string(m_serial_write_t serial, const char data[], size_t length)
|
m_ser1al_str_json_write_string(m_serial_write_t serial, const char data[], size_t length)
|
||||||
{
|
{
|
||||||
|
M_ASSERT(length == (m_str1ng_size_t) length);
|
||||||
M_ASSERT_SLOW(length == strlen(data) );
|
M_ASSERT_SLOW(length == strlen(data) );
|
||||||
struct m_string_s *f = (struct m_string_s *)serial->data[0].p;
|
struct m_string_s *f = (struct m_string_s *)serial->data[0].p;
|
||||||
M_ASSERT(f != NULL && data != NULL);
|
M_ASSERT(f != NULL && data != NULL);
|
||||||
/* HACK: Build dummy string to reuse m_string_get_str */
|
/* HACK: Build dummy string to reuse m_string_get_str */
|
||||||
m_string_t v2;
|
m_string_t v2;
|
||||||
uintptr_t ptr = (uintptr_t) data;
|
uintptr_t ptr = (uintptr_t) data;
|
||||||
v2->u.heap.size = length;
|
v2->u.heap.size = (m_str1ng_size_t) length;
|
||||||
v2->u.heap.alloc = length + 1;
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1;
|
||||||
v2->ptr = (char*)ptr;
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31;
|
||||||
|
v2->u.heap.ptr = (char*)ptr;
|
||||||
m_string_get_str(f, v2, true);
|
m_string_get_str(f, v2, true);
|
||||||
return M_SERIAL_OK_DONE;
|
return M_SERIAL_OK_DONE;
|
||||||
}
|
}
|
||||||
|
|||||||
+160
-88
@@ -40,22 +40,39 @@ M_BEGIN_PROTECTED_CODE
|
|||||||
M_ASSERT_SLOW (m_string_size(v) == strlen(m_string_get_cstr(v))); \
|
M_ASSERT_SLOW (m_string_size(v) == strlen(m_string_get_cstr(v))); \
|
||||||
M_ASSERT (m_string_get_cstr(v)[m_string_size(v)] == 0); \
|
M_ASSERT (m_string_get_cstr(v)[m_string_size(v)] == 0); \
|
||||||
M_ASSERT (m_string_size(v) < m_string_capacity(v)); \
|
M_ASSERT (m_string_size(v) < m_string_capacity(v)); \
|
||||||
M_ASSERT (m_string_capacity(v) < sizeof (m_str1ng_heap_ct) || !m_str1ng_stack_p(v)); \
|
|
||||||
} while(0)
|
} while(0)
|
||||||
|
|
||||||
// string if it is heap allocated
|
/* Define the maximum size of a string
|
||||||
|
By default it is a little bit less than 2^32 bytes, which should be enough for
|
||||||
|
most programs. Otherwise it can be increased. */
|
||||||
|
#ifdef M_USE_STRING_LARGE_INDEX
|
||||||
|
typedef uint64_t m_str1ng_size_t;
|
||||||
|
#define m_str1ng_size_clz m_core_clz64
|
||||||
|
#else
|
||||||
|
typedef uint32_t m_str1ng_size_t;
|
||||||
|
#define m_str1ng_size_clz m_core_clz32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// The string representation if it heap allocated
|
||||||
typedef struct m_str1ng_heap_s {
|
typedef struct m_str1ng_heap_s {
|
||||||
size_t size;
|
char *ptr;
|
||||||
size_t alloc;
|
m_str1ng_size_t size;
|
||||||
|
unsigned char alloc[sizeof (m_str1ng_size_t)];
|
||||||
} m_str1ng_heap_ct;
|
} m_str1ng_heap_ct;
|
||||||
// string if it is stack allocated
|
|
||||||
typedef struct m_str1ng_stack_s {
|
/* Define the union between a heap allocated string and its inline representation
|
||||||
char buffer[sizeof (m_str1ng_heap_ct)];
|
If buffer[sizeof(m_str1ng_heap_ct)-2] == 0 then
|
||||||
} m_str1ng_stack_ct;
|
It is a stack based string where buffer is the string, with a maximum allocation of 15 bytes
|
||||||
// both cases of string are possible
|
In this case buffer[sizeof(m_str1ng_heap_ct)-1] is the size of the string
|
||||||
|
Otherwise it is a heap representation then
|
||||||
|
ptr is the pointer to the data, size its size, and alloc a representation of the capacity:
|
||||||
|
alloc[sizeof (m_str1ng_size_t)-2] << alloc[sizeof (m_str1ng_size_t)-1]
|
||||||
|
we shall have alloc[sizeof (m_str1ng_size_t)-2] == buffer[sizeof(m_str1ng_heap_ct)-2]
|
||||||
|
so that in case of heap representation, the field buffer[sizeof(m_str1ng_heap_ct)-2] is != 0
|
||||||
|
*/
|
||||||
typedef union m_str1ng_union_u {
|
typedef union m_str1ng_union_u {
|
||||||
m_str1ng_heap_ct heap;
|
m_str1ng_heap_ct heap;
|
||||||
m_str1ng_stack_ct stack;
|
char buffer[sizeof (m_str1ng_heap_ct)];
|
||||||
} m_str1ng_union_ct;
|
} m_str1ng_union_ct;
|
||||||
|
|
||||||
/* State of the UTF8 decoding machine state */
|
/* State of the UTF8 decoding machine state */
|
||||||
@@ -101,7 +118,6 @@ typedef enum {
|
|||||||
/* This is the main structure of this module, representing a dynamic string */
|
/* This is the main structure of this module, representing a dynamic string */
|
||||||
typedef struct m_string_s {
|
typedef struct m_string_s {
|
||||||
m_str1ng_union_ct u;
|
m_str1ng_union_ct u;
|
||||||
char *ptr;
|
|
||||||
} m_string_t[1];
|
} m_string_t[1];
|
||||||
|
|
||||||
// Pointer to a Dynamic string
|
// Pointer to a Dynamic string
|
||||||
@@ -138,22 +154,15 @@ typedef struct m_string_it_s {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
/* Internal method to test if the string is stack based or heap based
|
/* Internal method to test if the string is stack based or heap based
|
||||||
We test if the ptr is NULL or not.
|
Stack representation ensures that the penultimate byte is 0.
|
||||||
This is not particularly efficient from a memory point of view
|
By encoding, we ensure that the penultimate byte of 'alloc' (of the heap representation)
|
||||||
(as we could reuse the pointer field to store some data)
|
cannot be 0.
|
||||||
but it should be enough for most of "small" strings as most
|
|
||||||
of the strings are less than 14 characters (64 bits architecture).
|
|
||||||
Moreover it generates quite efficient code.
|
|
||||||
NOTE: We set the pointer to NULL (instead of storing a pointer to
|
|
||||||
the stack buffer for example) for stack based allocation so that
|
|
||||||
the string structure remains trivially movable (which is an important
|
|
||||||
property to have).
|
|
||||||
*/
|
*/
|
||||||
M_INLINE bool
|
M_INLINE bool
|
||||||
m_str1ng_stack_p(const m_string_t s)
|
m_str1ng_stack_p(const m_string_t s)
|
||||||
{
|
{
|
||||||
// Function can be called when contract is not fulfilled
|
// Function can be called when contract is not fulfilled
|
||||||
return (s->ptr == NULL);
|
return (s->u.buffer[sizeof(m_str1ng_heap_ct)-2] == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Internal method to set the size of the string (excluding final nul char) */
|
/* Internal method to set the size of the string (excluding final nul char) */
|
||||||
@@ -164,9 +173,10 @@ m_str1ng_set_size(m_string_t s, size_t size)
|
|||||||
if (m_str1ng_stack_p(s)) {
|
if (m_str1ng_stack_p(s)) {
|
||||||
M_ASSERT (size < sizeof (m_str1ng_heap_ct) - 1);
|
M_ASSERT (size < sizeof (m_str1ng_heap_ct) - 1);
|
||||||
// The size of the string is stored as the last char of the buffer.
|
// The size of the string is stored as the last char of the buffer.
|
||||||
s->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] = (char) size;
|
s->u.buffer[sizeof(m_str1ng_heap_ct)-1] = (char) size;
|
||||||
} else {
|
} else {
|
||||||
s->u.heap.size = size;
|
M_ASSUME(size == (m_str1ng_size_t) size);
|
||||||
|
s->u.heap.size = (m_str1ng_size_t) size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,8 +185,7 @@ M_INLINE size_t
|
|||||||
m_string_size(const m_string_t s)
|
m_string_size(const m_string_t s)
|
||||||
{
|
{
|
||||||
// Function can be called when contract is not fulfilled
|
// Function can be called when contract is not fulfilled
|
||||||
// Reading both values before calling the '?' operator enables compiler to generate branchless code
|
const size_t s_stack = (size_t) s->u.buffer[sizeof(m_str1ng_heap_ct)-1];
|
||||||
const size_t s_stack = (size_t) s->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1];
|
|
||||||
const size_t s_heap = s->u.heap.size;
|
const size_t s_heap = s->u.heap.size;
|
||||||
return m_str1ng_stack_p(s) ? s_stack : s_heap;
|
return m_str1ng_stack_p(s) ? s_stack : s_heap;
|
||||||
}
|
}
|
||||||
@@ -186,19 +195,67 @@ M_INLINE size_t
|
|||||||
m_string_capacity(const m_string_t s)
|
m_string_capacity(const m_string_t s)
|
||||||
{
|
{
|
||||||
// Function can be called when contract is not fulfilled
|
// Function can be called when contract is not fulfilled
|
||||||
// Reading both values before calling the '?' operator enables compiler to generate branchless code
|
|
||||||
const size_t c_stack = sizeof (m_str1ng_heap_ct) - 1;
|
const size_t c_stack = sizeof (m_str1ng_heap_ct) - 1;
|
||||||
const size_t c_heap = s->u.heap.alloc;
|
const size_t c_heap = (m_str1ng_size_t) s->u.heap.alloc[sizeof (m_str1ng_size_t)-2] << s->u.heap.alloc[sizeof (m_str1ng_size_t)-1];
|
||||||
return m_str1ng_stack_p(s) ? c_stack : c_heap;
|
return m_str1ng_stack_p(s) ? c_stack : c_heap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Internal function to round up the allocation size to the next allowed representation
|
||||||
|
Return the rounded allocation and the pair (m,e) used to encode it
|
||||||
|
An overflow occurs if the result is strictly lower than 'alloc' before casting.
|
||||||
|
*/
|
||||||
|
M_INLINE m_str1ng_size_t
|
||||||
|
m_str1ng_round_capacity(unsigned char *m, unsigned char *e, m_str1ng_size_t alloc)
|
||||||
|
{
|
||||||
|
// We will encode 'alloc' as a number m<<e with (m,e) being unsigned char.
|
||||||
|
// alloc is strictly greater than 0, but with casting to m_str1ng_size_t, it may be 0.
|
||||||
|
// Compute the number of bits needed to represent 'alloc'
|
||||||
|
unsigned b = (unsigned) sizeof(m_str1ng_size_t) * CHAR_BIT - m_str1ng_size_clz(alloc);
|
||||||
|
if (b <= CHAR_BIT) {
|
||||||
|
// It can be fully representable. No rounding needed.
|
||||||
|
*m = (unsigned char) alloc;
|
||||||
|
*e = 0;
|
||||||
|
} else {
|
||||||
|
/* It cannot be fully represented: Round it to the next upper representation of m << e
|
||||||
|
with m and e unsigned char.
|
||||||
|
Keep only the '7' upper bits of alloc, and round it to the next representation so that
|
||||||
|
m << e is bigger or equal to alloc.
|
||||||
|
Worst case is when alloc = 0x7F01 (i.e. all 7 upper bits are 1)
|
||||||
|
Performing the rounding will generate a carry, so we get for mantissa 0x80 which is on 8 bits.
|
||||||
|
That's why we shift by '7' and not by '8', so that 0x80 remains representable as an
|
||||||
|
unsigned char. Otherwise we would need to take into account this case, and shift mantissa by
|
||||||
|
one and increase exponent by one.
|
||||||
|
If alloc was >= 0xFE000001, rounding will overflow the max of a m_str1ng_size_t
|
||||||
|
and *m == 0.
|
||||||
|
This case should be handled by the caller as an overflow error
|
||||||
|
(return value is lower than input)
|
||||||
|
*/
|
||||||
|
*m = (unsigned char) ((alloc + ((1U <<(b-(CHAR_BIT-1)))-1))>>(b-(CHAR_BIT-1)));
|
||||||
|
*e = (unsigned char) (b - (CHAR_BIT-1));
|
||||||
|
}
|
||||||
|
// Worst case is when the rounding overflow the size m_str1ng_size_t
|
||||||
|
M_ASSERT (((m_str1ng_size_t) *m << *e) >= alloc || *m == 0);
|
||||||
|
return (m_str1ng_size_t) *m << *e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Internal function to save the allocation size to the next allowed representation
|
||||||
|
as returned by m_str1ng_round_capacity */
|
||||||
|
M_INLINE void
|
||||||
|
m_str1ng_set_capacity(m_string_t s, unsigned char m, unsigned char e)
|
||||||
|
{
|
||||||
|
// Function can be called when contract is not fulfilled
|
||||||
|
M_ASSERT(m != 0);
|
||||||
|
s->u.heap.alloc[sizeof (m_str1ng_size_t)-2] = m;
|
||||||
|
s->u.heap.alloc[sizeof (m_str1ng_size_t)-1] = e;
|
||||||
|
}
|
||||||
|
|
||||||
/* Return a writable pointer to the array of char of the string */
|
/* Return a writable pointer to the array of char of the string */
|
||||||
M_INLINE char*
|
M_INLINE char*
|
||||||
m_str1ng_get_cstr(m_string_t v)
|
m_str1ng_get_cstr(m_string_t v)
|
||||||
{
|
{
|
||||||
// Function can be called when contract is not fulfilled
|
// Function can be called when contract is not fulfilled
|
||||||
char *const ptr_stack = &v->u.stack.buffer[0];
|
char *const ptr_stack = &v->u.buffer[0];
|
||||||
char *const ptr_heap = v->ptr;
|
char *const ptr_heap = v->u.heap.ptr;
|
||||||
return m_str1ng_stack_p(v) ? ptr_stack : ptr_heap;
|
return m_str1ng_stack_p(v) ? ptr_stack : ptr_heap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,8 +265,8 @@ m_string_get_cstr(const m_string_t v)
|
|||||||
{
|
{
|
||||||
// Function cannot be called when contract is not fulfilled
|
// Function cannot be called when contract is not fulfilled
|
||||||
// but it is called by contract (so no contract check to avoid infinite recursion).
|
// but it is called by contract (so no contract check to avoid infinite recursion).
|
||||||
const char *const ptr_stack = &v->u.stack.buffer[0];
|
const char *const ptr_stack = &v->u.buffer[0];
|
||||||
const char *const ptr_heap = v->ptr;
|
const char *const ptr_heap = v->u.heap.ptr;
|
||||||
return m_str1ng_stack_p(v) ? ptr_stack : ptr_heap;
|
return m_str1ng_stack_p(v) ? ptr_stack : ptr_heap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,9 +275,7 @@ m_string_get_cstr(const m_string_t v)
|
|||||||
M_INLINE void
|
M_INLINE void
|
||||||
m_string_init(m_string_t s)
|
m_string_init(m_string_t s)
|
||||||
{
|
{
|
||||||
s->ptr = NULL;
|
memset(s, 0, sizeof(m_string_t));
|
||||||
s->u.stack.buffer[0] = 0;
|
|
||||||
m_str1ng_set_size(s, 0);
|
|
||||||
M_STR1NG_CONTRACT(s);
|
M_STR1NG_CONTRACT(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,12 +285,13 @@ m_string_clear(m_string_t v)
|
|||||||
{
|
{
|
||||||
M_STR1NG_CONTRACT(v);
|
M_STR1NG_CONTRACT(v);
|
||||||
if (!m_str1ng_stack_p(v)) {
|
if (!m_str1ng_stack_p(v)) {
|
||||||
M_MEMORY_FREE(v->ptr);
|
M_MEMORY_FREE(v->u.heap.ptr);
|
||||||
v->ptr = NULL;
|
v->u.heap.ptr = NULL;
|
||||||
}
|
}
|
||||||
/* This is not needed but is safer to make
|
/* This is not needed but is safer to make
|
||||||
the string invalid so that it can be detected. */
|
the string invalid so that it can be detected. */
|
||||||
v->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] = CHAR_MAX;
|
v->u.buffer[sizeof(m_str1ng_heap_ct)-2] = 0;
|
||||||
|
v->u.buffer[sizeof(m_str1ng_heap_ct)-1] = CHAR_MAX;
|
||||||
}
|
}
|
||||||
|
|
||||||
M_INLINE m_string_ptr m_str1ng_init_ref(m_string_t v) { m_string_init(v); return v; }
|
M_INLINE m_string_ptr m_str1ng_init_ref(m_string_t v) { m_string_init(v); return v; }
|
||||||
@@ -248,10 +304,10 @@ M_INLINE char *
|
|||||||
m_string_clear_get_cstr(m_string_t v)
|
m_string_clear_get_cstr(m_string_t v)
|
||||||
{
|
{
|
||||||
M_STR1NG_CONTRACT(v);
|
M_STR1NG_CONTRACT(v);
|
||||||
char *p = v->ptr;
|
char *p = v->u.heap.ptr;
|
||||||
if (m_str1ng_stack_p(v)) {
|
if (m_str1ng_stack_p(v)) {
|
||||||
// The string was stack allocated.
|
// The string was stack allocated.
|
||||||
p = v->u.stack.buffer;
|
p = &v->u.buffer[0];
|
||||||
// Need to allocate a heap string to return the copy.
|
// Need to allocate a heap string to return the copy.
|
||||||
size_t alloc = m_string_size(v)+1;
|
size_t alloc = m_string_size(v)+1;
|
||||||
char *ptr = M_MEMORY_REALLOC (char, NULL, alloc);
|
char *ptr = M_MEMORY_REALLOC (char, NULL, alloc);
|
||||||
@@ -263,8 +319,8 @@ m_string_clear_get_cstr(m_string_t v)
|
|||||||
memcpy(ptr, p, alloc);
|
memcpy(ptr, p, alloc);
|
||||||
p = ptr;
|
p = ptr;
|
||||||
}
|
}
|
||||||
v->ptr = NULL;
|
v->u.buffer[sizeof(m_str1ng_heap_ct)-2] = 0;
|
||||||
v->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] = CHAR_MAX;
|
v->u.buffer[sizeof(m_str1ng_heap_ct)-1] = CHAR_MAX;
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,6 +364,7 @@ m_string_empty_p(const m_string_t v)
|
|||||||
Ensures that the string capacity is greater than size_alloc
|
Ensures that the string capacity is greater than size_alloc
|
||||||
(size_alloc shall include the final null char).
|
(size_alloc shall include the final null char).
|
||||||
It may move the string from stack based to heap based.
|
It may move the string from stack based to heap based.
|
||||||
|
The string 'v' no longer has a working size field.
|
||||||
Return a pointer to the writable string.
|
Return a pointer to the writable string.
|
||||||
*/
|
*/
|
||||||
M_INLINE char *
|
M_INLINE char *
|
||||||
@@ -323,7 +380,9 @@ m_str1ng_fit2size (m_string_t v, size_t size_alloc)
|
|||||||
if (M_UNLIKELY (size_alloc > old_alloc)) {
|
if (M_UNLIKELY (size_alloc > old_alloc)) {
|
||||||
// Insufficient current allocation to store the new string
|
// Insufficient current allocation to store the new string
|
||||||
// Perform an allocation on the heap.
|
// Perform an allocation on the heap.
|
||||||
size_t alloc = size_alloc + size_alloc / 2;
|
size_t alloc = (size_alloc + size_alloc / 2);
|
||||||
|
unsigned char m, e;
|
||||||
|
alloc = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) alloc);
|
||||||
if (M_UNLIKELY_NOMEM (alloc <= size_alloc)) {
|
if (M_UNLIKELY_NOMEM (alloc <= size_alloc)) {
|
||||||
/* Overflow in alloc computation */
|
/* Overflow in alloc computation */
|
||||||
M_MEMORY_FULL(sizeof (char) * alloc);
|
M_MEMORY_FULL(sizeof (char) * alloc);
|
||||||
@@ -331,27 +390,28 @@ m_str1ng_fit2size (m_string_t v, size_t size_alloc)
|
|||||||
abort();
|
abort();
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc);
|
char *ptr = m_str1ng_stack_p(v) ? NULL : v->u.heap.ptr;
|
||||||
|
ptr = M_MEMORY_REALLOC (char, ptr, alloc);
|
||||||
if (M_UNLIKELY_NOMEM (ptr == NULL)) {
|
if (M_UNLIKELY_NOMEM (ptr == NULL)) {
|
||||||
M_MEMORY_FULL(sizeof (char) * alloc);
|
M_MEMORY_FULL(sizeof (char) * alloc);
|
||||||
// NOTE: Return is currently broken.
|
// NOTE: Return is currently broken.
|
||||||
abort();
|
abort();
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
// The pointer cannot be the stack buffer of the string.
|
// The pointer cannot be the stack buffer of the string as it is heap allocated
|
||||||
// as it is heap allocated
|
M_ASSERT(ptr != &v->u.buffer[0]);
|
||||||
M_ASSERT(ptr != &v->u.stack.buffer[0]);
|
|
||||||
if (m_str1ng_stack_p(v)) {
|
if (m_str1ng_stack_p(v)) {
|
||||||
// The string was stack allocated.
|
// The string was stack allocated.
|
||||||
/* Copy the stack allocation into the new heap allocation */
|
/* Copy the stack allocation into the new heap allocation */
|
||||||
const size_t size = (size_t) v->u.stack.buffer[sizeof (m_str1ng_heap_ct) - 1] + 1U;
|
const size_t size = (size_t) v->u.buffer[sizeof(m_str1ng_heap_ct) - 1];
|
||||||
M_ASSERT( size <= alloc);
|
memcpy(ptr, &v->u.buffer[0], size+1);
|
||||||
M_ASSERT( size <= sizeof (v->u.stack.buffer)-1);
|
|
||||||
memcpy(ptr, &v->u.stack.buffer[0], size);
|
|
||||||
}
|
}
|
||||||
// The string cannot be stack allocated anymore.
|
// The string cannot be stack allocated anymore.
|
||||||
v->ptr = ptr;
|
v->u.heap.ptr = ptr;
|
||||||
v->u.heap.alloc = alloc;
|
m_str1ng_set_capacity(v, m, e);
|
||||||
|
// Size is not set as the function is called in context
|
||||||
|
// where the caller already know the final size of the string,
|
||||||
|
// so it will update it.
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
return m_str1ng_get_cstr(v);
|
return m_str1ng_get_cstr(v);
|
||||||
@@ -374,32 +434,39 @@ m_string_reserve(m_string_t v, size_t alloc)
|
|||||||
// Allocation can fit in the stack space
|
// Allocation can fit in the stack space
|
||||||
if (!m_str1ng_stack_p(v)) {
|
if (!m_str1ng_stack_p(v)) {
|
||||||
/* Transform Heap Allocate to Stack Allocate */
|
/* Transform Heap Allocate to Stack Allocate */
|
||||||
char *ptr = &v->u.stack.buffer[0];
|
char *ptr = &v->u.buffer[0];
|
||||||
memcpy(ptr, v->ptr, size+1);
|
char *oldptr = v->u.heap.ptr;
|
||||||
M_MEMORY_FREE(v->ptr);
|
memcpy(ptr, oldptr, size+1);
|
||||||
v->ptr = NULL;
|
M_MEMORY_FREE(oldptr);
|
||||||
m_str1ng_set_size(v, size);
|
v->u.buffer[sizeof(m_string_t)-2] = 0;
|
||||||
|
v->u.buffer[sizeof(m_string_t)-1] = (char) size;
|
||||||
} else {
|
} else {
|
||||||
/* Already a stack based alloc: nothing to do */
|
/* Already a stack based alloc: nothing to do */
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Allocation cannot fit in the stack space
|
// Allocation cannot fit in the stack space
|
||||||
// Need to allocate in heap space
|
// Need to allocate in heap space
|
||||||
// If the string is stack allocated, v->ptr is NULL
|
unsigned char m, e;
|
||||||
// and it will therefore perform the initial allocation
|
size_t r_alloc = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) alloc);
|
||||||
char *ptr = M_MEMORY_REALLOC (char, v->ptr, alloc);
|
if (M_UNLIKELY_NOMEM (alloc > r_alloc)) {
|
||||||
|
M_MEMORY_FULL(sizeof (char) * alloc);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char *ptr = m_str1ng_stack_p(v) ? NULL : v->u.heap.ptr;
|
||||||
|
ptr = M_MEMORY_REALLOC (char, ptr, r_alloc);
|
||||||
if (M_UNLIKELY_NOMEM (ptr == NULL) ) {
|
if (M_UNLIKELY_NOMEM (ptr == NULL) ) {
|
||||||
M_MEMORY_FULL(sizeof (char) * alloc);
|
M_MEMORY_FULL(sizeof (char) * alloc);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (m_str1ng_stack_p(v)) {
|
if (m_str1ng_stack_p(v)) {
|
||||||
// Copy from stack space to heap space the string
|
// Copy from stack space to heap space the string
|
||||||
char *ptr_stack = &v->u.stack.buffer[0];
|
char *ptr_stack = &v->u.buffer[0];
|
||||||
memcpy(ptr, ptr_stack, size+1);
|
memcpy(ptr, ptr_stack, size+1);
|
||||||
v->u.heap.size = size;
|
M_ASSERT(size == (m_str1ng_size_t) size);
|
||||||
|
v->u.heap.size = (m_str1ng_size_t) size;
|
||||||
}
|
}
|
||||||
v->ptr = ptr;
|
v->u.heap.ptr = ptr;
|
||||||
v->u.heap.alloc = alloc;
|
m_str1ng_set_capacity(v, m, e);
|
||||||
}
|
}
|
||||||
M_STR1NG_CONTRACT (v);
|
M_STR1NG_CONTRACT (v);
|
||||||
}
|
}
|
||||||
@@ -429,6 +496,7 @@ m_string_set_cstrn(m_string_t v, const char str[], size_t n)
|
|||||||
char *ptr = m_str1ng_fit2size(v, size+1);
|
char *ptr = m_str1ng_fit2size(v, size+1);
|
||||||
// The memcpy will not copy the final null char of the string
|
// The memcpy will not copy the final null char of the string
|
||||||
memcpy(ptr, str, size);
|
memcpy(ptr, str, size);
|
||||||
|
// Cannot copy the final null char using memcpy
|
||||||
ptr[size] = 0;
|
ptr[size] = 0;
|
||||||
m_str1ng_set_size(v, size);
|
m_str1ng_set_size(v, size);
|
||||||
M_STR1NG_CONTRACT (v);
|
M_STR1NG_CONTRACT (v);
|
||||||
@@ -460,6 +528,7 @@ m_string_set_n(m_string_t v, const m_string_t ref, size_t offset, size_t length)
|
|||||||
char *ptr = m_str1ng_fit2size(v, size+1);
|
char *ptr = m_str1ng_fit2size(v, size+1);
|
||||||
// v may be equal to ref, so a memmove is needed instead of a memcpy
|
// v may be equal to ref, so a memmove is needed instead of a memcpy
|
||||||
memmove(ptr, m_string_get_cstr(ref) + offset, size);
|
memmove(ptr, m_string_get_cstr(ref) + offset, size);
|
||||||
|
// Cannot copy the final null char using memcpy
|
||||||
ptr[size] = 0;
|
ptr[size] = 0;
|
||||||
m_str1ng_set_size(v, size);
|
m_str1ng_set_size(v, size);
|
||||||
M_STR1NG_CONTRACT (v);
|
M_STR1NG_CONTRACT (v);
|
||||||
@@ -498,7 +567,8 @@ m_string_init_move(m_string_t v1, m_string_t v2)
|
|||||||
M_STR1NG_CONTRACT (v2);
|
M_STR1NG_CONTRACT (v2);
|
||||||
memcpy(v1, v2, sizeof (m_string_t));
|
memcpy(v1, v2, sizeof (m_string_t));
|
||||||
// Note: nullify v2 to be safer
|
// Note: nullify v2 to be safer
|
||||||
v2->ptr = NULL;
|
v2->u.buffer[sizeof(m_str1ng_heap_ct)-2] = 0;
|
||||||
|
v2->u.buffer[sizeof(m_str1ng_heap_ct)-1] = CHAR_MAX;
|
||||||
M_STR1NG_CONTRACT (v1);
|
M_STR1NG_CONTRACT (v1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,11 +578,10 @@ m_string_swap(m_string_t v1, m_string_t v2)
|
|||||||
{
|
{
|
||||||
M_STR1NG_CONTRACT (v1);
|
M_STR1NG_CONTRACT (v1);
|
||||||
M_STR1NG_CONTRACT (v2);
|
M_STR1NG_CONTRACT (v2);
|
||||||
// Even if it is stack based, we swap the heap representation
|
m_string_t tmp;
|
||||||
// which alias the stack based
|
memcpy(tmp, v1, sizeof(m_string_t));
|
||||||
M_SWAP (size_t, v1->u.heap.size, v2->u.heap.size);
|
memcpy(v1, v2, sizeof(m_string_t));
|
||||||
M_SWAP (size_t, v1->u.heap.alloc, v2->u.heap.alloc);
|
memcpy(v2, tmp, sizeof(m_string_t));
|
||||||
M_SWAP (char *, v1->ptr, v2->ptr);
|
|
||||||
M_STR1NG_CONTRACT (v1);
|
M_STR1NG_CONTRACT (v1);
|
||||||
M_STR1NG_CONTRACT (v2);
|
M_STR1NG_CONTRACT (v2);
|
||||||
}
|
}
|
||||||
@@ -1389,15 +1458,15 @@ m_string_strim(m_string_t v, const char tab[])
|
|||||||
M_INLINE bool
|
M_INLINE bool
|
||||||
m_string_oor_equal_p(const m_string_t s, unsigned char n)
|
m_string_oor_equal_p(const m_string_t s, unsigned char n)
|
||||||
{
|
{
|
||||||
return (s->ptr == NULL) & (s->u.heap.alloc == ~(size_t)n);
|
return (s->u.buffer[sizeof (m_str1ng_heap_ct)-2] == 0) & (s->u.buffer[sizeof (m_str1ng_heap_ct)-1] == (char) ~n);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Set the uninitialized string to the OOR value */
|
/* Set the uninitialized string to the OOR value */
|
||||||
M_INLINE void
|
M_INLINE void
|
||||||
m_string_oor_set(m_string_t s, unsigned char n)
|
m_string_oor_set(m_string_t s, unsigned char n)
|
||||||
{
|
{
|
||||||
s->ptr = NULL;
|
s->u.buffer[sizeof (m_str1ng_heap_ct)-2] = 0;
|
||||||
s->u.heap.alloc = ~(size_t)n;
|
s->u.buffer[sizeof (m_str1ng_heap_ct)-1] = (char) ~n;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* I/O */
|
/* I/O */
|
||||||
@@ -1917,15 +1986,14 @@ m_string_it_set_ref(m_string_it_t it, m_string_t s, m_string_unicode_t new_u)
|
|||||||
size_t old_u_size = (size_t) (str - it->ptr);
|
size_t old_u_size = (size_t) (str - it->ptr);
|
||||||
// We need to replace old_u by new_u. Both are variable length
|
// We need to replace old_u by new_u. Both are variable length
|
||||||
size_t str_size = m_string_size(s);
|
size_t str_size = m_string_size(s);
|
||||||
m_str1ng_fit2size(s, str_size + new_u_size - old_u_size + 1);
|
|
||||||
ptr = m_str1ng_get_cstr(s); // ptr may be reallocated!
|
|
||||||
if (new_u_size != old_u_size) {
|
if (new_u_size != old_u_size) {
|
||||||
|
ptr = m_str1ng_fit2size(s, str_size + new_u_size - old_u_size + 1);
|
||||||
M_ASSUME( str_size+1 > (offset + old_u_size) );
|
M_ASSUME( str_size+1 > (offset + old_u_size) );
|
||||||
memmove(&ptr[offset+new_u_size], &ptr[offset + old_u_size],
|
memmove(&ptr[offset+new_u_size], &ptr[offset + old_u_size],
|
||||||
str_size+1 - offset - old_u_size);
|
str_size+1 - offset - old_u_size);
|
||||||
|
m_str1ng_set_size(s, str_size + new_u_size - old_u_size);
|
||||||
}
|
}
|
||||||
memcpy(&ptr[offset], &buffer[0], new_u_size);
|
memcpy(&ptr[offset], &buffer[0], new_u_size);
|
||||||
m_str1ng_set_size(s, str_size + new_u_size - old_u_size);
|
|
||||||
it->ptr = &ptr[offset];
|
it->ptr = &ptr[offset];
|
||||||
M_STR1NG_IT_CONTRACT(it);
|
M_STR1NG_IT_CONTRACT(it);
|
||||||
M_STR1NG_CONTRACT(s);
|
M_STR1NG_CONTRACT(s);
|
||||||
@@ -2055,8 +2123,9 @@ m_string_utf8_p(m_string_t str)
|
|||||||
#ifndef __cplusplus
|
#ifndef __cplusplus
|
||||||
/* Initialize a constant string with the given C string */
|
/* Initialize a constant string with the given C string */
|
||||||
# define M_STRING_CTE(s) \
|
# define M_STRING_CTE(s) \
|
||||||
(m_string_srcptr)((const m_string_t){{.u.heap = { .size = sizeof(s)-1, .alloc = sizeof(s) } , \
|
(m_string_srcptr)((const m_string_t){{.u.heap = { .size = sizeof(s)-1, \
|
||||||
.ptr = ((struct { long long _n; char _d[sizeof (s)]; }){ 0, s })._d }})
|
.alloc = { [sizeof(m_str1ng_size_t)-2] = 1, [sizeof(m_str1ng_size_t)-1] = 31}, \
|
||||||
|
.ptr = ((struct { long long _n; char _d[sizeof (s)]; }){ 0, s })._d }}})
|
||||||
#else
|
#else
|
||||||
namespace m_lib {
|
namespace m_lib {
|
||||||
template <unsigned int N>
|
template <unsigned int N>
|
||||||
@@ -2069,9 +2138,10 @@ namespace m_lib {
|
|||||||
inline m_aligned_string(const char lstr[])
|
inline m_aligned_string(const char lstr[])
|
||||||
{
|
{
|
||||||
this->string->u.heap.size = N -1;
|
this->string->u.heap.size = N -1;
|
||||||
this->string->u.heap.alloc = N;
|
this->string->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1;
|
||||||
|
this->string->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31;
|
||||||
memcpy (this->str, lstr, N);
|
memcpy (this->str, lstr, N);
|
||||||
this->string->ptr = this->str;
|
this->string->u.heap.ptr = this->str;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -2563,9 +2633,10 @@ namespace m_lib {
|
|||||||
/* Build dummy string to reuse m_string_get_str */ \
|
/* Build dummy string to reuse m_string_get_str */ \
|
||||||
uintptr_t ptr = (uintptr_t) &s->s[0]; \
|
uintptr_t ptr = (uintptr_t) &s->s[0]; \
|
||||||
m_string_t v2; \
|
m_string_t v2; \
|
||||||
v2->u.heap.size = strlen(s->s); \
|
v2->u.heap.size = (m_str1ng_size_t) strlen(s->s); \
|
||||||
v2->u.heap.alloc = v2->u.heap.size + 1; \
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1; \
|
||||||
v2->ptr = (char*)ptr; \
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31; \
|
||||||
|
v2->u.heap.ptr = (char*)ptr; \
|
||||||
m_string_get_str(v, v2, append); \
|
m_string_get_str(v, v2, append); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
@@ -2577,9 +2648,10 @@ namespace m_lib {
|
|||||||
/* Build dummy string to reuse m_string_get_str */ \
|
/* Build dummy string to reuse m_string_get_str */ \
|
||||||
uintptr_t ptr = (uintptr_t) &s->s[0]; \
|
uintptr_t ptr = (uintptr_t) &s->s[0]; \
|
||||||
m_string_t v2; \
|
m_string_t v2; \
|
||||||
v2->u.heap.size = strlen(s->s); \
|
v2->u.heap.size = (m_str1ng_size_t) strlen(s->s); \
|
||||||
v2->u.heap.alloc = v2->u.heap.size + 1; \
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-2] = 1; \
|
||||||
v2->ptr = (char*)ptr; \
|
v2->u.heap.alloc[sizeof(m_str1ng_size_t)-1] = 31; \
|
||||||
|
v2->u.heap.ptr = (char*)ptr; \
|
||||||
m_string_out_str(f, v2); \
|
m_string_out_str(f, v2); \
|
||||||
} \
|
} \
|
||||||
\
|
\
|
||||||
|
|||||||
+28
-2
@@ -765,11 +765,36 @@ static void test0(void)
|
|||||||
|
|
||||||
string_init(s1);
|
string_init(s1);
|
||||||
string_set_str(s1, "Hello");
|
string_set_str(s1, "Hello");
|
||||||
string_reserve(s1, 128);
|
for(i = 0 ; i < 2049; i++) {
|
||||||
assert(string_equal_str_p(s1, "Hello"));
|
string_reserve(s1, i);
|
||||||
|
assert(string_equal_str_p(s1, "Hello"));
|
||||||
|
}
|
||||||
string_clear(s1);
|
string_clear(s1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void test_rounding(void)
|
||||||
|
{
|
||||||
|
const size_t max = -(m_str1ng_size_t)1;
|
||||||
|
for(size_t a = max/2; a <= max; a++) {
|
||||||
|
unsigned char m, e;
|
||||||
|
size_t x = (size_t) a + a/2;
|
||||||
|
size_t nx = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) x);
|
||||||
|
if (nx >= x) {
|
||||||
|
assert( x == (m_str1ng_size_t)x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (max != -(size_t)1) {
|
||||||
|
for(size_t a = max; a <= 2*max; a++) {
|
||||||
|
unsigned char m, e;
|
||||||
|
size_t x = (size_t) a + a/2;
|
||||||
|
size_t nx = m_str1ng_round_capacity(&m, &e, (m_str1ng_size_t) x);
|
||||||
|
if (nx >= x) {
|
||||||
|
assert( x == (m_str1ng_size_t)x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void test_int(void)
|
static void test_int(void)
|
||||||
{
|
{
|
||||||
string_t s;
|
string_t s;
|
||||||
@@ -1005,6 +1030,7 @@ static void test_parse_standard_c_type(void)
|
|||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
test0();
|
test0();
|
||||||
|
test_rounding();
|
||||||
test_M_LET();
|
test_M_LET();
|
||||||
test_parse_standard_c_type();
|
test_parse_standard_c_type();
|
||||||
test_utf8_basic();
|
test_utf8_basic();
|
||||||
|
|||||||
Reference in new issue
Block a user