@scriptc/runtime 0.0.21 → 0.0.23

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/src/scr_runtime.h CHANGED
@@ -197,9 +197,15 @@ void scr_library_bytes_out(ScrBytes *b, const uint8_t **out, size_t *out_len);
197
197
  * collection walks the buffer (markGray: trial-decrement internal edges;
198
198
  * scan: restore externally-referenced subgraphs; collectWhite: free the
199
199
  * dead cycle members, releasing only edges that LEAVE the white set).
200
- * Collection points: program exit (before the RC audit), event-loop
201
- * quiescence, and a root-buffer threshold (SCR_CYCLE_THRESHOLD env var,
202
- * default 256). There is no concurrent or incremental collection.
200
+ * It is GENERATIONAL: each header carries a generation, a pass names the
201
+ * oldest one it will walk, and objects that survive a pass are promoted out
202
+ * of the nursery so later nursery passes never re-walk them. That is what
203
+ * keeps a pass proportional to recent allocation rather than to the whole
204
+ * live heap — see the generation note in scr_cycle.c for the soundness
205
+ * argument and the schedule. Collection points: program exit (before the RC
206
+ * audit), event-loop quiescence, and the per-generation triggers.
207
+ * SCR_CYCLE_THRESHOLD pins the nursery trigger to a fixed candidate count.
208
+ * There is no concurrent or incremental collection.
203
209
  *
204
210
  * Contract for trace/teardown pairs (the compiler emits them for shapes,
205
211
  * the runtime owns its own): trace(obj) visits exactly the strong
@@ -214,16 +220,44 @@ typedef void (*ScrTraceVisit)(void *child, void *ctx);
214
220
  typedef void (*ScrTraceFn)(void *obj, ScrTraceVisit visit, void *ctx);
215
221
  typedef void (*ScrCycFreeFn)(void *obj);
216
222
 
217
- enum { SCR_CYC_BLACK = 0, SCR_CYC_PURPLE = 1, SCR_CYC_GRAY = 2, SCR_CYC_WHITE = 3 };
223
+ /* DOOMED is WHITE that collectWhite has already gathered: it stops the
224
+ * gather recursing twice, and distinguishes "about to be freed" from a
225
+ * survivor for the re-buffering step (see scr_cycle.c). */
226
+ enum {
227
+ SCR_CYC_BLACK = 0, SCR_CYC_PURPLE = 1, SCR_CYC_GRAY = 2, SCR_CYC_WHITE = 3,
228
+ SCR_CYC_DOOMED = 4
229
+ };
230
+
231
+ /* Generations. A candidate sits in the buffer named by its own `gen`, and a
232
+ * pass walks only objects at or below the generation it collects. */
233
+ enum { SCR_CYC_NURSERY = 0, SCR_CYC_MATURE = 1, SCR_CYC_NGENS = 2 };
218
234
 
219
235
  typedef struct ScrCycHdr {
220
236
  ScrTraceFn trace;
221
237
  ScrCycFreeFn free_fn;
222
238
  uint32_t color; /* SCR_CYC_* */
223
- uint32_t buffered; /* 1 = sitting in the candidate-root buffer */
239
+ uint16_t buffered; /* 1 = sitting in its generation's candidate buffer */
240
+ uint16_t gen; /* SCR_CYC_NURSERY..SCR_CYC_MATURE (the walk filter) */
224
241
  size_t buf_index; /* position there (O(1) removal when rc hits 0) */
225
242
  } ScrCycHdr;
226
243
 
244
+ /* This layout is an ABI, not an implementation detail: the LLVM backend
245
+ * inlines scr_cyc_mark_live as a raw `store i32 0` at obj-16 (it is a
246
+ * static inline here, so there is no symbol to call) and reaches the header
247
+ * at obj-32. Three sites emit it — llvm/shapes.ts, llvm/classes.ts,
248
+ * llvm/emitter.ts. Nothing but `color` may share those four bytes: a field
249
+ * placed in them is silently zeroed by every retain, which is invisible to
250
+ * the type system and to the C compiler. Hence the assertions. */
251
+ _Static_assert(sizeof(ScrCycHdr) == 32, "LLVM backend reads the header at obj-32");
252
+ _Static_assert(offsetof(ScrCycHdr, color) == 16,
253
+ "LLVM backend's inlined mark-live stores i32 0 at obj-16");
254
+ _Static_assert(sizeof(((ScrCycHdr *)0)->color) == 4,
255
+ "mark-live is an i32 store: color must own all four bytes");
256
+ _Static_assert(SCR_CYC_BLACK == 0,
257
+ "the emitted mark-live stores the LITERAL 0, not the enumerator "
258
+ "— reordering the colors would make every compiled retain write "
259
+ "the wrong one");
260
+
227
261
  static inline ScrCycHdr *scr_cyc_hdr(void *obj) { return (ScrCycHdr *)obj - 1; }
