Allocate template literals at their exact length

A template literal of strings and numbers reserved 32 bytes for each
number, the longest number text, and kept that capacity for the result's
lifetime: an 8-byte name built from two numbers used a 96-byte block. The
numbers now format into a stack buffer first, so the result is allocated
once at its exact length.
This commit is contained in:
Chris Tate
2026-10-26 21:01:36 -05:00
parent 807f5f6712
commit 48ccee523e
3 changed files with 64 additions and 11 deletions
+22 -11
View File
@@ -706,31 +706,42 @@ static inline size_t scr_utf16_units_short(const char *data, size_t len) {
}
/* A fresh concatenation (no head, the template-literal shape): numbers
* format straight into the result, which reserves scr_f64_to_str's whole
* 32-byte buffer for each. */
* format into a stack buffer first, so the result is allocated at its exact
* length. */
#define SCR_F64_TEXT_MAX 32
static __attribute__((noinline)) ScrStr *scr_str_concat_fresh(ScrStr *const *parts,
const double *nums, size_t count) {
size_t reserve = 0;
char texts[SCR_STR_MIXED_MAX * SCR_F64_TEXT_MAX];
size_t text_len[SCR_STR_MIXED_MAX];
size_t len = 0, used = 0;
bool ascii = true;
for (size_t i = 0; i < count; i++) {
size_t n = parts[i] ? parts[i]->len : SCR_F64_TEXT_MAX;
if (n > SCR_STR_MAX_CAP - reserve) scr_oom();
reserve += n;
if (parts[i]) ascii &= parts[i]->ascii;
size_t n;
if (parts[i]) {
n = parts[i]->len;
ascii &= parts[i]->ascii;
} else {
n = scr_f64_to_str(nums[i], texts + used);
text_len[i] = n;
used += n;
}
if (n > SCR_STR_MAX_CAP - len) scr_oom();
len += n;
}
ScrStr *out = scr_str_take_spare(reserve);
if (!out) out = scr_str_alloc(reserve, reserve);
ScrStr *out = scr_str_take_spare(len);
if (!out) out = scr_str_alloc(len, len);
char *w = out->data;
const char *text = texts;
for (size_t i = 0; i < count; i++) {
if (parts[i]) {
scr_copy_bytes(w, parts[i]->data, parts[i]->len);
w += parts[i]->len;
} else {
w += scr_f64_to_str(nums[i], w);
scr_copy_bytes(w, text, text_len[i]);
w += text_len[i];
text += text_len[i];
}
}
size_t len = (size_t)(w - out->data);
out->len = len;
out->data[len] = '\0';
out->ascii = ascii;
+7
View File
@@ -88,6 +88,13 @@ test("string methods match Node on committed oracle cases", async () => {
expect(r.stderr.trim()).toMatch(/^(\d+)\/\1 cases passed$/);
});
// A template literal of strings and numbers allocates exactly its length:
// numbers format before the result is sized, not into a reserved maximum.
test("template-literal concatenation allocates its exact length", async () => {
const { stderr } = await execFileAsync(bin, ["--concat-capacity"]);
expect(stderr.trim()).toBe("0 concat capacity failures");
});
// repeat(count) with count < 0 or Infinity is Node's catchable RangeError
// ("Invalid count value: -1"); the runtime leaves it pending.
test.each([
+35
View File
@@ -982,7 +982,42 @@ static void ordering_asserts(void) {
}
}
/* Template-literal concatenations (no head) of strings and numbers: the
* result holds exactly its bytes, whatever the numbers format to. */
static int check_concat_capacity(void) {
ScrStr *a = scr_str_new("f", 1), *b = scr_str_new("_d", 2), *c = scr_str_new("", 0);
struct {
ScrStr *parts[4];
double nums[4];
size_t count;
const char *expected;
} cases[] = {
{{a, NULL, b, NULL}, {0, 7, 0, 2230}, 4, "f7_d2230"},
{{NULL, a}, {-1.5e-7, 0}, 2, "-1.5e-7f"},
{{NULL, NULL, NULL}, {1.7976931348623157e308, 0.1, -0.0}, 3, "1.7976931348623157e+3080.10"},
{{c, NULL, c}, {0, 42, 0}, 3, "42"},
};
int failed = 0;
for (size_t i = 0; i < sizeof cases / sizeof cases[0]; i++) {
ScrStr *r = scr_str_concat_mixed(NULL, cases[i].parts, cases[i].nums, cases[i].count);
size_t want = strlen(cases[i].expected);
if (r->len != want || memcmp(r->data, cases[i].expected, want) != 0 || r->data[want] != '\0' ||
r->cap != r->len) {
fprintf(stderr, "concat case %zu: got \"%.*s\" len %zu cap %zu, want \"%s\" len %zu\n", i,
(int)r->len, r->data, r->len, (size_t)r->cap, cases[i].expected, want);
failed++;
}
scr_str_release(r);
}
scr_str_release(a);
scr_str_release(b);
scr_str_release(c);
fprintf(stderr, "%d concat capacity failures\n", failed);
return failed != 0;
}
int main(int argc, char **argv) {
if (argc > 1 && strcmp(argv[1], "--concat-capacity") == 0) return check_concat_capacity();
if (argc > 1 && strncmp(argv[1], "--crash-repeat", 14) == 0) {
ScrStr *s = scr_str_new("ab", 2);
double count = strcmp(argv[1], "--crash-repeat-inf") == 0