Files
scriptc/packages/runtime/src/scr_bytes.test.c
T
Chris Tate 0d59abe143 Pool small Buffers as Node does
Buffer.from of strings, arrays and Uint8Arrays, Buffer.allocUnsafe and
Buffer.concat now return views into a per-thread pool slab of
Buffer.poolSize bytes (64 KiB by default) when the size is below
Buffer.poolSize >>> 1, following Node's allocate, fromStringFast and
fromArrayLike: 8-byte aligned offsets, a new slab when a request does not
fit, and the four-bytes-per-unit reservation for strings. Buffer.poolSize
can be read and set, and Buffer.allocUnsafeSlow returns an unpooled Buffer.
A slab lives while any of its views does. Its ArrayBuffer cannot be
transferred, and structuredClone of a typed array view copies the whole
ArrayBuffer and keeps the view's offset. Buffer.alloc, allocUnsafe and
allocUnsafeSlow validate sizes with Node's ERR_OUT_OF_RANGE error.

The first pooled allocation finds the startup slab, which has the
default 64 KiB size. A request it cannot hold gets a slab of the current
Buffer.poolSize, as in Node, and the room is checked after every new
slab, so a Buffer.poolSize raised before the first pooled allocation
cannot place a reservation past the end of a 64 KiB slab. A reservation
larger than a fresh slab traps instead of writing past it.

Corpus 5531 (typed) and 5532 (untyped) raise Buffer.poolSize before the
first pooled allocation and allocate just under the new threshold
through each pooled path, each case in a fresh child process, and also
shrink Buffer.poolSize and change it between allocations. The
node-types fenced fixture no longer lists Buffer.poolSize, which now
compiles.
2026-10-16 01:39:10 -05:00

136 lines
5.5 KiB
C