228
262
 
229
263
  /* Zeroed allocation with a cycle header in front; returns the OBJECT
@@ -243,9 +277,18 @@ static inline void scr_cyc_mark_live(void *obj) {
243
277
  scr_cyc_hdr(obj)->color = SCR_CYC_BLACK;
244
278
  }
245
279
 
246
- /* Run one full trial-deletion pass over the buffered candidates now. */
280
+ /* Full sweep: trial-deletion passes over EVERY generation, to a fixpoint.
281
+ * This is the exit / session-reset entry point (the RC audit runs straight
282
+ * after and wants nothing reclaimable left), and it costs a walk of the
283
+ * live heap — do not put it on a per-turn path. */
247
284
  void scr_collect_cycles(void);
248
285
 
286
+ /* One pass on the normal generational schedule, for callers that reach a
287
+ * natural collection point rather than a threshold (the event loop between
288
+ * turns). Cheap: usually a nursery pass; a waiting mature backlog ages into
289
+ * a bounded full pass so sparse roots cannot float forever. */
290
+ void scr_cyc_collect_scheduled(void);
291
+
249
292
  /* ── class hierarchies (single inheritance) ───────────────────────────
250
293
  * Classes in an `extends` hierarchy share a two-word object prefix: the
251
294
  * usual `size_t rc`, then a pointer to the class's static vtable (emitted
@@ -2416,7 +2459,10 @@ ScrStr *scr_path_win32_to_namespaced_path(ScrStr *path);
2416
2459
  * (failure throws the path-less "EBADF: bad file descriptor, close").
2417
2460
  * The pair behind spawn's fd-stdio form. */
2418
2461
  double scr_fs_open(ScrStr *path, ScrStr *flags);
2419
- double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length);
2462
+ /* position == -1 reads from and advances the descriptor's current offset;
2463
+ * nonnegative positions leave that offset unchanged (pread/ReadFile seam). */
2464
+ double scr_fs_read_sync(double fd, ScrBytes *buf, double offset, double length,
2465
+ double position);
2420
2466
  void scr_fs_close(double fd);
2421
2467
 
2422
2468
  /* ── WHATWG URL (scr_url.c) ──────────────────────────────────────────
@@ -2683,9 +2729,15 @@ typedef enum {
2683
2729
  * a silent wrong answer. The dyn→cell edge is NOT visible to the cycle
2684
2730
  * collector (the dyn→closure stance): a cycle dyn → cell → engine
2685
2731
  * object → host closure → dyn is merely never collected (the
2686
- * documented cross-boundary-cycle divergence). Enum position: LAST —
2687
- * the LLVM backend hardcodes the preceding kind numbers. */
2732
+ * documented cross-boundary-cycle divergence). */
2688
2733
  SCR_DYN_JSVAL,
2734
+ /* A compiler-owned typed reference in transit through a native Web
2735
+ * stream. Unlike an ordinary typed→unknown conversion, this capsule
2736
+ * retains the original value so a statically typed reader can recover
2737
+ * its identity. `materialize` supplies the ordinary deep-copy view for
2738
+ * consumers such as fetch request bodies that need a dyn chunk. Enum
2739
+ * position: LAST — LLVM hardcodes every preceding kind number. */
2740
+ SCR_DYN_TYPED_REF,
2689
2741
  } ScrDynKind;
2690
2742
 
2691
2743
  /* The handle-type tags the checked-dynamic tree can carry. The set is deliberately the
@@ -2701,12 +2753,28 @@ typedef enum {
2701
2753
  SCR_DYNH_H2_STREAM, /* ScrH2Stream — Http2Stream (client & server) */
2702
2754
  SCR_DYNH_HTTP_CLIENT, /* ScrHttpClientReq — http.ClientRequest */
2703
2755
  SCR_DYNH_HTTP_AGENT, /* ScrHttpAgent — http.Agent / https.Agent */
