mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-31 07:23:06 +08:00
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:
@@ -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;
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user