#include "scr_runtime.h"
#include <assert.h>
#include <math.h>
#include <string.h>
long scr_bytes_live_count(void);
/* Foreign wrappers may alias without a shared owner and may be unaligned. */
static ScrBytes *external(uint8_t *data, ScrBytesElem elem, size_t count) {
ScrBytes *view = scr_bytes_from_external(data, count * scr_bytes_elem_size(elem));
view->elem = elem;
view->len = count;
return view;
}
static void external_set(void) {
const size_t starts[] = {1, 9, 41, 3001};
uint8_t storage[6000], expected[6000];
for (ScrBytesElem from = SCR_BYTES_U8; from <= SCR_BYTES_U8C; from++) {
for (ScrBytesElem to = SCR_BYTES_U8; to <= SCR_BYTES_U8C; to++) {
for (size_t j = 0; j < sizeof starts / sizeof starts[0]; j++) {
memset(storage, 0x5a, sizeof storage);
ScrBytes *src = external(storage + 9, from, 259);
ScrBytes *dst = external(storage + starts[j], to, 263);
for (size_t i = 0; i < src->len; i++) scr_bytes_set(src, i, (double)i * 3.5 - 257);
memcpy(expected, storage, sizeof storage);
ScrBytes *want = external(expected + starts[j], to, 263);
for (size_t i = 0; i < src->len; i++) scr_bytes_set(want, i + 2, scr_bytes_get(src, i));
scr_bytes_set_from(dst, src, 2);
assert(!scr_exc_pending());
assert(memcmp(storage, expected, sizeof storage) == 0);
scr_bytes_release(want);
scr_bytes_release(dst);
scr_bytes_release(src);
}
}
}
}
static void copied_storage(void) {
/* Same-kind copies preserve NaN payloads, signed zero and ownership. */
const uint64_t bits[] = {UINT64_C(0x7ff8000000000042), UINT64_C(0x8000000000000000),
UINT64_C(0xfff0000000000000)};
ScrBytes *src = scr_bytes_new(SCR_BYTES_F64, 3);
memcpy(src->data, bits, sizeof bits);
ScrBytes *copy = scr_bytes_convert(SCR_BYTES_F64, src);
ScrBytes *slice = scr_bytes_slice(src, 0, 3);
assert(copy->data != src->data && slice->data != src->data);
assert(memcmp(copy->data, bits, sizeof bits) == 0);
assert(memcmp(slice->data, bits, sizeof bits) == 0);
ScrBytes *view = scr_bytes_subarray(copy, 1, 3);
scr_bytes_release(copy);
scr_bytes_release(src);
assert(memcmp(view->data, bits + 1, sizeof bits - sizeof bits[0]) == 0);
scr_bytes_release(view);
scr_bytes_release(slice);
}
static void filled_storage(void) {
uint8_t storage[2200], expected[2200];
const double values[] = {-0.0, -1.5, 2.5, 255.5, 4294967297.0, NAN, INFINITY};
for (ScrBytesElem elem = SCR_BYTES_U8; elem <= SCR_BYTES_U8C; elem++) {
for (size_t j = 0; j < sizeof values / sizeof values[0]; j++) {
memset(storage, 0xa5, sizeof storage);
memcpy(expected, storage, sizeof storage);
ScrBytes *dst = external(storage + 1, elem, 263);
ScrBytes *want = external(expected + 1, elem, 263);
for (size_t i = 2; i < 261; i++) scr_bytes_set(want, i, values[j]);
ScrBytes *result = scr_bytes_fill_elem(dst, values[j], 2, 261);
assert(result == dst && dst->rc == 2);
assert(memcmp(storage, expected, sizeof storage) == 0);
scr_bytes_release(result);
scr_bytes_release(dst);
scr_bytes_release(want);
}
}
}
void scr_bytes_test_bulk_storage(void) {
long before = scr_bytes_live_count();
external_set();
copied_storage();
filled_storage();
assert(scr_bytes_live_count() == before);
}
/* The Buffer pool: views into one slab at 8-byte aligned offsets, the
* threshold, a slab whose size is not a multiple of 8, the slab's lifetime
* through its views, and the reset that drops the pool's own reference. */
void scr_bytes_test_buffer_pool(void) {
long before = scr_bytes_live_count();
scr_buffer_pool_reset();
ScrBytes *a = scr_buffer_alloc_unsafe(3);
ScrBytes *b = scr_buffer_alloc_unsafe(9);
ScrBytes *c = scr_buffer_alloc_unsafe(1);
assert(a->backing && a->backing == b->backing && b->backing == c->backing);
assert(a->backing->len == 65536 && a->backing->untransferable);
assert(scr_bytes_byte_offset(a) == 0 && scr_bytes_byte_offset(b) == 8 &&
scr_bytes_byte_offset(c) == 24);
ScrBytes *slab = scr_bytes_retain(a->backing);
ScrBytes *big = scr_buffer_alloc_unsafe(32768);
assert(big && !big->backing && big->len == 32768);
ScrBytes *edge = scr_buffer_alloc_unsafe(32767);
assert(edge->backing == slab);
/* A 60-byte slab: after offset 56 four bytes remain, then none. */
scr_buffer_set_pool_size(60);
ScrBytes *t = scr_buffer_alloc_unsafe(29);
while (scr_bytes_byte_offset(t) != 0) {
scr_bytes_release(t);
t = scr_buffer_alloc_unsafe(29);
}
assert(t->backing->len == 60);
ScrBytes *u = scr_buffer_alloc_unsafe(20);
ScrStr *euro = scr_str_new("\xe2\x82\xac", 3), *utf16 = scr_str_new("utf16le", 7);
ScrBytes *w = scr_buffer_from_str(euro, utf16);
assert(w->backing == t->backing && scr_bytes_byte_offset(w) == 56 && w->len == 2);
assert(w->data[0] == 0xac && w->data[1] == 0x20);
ScrBytes *x = scr_buffer_alloc_unsafe(1);
assert(x->backing != t->backing && scr_bytes_byte_offset(x) == 0);
scr_buffer_set_pool_size(0);
ScrBytes *y = scr_buffer_alloc_unsafe(1);
assert(!y->backing);
ScrBytes *views[] = {a, b, c, edge, t, u, w, x, y, big};
for (size_t i = 0; i < sizeof views / sizeof views[0]; i++) scr_bytes_release(views[i]);
/* The first slab lives while a reference to it does. */
assert(slab->rc == 1);
scr_bytes_release(slab);
scr_str_release(euro);
scr_str_release(utf16);
scr_buffer_pool_reset();
assert(scr_buffer_pool_size() == 65536);
assert(scr_bytes_live_count() == before);
}