2756
+ SCR_DYNH_ABORT_SIGNAL, /* native static-fetch AbortSignal */
2757
+ SCR_DYNH_WEB_STREAM, /* native WHATWG ReadableStream */
2758
+ SCR_DYNH_WEB_READER, /* native ReadableStreamDefaultReader */
2759
+ SCR_DYNH_WEB_CONTROLLER, /* native ReadableStreamDefaultController */
2760
+ SCR_DYNH_FETCH_RESPONSE, /* native static-fetch Response */
2761
+ SCR_DYNH_FETCH_HEADERS, /* native static-fetch response Headers */
2762
+ SCR_DYNH_EVENT, /* native static-fetch abort Event */
2763
+ SCR_DYNH_ABORT_CONTROLLER, /* native static-fetch AbortController */
2704
2764
  SCR_DYNH_COUNT,
2705
2765
  } ScrDynHandleTag;
2706
2766
 
2707
2767
  typedef struct ScrDyn ScrDyn;
2708
2768
  typedef struct ScrBytes ScrBytes; /* full definition below (C11 repeat) */
2709
2769
  typedef struct ScrClosure ScrClosure; /* full definition below (C11 repeat) */
2770
+ typedef struct ScrDynTypedCast {
2771
+ const char *type_key;
2772
+ size_t type_key_len;
2773
+ void *ptr;
2774
+ void *(*retain)(void *);
2775
+ void (*release)(void *);
2776
+ struct ScrDynTypedCast *next;
2777
+ } ScrDynTypedCast;
2710
2778
  typedef struct ScrJsval ScrJsval; /* opaque island cell (C11 repeat; the
2711
2779
  * always-linked dyn core never touches
2712
2780
  * its engine value — only the gated ops
@@ -2752,7 +2820,19 @@ struct ScrDyn {
2752
2820
  ScrStr *str; /* owned */
2753
2821
  ScrBytes *bytes; /* owned (SCR_DYN_BYTES) */
2754
2822
  struct { size_t len; size_t cap; ScrDyn **items; } arr; /* owned */
2755
- struct { size_t len; size_t cap; ScrDynEntry *entries; } obj; /* owned */
2823
+ struct {
2824
+ size_t len;
2825
+ size_t cap;
2826
+ ScrDynEntry *entries;
2827
+ /* Optional identity of the typed record that produced this ordinary
2828
+ * deep-copy snapshot. EventTarget uses it to recognize the same
2829
+ * EventListenerObject across repeated static→dyn crossings without
2830
+ * changing generic dyn-object `===` semantics. */
2831
+ /* Owned through source_access(source_identity, false). Callback
2832
+ * interfaces may request a fresh snapshot with the true arm. */
2833
+ void *source_identity;
2834
+ ScrDyn *(*source_access)(void *, bool materialize);
2835
+ } obj; /* owned */
2756
2836
  /* SCR_DYN_FUNC: the boxed closure (owned) + its call descriptor. `sig`
2757
2837
  * and `name` are static compiler-emitted literals (never freed); name
2758
2838
  * may be NULL (anonymous — inspect prints [Function (anonymous)]).
@@ -2767,6 +2847,23 @@ struct ScrDyn {
2767
2847
  * so a listener capturing its own boxed handle never cycles past the
2768
2848
  * settle — the settle-releases-listeners story. */
2769
2849
  struct { void *ptr; ScrDynHandleTag tag; } handle;
2850
+ /* SCR_DYN_TYPED_REF: original static value + its compiler-emitted
2851
+ * identity tag, RC adapters, and ordinary dyn snapshot adapter. */
2852
+ struct {
2853
+ void *ptr;
2854
+ void *(*retain)(void *);
2855
+ void (*release)(void *);
2856
+ const char *type_key;
2857
+ size_t type_key_len;
2858
+ ScrDyn *(*materialize)(void *);
2859
+ void (*commit)(void *, const ScrDyn *);
2860
+ /* Both caches belong to this capsule. ReadableStream canonicalizes
2861
+ * capsules for repeated source references, so the refreshed dyn view
2862
+ * and every structurally converted static view keep JS reference
2863
+ * identity while the source reference remains observable. */
2864
+ ScrDyn *materialized;
2865
+ ScrDynTypedCast *casts;
2866
+ } typed_ref;
2770
2867
  /* SCR_DYN_PROMISE: the retained promise. The boundary contract: it
2771
2868
  * settles with a dyn payload (SCR_EXC_REF ScrDyn fulfillment or a
2772
2869
  * void fulfillment awaiters read as the undefined value) — the
@@ -2841,6 +2938,14 @@ ScrDyn *scr_dyn_new_num(double n);
2841
2938
  ScrDyn *scr_dyn_new_str(ScrStr *s);
2842
2939
  ScrDyn *scr_dyn_new_arr(void);
2843
2940
  ScrDyn *scr_dyn_new_obj(void);
2941
+ /* The ordinary deep-copy object plus a retained typed source. source_access
2942
+ * releases that source when materialize=false and returns a fresh dyn snapshot
2943
+ * when true. Generic dyn member reads and strict equality remain snapshot/node
2944
+ * based; callback-interface consumers opt into the live arm explicitly. */
2945
+ ScrDyn *scr_dyn_new_obj_with_identity(
2946
+ void *source, void *(*source_retain)(void *),
2947
+ ScrDyn *(*source_access)(void *, bool materialize));
2948
+ bool scr_dyn_obj_same_source(const ScrDyn *a, const ScrDyn *b);
2844
2949
  /* Object.create(null): the fresh null-prototype dictionary (see the
2845
2950
  * null_proto flavor flag above). */
