@mikrojs/quickjs 0.17.0 → 0.17.1-pr-300.20260731003316
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apply-patches.js +100 -13
- package/deps/quickjs/qjsc.c +72 -0
- package/deps/quickjs/quickjs.c +533 -68
- package/deps/quickjs/quickjs.h +14 -0
- package/package.json +1 -1
- package/postinstall.js +17 -1
- package/quickjs.cmake +29 -0
package/apply-patches.js
CHANGED
|
@@ -14,7 +14,9 @@
|
|
|
14
14
|
* @mikrojs/native (which calls the patched module-management symbols).
|
|
15
15
|
*/
|
|
16
16
|
import {execFileSync} from 'node:child_process'
|
|
17
|
-
import {
|
|
17
|
+
import {createHash} from 'node:crypto'
|
|
18
|
+
import {existsSync, readdirSync, readFileSync, writeFileSync} from 'node:fs'
|
|
19
|
+
import {tmpdir} from 'node:os'
|
|
18
20
|
import {dirname, join} from 'node:path'
|
|
19
21
|
import {fileURLToPath} from 'node:url'
|
|
20
22
|
|
|
@@ -22,31 +24,116 @@ const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
|
22
24
|
const qjsDir = join(__dirname, 'deps', 'quickjs')
|
|
23
25
|
const patchesDir = join(__dirname, 'patches')
|
|
24
26
|
|
|
27
|
+
function seriesHash(files) {
|
|
28
|
+
const hash = createHash('sha256')
|
|
29
|
+
for (const f of files) hash.update(readFileSync(join(patchesDir, f)))
|
|
30
|
+
return hash.digest('hex')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function applyAll(files) {
|
|
34
|
+
for (const f of files) {
|
|
35
|
+
try {
|
|
36
|
+
execFileSync('git', ['apply', join(patchesDir, f)], {cwd: qjsDir, stdio: 'inherit'})
|
|
37
|
+
console.log(`@mikrojs/quickjs: applied patch ${f}`)
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(`@mikrojs/quickjs: failed to apply ${f}`, err.message)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
25
45
|
export function applyPatches() {
|
|
46
|
+
// Published tarballs ship pre-patched sources and exclude patches/ from
|
|
47
|
+
// the package files, so consumers return here and never need git.
|
|
26
48
|
if (!existsSync(patchesDir)) return
|
|
27
49
|
const files = readdirSync(patchesDir)
|
|
28
50
|
.filter((f) => f.endsWith('.patch'))
|
|
29
51
|
.sort()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
52
|
+
if (files.length === 0) return
|
|
53
|
+
|
|
54
|
+
// Patches may stack (a later patch can modify code an earlier one added),
|
|
55
|
+
// so they are only meaningful as a whole series applied in order to a
|
|
56
|
+
// pristine tree. A stamp records which series the tree currently carries.
|
|
57
|
+
const stampFile = join(qjsDir, '.patches-stamp')
|
|
58
|
+
const hash = seriesHash(files)
|
|
59
|
+
if (existsSync(stampFile) && readFileSync(stampFile, 'utf8').trim() === hash) {
|
|
60
|
+
// The stamp can outlive the tree it describes (e.g. `git submodule
|
|
61
|
+
// update` resets sources but leaves untracked files); trust it only if
|
|
62
|
+
// the series bounds are actually present. First and last together catch
|
|
63
|
+
// both a reset tree and a partially reverted one.
|
|
64
|
+
try {
|
|
65
|
+
for (const probe of [files[0], files[files.length - 1]]) {
|
|
66
|
+
execFileSync('git', ['apply', '--reverse', '--check', join(patchesDir, probe)], {
|
|
67
|
+
cwd: qjsDir,
|
|
68
|
+
stdio: 'pipe',
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
return // series already applied
|
|
72
|
+
} catch {
|
|
73
|
+
// stale stamp — fall through and re-apply
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let inRepo = true
|
|
78
|
+
try {
|
|
79
|
+
execFileSync('git', ['-C', qjsDir, 'rev-parse', 'HEAD'], {stdio: 'pipe'})
|
|
80
|
+
} catch {
|
|
81
|
+
inRepo = false
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (!inRepo) {
|
|
85
|
+
// Sources without git metadata (should not happen: tarballs exclude
|
|
86
|
+
// patches/ and return above). Refuse rather than guess.
|
|
87
|
+
console.error('@mikrojs/quickjs: patches/ present but deps/quickjs is not a git checkout')
|
|
88
|
+
process.exit(1)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// In-repo: reset to the pinned commit, then apply the whole series.
|
|
92
|
+
// Only reset when the tree state is accounted for (a previous series
|
|
93
|
+
// stamp, a clean tree, or a tree matching the pre-stamp-era series);
|
|
94
|
+
// otherwise refuse rather than discard unknown local edits. Untracked
|
|
95
|
+
// files (editor droppings, agent scratch dirs) are ignored: they cannot
|
|
96
|
+
// conflict with `git apply` of tracked-file patches, and `checkout -- .`
|
|
97
|
+
// could not remove them anyway.
|
|
98
|
+
const dirty =
|
|
99
|
+
execFileSync('git', ['-C', qjsDir, 'status', '--porcelain', '--untracked-files=no'], {
|
|
100
|
+
encoding: 'utf8',
|
|
101
|
+
})
|
|
102
|
+
.split('\n')
|
|
103
|
+
.filter((l) => l.trim()).length > 0
|
|
104
|
+
if (dirty && !existsSync(stampFile)) {
|
|
105
|
+
// Pre-stamp checkouts have some prefix of the series applied; the
|
|
106
|
+
// first patch reverse-checking is strong evidence of that state.
|
|
33
107
|
try {
|
|
34
|
-
execFileSync('git', ['apply', '--reverse', '--check',
|
|
108
|
+
execFileSync('git', ['apply', '--reverse', '--check', join(patchesDir, files[0])], {
|
|
35
109
|
cwd: qjsDir,
|
|
36
110
|
stdio: 'pipe',
|
|
37
111
|
})
|
|
38
|
-
continue // already applied
|
|
39
112
|
} catch {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
console.log(`@mikrojs/quickjs: applied patch ${f}`)
|
|
45
|
-
} catch (err) {
|
|
46
|
-
console.error(`@mikrojs/quickjs: failed to apply ${f}`, err.message)
|
|
113
|
+
console.error(
|
|
114
|
+
'@mikrojs/quickjs: submodule tree has local changes that do not match the patch series.\n' +
|
|
115
|
+
`Commit them as a patch or discard them: git -C ${qjsDir} checkout -- .`,
|
|
116
|
+
)
|
|
47
117
|
process.exit(1)
|
|
48
118
|
}
|
|
49
119
|
}
|
|
120
|
+
if (dirty) {
|
|
121
|
+
// The tree is about to be discarded and re-derived from the patch
|
|
122
|
+
// series. This also runs from CMake configure, i.e. mid-development
|
|
123
|
+
// when someone is iterating on a patch — keep a backup of whatever
|
|
124
|
+
// tracked changes are thrown away so an unregenerated edit is
|
|
125
|
+
// recoverable instead of silently gone.
|
|
126
|
+
const diff = execFileSync('git', ['-C', qjsDir, 'diff'], {encoding: 'utf8'})
|
|
127
|
+
if (diff.length > 0) {
|
|
128
|
+
const backup = join(tmpdir(), `quickjs-discarded-${Date.now()}.patch`)
|
|
129
|
+
writeFileSync(backup, diff)
|
|
130
|
+
console.log(`@mikrojs/quickjs: re-deriving submodule tree from the patch series`)
|
|
131
|
+
console.log(`@mikrojs/quickjs: previous tracked changes saved to ${backup}`)
|
|
132
|
+
}
|
|
133
|
+
execFileSync('git', ['-C', qjsDir, 'checkout', '--', '.'], {stdio: 'inherit'})
|
|
134
|
+
}
|
|
135
|
+
applyAll(files)
|
|
136
|
+
writeFileSync(stampFile, hash + '\n')
|
|
50
137
|
}
|
|
51
138
|
|
|
52
139
|
// Run directly: sync the submodule, then apply patches.
|
package/deps/quickjs/qjsc.c
CHANGED
|
@@ -331,6 +331,65 @@ static const char main_c_template2[] =
|
|
|
331
331
|
|
|
332
332
|
#define PROG_NAME "qjsc"
|
|
333
333
|
|
|
334
|
+
/* mikrojs patch: load a frozen atom table (see JS_PreloadFrozenAtoms) from
|
|
335
|
+
a little-endian binary file: u32 count, then per atom u32 len + bytes. */
|
|
336
|
+
static int load_frozen_atoms(JSRuntime *rt, const char *filename)
|
|
337
|
+
{
|
|
338
|
+
FILE *f;
|
|
339
|
+
uint8_t hdr[4];
|
|
340
|
+
uint32_t count = 0, i, len;
|
|
341
|
+
char **names = NULL;
|
|
342
|
+
uint32_t *lens = NULL;
|
|
343
|
+
int ret = -1;
|
|
344
|
+
|
|
345
|
+
f = fopen(filename, "rb");
|
|
346
|
+
if (!f) {
|
|
347
|
+
fprintf(stderr, "qjsc: could not open atom table '%s'\n", filename);
|
|
348
|
+
return -1;
|
|
349
|
+
}
|
|
350
|
+
if (fread(hdr, 1, 4, f) != 4)
|
|
351
|
+
goto done;
|
|
352
|
+
count = hdr[0] | (hdr[1] << 8) | (hdr[2] << 16) | ((uint32_t)hdr[3] << 24);
|
|
353
|
+
names = calloc(count, sizeof(*names));
|
|
354
|
+
lens = calloc(count, sizeof(*lens));
|
|
355
|
+
if (!names || !lens)
|
|
356
|
+
goto done;
|
|
357
|
+
for (i = 0; i < count; i++) {
|
|
358
|
+
if (fread(hdr, 1, 4, f) != 4)
|
|
359
|
+
goto done;
|
|
360
|
+
len = hdr[0] | (hdr[1] << 8) | (hdr[2] << 16) | ((uint32_t)hdr[3] << 24);
|
|
361
|
+
if (len > (1u << 20))
|
|
362
|
+
goto done; /* corrupted table; also guards the len + 1 overflow */
|
|
363
|
+
names[i] = malloc(len + 1);
|
|
364
|
+
if (!names[i])
|
|
365
|
+
goto done;
|
|
366
|
+
if (len != 0 && fread(names[i], 1, len, f) != len)
|
|
367
|
+
goto done;
|
|
368
|
+
names[i][len] = '\0';
|
|
369
|
+
lens[i] = len;
|
|
370
|
+
}
|
|
371
|
+
if (JS_PreloadFrozenAtoms(rt, (const char *const *)names, lens, count)) {
|
|
372
|
+
fprintf(stderr, "qjsc: failed to preload frozen atom table\n");
|
|
373
|
+
ret = -2; /* already reported */
|
|
374
|
+
goto done;
|
|
375
|
+
}
|
|
376
|
+
/* The runtime keeps pointers into names/lens for lazy atom
|
|
377
|
+
materialization; leak them intentionally (process-lifetime tool). */
|
|
378
|
+
fclose(f);
|
|
379
|
+
return 0;
|
|
380
|
+
done:
|
|
381
|
+
if (ret == -1)
|
|
382
|
+
fprintf(stderr, "qjsc: bad atom table '%s'\n", filename);
|
|
383
|
+
if (names) {
|
|
384
|
+
for (i = 0; i < count; i++)
|
|
385
|
+
free(names[i]);
|
|
386
|
+
free(names);
|
|
387
|
+
}
|
|
388
|
+
free(lens);
|
|
389
|
+
fclose(f);
|
|
390
|
+
return ret;
|
|
391
|
+
}
|
|
392
|
+
|
|
334
393
|
void help(void)
|
|
335
394
|
{
|
|
336
395
|
printf("QuickJS-ng Compiler version %s\n"
|
|
@@ -349,6 +408,7 @@ void help(void)
|
|
|
349
408
|
"-p prefix set the prefix of the generated C names\n"
|
|
350
409
|
"-P do not add default system modules\n"
|
|
351
410
|
"-s strip the source code, specify twice to also strip debug info\n"
|
|
411
|
+
"-A file preload a frozen atom table (binary: u32le count, then u32le len + bytes per atom)\n"
|
|
352
412
|
"-S n set the maximum stack size to 'n' bytes (default=%d)\n",
|
|
353
413
|
JS_GetVersion(),
|
|
354
414
|
JS_DEFAULT_STACK_SIZE);
|
|
@@ -407,6 +467,8 @@ int main(int argc, char **argv)
|
|
|
407
467
|
namelist_t dynamic_module_list;
|
|
408
468
|
bool load_system_modules = true;
|
|
409
469
|
|
|
470
|
+
const char *frozen_atoms_file = NULL;
|
|
471
|
+
|
|
410
472
|
out_filename = NULL;
|
|
411
473
|
script_name = NULL;
|
|
412
474
|
output_type = OUTPUT_C;
|
|
@@ -462,6 +524,14 @@ int main(int argc, char **argv)
|
|
|
462
524
|
output_type = OUTPUT_C_MAIN;
|
|
463
525
|
continue;
|
|
464
526
|
}
|
|
527
|
+
if (opt == 'A') {
|
|
528
|
+
if (!optarg) {
|
|
529
|
+
check_hasarg(optind, argc, opt);
|
|
530
|
+
optarg = argv[optind++];
|
|
531
|
+
}
|
|
532
|
+
frozen_atoms_file = optarg;
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
465
535
|
if (opt == 'n') {
|
|
466
536
|
if (!optarg) {
|
|
467
537
|
check_hasarg(optind, argc, opt);
|
|
@@ -571,6 +641,8 @@ int main(int argc, char **argv)
|
|
|
571
641
|
outfile = fo;
|
|
572
642
|
|
|
573
643
|
rt = JS_NewRuntime();
|
|
644
|
+
if (frozen_atoms_file && load_frozen_atoms(rt, frozen_atoms_file))
|
|
645
|
+
exit(1);
|
|
574
646
|
ctx = JS_NewContext(rt);
|
|
575
647
|
|
|
576
648
|
/* loader for ES6 modules */
|
package/deps/quickjs/quickjs.c
CHANGED
|
@@ -279,6 +279,18 @@ struct JSRuntime {
|
|
|
279
279
|
uint32_t *atom_hash;
|
|
280
280
|
JSAtomStruct **atom_array;
|
|
281
281
|
int atom_free_index; /* 0 = none */
|
|
282
|
+
/* mikrojs patch: frozen atom table (JS_PreloadFrozenAtoms). Atoms below
|
|
283
|
+
frozen_atom_end serialize as final ids; hash is nonzero once loaded.
|
|
284
|
+
Ids in [frozen_first, frozen_first + frozen_count) are reserved slots
|
|
285
|
+
whose JSString bodies materialize lazily from the (flash-resident)
|
|
286
|
+
frozen_names table on first string use. */
|
|
287
|
+
uint32_t frozen_atom_end;
|
|
288
|
+
uint32_t frozen_atom_hash;
|
|
289
|
+
const char *const *frozen_names;
|
|
290
|
+
const uint32_t *frozen_lens;
|
|
291
|
+
uint32_t *frozen_sorted; /* rt-owned indexes into frozen_names, sorted */
|
|
292
|
+
uint32_t frozen_count;
|
|
293
|
+
JSAtom frozen_first;
|
|
282
294
|
|
|
283
295
|
JSClassID js_class_id_alloc; /* counter for user defined classes */
|
|
284
296
|
int class_count; /* size of class_array */
|
|
@@ -790,7 +802,13 @@ typedef struct JSFunctionBytecode {
|
|
|
790
802
|
uint8_t super_allowed : 1;
|
|
791
803
|
uint8_t arguments_allowed : 1;
|
|
792
804
|
uint8_t backtrace_barrier : 1; /* stop backtrace on this function */
|
|
793
|
-
/*
|
|
805
|
+
/* mikrojs patch: byte_code_buf points into the (ROM) input buffer of
|
|
806
|
+
JS_ReadObject and must not be freed or written */
|
|
807
|
+
uint8_t bc_in_rom : 1;
|
|
808
|
+
/* mikrojs patch: byte_code_buf is a separate heap allocation instead of
|
|
809
|
+
the tail of the function block (in-place read fallback) */
|
|
810
|
+
uint8_t bc_separate : 1;
|
|
811
|
+
/* XXX: 3 bits available */
|
|
794
812
|
uint8_t *byte_code_buf; /* (self pointer) */
|
|
795
813
|
int byte_code_len;
|
|
796
814
|
JSAtom func_name;
|
|
@@ -1598,6 +1616,15 @@ static void js_trigger_gc(JSRuntime *rt, size_t size)
|
|
|
1598
1616
|
JS_RunGC(rt);
|
|
1599
1617
|
rt->malloc_gc_threshold = rt->malloc_state.malloc_size +
|
|
1600
1618
|
(rt->malloc_state.malloc_size >> 1);
|
|
1619
|
+
/* mikrojs patch: never re-arm the threshold above the memory limit,
|
|
1620
|
+
else a single long job whose live size exceeds ~2/3 of the limit
|
|
1621
|
+
accumulates collectible garbage straight into OOM. */
|
|
1622
|
+
if (rt->malloc_state.malloc_limit &&
|
|
1623
|
+
rt->malloc_gc_threshold >
|
|
1624
|
+
rt->malloc_state.malloc_limit - (rt->malloc_state.malloc_limit >> 3)) {
|
|
1625
|
+
rt->malloc_gc_threshold =
|
|
1626
|
+
rt->malloc_state.malloc_limit - (rt->malloc_state.malloc_limit >> 3);
|
|
1627
|
+
}
|
|
1601
1628
|
}
|
|
1602
1629
|
}
|
|
1603
1630
|
|
|
@@ -1991,6 +2018,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
|
|
|
1991
2018
|
|
|
1992
2019
|
if (JS_InitAtoms(rt))
|
|
1993
2020
|
goto fail;
|
|
2021
|
+
rt->frozen_atom_end = JS_ATOM_END;
|
|
1994
2022
|
|
|
1995
2023
|
/* create the object, array and function classes */
|
|
1996
2024
|
if (init_class_range(rt, js_std_class_def, JS_CLASS_OBJECT,
|
|
@@ -2216,6 +2244,33 @@ static inline JSAtomStruct *atom_set_free(uint32_t v)
|
|
|
2216
2244
|
return (JSAtomStruct *)(((uintptr_t)v << 1) | 1);
|
|
2217
2245
|
}
|
|
2218
2246
|
|
|
2247
|
+
/* mikrojs patch: shared placeholder occupying reserved frozen-atom slots
|
|
2248
|
+
until their string materializes. Deliberately a valid, readable JSString
|
|
2249
|
+
(empty, narrow, type STRING) so walkers that only inspect atom_type/kind
|
|
2250
|
+
need no guard; only string-producing paths and the free/teardown loops
|
|
2251
|
+
check for it. Never hashed, never refcounted, never freed. */
|
|
2252
|
+
static JSAtomStruct js_lazy_atom_placeholder = {
|
|
2253
|
+
.header = {.ref_count = 1},
|
|
2254
|
+
.len = 0,
|
|
2255
|
+
.is_wide_char = 0,
|
|
2256
|
+
.hash = 0,
|
|
2257
|
+
.kind = JS_STRING_KIND_NORMAL,
|
|
2258
|
+
.atom_type = JS_ATOM_TYPE_STRING,
|
|
2259
|
+
.hash_next = 0,
|
|
2260
|
+
};
|
|
2261
|
+
|
|
2262
|
+
static inline bool js_atom_is_lazy(const JSAtomStruct *p)
|
|
2263
|
+
{
|
|
2264
|
+
return p == &js_lazy_atom_placeholder;
|
|
2265
|
+
}
|
|
2266
|
+
|
|
2267
|
+
/* mikrojs patch: frozen atoms are refcount-exempt like predefined atoms;
|
|
2268
|
+
the runtime pins them for its whole lifetime. */
|
|
2269
|
+
static inline bool js_atom_is_frozen_id(const JSRuntime *rt, JSAtom v)
|
|
2270
|
+
{
|
|
2271
|
+
return v - rt->frozen_first < rt->frozen_count;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2219
2274
|
/* Note: the string contents are uninitialized */
|
|
2220
2275
|
static JSString *js_alloc_string_rt(JSRuntime *rt, int max_len, int is_wide_char)
|
|
2221
2276
|
{
|
|
@@ -2363,7 +2418,7 @@ void JS_FreeRuntime(JSRuntime *rt)
|
|
|
2363
2418
|
|
|
2364
2419
|
for(i = 0; i < rt->atom_size; i++) {
|
|
2365
2420
|
JSAtomStruct *p = rt->atom_array[i];
|
|
2366
|
-
if (!atom_is_free(p) /* && p->str*/) {
|
|
2421
|
+
if (!atom_is_free(p) && !js_atom_is_lazy(p) /* && p->str*/) {
|
|
2367
2422
|
if (i >= JS_ATOM_END || p->header.ref_count != 1) {
|
|
2368
2423
|
if (!header_done) {
|
|
2369
2424
|
header_done = true;
|
|
@@ -2418,7 +2473,7 @@ void JS_FreeRuntime(JSRuntime *rt)
|
|
|
2418
2473
|
/* free the atoms */
|
|
2419
2474
|
for(i = 0; i < rt->atom_size; i++) {
|
|
2420
2475
|
JSAtomStruct *p = rt->atom_array[i];
|
|
2421
|
-
if (!atom_is_free(p)) {
|
|
2476
|
+
if (!atom_is_free(p) && !js_atom_is_lazy(p)) {
|
|
2422
2477
|
#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS
|
|
2423
2478
|
list_del(&p->link);
|
|
2424
2479
|
#endif
|
|
@@ -2427,6 +2482,7 @@ void JS_FreeRuntime(JSRuntime *rt)
|
|
|
2427
2482
|
}
|
|
2428
2483
|
js_free_rt(rt, rt->atom_array);
|
|
2429
2484
|
js_free_rt(rt, rt->atom_hash);
|
|
2485
|
+
js_free_rt(rt, rt->frozen_sorted);
|
|
2430
2486
|
js_free_rt(rt, rt->shape_hash);
|
|
2431
2487
|
#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS
|
|
2432
2488
|
if (check_dump_flag(rt, JS_DUMP_LEAKS) && !list_empty(&rt->string_list)) {
|
|
@@ -2952,7 +3008,7 @@ static __maybe_unused void JS_DumpAtoms(JSRuntime *rt)
|
|
|
2952
3008
|
printf("JSAtom table: {\n");
|
|
2953
3009
|
for(i = 0; i < rt->atom_size; i++) {
|
|
2954
3010
|
p = rt->atom_array[i];
|
|
2955
|
-
if (!atom_is_free(p)) {
|
|
3011
|
+
if (!atom_is_free(p) && !js_atom_is_lazy(p)) {
|
|
2956
3012
|
printf(" %d: { %d %08x ", i, p->atom_type, p->hash);
|
|
2957
3013
|
if (!(p->len == 0 && p->is_wide_char != 0))
|
|
2958
3014
|
JS_DumpString(rt, p);
|
|
@@ -3025,7 +3081,7 @@ JSAtom JS_DupAtomRT(JSRuntime *rt, JSAtom v)
|
|
|
3025
3081
|
{
|
|
3026
3082
|
JSAtomStruct *p;
|
|
3027
3083
|
|
|
3028
|
-
if (!__JS_AtomIsConst(v)) {
|
|
3084
|
+
if (!__JS_AtomIsConst(v) && !js_atom_is_frozen_id(rt, v)) {
|
|
3029
3085
|
p = rt->atom_array[v];
|
|
3030
3086
|
p->header.ref_count++;
|
|
3031
3087
|
}
|
|
@@ -3037,8 +3093,8 @@ JSAtom JS_DupAtom(JSContext *ctx, JSAtom v)
|
|
|
3037
3093
|
JSRuntime *rt;
|
|
3038
3094
|
JSAtomStruct *p;
|
|
3039
3095
|
|
|
3040
|
-
|
|
3041
|
-
|
|
3096
|
+
rt = ctx->rt;
|
|
3097
|
+
if (!__JS_AtomIsConst(v) && !js_atom_is_frozen_id(rt, v)) {
|
|
3042
3098
|
p = rt->atom_array[v];
|
|
3043
3099
|
p->header.ref_count++;
|
|
3044
3100
|
}
|
|
@@ -3091,6 +3147,133 @@ static JSAtom js_get_atom_index(JSRuntime *rt, JSAtomStruct *p)
|
|
|
3091
3147
|
return i;
|
|
3092
3148
|
}
|
|
3093
3149
|
|
|
3150
|
+
/* mikrojs patch: compare a JSString against a frozen table entry with a
|
|
3151
|
+
total order (length first, then charwise numeric) matching the order
|
|
3152
|
+
frozen_sorted was built with. */
|
|
3153
|
+
static int js_frozen_cmp_str(JSString *str, const uint8_t *name,
|
|
3154
|
+
uint32_t nlen)
|
|
3155
|
+
{
|
|
3156
|
+
uint32_t i, len = str->len;
|
|
3157
|
+
if (len != nlen)
|
|
3158
|
+
return len < nlen ? -1 : 1;
|
|
3159
|
+
for (i = 0; i < len; i++) {
|
|
3160
|
+
uint32_t c = str->is_wide_char ? str16(str)[i] : str8(str)[i];
|
|
3161
|
+
if (c != name[i])
|
|
3162
|
+
return c < name[i] ? -1 : 1;
|
|
3163
|
+
}
|
|
3164
|
+
return 0;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
/* mikrojs patch: find a frozen atom id by content, or JS_ATOM_NULL. */
|
|
3168
|
+
static JSAtom js_frozen_lookup(JSRuntime *rt, JSString *str)
|
|
3169
|
+
{
|
|
3170
|
+
uint32_t lo = 0, hi = rt->frozen_count;
|
|
3171
|
+
while (lo < hi) {
|
|
3172
|
+
uint32_t mid = (lo + hi) / 2;
|
|
3173
|
+
uint32_t idx = rt->frozen_sorted[mid];
|
|
3174
|
+
int cmp = js_frozen_cmp_str(str, (const uint8_t *)rt->frozen_names[idx],
|
|
3175
|
+
rt->frozen_lens[idx]);
|
|
3176
|
+
if (cmp == 0)
|
|
3177
|
+
return rt->frozen_first + idx;
|
|
3178
|
+
if (cmp < 0)
|
|
3179
|
+
hi = mid;
|
|
3180
|
+
else
|
|
3181
|
+
lo = mid + 1;
|
|
3182
|
+
}
|
|
3183
|
+
return JS_ATOM_NULL;
|
|
3184
|
+
}
|
|
3185
|
+
|
|
3186
|
+
/* mikrojs patch: materialize a reserved frozen atom's JSString from the
|
|
3187
|
+
flash name table and insert it into the hash. Returns NULL on OOM. */
|
|
3188
|
+
static JSAtomStruct *js_frozen_materialize(JSRuntime *rt, JSAtom v)
|
|
3189
|
+
{
|
|
3190
|
+
uint32_t idx = v - rt->frozen_first;
|
|
3191
|
+
const char *name = rt->frozen_names[idx];
|
|
3192
|
+
uint32_t len = rt->frozen_lens[idx];
|
|
3193
|
+
uint32_t h, h1;
|
|
3194
|
+
JSString *p;
|
|
3195
|
+
|
|
3196
|
+
p = js_alloc_string_rt(rt, len, 0);
|
|
3197
|
+
if (!p)
|
|
3198
|
+
return NULL;
|
|
3199
|
+
memcpy(str8(p), name, len);
|
|
3200
|
+
str8(p)[len] = '\0';
|
|
3201
|
+
h = hash_string8((const uint8_t *)name, len, JS_ATOM_TYPE_STRING);
|
|
3202
|
+
h &= JS_ATOM_HASH_MASK;
|
|
3203
|
+
p->hash = h;
|
|
3204
|
+
p->atom_type = JS_ATOM_TYPE_STRING;
|
|
3205
|
+
p->first_weak_ref = NULL;
|
|
3206
|
+
rt->atom_array[v] = p;
|
|
3207
|
+
h1 = h & (rt->atom_hash_size - 1);
|
|
3208
|
+
p->hash_next = rt->atom_hash[h1];
|
|
3209
|
+
rt->atom_hash[h1] = v;
|
|
3210
|
+
rt->atom_count++;
|
|
3211
|
+
if (unlikely(rt->atom_count >= rt->atom_count_resize))
|
|
3212
|
+
JS_ResizeAtomHash(rt, rt->atom_hash_size * 2);
|
|
3213
|
+
return p;
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
/* mikrojs patch: atom_array access that materializes reserved frozen slots
|
|
3217
|
+
on demand; NULL only on OOM. */
|
|
3218
|
+
static JSAtomStruct *js_atom_ensure_str(JSRuntime *rt, JSAtom v)
|
|
3219
|
+
{
|
|
3220
|
+
JSAtomStruct *p = rt->atom_array[v];
|
|
3221
|
+
if (unlikely(js_atom_is_lazy(p)))
|
|
3222
|
+
p = js_frozen_materialize(rt, v);
|
|
3223
|
+
return p;
|
|
3224
|
+
}
|
|
3225
|
+
|
|
3226
|
+
/* mikrojs patch: grow the atom array so at least one free slot exists.
|
|
3227
|
+
Extracted from __JS_NewAtom so frozen-slot reservation can share it. */
|
|
3228
|
+
static int js_atom_grow_free(JSRuntime *rt)
|
|
3229
|
+
{
|
|
3230
|
+
uint32_t new_size, start, i;
|
|
3231
|
+
JSAtomStruct **new_array;
|
|
3232
|
+
JSAtomStruct *p;
|
|
3233
|
+
|
|
3234
|
+
/* alloc new with size progression 3/2:
|
|
3235
|
+
4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092
|
|
3236
|
+
preallocating space for predefined atoms (at least 504).
|
|
3237
|
+
*/
|
|
3238
|
+
new_size = max_int(711, rt->atom_size * 3 / 2);
|
|
3239
|
+
if (new_size > JS_ATOM_MAX)
|
|
3240
|
+
return -1;
|
|
3241
|
+
/* XXX: should use realloc2 to use slack space */
|
|
3242
|
+
new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size);
|
|
3243
|
+
if (!new_array)
|
|
3244
|
+
return -1;
|
|
3245
|
+
/* Note: the atom 0 is not used */
|
|
3246
|
+
start = rt->atom_size;
|
|
3247
|
+
if (start == 0) {
|
|
3248
|
+
/* JS_ATOM_NULL entry */
|
|
3249
|
+
p = js_mallocz_rt(rt, sizeof(JSAtomStruct));
|
|
3250
|
+
if (!p) {
|
|
3251
|
+
js_free_rt(rt, new_array);
|
|
3252
|
+
return -1;
|
|
3253
|
+
}
|
|
3254
|
+
p->header.ref_count = 1; /* not refcounted */
|
|
3255
|
+
p->atom_type = JS_ATOM_TYPE_SYMBOL;
|
|
3256
|
+
#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS
|
|
3257
|
+
list_add_tail(&p->link, &rt->string_list);
|
|
3258
|
+
#endif
|
|
3259
|
+
new_array[0] = p;
|
|
3260
|
+
rt->atom_count++;
|
|
3261
|
+
start = 1;
|
|
3262
|
+
}
|
|
3263
|
+
rt->atom_size = new_size;
|
|
3264
|
+
rt->atom_array = new_array;
|
|
3265
|
+
rt->atom_free_index = start;
|
|
3266
|
+
for(i = start; i < new_size; i++) {
|
|
3267
|
+
uint32_t next;
|
|
3268
|
+
if (i == (new_size - 1))
|
|
3269
|
+
next = 0;
|
|
3270
|
+
else
|
|
3271
|
+
next = i + 1;
|
|
3272
|
+
rt->atom_array[i] = atom_set_free(next);
|
|
3273
|
+
}
|
|
3274
|
+
return 0;
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3094
3277
|
/* string case (internal). Return JS_ATOM_NULL if error. 'str' is
|
|
3095
3278
|
freed. */
|
|
3096
3279
|
static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|
@@ -3105,7 +3288,7 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|
|
3105
3288
|
/* str is the atom, return its index */
|
|
3106
3289
|
i = js_get_atom_index(rt, str);
|
|
3107
3290
|
/* reduce string refcount and increase atom's unless constant */
|
|
3108
|
-
if (__JS_AtomIsConst(i))
|
|
3291
|
+
if (__JS_AtomIsConst(i) || js_atom_is_frozen_id(rt, i))
|
|
3109
3292
|
str->header.ref_count--;
|
|
3110
3293
|
return i;
|
|
3111
3294
|
}
|
|
@@ -3121,12 +3304,23 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|
|
3121
3304
|
p->atom_type == atom_type &&
|
|
3122
3305
|
p->len == len &&
|
|
3123
3306
|
js_string_memcmp(p, str, len) == 0) {
|
|
3124
|
-
if (!__JS_AtomIsConst(i))
|
|
3307
|
+
if (!__JS_AtomIsConst(i) && !js_atom_is_frozen_id(rt, i))
|
|
3125
3308
|
p->header.ref_count++;
|
|
3126
3309
|
goto done;
|
|
3127
3310
|
}
|
|
3128
3311
|
i = p->hash_next;
|
|
3129
3312
|
}
|
|
3313
|
+
/* mikrojs patch: an equal string may exist as a reserved frozen
|
|
3314
|
+
atom that is not in the hash yet; materialize it instead of
|
|
3315
|
+
creating a second atom with a different id */
|
|
3316
|
+
if (atom_type == JS_ATOM_TYPE_STRING && rt->frozen_count != 0) {
|
|
3317
|
+
i = js_frozen_lookup(rt, str);
|
|
3318
|
+
if (i != JS_ATOM_NULL && js_atom_is_lazy(rt->atom_array[i])) {
|
|
3319
|
+
if (!js_frozen_materialize(rt, i))
|
|
3320
|
+
goto fail;
|
|
3321
|
+
goto done; /* frozen: refcount-exempt, str freed at done */
|
|
3322
|
+
}
|
|
3323
|
+
}
|
|
3130
3324
|
} else {
|
|
3131
3325
|
h1 = 0; /* avoid warning */
|
|
3132
3326
|
if (atom_type == JS_ATOM_TYPE_SYMBOL) {
|
|
@@ -3139,49 +3333,8 @@ static JSAtom __JS_NewAtom(JSRuntime *rt, JSString *str, int atom_type)
|
|
|
3139
3333
|
|
|
3140
3334
|
if (rt->atom_free_index == 0) {
|
|
3141
3335
|
/* allow new atom entries */
|
|
3142
|
-
|
|
3143
|
-
JSAtomStruct **new_array;
|
|
3144
|
-
|
|
3145
|
-
/* alloc new with size progression 3/2:
|
|
3146
|
-
4 6 9 13 19 28 42 63 94 141 211 316 474 711 1066 1599 2398 3597 5395 8092
|
|
3147
|
-
preallocating space for predefined atoms (at least 504).
|
|
3148
|
-
*/
|
|
3149
|
-
new_size = max_int(711, rt->atom_size * 3 / 2);
|
|
3150
|
-
if (new_size > JS_ATOM_MAX)
|
|
3336
|
+
if (js_atom_grow_free(rt))
|
|
3151
3337
|
goto fail;
|
|
3152
|
-
/* XXX: should use realloc2 to use slack space */
|
|
3153
|
-
new_array = js_realloc_rt(rt, rt->atom_array, sizeof(*new_array) * new_size);
|
|
3154
|
-
if (!new_array)
|
|
3155
|
-
goto fail;
|
|
3156
|
-
/* Note: the atom 0 is not used */
|
|
3157
|
-
start = rt->atom_size;
|
|
3158
|
-
if (start == 0) {
|
|
3159
|
-
/* JS_ATOM_NULL entry */
|
|
3160
|
-
p = js_mallocz_rt(rt, sizeof(JSAtomStruct));
|
|
3161
|
-
if (!p) {
|
|
3162
|
-
js_free_rt(rt, new_array);
|
|
3163
|
-
goto fail;
|
|
3164
|
-
}
|
|
3165
|
-
p->header.ref_count = 1; /* not refcounted */
|
|
3166
|
-
p->atom_type = JS_ATOM_TYPE_SYMBOL;
|
|
3167
|
-
#ifdef ENABLE_DUMPS // JS_DUMP_LEAKS
|
|
3168
|
-
list_add_tail(&p->link, &rt->string_list);
|
|
3169
|
-
#endif
|
|
3170
|
-
new_array[0] = p;
|
|
3171
|
-
rt->atom_count++;
|
|
3172
|
-
start = 1;
|
|
3173
|
-
}
|
|
3174
|
-
rt->atom_size = new_size;
|
|
3175
|
-
rt->atom_array = new_array;
|
|
3176
|
-
rt->atom_free_index = start;
|
|
3177
|
-
for(i = start; i < new_size; i++) {
|
|
3178
|
-
uint32_t next;
|
|
3179
|
-
if (i == (new_size - 1))
|
|
3180
|
-
next = 0;
|
|
3181
|
-
else
|
|
3182
|
-
next = i + 1;
|
|
3183
|
-
rt->atom_array[i] = atom_set_free(next);
|
|
3184
|
-
}
|
|
3185
3338
|
}
|
|
3186
3339
|
|
|
3187
3340
|
if (str) {
|
|
@@ -3262,6 +3415,132 @@ static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
|
|
3262
3415
|
return __JS_NewAtom(rt, p, atom_type);
|
|
3263
3416
|
}
|
|
3264
3417
|
|
|
3418
|
+
typedef struct JSFrozenSortCtx {
|
|
3419
|
+
const char *const *names;
|
|
3420
|
+
const uint32_t *lens;
|
|
3421
|
+
} JSFrozenSortCtx;
|
|
3422
|
+
|
|
3423
|
+
static int js_frozen_sort_cmp(const void *a, const void *b, void *opaque)
|
|
3424
|
+
{
|
|
3425
|
+
JSFrozenSortCtx *c = opaque;
|
|
3426
|
+
uint32_t ia = *(const uint32_t *)a, ib = *(const uint32_t *)b;
|
|
3427
|
+
uint32_t la = c->lens[ia], lb = c->lens[ib];
|
|
3428
|
+
int cmp;
|
|
3429
|
+
if (la != lb)
|
|
3430
|
+
return la < lb ? -1 : 1;
|
|
3431
|
+
cmp = memcmp(c->names[ia], c->names[ib], la);
|
|
3432
|
+
return cmp < 0 ? -1 : cmp > 0 ? 1 : 0;
|
|
3433
|
+
}
|
|
3434
|
+
|
|
3435
|
+
/* mikrojs patch: reserve a contiguous id range for a fixed atom table so
|
|
3436
|
+
* blobs compiled against the same list (in a runtime that ran the same
|
|
3437
|
+
* preload) reference them by final id with no relocation at load time.
|
|
3438
|
+
* The JSString bodies are NOT allocated here: reserved slots hold a shared
|
|
3439
|
+
* placeholder and materialize lazily from `names` on first string use, so
|
|
3440
|
+
* the boot-time heap cost is one atom_array slot per name. `names`/`lens`
|
|
3441
|
+
* must outlive the runtime (e.g. rodata). The writer/reader mapping is
|
|
3442
|
+
* validated via a hash over the pre-preload dynamic atoms and the table,
|
|
3443
|
+
* so any divergence rejects blobs instead of silently resolving atoms to
|
|
3444
|
+
* the wrong strings. Names must be raw 8-bit, unique, non-numeric strings.
|
|
3445
|
+
* Must run before any dynamic atom is freed. Returns 0 on success. */
|
|
3446
|
+
int JS_PreloadFrozenAtoms(JSRuntime *rt, const char *const *names,
|
|
3447
|
+
const uint32_t *lens, uint32_t count)
|
|
3448
|
+
{
|
|
3449
|
+
uint32_t i, h, slot, reserved;
|
|
3450
|
+
uint32_t *sorted;
|
|
3451
|
+
JSAtom first = 0;
|
|
3452
|
+
JSFrozenSortCtx sctx = {names, lens};
|
|
3453
|
+
|
|
3454
|
+
if (rt->frozen_atom_hash != 0 || rt->frozen_count != 0)
|
|
3455
|
+
return -1; /* already preloaded */
|
|
3456
|
+
if (count == 0)
|
|
3457
|
+
return 0;
|
|
3458
|
+
|
|
3459
|
+
sorted = js_malloc_rt(rt, sizeof(*sorted) * count);
|
|
3460
|
+
if (!sorted)
|
|
3461
|
+
return -1;
|
|
3462
|
+
for (i = 0; i < count; i++)
|
|
3463
|
+
sorted[i] = i;
|
|
3464
|
+
rqsort(sorted, count, sizeof(*sorted), js_frozen_sort_cmp, &sctx);
|
|
3465
|
+
for (i = 1; i < count; i++) {
|
|
3466
|
+
if (js_frozen_sort_cmp(&sorted[i - 1], &sorted[i], &sctx) == 0)
|
|
3467
|
+
goto fail_sorted; /* duplicate names break the id mapping */
|
|
3468
|
+
}
|
|
3469
|
+
|
|
3470
|
+
/* reserve contiguous ids; the free list is sequential on a fresh
|
|
3471
|
+
runtime, so pops come out ascending */
|
|
3472
|
+
reserved = 0;
|
|
3473
|
+
for (i = 0; i < count; i++) {
|
|
3474
|
+
if (rt->atom_free_index == 0 && js_atom_grow_free(rt))
|
|
3475
|
+
goto fail_slots;
|
|
3476
|
+
slot = rt->atom_free_index;
|
|
3477
|
+
rt->atom_free_index = atom_get_free(rt->atom_array[slot]);
|
|
3478
|
+
rt->atom_array[slot] = &js_lazy_atom_placeholder;
|
|
3479
|
+
if (i == 0) {
|
|
3480
|
+
first = slot;
|
|
3481
|
+
} else if (slot != first + i) {
|
|
3482
|
+
/* atoms were freed before preload; put this stray slot back
|
|
3483
|
+
first, then unwind the sequential prefix */
|
|
3484
|
+
rt->atom_array[slot] = atom_set_free(rt->atom_free_index);
|
|
3485
|
+
rt->atom_free_index = slot;
|
|
3486
|
+
goto fail_slots;
|
|
3487
|
+
}
|
|
3488
|
+
reserved++;
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
/* hash the pre-preload dynamic atoms (identical on writer and reader
|
|
3492
|
+
by construction) plus the frozen names: any divergence changes the
|
|
3493
|
+
mapping, so it must change the hash */
|
|
3494
|
+
h = 5381;
|
|
3495
|
+
for (i = JS_ATOM_END; i < (uint32_t)first; i++) {
|
|
3496
|
+
JSAtomStruct *p = rt->atom_array[i];
|
|
3497
|
+
if (atom_is_free(p)) {
|
|
3498
|
+
h = h * 33 ^ 0xfe;
|
|
3499
|
+
continue;
|
|
3500
|
+
}
|
|
3501
|
+
h = h * 33 ^ p->atom_type;
|
|
3502
|
+
if (p->is_wide_char) {
|
|
3503
|
+
uint32_t j;
|
|
3504
|
+
for (j = 0; j < p->len; j++) {
|
|
3505
|
+
h = h * 33 ^ (str16(p)[j] & 0xff);
|
|
3506
|
+
h = h * 33 ^ (str16(p)[j] >> 8);
|
|
3507
|
+
}
|
|
3508
|
+
} else {
|
|
3509
|
+
uint32_t j;
|
|
3510
|
+
for (j = 0; j < p->len; j++)
|
|
3511
|
+
h = h * 33 ^ str8(p)[j];
|
|
3512
|
+
}
|
|
3513
|
+
h = h * 33 ^ 0xff;
|
|
3514
|
+
}
|
|
3515
|
+
for (i = 0; i < count; i++) {
|
|
3516
|
+
const uint8_t *nb = (const uint8_t *)names[i];
|
|
3517
|
+
uint32_t j;
|
|
3518
|
+
for (j = 0; j < lens[i]; j++)
|
|
3519
|
+
h = h * 33 ^ nb[j];
|
|
3520
|
+
h = h * 33 ^ 0xff;
|
|
3521
|
+
}
|
|
3522
|
+
h = h * 33 ^ first;
|
|
3523
|
+
|
|
3524
|
+
rt->frozen_names = names;
|
|
3525
|
+
rt->frozen_lens = lens;
|
|
3526
|
+
rt->frozen_sorted = sorted;
|
|
3527
|
+
rt->frozen_count = count;
|
|
3528
|
+
rt->frozen_first = first;
|
|
3529
|
+
rt->frozen_atom_end = first + count;
|
|
3530
|
+
rt->frozen_atom_hash = h | 1; /* nonzero marks "loaded" */
|
|
3531
|
+
return 0;
|
|
3532
|
+
|
|
3533
|
+
fail_slots:
|
|
3534
|
+
/* return reserved slots to the free list */
|
|
3535
|
+
for (i = 0; i < reserved; i++) {
|
|
3536
|
+
rt->atom_array[first + i] = atom_set_free(rt->atom_free_index);
|
|
3537
|
+
rt->atom_free_index = first + i;
|
|
3538
|
+
}
|
|
3539
|
+
fail_sorted:
|
|
3540
|
+
js_free_rt(rt, sorted);
|
|
3541
|
+
return -1;
|
|
3542
|
+
}
|
|
3543
|
+
|
|
3265
3544
|
// XXX: `str` must be raw 8-bit contents. No UTF-8 encoded strings
|
|
3266
3545
|
static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len,
|
|
3267
3546
|
int atom_type)
|
|
@@ -3280,12 +3559,42 @@ static JSAtom __JS_FindAtom(JSRuntime *rt, const char *str, size_t len,
|
|
|
3280
3559
|
p->len == len &&
|
|
3281
3560
|
p->is_wide_char == 0 &&
|
|
3282
3561
|
memcmp(str8(p), str, len) == 0) {
|
|
3283
|
-
if (!__JS_AtomIsConst(i))
|
|
3562
|
+
if (!__JS_AtomIsConst(i) && !js_atom_is_frozen_id(rt, i))
|
|
3284
3563
|
p->header.ref_count++;
|
|
3285
3564
|
return i;
|
|
3286
3565
|
}
|
|
3287
3566
|
i = p->hash_next;
|
|
3288
3567
|
}
|
|
3568
|
+
/* mikrojs patch: a reserved frozen atom exists even before it is in
|
|
3569
|
+
the hash; report it (materialized) rather than "not found" */
|
|
3570
|
+
if (atom_type == JS_ATOM_TYPE_STRING && rt->frozen_count != 0) {
|
|
3571
|
+
uint32_t lo = 0, hi = rt->frozen_count;
|
|
3572
|
+
while (lo < hi) {
|
|
3573
|
+
uint32_t mid = (lo + hi) / 2;
|
|
3574
|
+
uint32_t idx = rt->frozen_sorted[mid];
|
|
3575
|
+
uint32_t nlen = rt->frozen_lens[idx];
|
|
3576
|
+
const uint8_t *name = (const uint8_t *)rt->frozen_names[idx];
|
|
3577
|
+
int cmp;
|
|
3578
|
+
if (len != nlen) {
|
|
3579
|
+
cmp = len < nlen ? -1 : 1;
|
|
3580
|
+
} else {
|
|
3581
|
+
cmp = memcmp(str, name, len);
|
|
3582
|
+
if (cmp != 0)
|
|
3583
|
+
cmp = cmp < 0 ? -1 : 1;
|
|
3584
|
+
}
|
|
3585
|
+
if (cmp == 0) {
|
|
3586
|
+
i = rt->frozen_first + idx;
|
|
3587
|
+
if (js_atom_is_lazy(rt->atom_array[i]) &&
|
|
3588
|
+
!js_frozen_materialize(rt, i))
|
|
3589
|
+
return JS_ATOM_NULL;
|
|
3590
|
+
return i;
|
|
3591
|
+
}
|
|
3592
|
+
if (cmp < 0)
|
|
3593
|
+
hi = mid;
|
|
3594
|
+
else
|
|
3595
|
+
lo = mid + 1;
|
|
3596
|
+
}
|
|
3597
|
+
}
|
|
3289
3598
|
return JS_ATOM_NULL;
|
|
3290
3599
|
}
|
|
3291
3600
|
|
|
@@ -3428,7 +3737,13 @@ static JSValue JS_NewSymbolFromAtom(JSContext *ctx, JSAtom descr,
|
|
|
3428
3737
|
|
|
3429
3738
|
assert(!__JS_AtomIsTaggedInt(descr));
|
|
3430
3739
|
assert(descr < rt->atom_size);
|
|
3431
|
-
|
|
3740
|
+
/* mikrojs patch: the symbol takes ownership of the description string,
|
|
3741
|
+
so a reserved frozen atom must materialize first — duping the shared
|
|
3742
|
+
lazy placeholder would mutate and eventually free a static. Reachable
|
|
3743
|
+
via OP_private_symbol when a #private field name is a frozen atom. */
|
|
3744
|
+
p = js_atom_ensure_str(rt, descr);
|
|
3745
|
+
if (unlikely(!p))
|
|
3746
|
+
return JS_ThrowOutOfMemory(ctx);
|
|
3432
3747
|
js_dup(JS_MKPTR(JS_TAG_STRING, p));
|
|
3433
3748
|
return JS_NewSymbolInternal(ctx, p, atom_type);
|
|
3434
3749
|
}
|
|
@@ -3470,6 +3785,15 @@ static const char *JS_AtomGetStrRT(JSRuntime *rt, char *buf, int buf_size,
|
|
|
3470
3785
|
} else {
|
|
3471
3786
|
JSAtomStruct *p = rt->atom_array[atom];
|
|
3472
3787
|
*buf = '\0';
|
|
3788
|
+
if (unlikely(js_atom_is_lazy(p))) {
|
|
3789
|
+
/* mikrojs patch: read straight from the frozen table; this
|
|
3790
|
+
path must not allocate (used while reporting errors) */
|
|
3791
|
+
uint32_t idx = atom - rt->frozen_first;
|
|
3792
|
+
utf8_encode_buf8(buf, buf_size,
|
|
3793
|
+
(const uint8_t *)rt->frozen_names[idx],
|
|
3794
|
+
rt->frozen_lens[idx]);
|
|
3795
|
+
return buf;
|
|
3796
|
+
}
|
|
3473
3797
|
if (atom_is_free(p)) {
|
|
3474
3798
|
snprintf(buf, buf_size, "<free %x>", atom);
|
|
3475
3799
|
} else if (p != NULL) {
|
|
@@ -3501,7 +3825,9 @@ static JSValue __JS_AtomToValue(JSContext *ctx, JSAtom atom, bool force_string)
|
|
|
3501
3825
|
JSRuntime *rt = ctx->rt;
|
|
3502
3826
|
JSAtomStruct *p;
|
|
3503
3827
|
assert(atom < rt->atom_size);
|
|
3504
|
-
p = rt
|
|
3828
|
+
p = js_atom_ensure_str(rt, atom);
|
|
3829
|
+
if (unlikely(!p))
|
|
3830
|
+
return JS_ThrowOutOfMemory(ctx);
|
|
3505
3831
|
if (p->atom_type == JS_ATOM_TYPE_STRING) {
|
|
3506
3832
|
goto ret_string;
|
|
3507
3833
|
} else if (force_string) {
|
|
@@ -3540,7 +3866,12 @@ static bool JS_AtomIsArrayIndex(JSContext *ctx, uint32_t *pval, JSAtom atom)
|
|
|
3540
3866
|
uint32_t val;
|
|
3541
3867
|
|
|
3542
3868
|
assert(atom < rt->atom_size);
|
|
3543
|
-
p = rt
|
|
3869
|
+
p = js_atom_ensure_str(rt, atom);
|
|
3870
|
+
if (unlikely(!p)) {
|
|
3871
|
+
/* OOM while materializing; a frozen name is not an index */
|
|
3872
|
+
*pval = 0;
|
|
3873
|
+
return false;
|
|
3874
|
+
}
|
|
3544
3875
|
if (p->atom_type == JS_ATOM_TYPE_STRING &&
|
|
3545
3876
|
is_num_string(&val, p) && val != -1) {
|
|
3546
3877
|
*pval = val;
|
|
@@ -3566,7 +3897,9 @@ static JSValue JS_AtomIsNumericIndex1(JSContext *ctx, JSAtom atom)
|
|
|
3566
3897
|
if (__JS_AtomIsTaggedInt(atom))
|
|
3567
3898
|
return js_int32(__JS_AtomToUInt32(atom));
|
|
3568
3899
|
assert(atom < rt->atom_size);
|
|
3569
|
-
p1 = rt
|
|
3900
|
+
p1 = js_atom_ensure_str(rt, atom);
|
|
3901
|
+
if (unlikely(!p1))
|
|
3902
|
+
return JS_ThrowOutOfMemory(ctx);
|
|
3570
3903
|
if (p1->atom_type != JS_ATOM_TYPE_STRING)
|
|
3571
3904
|
return JS_UNDEFINED;
|
|
3572
3905
|
p = p1;
|
|
@@ -3649,13 +3982,13 @@ static int JS_AtomIsNumericIndex(JSContext *ctx, JSAtom atom)
|
|
|
3649
3982
|
|
|
3650
3983
|
void JS_FreeAtom(JSContext *ctx, JSAtom v)
|
|
3651
3984
|
{
|
|
3652
|
-
if (!__JS_AtomIsConst(v))
|
|
3985
|
+
if (!__JS_AtomIsConst(v) && !js_atom_is_frozen_id(ctx->rt, v))
|
|
3653
3986
|
__JS_FreeAtom(ctx->rt, v);
|
|
3654
3987
|
}
|
|
3655
3988
|
|
|
3656
3989
|
void JS_FreeAtomRT(JSRuntime *rt, JSAtom v)
|
|
3657
3990
|
{
|
|
3658
|
-
if (!__JS_AtomIsConst(v))
|
|
3991
|
+
if (!__JS_AtomIsConst(v) && !js_atom_is_frozen_id(rt, v))
|
|
3659
3992
|
__JS_FreeAtom(rt, v);
|
|
3660
3993
|
}
|
|
3661
3994
|
|
|
@@ -7144,7 +7477,7 @@ static void compute_bytecode_size(JSFunctionBytecode *b, JSMemoryUsage_helper *h
|
|
|
7144
7477
|
if (b->closure_var) {
|
|
7145
7478
|
js_func_size += b->closure_var_count * sizeof(*b->closure_var);
|
|
7146
7479
|
}
|
|
7147
|
-
if (b->byte_code_buf) {
|
|
7480
|
+
if (b->byte_code_buf && !b->bc_in_rom) {
|
|
7148
7481
|
hp->js_func_code_size += b->byte_code_len;
|
|
7149
7482
|
}
|
|
7150
7483
|
memory_used_count++;
|
|
@@ -7441,7 +7774,7 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s)
|
|
|
7441
7774
|
sizeof(rt->atom_hash[0]) * rt->atom_hash_size;
|
|
7442
7775
|
for(i = 0; i < rt->atom_size; i++) {
|
|
7443
7776
|
JSAtomStruct *p = rt->atom_array[i];
|
|
7444
|
-
if (!atom_is_free(p)) {
|
|
7777
|
+
if (!atom_is_free(p) && !js_atom_is_lazy(p)) {
|
|
7445
7778
|
s->atom_size += (sizeof(*p) + (p->len << p->is_wide_char) +
|
|
7446
7779
|
1 - p->is_wide_char);
|
|
7447
7780
|
}
|
|
@@ -31238,9 +31571,19 @@ done:
|
|
|
31238
31571
|
goto done;
|
|
31239
31572
|
}
|
|
31240
31573
|
|
|
31574
|
+
/* mikrojs patch: once a module reaches EVALUATED its init function can
|
|
31575
|
+
never run again; drop the bytecode so top-level-only code is reclaimed.
|
|
31576
|
+
Live closures and export binding var_refs hold their own references. */
|
|
31577
|
+
static void js_module_release_func(JSContext *ctx, JSModuleDef *m)
|
|
31578
|
+
{
|
|
31579
|
+
JS_FreeValue(ctx, m->func_obj);
|
|
31580
|
+
m->func_obj = JS_UNDEFINED;
|
|
31581
|
+
}
|
|
31582
|
+
|
|
31241
31583
|
static void js_set_module_evaluated(JSContext *ctx, JSModuleDef *m)
|
|
31242
31584
|
{
|
|
31243
31585
|
m->status = JS_MODULE_STATUS_EVALUATED;
|
|
31586
|
+
js_module_release_func(ctx, m);
|
|
31244
31587
|
if (!JS_IsUndefined(m->promise)) {
|
|
31245
31588
|
JSValue ret_val;
|
|
31246
31589
|
assert(m->cycle_root == m);
|
|
@@ -31335,6 +31678,7 @@ static JSValue js_async_module_execution_rejected(JSContext *ctx, JSValueConst t
|
|
|
31335
31678
|
module->eval_has_exception = true;
|
|
31336
31679
|
module->eval_exception = js_dup(error);
|
|
31337
31680
|
module->status = JS_MODULE_STATUS_EVALUATED;
|
|
31681
|
+
js_module_release_func(ctx, module);
|
|
31338
31682
|
|
|
31339
31683
|
for(i = 0; i < module->async_parent_modules_count; i++) {
|
|
31340
31684
|
JSModuleDef *m = module->async_parent_modules[i];
|
|
@@ -31564,6 +31908,7 @@ static int js_inner_module_evaluation(JSContext *ctx, JSModuleDef *m,
|
|
|
31564
31908
|
*pstack_top = m1->stack_prev;
|
|
31565
31909
|
if (!m1->async_evaluation) {
|
|
31566
31910
|
m1->status = JS_MODULE_STATUS_EVALUATED;
|
|
31911
|
+
js_module_release_func(ctx, m1);
|
|
31567
31912
|
} else {
|
|
31568
31913
|
m1->status = JS_MODULE_STATUS_EVALUATING_ASYNC;
|
|
31569
31914
|
}
|
|
@@ -31604,6 +31949,7 @@ static JSValue js_evaluate_module(JSContext *ctx, JSModuleDef *m)
|
|
|
31604
31949
|
m1 = stack_top;
|
|
31605
31950
|
assert(m1->status == JS_MODULE_STATUS_EVALUATING);
|
|
31606
31951
|
m1->status = JS_MODULE_STATUS_EVALUATED;
|
|
31952
|
+
js_module_release_func(ctx, m1);
|
|
31607
31953
|
m1->eval_has_exception = true;
|
|
31608
31954
|
m1->eval_exception = js_dup(result);
|
|
31609
31955
|
m1->cycle_root = m; /* spec bug: should be present */
|
|
@@ -36360,6 +36706,8 @@ static void free_function_bytecode(JSRuntime *rt, JSFunctionBytecode *b)
|
|
|
36360
36706
|
|
|
36361
36707
|
if (b->byte_code_buf)
|
|
36362
36708
|
free_bytecode_atoms(rt, b->byte_code_buf, b->byte_code_len, true);
|
|
36709
|
+
if (b->bc_separate)
|
|
36710
|
+
js_free_rt(rt, b->byte_code_buf);
|
|
36363
36711
|
|
|
36364
36712
|
if (b->vardefs) {
|
|
36365
36713
|
for(i = 0; i < b->arg_count + b->var_count; i++) {
|
|
@@ -37696,6 +38044,11 @@ typedef enum BCTagEnum {
|
|
|
37696
38044
|
} BCTagEnum;
|
|
37697
38045
|
|
|
37698
38046
|
#define BC_VERSION 26
|
|
38047
|
+
/* mikrojs patch: variant of BC_VERSION for blobs whose atom references
|
|
38048
|
+
below the frozen boundary are final runtime atom ids (see
|
|
38049
|
+
JS_PreloadFrozenAtoms). The header carries the boundary and the frozen
|
|
38050
|
+
table hash so a mismatched runtime rejects the blob at load. */
|
|
38051
|
+
#define BC_VERSION_FROZEN (BC_VERSION | 0x40)
|
|
37699
38052
|
|
|
37700
38053
|
typedef struct BCWriterState {
|
|
37701
38054
|
JSContext *ctx;
|
|
@@ -38432,8 +38785,16 @@ static int JS_WriteObjectAtoms(BCWriterState *s)
|
|
|
38432
38785
|
|
|
38433
38786
|
dbuf1 = s->dbuf;
|
|
38434
38787
|
js_dbuf_init(s->ctx, &s->dbuf);
|
|
38435
|
-
|
|
38436
|
-
|
|
38788
|
+
if (s->allow_bytecode && s->first_atom > JS_ATOM_END) {
|
|
38789
|
+
/* mikrojs patch: frozen-atom blob; record boundary + table hash */
|
|
38790
|
+
bc_put_u8(s, BC_VERSION_FROZEN);
|
|
38791
|
+
bc_put_u32(s, 0); // checksum, filled in after serialization
|
|
38792
|
+
bc_put_leb128(s, s->first_atom);
|
|
38793
|
+
bc_put_u32(s, rt->frozen_atom_hash);
|
|
38794
|
+
} else {
|
|
38795
|
+
bc_put_u8(s, BC_VERSION);
|
|
38796
|
+
bc_put_u32(s, 0); // checksum, filled in after serialization
|
|
38797
|
+
}
|
|
38437
38798
|
|
|
38438
38799
|
bc_put_leb128(s, s->idx_to_atom_count);
|
|
38439
38800
|
for(i = 0; i < s->idx_to_atom_count; i++) {
|
|
@@ -38444,8 +38805,11 @@ static int JS_WriteObjectAtoms(BCWriterState *s)
|
|
|
38444
38805
|
more efficient. */
|
|
38445
38806
|
bc_put_u32(s, atom);
|
|
38446
38807
|
} else {
|
|
38447
|
-
JSAtomStruct *p = rt
|
|
38448
|
-
uint8_t type
|
|
38808
|
+
JSAtomStruct *p = js_atom_ensure_str(rt, atom);
|
|
38809
|
+
uint8_t type;
|
|
38810
|
+
if (!p)
|
|
38811
|
+
goto fail;
|
|
38812
|
+
type = p->atom_type;
|
|
38449
38813
|
assert(type != JS_ATOM_TYPE_PRIVATE);
|
|
38450
38814
|
bc_put_u8(s, type);
|
|
38451
38815
|
JS_WriteString(s, p);
|
|
@@ -38486,7 +38850,9 @@ uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValueConst obj,
|
|
|
38486
38850
|
s->allow_debug = ((flags & JS_WRITE_OBJ_STRIP_DEBUG) == 0);
|
|
38487
38851
|
/* XXX: could use a different version when bytecode is included */
|
|
38488
38852
|
if (s->allow_bytecode)
|
|
38489
|
-
|
|
38853
|
+
/* mikrojs patch: frozen atoms (< frozen_atom_end) serialize as
|
|
38854
|
+
final ids; JS_ATOM_END when no frozen table is loaded */
|
|
38855
|
+
s->first_atom = ctx->rt->frozen_atom_end;
|
|
38490
38856
|
else
|
|
38491
38857
|
s->first_atom = 1;
|
|
38492
38858
|
js_dbuf_init(ctx, &s->dbuf);
|
|
@@ -38540,6 +38906,9 @@ typedef struct BCReaderState {
|
|
|
38540
38906
|
bool allow_sab;
|
|
38541
38907
|
bool allow_bytecode;
|
|
38542
38908
|
bool allow_reference;
|
|
38909
|
+
/* mikrojs patch: keep qualifying bytecode in the input buffer (which
|
|
38910
|
+
must then outlive the runtime, e.g. flash rodata) */
|
|
38911
|
+
bool inplace_bytecode;
|
|
38543
38912
|
/* object references */
|
|
38544
38913
|
JSObject **objects;
|
|
38545
38914
|
int objects_count;
|
|
@@ -38780,6 +39149,37 @@ static uint32_t bc_get_flags(uint32_t flags, int *pidx, int n)
|
|
|
38780
39149
|
return val;
|
|
38781
39150
|
}
|
|
38782
39151
|
|
|
39152
|
+
/* mikrojs patch: true if every atom operand is already a final atom id
|
|
39153
|
+
(tagged int or below first_atom), i.e. loading needs no relocation
|
|
39154
|
+
writes and the instruction stream can stay in the (ROM) input buffer */
|
|
39155
|
+
static bool bc_bytecode_is_final(BCReaderState *s, const uint8_t *bc_buf,
|
|
39156
|
+
uint32_t bc_len)
|
|
39157
|
+
{
|
|
39158
|
+
uint32_t pos = 0;
|
|
39159
|
+
int op, len;
|
|
39160
|
+
uint32_t idx;
|
|
39161
|
+
|
|
39162
|
+
while (pos < bc_len) {
|
|
39163
|
+
op = bc_buf[pos];
|
|
39164
|
+
len = short_opcode_info(op).size;
|
|
39165
|
+
switch(short_opcode_info(op).fmt) {
|
|
39166
|
+
case OP_FMT_atom:
|
|
39167
|
+
case OP_FMT_atom_u8:
|
|
39168
|
+
case OP_FMT_atom_u16:
|
|
39169
|
+
case OP_FMT_atom_label_u8:
|
|
39170
|
+
case OP_FMT_atom_label_u16:
|
|
39171
|
+
idx = get_u32(bc_buf + pos + 1);
|
|
39172
|
+
if (!__JS_AtomIsTaggedInt(idx) && idx >= s->first_atom)
|
|
39173
|
+
return false;
|
|
39174
|
+
break;
|
|
39175
|
+
default:
|
|
39176
|
+
break;
|
|
39177
|
+
}
|
|
39178
|
+
pos += len;
|
|
39179
|
+
}
|
|
39180
|
+
return true;
|
|
39181
|
+
}
|
|
39182
|
+
|
|
38783
39183
|
static int JS_ReadFunctionBytecode(BCReaderState *s, JSFunctionBytecode *b,
|
|
38784
39184
|
int byte_code_offset, uint32_t bc_len)
|
|
38785
39185
|
{
|
|
@@ -38788,7 +39188,50 @@ static int JS_ReadFunctionBytecode(BCReaderState *s, JSFunctionBytecode *b,
|
|
|
38788
39188
|
JSAtom atom;
|
|
38789
39189
|
uint32_t idx;
|
|
38790
39190
|
|
|
38791
|
-
|
|
39191
|
+
if (s->inplace_bytecode) {
|
|
39192
|
+
/* mikrojs patch: zero-copy when possible, else a separate heap
|
|
39193
|
+
copy (the function block was allocated without a bytecode tail) */
|
|
39194
|
+
if (bc_len != 0 && (uint32_t)(s->buf_end - s->ptr) >= bc_len &&
|
|
39195
|
+
bc_bytecode_is_final(s, s->ptr, bc_len)) {
|
|
39196
|
+
const uint8_t *rom = s->ptr;
|
|
39197
|
+
/* take a reference on each atom operand so the free path
|
|
39198
|
+
(free_bytecode_atoms) stays symmetric with the copy path */
|
|
39199
|
+
pos = 0;
|
|
39200
|
+
while (pos < (int)bc_len) {
|
|
39201
|
+
op = rom[pos];
|
|
39202
|
+
len = short_opcode_info(op).size;
|
|
39203
|
+
switch(short_opcode_info(op).fmt) {
|
|
39204
|
+
case OP_FMT_atom:
|
|
39205
|
+
case OP_FMT_atom_u8:
|
|
39206
|
+
case OP_FMT_atom_u16:
|
|
39207
|
+
case OP_FMT_atom_label_u8:
|
|
39208
|
+
case OP_FMT_atom_label_u16:
|
|
39209
|
+
idx = get_u32(rom + pos + 1);
|
|
39210
|
+
if (!__JS_AtomIsTaggedInt(idx))
|
|
39211
|
+
JS_DupAtom(s->ctx, idx);
|
|
39212
|
+
break;
|
|
39213
|
+
default:
|
|
39214
|
+
break;
|
|
39215
|
+
}
|
|
39216
|
+
pos += len;
|
|
39217
|
+
}
|
|
39218
|
+
b->byte_code_buf = (uint8_t *)rom;
|
|
39219
|
+
b->bc_in_rom = 1;
|
|
39220
|
+
s->ptr += bc_len;
|
|
39221
|
+
return 0;
|
|
39222
|
+
}
|
|
39223
|
+
if (bc_len == 0) {
|
|
39224
|
+
b->byte_code_buf = NULL;
|
|
39225
|
+
return 0;
|
|
39226
|
+
}
|
|
39227
|
+
bc_buf = js_mallocz(s->ctx, bc_len);
|
|
39228
|
+
if (!bc_buf)
|
|
39229
|
+
return s->error_state = -1;
|
|
39230
|
+
b->bc_separate = 1;
|
|
39231
|
+
b->byte_code_buf = bc_buf; /* owned now; freed on error paths too */
|
|
39232
|
+
} else {
|
|
39233
|
+
bc_buf = (uint8_t*)b + byte_code_offset;
|
|
39234
|
+
}
|
|
38792
39235
|
if (bc_get_buf(s, bc_buf, bc_len))
|
|
38793
39236
|
return -1;
|
|
38794
39237
|
b->byte_code_buf = bc_buf;
|
|
@@ -38958,7 +39401,10 @@ static JSValue JS_ReadFunctionTag(BCReaderState *s)
|
|
|
38958
39401
|
closure_var_offset = function_size;
|
|
38959
39402
|
function_size += bc.closure_var_count * sizeof(*bc.closure_var);
|
|
38960
39403
|
byte_code_offset = function_size;
|
|
38961
|
-
|
|
39404
|
+
/* mikrojs patch: in-place reads keep the bytecode out of the function
|
|
39405
|
+
block (ROM pointer, or a separate heap copy on fallback) */
|
|
39406
|
+
if (!s->inplace_bytecode)
|
|
39407
|
+
function_size += bc.byte_code_len;
|
|
38962
39408
|
|
|
38963
39409
|
b = js_mallocz(ctx, function_size);
|
|
38964
39410
|
if (!b)
|
|
@@ -39698,7 +40144,8 @@ static int JS_ReadObjectAtoms(BCReaderState *s)
|
|
|
39698
40144
|
|
|
39699
40145
|
if (bc_get_u8(s, &v8))
|
|
39700
40146
|
return -1;
|
|
39701
|
-
if (v8 != BC_VERSION
|
|
40147
|
+
if (v8 != BC_VERSION &&
|
|
40148
|
+
!(v8 == BC_VERSION_FROZEN && s->allow_bytecode)) {
|
|
39702
40149
|
JS_ThrowSyntaxError(s->ctx, "invalid version (%d expected=%d)",
|
|
39703
40150
|
v8, BC_VERSION);
|
|
39704
40151
|
return -1;
|
|
@@ -39711,6 +40158,22 @@ static int JS_ReadObjectAtoms(BCReaderState *s)
|
|
|
39711
40158
|
JS_ThrowSyntaxError(s->ctx, "checksum error");
|
|
39712
40159
|
return -1;
|
|
39713
40160
|
}
|
|
40161
|
+
if (v8 == BC_VERSION_FROZEN) {
|
|
40162
|
+
/* mikrojs patch: atom ids below the boundary are final; require the
|
|
40163
|
+
exact same frozen table the blob was compiled against */
|
|
40164
|
+
uint32_t boundary, fhash;
|
|
40165
|
+
JSRuntime *rt = s->ctx->rt;
|
|
40166
|
+
if (bc_get_leb128(s, &boundary))
|
|
40167
|
+
return -1;
|
|
40168
|
+
if (bc_get_u32(s, &fhash))
|
|
40169
|
+
return -1;
|
|
40170
|
+
if (boundary != rt->frozen_atom_end ||
|
|
40171
|
+
fhash != rt->frozen_atom_hash) {
|
|
40172
|
+
JS_ThrowSyntaxError(s->ctx, "frozen atom table mismatch");
|
|
40173
|
+
return -1;
|
|
40174
|
+
}
|
|
40175
|
+
s->first_atom = boundary;
|
|
40176
|
+
}
|
|
39714
40177
|
if (bc_get_leb128(s, &s->idx_to_atom_count))
|
|
39715
40178
|
return -1;
|
|
39716
40179
|
if (s->idx_to_atom_count > 1000*1000) {
|
|
@@ -39785,6 +40248,8 @@ JSValue JS_ReadObject2(JSContext *ctx, const uint8_t *buf, size_t buf_len,
|
|
|
39785
40248
|
s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0);
|
|
39786
40249
|
s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0);
|
|
39787
40250
|
s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0);
|
|
40251
|
+
s->inplace_bytecode = s->allow_bytecode &&
|
|
40252
|
+
((flags & JS_READ_OBJ_INPLACE) != 0);
|
|
39788
40253
|
if (s->allow_bytecode)
|
|
39789
40254
|
s->first_atom = JS_ATOM_END;
|
|
39790
40255
|
else
|
package/deps/quickjs/quickjs.h
CHANGED
|
@@ -1251,6 +1251,20 @@ JS_EXTERN uint8_t *JS_WriteObject2(JSContext *ctx, size_t *psize, JSValueConst o
|
|
|
1251
1251
|
writer in the same process (e.g. another Worker on the same runtime). */
|
|
1252
1252
|
#define JS_READ_OBJ_SAB (1 << 2) /* allow SharedArrayBuffer */
|
|
1253
1253
|
#define JS_READ_OBJ_REFERENCE (1 << 3) /* allow object references */
|
|
1254
|
+
/* mikrojs patch: keep bytecode whose atom operands are all final ids
|
|
1255
|
+
(predefined or frozen, see JS_PreloadFrozenAtoms) in the input buffer
|
|
1256
|
+
instead of copying it to the heap. The buffer must then outlive every
|
|
1257
|
+
function read from it (e.g. flash rodata / static const data). */
|
|
1258
|
+
#define JS_READ_OBJ_INPLACE (1 << 4)
|
|
1259
|
+
|
|
1260
|
+
/* mikrojs patch: reserve ids for `count` atoms right after JS_NewRuntime so
|
|
1261
|
+
blobs compiled by a writer runtime with the same preload reference them
|
|
1262
|
+
as final ids (enables JS_READ_OBJ_INPLACE zero-copy). Atom strings
|
|
1263
|
+
materialize lazily from `names` on first string use, so `names` and
|
|
1264
|
+
`lens` must outlive the runtime (e.g. rodata). Must be called before any
|
|
1265
|
+
dynamic atom is freed; returns 0 on success. */
|
|
1266
|
+
JS_EXTERN int JS_PreloadFrozenAtoms(JSRuntime *rt, const char *const *names,
|
|
1267
|
+
const uint32_t *lens, uint32_t count);
|
|
1254
1268
|
JS_EXTERN JSValue JS_ReadObject(JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags);
|
|
1255
1269
|
JS_EXTERN JSValue JS_ReadObject2(JSContext *ctx, const uint8_t *buf, size_t buf_len,
|
|
1256
1270
|
int flags, JSSABTab *psab_tab);
|
package/package.json
CHANGED
package/postinstall.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
* Skips gracefully if cc or the submodule is not available.
|
|
9
9
|
*/
|
|
10
10
|
import {execFileSync} from 'node:child_process'
|
|
11
|
-
import {
|
|
11
|
+
import {createHash} from 'node:crypto'
|
|
12
|
+
import {existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync} from 'node:fs'
|
|
12
13
|
import {dirname, join} from 'node:path'
|
|
13
14
|
import {fileURLToPath} from 'node:url'
|
|
14
15
|
|
|
@@ -51,6 +52,21 @@ try {
|
|
|
51
52
|
// Not a git repo (e.g. published package), skip commit tracking
|
|
52
53
|
}
|
|
53
54
|
|
|
55
|
+
// Patches change qjsc behavior without moving the submodule commit, so the
|
|
56
|
+
// rebuild stamp must cover them too or bin/qjsc goes stale silently.
|
|
57
|
+
if (currentCommit) {
|
|
58
|
+
const patchesDir = join(__dirname, 'patches')
|
|
59
|
+
if (existsSync(patchesDir)) {
|
|
60
|
+
const hash = createHash('sha256')
|
|
61
|
+
for (const f of readdirSync(patchesDir)
|
|
62
|
+
.filter((f) => f.endsWith('.patch'))
|
|
63
|
+
.sort()) {
|
|
64
|
+
hash.update(readFileSync(join(patchesDir, f)))
|
|
65
|
+
}
|
|
66
|
+
currentCommit += `+${hash.digest('hex').slice(0, 16)}`
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
// On Windows the C compiler appends .exe automatically.
|
|
55
71
|
const exeSuffix = process.platform === 'win32' ? '.exe' : ''
|
|
56
72
|
const qjscPath = join(binDir, `qjsc${exeSuffix}`)
|
package/quickjs.cmake
CHANGED
|
@@ -28,6 +28,35 @@ endif()
|
|
|
28
28
|
get_filename_component(_QUICKJS_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY)
|
|
29
29
|
set(QUICKJS_INCLUDE_DIR "${_QUICKJS_CMAKE_DIR}/deps/quickjs")
|
|
30
30
|
|
|
31
|
+
# Sync the patched QuickJS tree AND the qjsc compiler before anything
|
|
32
|
+
# compiles or generates bytecode. postinstall covers `pnpm install`, but a
|
|
33
|
+
# patch file added or edited after install was previously a silent no-op:
|
|
34
|
+
# the tree built unpatched and the full test suite ran against code the
|
|
35
|
+
# patch never touched. Running postinstall (not just apply-patches) matters
|
|
36
|
+
# because qjsc is compiled FROM the patched sources: a stale qjsc paired
|
|
37
|
+
# with a fresh runtime emits bytecode the runtime rejects at import time
|
|
38
|
+
# (frozen atom table mismatch) — a runtime failure born from a green build.
|
|
39
|
+
# Both steps are stamp-guarded, so the no-change case is cheap; the GLOB's
|
|
40
|
+
# CONFIGURE_DEPENDS reruns configure when the patch set changes, and
|
|
41
|
+
# CMAKE_CONFIGURE_DEPENDS reruns it when patch contents change.
|
|
42
|
+
#
|
|
43
|
+
# Guarded for CMake script mode: ESP-IDF's early component-requirements
|
|
44
|
+
# pass includes component CMakeLists (and through them this file) via
|
|
45
|
+
# script-mode CMake, where CONFIGURE_DEPENDS globs and directory
|
|
46
|
+
# properties are hard errors. The real project-mode configure that
|
|
47
|
+
# follows runs the sync; the requirements pass only needs the variables.
|
|
48
|
+
if(NOT CMAKE_SCRIPT_MODE_FILE)
|
|
49
|
+
file(GLOB _QUICKJS_PATCHES CONFIGURE_DEPENDS "${_QUICKJS_CMAKE_DIR}/patches/*.patch")
|
|
50
|
+
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${_QUICKJS_PATCHES})
|
|
51
|
+
execute_process(
|
|
52
|
+
COMMAND node "${_QUICKJS_CMAKE_DIR}/postinstall.js"
|
|
53
|
+
RESULT_VARIABLE _QUICKJS_PATCH_RC
|
|
54
|
+
)
|
|
55
|
+
if(NOT _QUICKJS_PATCH_RC EQUAL 0)
|
|
56
|
+
message(FATAL_ERROR "quickjs.cmake: syncing QuickJS patches/qjsc failed (see output above)")
|
|
57
|
+
endif()
|
|
58
|
+
endif()
|
|
59
|
+
|
|
31
60
|
# Source files
|
|
32
61
|
set(QUICKJS_SOURCES
|
|
33
62
|
"${QUICKJS_INCLUDE_DIR}/quickjs.c"
|