2846
2951
  ScrDyn *scr_dyn_new_obj_null_proto(void);
@@ -2850,6 +2955,26 @@ ScrDyn *scr_dyn_new_bytes_copy(const ScrBytes *b);
2850
2955
  /* The Buffer-flavored twin (stream chunks): string coercion/toString
2851
2956
  * decode utf8 instead of joining elements. */
2852
2957
  ScrDyn *scr_dyn_new_buffer_copy(const ScrBytes *b);
2958
+ /* Identity-preserving transit capsule used by ReadableStream.from over
2959
+ * typed arrays. The constructor retains `ptr`; matching unbox returns +1.
2960
+ * materialize lazily creates and then retains one detached dyn snapshot.
2961
+ * The cast cache lets a non-exact dynCheck reuse one safe, compiler-built
2962
+ * target view instead of raw-casting structurally different layouts. */
2963
+ ScrDyn *scr_dyn_new_typed_ref(
2964
+ void *ptr, void *(*retain)(void *), void (*release)(void *),
2965
+ const char *type_key, size_t type_key_len,
2966
+ ScrDyn *(*materialize)(void *),
2967
+ void (*commit)(void *, const ScrDyn *));
2968
+ bool scr_dyn_typed_ref_is(
2969
+ const ScrDyn *d, const char *type_key, size_t type_key_len);
2970
+ void *scr_dyn_typed_ref_unbox(const ScrDyn *d); /* +1 */
2971
+ ScrDyn *scr_dyn_typed_ref_materialize(const ScrDyn *d); /* +1 */
2972
+ void scr_dyn_typed_ref_commit(ScrDyn *d);
2973
+ void *scr_dyn_typed_ref_cached_cast(
2974
+ const ScrDyn *d, const char *type_key, size_t type_key_len); /* +1/NULL */
2975
+ void scr_dyn_typed_ref_cache_cast(
2976
+ ScrDyn *d, const char *type_key, size_t type_key_len, void *ptr,
2977
+ void *(*retain)(void *), void (*release)(void *));
2853
2978
  /* A fresh u8 COPY of a SCR_DYN_BYTES payload (+1) — the dynCheck
2854
2979
  * extraction (`u as Uint8Array`). */
2855
2980
  ScrBytes *scr_dyn_bytes_copy_out(const ScrDyn *d);
@@ -2902,6 +3027,7 @@ ScrStr *scr_dyn_string_coerce(const ScrDyn *d);
2902
3027
  * called, their throws propagating) — the WHATWG USVString conversions.
2903
3028
  * Borrows; +1 or NULL with the exception pending. */
2904
3029
  ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d);
3030
+ bool scr_dyn_number_coerce_js(const ScrDyn *d, double *out);
2905
3031
 
2906
3032
  /* `d instanceof TypeError` (and the other builtin error classes) on a
2907
3033
  * checked-dynamic value: the from_error cache resolves the dyn encoding
@@ -3137,9 +3263,10 @@ ScrDyn *scr_dyn_alloc_jsval(ScrJsval *cell, const ScrDynJsvalOps *ops);
3137
3263
  /* The installed ops (traps on a missing install — impossible unless a
3138
3264
  * JSVAL node was forged without the constructor). */
3139
3265
  const ScrDynJsvalOps *scr_dyn_jsval_ops(void);
3140
- /* dynTest arms that need the ENGINE's answer on a JSVAL node. Each
3141
- * answers false for every other kind (callers test unconditionally —
3142
- * the emitted narrowing tests stay branch-free). Never throw. */
3266
+ /* dynTest arms that need the ENGINE's answer on a JSVAL node, or the
3267
+ * materialized answer for a live typed-reference capsule. Each answers
3268
+ * false for every other kind (callers test unconditionally — the emitted
3269
+ * narrowing tests stay branch-free). Never throw. */
3143
3270
  bool scr_dyn_isl_typeof_is(const ScrDyn *d, const char *name);
3144
3271
  bool scr_dyn_isl_is_array(const ScrDyn *d);
3145
3272
  bool scr_dyn_isl_is_error(const ScrDyn *d);
@@ -3819,6 +3946,35 @@ ScrGen *scr_gen_of_fiber(ScrFiber *f);
3819
3946
  long scr_promise_live_count(void);
3820
3947
  #endif
3821
3948
 
3949
+ /* ── fetch (scr_fetch.c) ──────────────────────────────────────────────
3950
+ * The native bridge over scr_net + scr_tls + scr_http's client parser.
3951
+ * Static fetch(url) resolves at the response head with native Response
3952
+ * and ReadableStream handles; Response.json consumes that same stream.
3953
+ * AbortSignal and streaming request bodies stay native too. Under
3954
+ * --dynamic the same TU boots the broader engine web surface. The
3955
+ * emitted main calls scr_fetch_install in either form. */
3956
+ void scr_fetch_install(void);
3957
+ ScrPromise *scr_fetch_static(ScrStr *url, ScrDyn *init); /* +1 promise<Response handle> */
3958
+ ScrDyn *scr_fetch_response_new(ScrDyn *body, ScrDyn *init); /* borrowed args; +1 Response handle or NULL pending */
3959
+ ScrPromise *scr_fetch_response_json(ScrDyn *response); /* +1 promise<dyn> */
3960
+ ScrPromise *scr_fetch_response_text(ScrDyn *response); /* +1 promise<dyn> */
3961
+ ScrPromise *scr_fetch_response_bytes(ScrDyn *response); /* +1 promise<dyn> */
3962
+ ScrDyn *scr_fetch_abort_controller_new(void); /* +1 AbortController handle */
3963
+ /* Borrowed number; +1 AbortSignal handle or NULL pending. */
3964
+ ScrDyn *scr_fetch_abort_timeout(ScrDyn *delay);
3965
+ ScrDyn *scr_fetch_abort_now(ScrDyn *reason); /* borrowed reason; +1 handle */
3966
+ ScrDyn *scr_fetch_abort_any(ScrDyn *signals); /* borrowed array; +1 handle or NULL pending */
3967
+ ScrDyn *scr_fetch_stream_new(ScrDyn *source); /* borrowed source; +1 handle or NULL pending */
3968
+ ScrDyn *scr_fetch_stream_from(ScrDyn *iterable); /* borrowed iterable; +1 handle or NULL pending */
3969
+ ScrDyn *scr_fetch_stream_from_array(
3970
+ ScrArr *iterable,
3971
+ ScrDyn *(*item)(ScrArr *, double)); /* borrowed array/callback; +1 handle */
3972
+ ScrDyn *scr_fetch_stream_from_bytes(
3973
+ ScrBytes *iterable); /* borrowed bytes; +1 handle */
3974
+ ScrDyn *scr_fetch_stream_from_string(
3975
+ ScrStr *iterable); /* borrowed string; +1 handle */
3976
+ ScrPromise *scr_fetch_reader_read(ScrDyn *reader); /* borrowed handle; +1 */
3977
+
3822
3978
  /* ── dynamic island (scr_island.c; --dynamic builds ONLY) ────────────
3823
3979
  * The embedded QuickJS-ng engine. Compiled and linked only under
3824
3980
  * -DSCR_DYNAMIC; static builds never reference these symbols (nor the
@@ -3948,18 +4104,12 @@ bool scr_island_timers_due(void);
3948
4104
  bool scr_island_timers_fire_due(void); /* true = fired at least one */
3949
4105
  void scr_island_timers_teardown(void);
3950
4106
 
3951
- /* ── fetch (scr_fetch.c) ──────────────────────────────────────────────
3952
- * The native bridge behind the island's fetch (scr_net + scr_tls +
3953
- * scr_http's client parser + zlib), compiled and linked ONLY into
3954
- * --dynamic builds whose graph references fetch. The emitted main calls
3955
- * scr_fetch_install BEFORE any island entry; install registers the
3956
- * bridge's hooks with the island, which boots the fetch glue with the
3957
- * engine. The native bridge registers NO pending/poll hooks (its
4107
+ /* ── dynamic fetch hooks ──────────────────────────────────────────────
4108
+ * The native bridge registers NO pending/poll hooks (its
3958
4109
  * transfers live on real sockets the loop's poller sleeps on); the curl
3959
4110
  * reference implementation (scr_fetch_curl.c, SCRIPTC_FETCH_CURL=1 —
3960
4111
  * one release as the flip's reference) still registers all four so the
3961
4112
  * loop can sleep on curl's fds. */
3962
- void scr_fetch_install(void);
3963
4113
  void scr_island_set_fetch(void (*boot)(void *jsctx), bool (*pending)(void),
3964
4114
  void (*poll)(double max_wait_ms), void (*teardown)(void));
3965
4115
 
@@ -4078,6 +4228,7 @@ ScrJsval *scr_jsval_destr_check(ScrJsval *v, const char *spell, const char *firs
4078
4228
  ScrJsval *scr_jsval_iter_n(ScrJsval *v, double n);
4079
4229
  int scr_jsval_set_idx(ScrJsval *o, ScrJsval *key, ScrJsval *v);
4080
4230
  ScrJsval *scr_jsval_call_method(ScrJsval *o, const ScrStr *name, int argc, ScrJsval **argv);
4231
+ ScrJsval *scr_jsval_call_this(ScrJsval *f, ScrJsval *receiver, int argc, ScrJsval **argv);
4081
4232
  /* `o.name?.(...)`: a nullish member answers the engine's undefined;
4082
4233
  * anything else calls with this = o (non-callables throw in the engine). */
4083
4234
  ScrJsval *scr_jsval_opt_call_method(ScrJsval *o, const ScrStr *name, int argc, ScrJsval **argv);
@@ -4297,10 +4448,13 @@ bool scr_num_same_value(double a, double b);
4297
4448
  * en-US is the one embedded locale. Result +1; never throws. */
4298
4449
  ScrStr *scr_intl_num_format_en_us(double x);
4299
4450
 
4300
- /* ── Date, the composed slice (scr_lib.c) ─────────────────────────────
4301
- * Date values have no representation; the runtime surface is exactly
4302
- * Date.now() and toISOString over a millisecond time value. */
4451
+ /* ── Date, the read-only value slice (scr_lib.c) ──────────────────────
4452
+ * A Date value is its TimeClip'd epoch-millisecond scalar. Identity and
4453
+ * mutation are frontend-fenced; construction, storage, getters, and ISO
4454
+ * formatting are exact over this representation. */
4303
4455
  double scr_date_now(void); /* integer ms since epoch, like Node */
4456
+ double scr_date_new_ms(double ms); /* TimeClip (NaN when invalid) */
4457
+ double scr_date_get_time(double ms);
4304
4458
  /* Node's exact ISO 8601 UTC format (expanded ±YYYYYY years outside
4305
4459
  * 0–9999). THROWS Node's "Invalid time value" RangeError on NaN or
4306
4460
  * |ms| > 8.64e15 and returns NULL; +1 otherwise. */
@@ -4314,6 +4468,30 @@ double scr_date_parse_get_time(ScrStr *s);
4314
4468
  * and years past V8's ±1e6 MakeDay bound. Never throws. */
4315
4469
  double scr_date_utc(double y, double mo, double d,
4316
4470
  double h, double mi, double s, double ms);
4471
+ double scr_date_get_full_year(double ms, bool utc);
4472
+ double scr_date_get_month(double ms, bool utc);
4473
+ double scr_date_get_date(double ms, bool utc);
4474
+ double scr_date_get_day(double ms, bool utc);
4475
+ double scr_date_get_hours(double ms, bool utc);
4476
+ double scr_date_get_minutes(double ms, bool utc);
4477
+ double scr_date_get_seconds(double ms, bool utc);
4478
+ double scr_date_get_milliseconds(double ms);
4479
+ double scr_date_get_timezone_offset(double ms);
4480
+ /* One-argument ABI wrappers used by the LLVM lib-call table. */
4481
+ double scr_date_get_full_year_local(double ms);
4482
+ double scr_date_get_full_year_utc(double ms);
4483
+ double scr_date_get_month_local(double ms);
4484
+ double scr_date_get_month_utc(double ms);
4485
+ double scr_date_get_date_local(double ms);
4486
+ double scr_date_get_date_utc(double ms);
4487
+ double scr_date_get_day_local(double ms);
4488
+ double scr_date_get_day_utc(double ms);
4489
+ double scr_date_get_hours_local(double ms);
4490
+ double scr_date_get_hours_utc(double ms);
4491
+ double scr_date_get_minutes_local(double ms);
4492
+ double scr_date_get_minutes_utc(double ms);
4493
+ double scr_date_get_seconds_local(double ms);
4494
+ double scr_date_get_seconds_utc(double ms);
4317
4495
 
4318
4496
  /* ── bitwise operators (scr_lib.c) ────────────────────────────────────
4319
4497
  * JS-exact ToInt32/ToUint32 semantics: NaN/±Infinity → 0, truncation
@@ -4468,6 +4646,9 @@ void scr_dataview_set(ScrBytes *b, double byte_off, double value, ScrDataViewGet
4468
4646
  * toward zero, wrap mod 2^8/2^32), f32 by double→float rounding. */
4469
4647
  double scr_bytes_get(const ScrBytes *b, double i);
4470
4648
  void scr_bytes_set(ScrBytes *b, double i, double v);
4649
+ /* Replace a live typed-array capsule's fixed-size payload from its dyn
4650
+ * snapshot. Source and target have the same compiler-checked bytes type. */
4651
+ void scr_bytes_copy_contents(ScrBytes *dst, const ScrBytes *src);
4471
4652
 
4472
4653
  /* TypedArray.prototype.slice(start, end): relative indices clamp like
4473
4654
  * string/array slice (ToIntegerOrInfinity, negatives from the end); the
@@ -5125,9 +5306,12 @@ long scr_secure_ctx_live_count(void);
5125
5306
  * /etc/ssl/cert.pem probe order scr_tls.c documents) stands in for both
5126
5307
  * Node's compiled-in Mozilla roots ('bundled', rootCertificates) and the
5127
5308
  * platform store ('system') — the established SEMANTICS divergence,
5128
- * extended to introspection; 'extra' reads NODE_EXTRA_CA_CERTS. Arrays
5129
- * are cached per type (+1 retained answers each call — Node's own
5130
- * caching, and the identity the suite pins with strictEqual). */
5309
+ * extended to introspection; 'extra' uses the NODE_EXTRA_CA_CERTS file
5310
+ * captured before user code runs. Arrays are cached per type (+1 retained
5311
+ * answers each call — Node's own caching, and the identity the suite pins
5312
+ * with strictEqual). */
5313
+ void scr_tls_ca_install(void); /* snapshots NODE_EXTRA_CA_CERTS + file bytes */
5314
+ bool scr_tls_ca_extra_pem(const char **pem, size_t *len); /* borrowed launch snapshot */
5131
5315
  ScrArr *scr_tls_ca_get(ScrStr *type); /* +1; throws ERR_INVALID_ARG_VALUE on unknown types */
5132
5316
  ScrArr *scr_tls_ca_root(void); /* +1; === getCACertificates("bundled") */
5133
5317
  /* Replaces the 'default' set: entries filter to their PEM certificate
package/src/scr_tls.c CHANGED
@@ -336,17 +336,14 @@ typedef struct ScrTlsCli {
336
336
  } ScrTlsCli;
337
337
 
338
338
  /* The default trust anchors when no `ca` option is given: the system
339
- * bundle, standing in for Node's compiled-in Mozilla roots. Probed once
340
- * across the distro spellings /etc/ssl/cert.pem first (macOS ships it;
341
- * Alpine links it), then Debian/Ubuntu's ca-certificates.crt,
342
- * Fedora/RHEL's ca-bundle.crt, and openSUSE's ca-bundle.pem. On macOS
343
- * only the first path exists, so the probe order changes nothing there.
344
- * A host with none leaves the chain empty: verification fails, exactly
345
- * the no-roots behavior /etc/ssl/cert.pem-only had. Windows is that host
346
- * by construction (no PEM bundle ships; the OS store is cert-database-
347
- * shaped) — a no-`ca` client there fails verification like a bundle-less
348
- * Unix host, which agrees with the oracle wherever the fixtures tread
349
- * (their CAs are local, never in Node's Mozilla roots either). */
339
+ * bundle, standing in for Node's compiled-in Mozilla roots, plus
340
+ * NODE_EXTRA_CA_CERTS. The system bundle is probed once across the distro
341
+ * spellings — /etc/ssl/cert.pem first (macOS ships it; Alpine links it),
342
+ * then Debian/Ubuntu's ca-certificates.crt, Fedora/RHEL's ca-bundle.crt,
343
+ * and openSUSE's ca-bundle.pem. On macOS only the first path exists, so
344
+ * the probe order changes nothing there. A host with none leaves only the
345
+ * optional extra chain. Windows is that host by construction (no PEM
346
+ * bundle ships; the OS store is cert-database-shaped). */
350
347
  static mbedtls_x509_crt scr_tls_system_roots;
351
348
  static bool scr_tls_system_roots_loaded = false;
352
349
 
@@ -386,6 +383,13 @@ static mbedtls_x509_crt *scr_tls_system_ca(void) {
386
383
  for (size_t i = 0; i < sizeof bundles / sizeof bundles[0]; i++) {
387
384
  if (mbedtls_x509_crt_parse_file(&scr_tls_system_roots, bundles[i]) == 0) break;
388
385
  }
386
+ const char *extra = NULL;
387
+ size_t extra_len = 0;
388
+ if (scr_tls_ca_extra_pem(&extra, &extra_len) && extra_len > 0) {
389
+ (void)mbedtls_x509_crt_parse(
390
+ &scr_tls_system_roots, (const unsigned char *)extra,
391
+ extra_len + 1);
392
+ }
389
393
  }
390
394
  return &scr_tls_system_roots;
391
395
  }
package/src/scr_tls_ca.c CHANGED
@@ -32,6 +32,9 @@ static ScrArr *scr_ca_bundled = NULL;
32
32
  static ScrArr *scr_ca_system = NULL;
33
33
  static ScrArr *scr_ca_extra = NULL;
34
34
  static ScrArr *scr_ca_default = NULL;
35
+ static char *scr_ca_extra_buf = NULL;
36
+ static size_t scr_ca_extra_len = 0;
37
+ static bool scr_ca_installed = false;
35
38
  static char *scr_ca_override_buf = NULL;
36
39
  static size_t scr_ca_override_len = 0;
37
40
  static uint64_t scr_ca_override_gen = 0; /* 0 = never set */
@@ -47,6 +50,9 @@ static void scr_ca_teardown(void) {
47
50
  if (scr_ca_extra != NULL) scr_arr_release(scr_ca_extra);
48
51
  if (scr_ca_default != NULL) scr_arr_release(scr_ca_default);
49
52
  scr_ca_bundled = scr_ca_system = scr_ca_extra = scr_ca_default = NULL;
53
+ free(scr_ca_extra_buf);
54
+ scr_ca_extra_buf = NULL;
55
+ scr_ca_extra_len = 0;
50
56
  free(scr_ca_override_buf);
51
57
  scr_ca_override_buf = NULL;
52
58
  }
@@ -123,6 +129,31 @@ static char *scr_ca_read_file(const char *path, size_t *out_len) {
123
129
  return buf;
124
130
  }
125
131
 
132
+ /*
133
+ * Node consumes NODE_EXTRA_CA_CERTS while the process is initialized:
134
+ * changing process.env or replacing the referenced file later does not
135
+ * alter either TLS trust or getCACertificates("extra"). Generated main
136
+ * calls this install hook before user code; the lazy fallback only serves
137
+ * library/direct-runtime callers that have no executable startup hook.
138
+ */
139
+ void scr_tls_ca_install(void) {
140
+ if (scr_ca_installed) return;
141
+ scr_ca_installed = true;
142
+ scr_ca_arm_teardown();
143
+ const char *path = getenv("NODE_EXTRA_CA_CERTS");
144
+ if (path != NULL && path[0] != '\0') {
145
+ scr_ca_extra_buf = scr_ca_read_file(path, &scr_ca_extra_len);
146
+ }
147
+ }
148
+
149
+ bool scr_tls_ca_extra_pem(const char **pem, size_t *len) {
150
+ scr_tls_ca_install();
151
+ if (scr_ca_extra_buf == NULL) return false;
152
+ *pem = scr_ca_extra_buf;
153
+ *len = scr_ca_extra_len;
154
+ return true;
155
+ }
156
+
126
157
  /* The host bundle, probed in scr_tls.c's documented order. */
127
158
  static ScrArr *scr_ca_load_host_bundle(void) {
128
159
  ScrArr *arr = scr_arr_new(SCR_ELEM_STR, 128);
@@ -163,14 +194,10 @@ static ScrArr *scr_ca_extra_arr(void) {
163
194
  if (scr_ca_extra == NULL) {
164
195
  scr_ca_arm_teardown();
165
196
  scr_ca_extra = scr_arr_new(SCR_ELEM_STR, 4);
166
- const char *path = getenv("NODE_EXTRA_CA_CERTS");
167
- if (path != NULL && path[0] != '\0') {
168
- size_t len = 0;
169
- char *text = scr_ca_read_file(path, &len);
170
- if (text != NULL) {
171
- scr_ca_push_pem_blocks(scr_ca_extra, text, len);
172
- free(text);
173
- }
197
+ const char *pem = NULL;
198
+ size_t len = 0;
199
+ if (scr_tls_ca_extra_pem(&pem, &len)) {
200
+ scr_ca_push_pem_blocks(scr_ca_extra, pem, len);
174
201
  }
175
202
  }
176
203
  return scr_ca_extra;